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

Signed-off-by: Kent Gibson <[email protected]>
---
 drivers/gpio/gpiolib-cdev.c | 92 +++++++++++++++++++++++++++++++++++++
 1 file changed, 92 insertions(+)

diff --git a/drivers/gpio/gpiolib-cdev.c b/drivers/gpio/gpiolib-cdev.c
index 05b15f20fe2c..9c1e3f5f01af 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>
@@ -423,6 +424,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 {
@@ -432,6 +435,7 @@ struct linereq {
        wait_queue_head_t wait;
        DECLARE_KFIFO_PTR(events, struct gpio_v2_line_event);
        atomic_t seqno;
+       struct mutex config_mutex;
        struct line lines[];
 };
 
@@ -693,6 +697,29 @@ static int gpio_v2_line_config_validate(struct 
gpio_v2_line_config *lc,
        return 0;
 }
 
+static int gpio_v2_line_config_change_validate(struct linereq *lr,
+                                              struct gpio_v2_line_config *lc)
+{
+       unsigned int i;
+       u64 flags;
+
+       for (i = 0; i < lr->num_lines; i++) {
+               flags = gpio_v2_line_config_flags(lc, i);
+               /* disallow edge detection changes */
+               if (lr->lines[i].eflags != (flags & GPIO_V2_LINE_EDGE_FLAGS))
+                       return -EINVAL;
+
+               if (lr->lines[i].eflags & GPIO_V2_LINE_EDGE_FLAGS) {
+                       /* disallow polarity changes */
+                       if (test_bit(FLAG_ACTIVE_LOW,
+                                    &lr->lines[i].desc->flags) !=
+                           ((flags & GPIO_V2_LINE_FLAG_ACTIVE_LOW) != 0))
+                               return -EINVAL;
+               }
+       }
+       return 0;
+}
+
 static void gpio_v2_line_config_flags_to_desc_flags(u64 flags,
                                                    unsigned long *flagsp)
 {
@@ -772,6 +799,68 @@ 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;
+       int ret;
+
+       ret = gpio_v2_line_config_change_validate(lr, lc);
+       if (ret)
+               return ret;
+
+       for (i = 0; i < lr->num_lines; i++) {
+               desc = lr->lines[i].desc;
+               flags = gpio_v2_line_config_flags(lc, i);
+
+               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;
+               }
+
+               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)
 {
@@ -780,6 +869,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;
 }
@@ -946,6 +1037,7 @@ static int linereq_create(struct gpio_device *gdev, void 
__user *ip)
                }
        }
 
+       mutex_init(&lr->config_mutex);
        init_waitqueue_head(&lr->wait);
        if (has_edge_detection) {
                size = ulr.event_buffer_size;
-- 
2.28.0

Reply via email to