diff mbox series

[v9,10/20] gpiolib: cdev: support GPIO_V2_LINE_SET_CONFIG_IOCTL

Message ID 20200922023151.387447-11-warthog618@gmail.com
State Accepted
Commit a54756cb24eafac70ce92bfbd9bb4a4195689fb4
Headers show
Series gpio: cdev: add uAPI v2 | expand

Commit Message

Kent Gibson Sept. 22, 2020, 2:31 a.m. UTC
Add support for GPIO_V2_LINE_SET_CONFIG_IOCTL, the uAPI v2
line set config ioctl.

Signed-off-by: Kent Gibson <warthog618@gmail.com>
---
 drivers/gpio/gpiolib-cdev.c | 88 +++++++++++++++++++++++++++++++++++++
 1 file changed, 88 insertions(+)

Comments

Andy Shevchenko Sept. 23, 2020, 4:14 p.m. UTC | #1
On Tue, Sep 22, 2020 at 5:35 AM Kent Gibson <warthog618@gmail.com> wrote:
>

> Add support for GPIO_V2_LINE_SET_CONFIG_IOCTL, the uAPI v2

> line set config ioctl.

>

> Signed-off-by: Kent Gibson <warthog618@gmail.com>

> ---

>  drivers/gpio/gpiolib-cdev.c | 88 +++++++++++++++++++++++++++++++++++++

>  1 file changed, 88 insertions(+)

>

> diff --git a/drivers/gpio/gpiolib-cdev.c b/drivers/gpio/gpiolib-cdev.c

> index 145bda2151fb..debd3b277523 100644

> --- a/drivers/gpio/gpiolib-cdev.c

> +++ b/drivers/gpio/gpiolib-cdev.c

> @@ -16,6 +16,7 @@

>  #include <linux/kernel.h>

>  #include <linux/kfifo.h>

>  #include <linux/module.h>

> +#include <linux/mutex.h>

>  #include <linux/pinctrl/consumer.h>

>  #include <linux/poll.h>

>  #include <linux/spinlock.h>

> @@ -444,6 +445,8 @@ struct line {

>   * @seqno: the sequence number for edge events generated on all lines in

>   * this line request.  Note that this is not used when @num_lines is 1, as

>   * the line_seqno is then the same and is cheaper to calculate.

> + * @config_mutex: mutex for serializing ioctl() calls to ensure consistency

> + * of configuration, particularly multi-step accesses to desc flags.

>   * @lines: the lines held by this line request, with @num_lines elements.

>   */

>  struct linereq {

> @@ -454,6 +457,7 @@ struct linereq {

>         u32 event_buffer_size;

>         DECLARE_KFIFO_PTR(events, struct gpio_v2_line_event);

>         atomic_t seqno;

> +       struct mutex config_mutex;

>         struct line lines[];

>  };

>

> @@ -573,6 +577,8 @@ static void edge_detector_stop(struct line *line)

>                 free_irq(line->irq, line);

>                 line->irq = 0;

>         }

> +

> +       line->eflags = 0;

>  }

>

