Add support for GPIO_V2_LINE_SET_CONFIG_IOCTL, the uAPI v2
line set config ioctl.

Signed-off-by: Kent Gibson <warthog...@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 904216a67ed1..936b8c9958dd 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>
@@ -424,6 +425,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 {
@@ -434,6 +437,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[];
 };
 
@@ -541,6 +545,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,
@@ -582,6 +588,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)
 {
@@ -760,6 +777,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)
 {
@@ -768,6 +853,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;
 }
@@ -928,6 +1015,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

Reply via email to