gpiolib: cdev: support GPIO_V2_LINE_SET_CONFIG_IOCTL
authorKent Gibson <warthog618@gmail.com>
Mon, 28 Sep 2020 00:27:57 +0000 (08:27 +0800)
committerBartosz Golaszewski <bgolaszewski@baylibre.com>
Wed, 30 Sep 2020 08:56:59 +0000 (10:56 +0200)
Add support for GPIO_V2_LINE_SET_CONFIG_IOCTL, the uAPI v2
line set config ioctl.

Signed-off-by: Kent Gibson <warthog618@gmail.com>
Signed-off-by: Bartosz Golaszewski <bgolaszewski@baylibre.com>
drivers/gpio/gpiolib-cdev.c

index 868fcf8..608cdbd 100644 (file)
@@ -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[];
 };
 
@@ -574,6 +578,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,
@@ -615,6 +621,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)
 {
@@ -799,6 +816,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)
 {
@@ -807,6 +892,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;
 }
@@ -968,6 +1055,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)