>  static int edge_detector_setup(struct line *line,

> @@ -614,6 +620,17 @@ static int edge_detector_setup(struct line *line,

>         return 0;

>  }

>

> +static int edge_detector_update(struct line *line, u64 eflags,

> +                               bool polarity_change)

> +{

> +       if ((line->eflags == eflags) && !polarity_change)

> +               return 0;

> +

> +       edge_detector_stop(line);

> +

> +       return edge_detector_setup(line, eflags);

> +}

> +

>  static u64 gpio_v2_line_config_flags(struct gpio_v2_line_config *lc,

>                                      unsigned int line_idx)

>  {

> @@ -796,6 +813,74 @@ static long linereq_get_values(struct linereq *lr, void __user *ip)

>         return 0;

>  }

>

> +static long linereq_set_config_unlocked(struct linereq *lr,

> +                                       struct gpio_v2_line_config *lc)

> +{

> +       struct gpio_desc *desc;

> +       unsigned int i;

> +       u64 flags;

> +       bool polarity_change;

> +       int ret;

> +

> +       for (i = 0; i < lr->num_lines; i++) {

> +               desc = lr->lines[i].desc;

> +               flags = gpio_v2_line_config_flags(lc, i);


> +               polarity_change =

> +                       (test_bit(FLAG_ACTIVE_LOW, &desc->flags) !=

> +                        ((flags & GPIO_V2_LINE_FLAG_ACTIVE_LOW) != 0));


Comparison

> +

> +               gpio_v2_line_config_flags_to_desc_flags(flags, &desc->flags);

> +               /*

> +                * Lines have to be requested explicitly for input

> +                * or output, else the line will be treated "as is".

> +                */

> +               if (flags & GPIO_V2_LINE_FLAG_OUTPUT) {

> +                       int val = gpio_v2_line_config_output_value(lc, i);

> +

> +                       edge_detector_stop(&lr->lines[i]);

> +                       ret = gpiod_direction_output(desc, val);

> +                       if (ret)

> +                               return ret;

> +               } else if (flags & GPIO_V2_LINE_FLAG_INPUT) {

> +                       ret = gpiod_direction_input(desc);

> +                       if (ret)

> +                               return ret;

> +

> +                       ret = edge_detector_update(&lr->lines[i],

> +                                       flags & GPIO_V2_LINE_EDGE_FLAGS,

> +                                       polarity_change);

> +                       if (ret)

> +                               return ret;

> +               }

> +

> +               blocking_notifier_call_chain(&desc->gdev->notifier,

> +                                            GPIO_V2_LINE_CHANGED_CONFIG,

> +                                            desc);

> +       }

> +       return 0;

> +}

> +

> +static long linereq_set_config(struct linereq *lr, void __user *ip)

> +{

> +       struct gpio_v2_line_config lc;

> +       int ret;

> +

> +       if (copy_from_user(&lc, ip, sizeof(lc)))

> +               return -EFAULT;

> +

> +       ret = gpio_v2_line_config_validate(&lc, lr->num_lines);

> +       if (ret)

> +               return ret;

> +

> +       mutex_lock(&lr->config_mutex);

> +

> +       ret = linereq_set_config_unlocked(lr, &lc);

> +

> +       mutex_unlock(&lr->config_mutex);

> +

> +       return ret;

> +}

> +

>  static long linereq_ioctl(struct file *file, unsigned int cmd,

>                           unsigned long arg)

>  {

> @@ -804,6 +889,8 @@ static long linereq_ioctl(struct file *file, unsigned int cmd,

>

>         if (cmd == GPIO_V2_LINE_GET_VALUES_IOCTL)

>                 return linereq_get_values(lr, ip);

> +       else if (cmd == GPIO_V2_LINE_SET_CONFIG_IOCTL)

> +               return linereq_set_config(lr, ip);

>

>         return -EINVAL;

>  }

> @@ -964,6 +1051,7 @@ static int linereq_create(struct gpio_device *gdev, void __user *ip)

>                 }

>         }

>

> +       mutex_init(&lr->config_mutex);

>         init_waitqueue_head(&lr->wait);

>         lr->event_buffer_size = ulr.event_buffer_size;

>         if (lr->event_buffer_size == 0)

> --

> 2.28.0

>



--
With Best Regards,
Andy Shevchenko
Andy Shevchenko Sept. 23, 2020, 4:15 p.m. UTC | #2
On Wed, Sep 23, 2020 at 7:14 PM Andy Shevchenko
<andy.shevchenko@gmail.com> wrote:
>

> On Tue, Sep 22, 2020 at 5:35 AM Kent Gibson <warthog618@gmail.com> wrote:

> >

> > Add support for GPIO_V2_LINE_SET_CONFIG_IOCTL, the uAPI v2

> > line set config ioctl.


> > +static long linereq_set_config_unlocked(struct linereq *lr,

> > +                                       struct gpio_v2_line_config *lc)

> > +{

> > +       struct gpio_desc *desc;

> > +       unsigned int i;

> > +       u64 flags;

> > +       bool polarity_change;

> > +       int ret;

> > +

> > +       for (i = 0; i < lr->num_lines; i++) {

> > +               desc = lr->lines[i].desc;

> > +               flags = gpio_v2_line_config_flags(lc, i);

>

> > +               polarity_change =

> > +                       (test_bit(FLAG_ACTIVE_LOW, &desc->flags) !=

> > +                        ((flags & GPIO_V2_LINE_FLAG_ACTIVE_LOW) != 0));

>

> Comparison


Comparison between int / long (not all archs are agreed on this) and
boolean is not the best we can do.

What about

  bool old_polarity = test_bit(FLAG_ACTIVE_LOW, &desc->flags);
  bool new_polarity = flags & GPIO_V2_LINE_FLAG_ACTIVE_LOW;

  old_polarity ^ new_polarity

and move this under INPUT conditional?

> > +

> > +               gpio_v2_line_config_flags_to_desc_flags(flags, &desc->flags);

> > +               /*

> > +                * Lines have to be requested explicitly for input

> > +                * or output, else the line will be treated "as is".

> > +                */

> > +               if (flags & GPIO_V2_LINE_FLAG_OUTPUT) {

> > +                       int val = gpio_v2_line_config_output_value(lc, i);

> > +

> > +                       edge_detector_stop(&lr->lines[i]);

> > +                       ret = gpiod_direction_output(desc, val);

> > +                       if (ret)

> > +                               return ret;

> > +               } else if (flags & GPIO_V2_LINE_FLAG_INPUT) {

> > +                       ret = gpiod_direction_input(desc);

> > +                       if (ret)

> > +                               return ret;

> > +

> > +                       ret = edge_detector_update(&lr->lines[i],

> > +                                       flags & GPIO_V2_LINE_EDGE_FLAGS,

> > +                                       polarity_change);

> > +                       if (ret)

> > +                               return ret;

> > +               }

> > +

> > +               blocking_notifier_call_chain(&desc->gdev->notifier,

> > +                                            GPIO_V2_LINE_CHANGED_CONFIG,

> > +                                            desc);

> > +       }

> > +       return 0;

> > +}


-- 
With Best Regards,
Andy Shevchenko
Kent Gibson Sept. 24, 2020, 3:24 a.m. UTC | #3
On Wed, Sep 23, 2020 at 07:15:46PM +0300, Andy Shevchenko wrote:
> On Wed, Sep 23, 2020 at 7:14 PM Andy Shevchenko

> <andy.shevchenko@gmail.com> wrote:

> >

> > On Tue, Sep 22, 2020 at 5:35 AM Kent Gibson <warthog618@gmail.com> wrote:

> > >

> > > Add support for GPIO_V2_LINE_SET_CONFIG_IOCTL, the uAPI v2

> > > line set config ioctl.

> 

> > > +static long linereq_set_config_unlocked(struct linereq *lr,

> > > +                                       struct gpio_v2_line_config *lc)

> > > +{

> > > +       struct gpio_desc *desc;

> > > +       unsigned int i;

> > > +       u64 flags;

> > > +       bool polarity_change;

> > > +       int ret;

> > > +

> > > +       for (i = 0; i < lr->num_lines; i++) {

> > > +               desc = lr->lines[i].desc;

> > > +               flags = gpio_v2_line_config_flags(lc, i);

> >

> > > +               polarity_change =

> > > +                       (test_bit(FLAG_ACTIVE_LOW, &desc->flags) !=

> > > +                        ((flags & GPIO_V2_LINE_FLAG_ACTIVE_LOW) != 0));

> >

> > Comparison

> 

> Comparison between int / long (not all archs are agreed on this) and

> boolean is not the best we can do.

> 


There is no bool to int comparision here.

There are two comparisons - the inner int vs int => bool and the
outer bool vs bool.  The "!= 0" is effectively an implicit cast to
bool, as is your new_polarity initialisation below.

> What about

> 

>   bool old_polarity = test_bit(FLAG_ACTIVE_LOW, &desc->flags);

>   bool new_polarity = flags & GPIO_V2_LINE_FLAG_ACTIVE_LOW;

> 

>   old_polarity ^ new_polarity

> 


So using bitwise operators on bools is ok??

> and move this under INPUT conditional?

> 


It has to be before the gpio_v2_line_config_flags_to_desc_flags() call,
as that modifies the desc flags, including the new polarity, so
polarity_change would then always be false :-).

Cheers,
Kent.

> > > +

> > > +               gpio_v2_line_config_flags_to_desc_flags(flags, &desc->flags);

> > > +               /*

> > > +                * Lines have to be requested explicitly for input

> > > +                * or output, else the line will be treated "as is".

> > > +                */

> > > +               if (flags & GPIO_V2_LINE_FLAG_OUTPUT) {

> > > +                       int val = gpio_v2_line_config_output_value(lc, i);

> > > +

> > > +                       edge_detector_stop(&lr->lines[i]);

> > > +                       ret = gpiod_direction_output(desc, val);

> > > +                       if (ret)

> > > +                               return ret;

> > > +               } else if (flags & GPIO_V2_LINE_FLAG_INPUT) {

> > > +                       ret = gpiod_direction_input(desc);

> > > +                       if (ret)

> > > +                               return ret;

> > > +

> > > +                       ret = edge_detector_update(&lr->lines[i],

> > > +                                       flags & GPIO_V2_LINE_EDGE_FLAGS,

> > > +                                       polarity_change);

> > > +                       if (ret)

> > > +                               return ret;

> > > +               }

> > > +

> > > +               blocking_notifier_call_chain(&desc->gdev->notifier,

> > > +                                            GPIO_V2_LINE_CHANGED_CONFIG,

> > > +                                            desc);

> > > +       }

> > > +       return 0;

> > > +}

> 

> -- 

> With Best Regards,

> Andy Shevchenko
Andy Shevchenko Sept. 24, 2020, 8:26 a.m. UTC | #4
On Thu, Sep 24, 2020 at 6:24 AM Kent Gibson <warthog618@gmail.com> wrote:
> On Wed, Sep 23, 2020 at 07:15:46PM +0300, Andy Shevchenko wrote:

> > On Wed, Sep 23, 2020 at 7:14 PM Andy Shevchenko

> > <andy.shevchenko@gmail.com> wrote:

> > > On Tue, Sep 22, 2020 at 5:35 AM Kent Gibson <warthog618@gmail.com> wrote:

> > > >

> > > > Add support for GPIO_V2_LINE_SET_CONFIG_IOCTL, the uAPI v2

> > > > line set config ioctl.

> >

> > > > +static long linereq_set_config_unlocked(struct linereq *lr,

> > > > +                                       struct gpio_v2_line_config *lc)

> > > > +{

> > > > +       struct gpio_desc *desc;

> > > > +       unsigned int i;

> > > > +       u64 flags;

> > > > +       bool polarity_change;

> > > > +       int ret;

> > > > +

> > > > +       for (i = 0; i < lr->num_lines; i++) {

> > > > +               desc = lr->lines[i].desc;

> > > > +               flags = gpio_v2_line_config_flags(lc, i);

> > >

> > > > +               polarity_change =

> > > > +                       (test_bit(FLAG_ACTIVE_LOW, &desc->flags) !=

> > > > +                        ((flags & GPIO_V2_LINE_FLAG_ACTIVE_LOW) != 0));

> > >

> > > Comparison

> >

> > Comparison between int / long (not all archs are agreed on this) and

> > boolean is not the best we can do.

> >

>

> There is no bool to int comparision here.


test_bit() returns int or long depending on arch... Then you compare
it to bool (which is a product of != 0).

> There are two comparisons - the inner int vs int => bool and the

> outer bool vs bool.  The "!= 0" is effectively an implicit cast to

> bool, as is your new_polarity initialisation below.

>

> > What about

> >

> >   bool old_polarity = test_bit(FLAG_ACTIVE_LOW, &desc->flags);

> >   bool new_polarity = flags & GPIO_V2_LINE_FLAG_ACTIVE_LOW;

> >

> >   old_polarity ^ new_polarity

>

> So using bitwise operators on bools is ok??


XOR is special. There were never bitwise/boolean XORs.

> > and move this under INPUT conditional?

> >

>

> It has to be before the gpio_v2_line_config_flags_to_desc_flags() call,

> as that modifies the desc flags, including the new polarity, so

> polarity_change would then always be false :-).


I really don't see in the code how polarity_change value is used in
FLAG_OUTPUT branch below.

> > > > +

> > > > +               gpio_v2_line_config_flags_to_desc_flags(flags, &desc->flags);

> > > > +               /*

> > > > +                * Lines have to be requested explicitly for input

> > > > +                * or output, else the line will be treated "as is".

> > > > +                */

> > > > +               if (flags & GPIO_V2_LINE_FLAG_OUTPUT) {

> > > > +                       int val = gpio_v2_line_config_output_value(lc, i);

> > > > +

> > > > +                       edge_detector_stop(&lr->lines[i]);

> > > > +                       ret = gpiod_direction_output(desc, val);

> > > > +                       if (ret)

> > > > +                               return ret;

> > > > +               } else if (flags & GPIO_V2_LINE_FLAG_INPUT) {

> > > > +                       ret = gpiod_direction_input(desc);

> > > > +                       if (ret)

> > > > +                               return ret;

> > > > +

> > > > +                       ret = edge_detector_update(&lr->lines[i],

> > > > +                                       flags & GPIO_V2_LINE_EDGE_FLAGS,

> > > > +                                       polarity_change);

> > > > +                       if (ret)

> > > > +                               return ret;

> > > > +               }

> > > > +

> > > > +               blocking_notifier_call_chain(&desc->gdev->notifier,

> > > > +                                            GPIO_V2_LINE_CHANGED_CONFIG,

> > > > +                                            desc);

> > > > +       }

> > > > +       return 0;

> > > > +}

> >

> > --

> > With Best Regards,

> > Andy Shevchenko




-- 
With Best Regards,
Andy Shevchenko
Kent Gibson Sept. 24, 2020, 9:26 a.m. UTC | #5
On Thu, Sep 24, 2020 at 11:26:49AM +0300, Andy Shevchenko wrote:
> On Thu, Sep 24, 2020 at 6:24 AM Kent Gibson <warthog618@gmail.com> wrote:

> > On Wed, Sep 23, 2020 at 07:15:46PM +0300, Andy Shevchenko wrote:

> > > On Wed, Sep 23, 2020 at 7:14 PM Andy Shevchenko

> > > <andy.shevchenko@gmail.com> wrote:

> > > > On Tue, Sep 22, 2020 at 5:35 AM Kent Gibson <warthog618@gmail.com> wrote:

> > > > >

> > > > > Add support for GPIO_V2_LINE_SET_CONFIG_IOCTL, the uAPI v2

> > > > > line set config ioctl.

> > >

> > > > > +static long linereq_set_config_unlocked(struct linereq *lr,

> > > > > +                                       struct gpio_v2_line_config *lc)

> > > > > +{

> > > > > +       struct gpio_desc *desc;

> > > > > +       unsigned int i;

> > > > > +       u64 flags;

> > > > > +       bool polarity_change;

> > > > > +       int ret;

> > > > > +

> > > > > +       for (i = 0; i < lr->num_lines; i++) {

> > > > > +               desc = lr->lines[i].desc;

> > > > > +               flags = gpio_v2_line_config_flags(lc, i);

> > > >

> > > > > +               polarity_change =

> > > > > +                       (test_bit(FLAG_ACTIVE_LOW, &desc->flags) !=

> > > > > +                        ((flags & GPIO_V2_LINE_FLAG_ACTIVE_LOW) != 0));

> > > >

> > > > Comparison

> > >

> > > Comparison between int / long (not all archs are agreed on this) and

> > > boolean is not the best we can do.

> > >

> >

> > There is no bool to int comparision here.

> 

> test_bit() returns int or long depending on arch... Then you compare

> it to bool (which is a product of != 0).

> 


Really - I thought it returned bool.
It is a test - why would it return int or long?
Surely it is guaranteed to return 0 or 1?

> > There are two comparisons - the inner int vs int => bool and the

> > outer bool vs bool.  The "!= 0" is effectively an implicit cast to

> > bool, as is your new_polarity initialisation below.

> >

> > > What about

> > >

> > >   bool old_polarity = test_bit(FLAG_ACTIVE_LOW, &desc->flags);

> > >   bool new_polarity = flags & GPIO_V2_LINE_FLAG_ACTIVE_LOW;

> > >

> > >   old_polarity ^ new_polarity

> >

> > So using bitwise operators on bools is ok??

> 

> XOR is special. There were never bitwise/boolean XORs.

> 


We must live in different universes, cos there has been a bitwise XOR in
mine since K&R.  The logical XOR is '!='.

> > > and move this under INPUT conditional?

> > >

> >

> > It has to be before the gpio_v2_line_config_flags_to_desc_flags() call,

> > as that modifies the desc flags, including the new polarity, so

> > polarity_change would then always be false :-).

> 

> I really don't see in the code how polarity_change value is used in

> FLAG_OUTPUT branch below.

> 


It isn't.  But desc->flags is modified before both - and so the
polarity_change initialization has to go before both SINCE IT TESTS
THE FLAGS.

Cheers,
Kent.
Andy Shevchenko Sept. 25, 2020, 9:49 a.m. UTC | #6
On Thu, Sep 24, 2020 at 12:26 PM Kent Gibson <warthog618@gmail.com> wrote:
> On Thu, Sep 24, 2020 at 11:26:49AM +0300, Andy Shevchenko wrote:

> > On Thu, Sep 24, 2020 at 6:24 AM Kent Gibson <warthog618@gmail.com> wrote:

> > > On Wed, Sep 23, 2020 at 07:15:46PM +0300, Andy Shevchenko wrote:

> > > > On Wed, Sep 23, 2020 at 7:14 PM Andy Shevchenko

> > > > <andy.shevchenko@gmail.com> wrote:

> > > > > On Tue, Sep 22, 2020 at 5:35 AM Kent Gibson <warthog618@gmail.com> wrote:


...

> > > > > > +               polarity_change =

> > > > > > +                       (test_bit(FLAG_ACTIVE_LOW, &desc->flags) !=

> > > > > > +                        ((flags & GPIO_V2_LINE_FLAG_ACTIVE_LOW) != 0));

> > > > >

> > > > > Comparison

> > > >

> > > > Comparison between int / long (not all archs are agreed on this) and

> > > > boolean is not the best we can do.

> > > >

> > >

> > > There is no bool to int comparision here.

> >

> > test_bit() returns int or long depending on arch... Then you compare

> > it to bool (which is a product of != 0).


> Really - I thought it returned bool.

> It is a test - why would it return int or long?


I assume due to arch relation. Some archs may convert test_bit() to a
single assembly instruction that returns a register which definitely
fits long or int depending on case.

> Surely it is guaranteed to return 0 or 1?


Not sure about this, it's all in arch/* and needs to be investigated.
Would be nice to have it cleaned up if there is any inconsistency (and
document if not yet). But It's out of scope of this series I believe.

> > > There are two comparisons - the inner int vs int => bool and the

> > > outer bool vs bool.  The "!= 0" is effectively an implicit cast to

> > > bool, as is your new_polarity initialisation below.

> > >

> > > > What about

> > > >

> > > >   bool old_polarity = test_bit(FLAG_ACTIVE_LOW, &desc->flags);

> > > >   bool new_polarity = flags & GPIO_V2_LINE_FLAG_ACTIVE_LOW;

> > > >

> > > >   old_polarity ^ new_polarity

> > >

> > > So using bitwise operators on bools is ok??

> >

> > XOR is special. There were never bitwise/boolean XORs.

> >

>

> We must live in different universes, cos there has been a bitwise XOR in

> mine since K&R.  The logical XOR is '!='.


Oops, you are right, It was never boolean XOR because it's the same as
a simple comparison.

...

> > > > and move this under INPUT conditional?

> > >

> > > It has to be before the gpio_v2_line_config_flags_to_desc_flags() call,

> > > as that modifies the desc flags, including the new polarity, so

> > > polarity_change would then always be false :-).

> >

> > I really don't see in the code how polarity_change value is used in

> > FLAG_OUTPUT branch below.

>

> It isn't.  But desc->flags is modified before both - and so the

> polarity_change initialization has to go before both SINCE IT TESTS

> THE FLAGS.


I see now. Sorry for being too blind.

-- 
With Best Regards,
Andy Shevchenko
diff mbox series

Patch

diff --git a/drivers/gpio/gpiolib-cdev.c b/drivers/gpio/gpiolib-cdev.c
index 145bda2151fb..debd3b277523 100644
--- a/drivers/gpio/gpiolib-cdev.c
+++ b/drivers/gpio/gpiolib-cdev.c
@@ -16,6 +16,7 @@ 
 #include <linux/kernel.h>
 #include <linux/kfifo.h>
 #include <linux/module.h>
+#include <linux/mutex.h>
 #include <linux/pinctrl/consumer.h>
 #include <linux/poll.h>
 #include <linux/spinlock.h>
@@ -444,6 +445,8 @@  struct line {
  * @seqno: the sequence number for edge events generated on all lines in
  * this line request.  Note that this is not used when @num_lines is 1, as
  * the line_seqno is then the same and is cheaper to calculate.
+ * @config_mutex: mutex for serializing ioctl() calls to ensure consistency
+ * of configuration, particularly multi-step accesses to desc flags.
  * @lines: the lines held by this line request, with @num_lines elements.
  */
 struct linereq {
@@ -454,6 +457,7 @@  struct linereq {
 	u32 event_buffer_size;
 	DECLARE_KFIFO_PTR(events, struct gpio_v2_line_event);
 	atomic_t seqno;
+	struct mutex config_mutex;
 	struct line lines[];
 };
 
@@ -573,6 +577,8 @@  static void edge_detector_stop(struct line *line)
 		free_irq(line->irq, line);
 		line->irq = 0;
 	}
+
+	line->eflags = 0;
 }
 
 static int edge_detector_setup(struct line *line,
@@ -614,6 +620,17 @@  static int edge_detector_setup(struct line *line,
 	return 0;
 }
 
+static int edge_detector_update(struct line *line, u64 eflags,
+				bool polarity_change)
+{
+	if ((line->eflags == eflags) && !polarity_change)
+		return 0;
+
+	edge_detector_stop(line);
+
+	return edge_detector_setup(line, eflags);
+}
+
 static u64 gpio_v2_line_config_flags(struct gpio_v2_line_config *lc,
 				     unsigned int line_idx)
 {
@@ -796,6 +813,74 @@  static long linereq_get_values(struct linereq *lr, void __user *ip)
 	return 0;
 }
 
+static long linereq_set_config_unlocked(struct linereq *lr,
+					struct gpio_v2_line_config *lc)
+{
+	struct gpio_desc *desc;
+	unsigned int i;
+	u64 flags;
+	bool polarity_change;
+	int ret;
+
+	for (i = 0; i < lr->num_lines; i++) {
+		desc = lr->lines[i].desc;
+		flags = gpio_v2_line_config_flags(lc, i);
+		polarity_change =
+			(test_bit(FLAG_ACTIVE_LOW, &desc->flags) !=
+			 ((flags & GPIO_V2_LINE_FLAG_ACTIVE_LOW) != 0));
+
+		gpio_v2_line_config_flags_to_desc_flags(flags, &desc->flags);
+		/*
+		 * Lines have to be requested explicitly for input
+		 * or output, else the line will be treated "as is".
+		 */
+		if (flags & GPIO_V2_LINE_FLAG_OUTPUT) {
+			int val = gpio_v2_line_config_output_value(lc, i);
+
+			edge_detector_stop(&lr->lines[i]);
+			ret = gpiod_direction_output(desc, val);
+			if (ret)
+				return ret;
+		} else if (flags & GPIO_V2_LINE_FLAG_INPUT) {
+			ret = gpiod_direction_input(desc);
+			if (ret)
+				return ret;
+
+			ret = edge_detector_update(&lr->lines[i],
+					flags & GPIO_V2_LINE_EDGE_FLAGS,
+					polarity_change);
+			if (ret)
+				return ret;
+		}
+
+		blocking_notifier_call_chain(&desc->gdev->notifier,
+					     GPIO_V2_LINE_CHANGED_CONFIG,
+					     desc);
+	}
+	return 0;
+}
+
+static long linereq_set_config(struct linereq *lr, void __user *ip)
+{
+	struct gpio_v2_line_config lc;
+	int ret;
+
+	if (copy_from_user(&lc, ip, sizeof(lc)))
+		return -EFAULT;
+
+	ret = gpio_v2_line_config_validate(&lc, lr->num_lines);
+	if (ret)
+		return ret;
+
+	mutex_lock(&lr->config_mutex);
+
+	ret = linereq_set_config_unlocked(lr, &lc);
+
+	mutex_unlock(&lr->config_mutex);
+
+	return ret;
+}
+
 static long linereq_ioctl(struct file *file, unsigned int cmd,
 			  unsigned long arg)
 {
@@ -804,6 +889,8 @@  static long linereq_ioctl(struct file *file, unsigned int cmd,
 
 	if (cmd == GPIO_V2_LINE_GET_VALUES_IOCTL)
 		return linereq_get_values(lr, ip);
+	else if (cmd == GPIO_V2_LINE_SET_CONFIG_IOCTL)
+		return linereq_set_config(lr, ip);
 
 	return -EINVAL;
 }
@@ -964,6 +1051,7 @@  static int linereq_create(struct gpio_device *gdev, void __user *ip)
 		}
 	}
 
+	mutex_init(&lr->config_mutex);
 	init_waitqueue_head(&lr->wait);
 	lr->event_buffer_size = ulr.event_buffer_size;
 	if (lr->event_buffer_size == 0)