]> www.infradead.org Git - users/willy/pagecache.git/commitdiff
gpiolib: protect gpio_chip with SRCU in array_info paths in multi get/set
authorBartosz Golaszewski <bartosz.golaszewski@linaro.org>
Sat, 15 Feb 2025 09:56:55 +0000 (10:56 +0100)
committerBartosz Golaszewski <bartosz.golaszewski@linaro.org>
Tue, 18 Feb 2025 10:23:34 +0000 (11:23 +0100)
During the locking rework in GPIOLIB, we omitted one important use-case,
namely: setting and getting values for GPIO descriptor arrays with
array_info present.

This patch does two things: first it makes struct gpio_array store the
address of the underlying GPIO device and not chip. Next: it protects
the chip with SRCU from removal in gpiod_get_array_value_complex() and
gpiod_set_array_value_complex().

Cc: stable@vger.kernel.org
Link: https://lore.kernel.org/r/20250215095655.23152-1-brgl@bgdev.pl
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
drivers/gpio/gpiolib.c
drivers/gpio/gpiolib.h

index 29110dc436f15ab578432551270ef574c1144cdb..5529d8b65f6fba207dbd0583b1b84b2917b55c20 100644 (file)
@@ -3143,6 +3143,8 @@ static int gpiod_get_raw_value_commit(const struct gpio_desc *desc)
 static int gpio_chip_get_multiple(struct gpio_chip *gc,
                                  unsigned long *mask, unsigned long *bits)
 {
+       lockdep_assert_held(&gc->gpiodev->srcu);
+
        if (gc->get_multiple)
                return gc->get_multiple(gc, mask, bits);
        if (gc->get) {
@@ -3173,6 +3175,7 @@ int gpiod_get_array_value_complex(bool raw, bool can_sleep,
                                  struct gpio_array *array_info,
                                  unsigned long *value_bitmap)
 {
+       struct gpio_chip *gc;
        int ret, i = 0;
 
        /*
@@ -3184,10 +3187,15 @@ int gpiod_get_array_value_complex(bool raw, bool can_sleep,
            array_size <= array_info->size &&
            (void *)array_info == desc_array + array_info->size) {
                if (!can_sleep)
-                       WARN_ON(array_info->chip->can_sleep);
+                       WARN_ON(array_info->gdev->can_sleep);
+
+               guard(srcu)(&array_info->gdev->srcu);
+               gc = srcu_dereference(array_info->gdev->chip,
+                                     &array_info->gdev->srcu);
+               if (!gc)
+                       return -ENODEV;
 
-               ret = gpio_chip_get_multiple(array_info->chip,
-                                            array_info->get_mask,
+               ret = gpio_chip_get_multiple(gc, array_info->get_mask,
                                             value_bitmap);
                if (ret)
                        return ret;
@@ -3468,6 +3476,8 @@ static void gpiod_set_raw_value_commit(struct gpio_desc *desc, bool value)
 static void gpio_chip_set_multiple(struct gpio_chip *gc,
                                   unsigned long *mask, unsigned long *bits)
 {
+       lockdep_assert_held(&gc->gpiodev->srcu);
+
        if (gc->set_multiple) {
                gc->set_multiple(gc, mask, bits);
        } else {
@@ -3485,6 +3495,7 @@ int gpiod_set_array_value_complex(bool raw, bool can_sleep,
                                  struct gpio_array *array_info,
                                  unsigned long *value_bitmap)
 {
+       struct gpio_chip *gc;
        int i = 0;
 
        /*
@@ -3496,14 +3507,19 @@ int gpiod_set_array_value_complex(bool raw, bool can_sleep,
            array_size <= array_info->size &&
            (void *)array_info == desc_array + array_info->size) {
                if (!can_sleep)
-                       WARN_ON(array_info->chip->can_sleep);
+                       WARN_ON(array_info->gdev->can_sleep);
+
+               guard(srcu)(&array_info->gdev->srcu);
+               gc = srcu_dereference(array_info->gdev->chip,
+                                     &array_info->gdev->srcu);
+               if (!gc)
+                       return -ENODEV;
 
                if (!raw && !bitmap_empty(array_info->invert_mask, array_size))
                        bitmap_xor(value_bitmap, value_bitmap,
                                   array_info->invert_mask, array_size);
 
-               gpio_chip_set_multiple(array_info->chip, array_info->set_mask,
-                                      value_bitmap);
+               gpio_chip_set_multiple(gc, array_info->set_mask, value_bitmap);
 
                i = find_first_zero_bit(array_info->set_mask, array_size);
                if (i == array_size)
@@ -4765,9 +4781,10 @@ struct gpio_descs *__must_check gpiod_get_array(struct device *dev,
 {
        struct gpio_desc *desc;
        struct gpio_descs *descs;
+       struct gpio_device *gdev;
        struct gpio_array *array_info = NULL;
-       struct gpio_chip *gc;
        int count, bitmap_size;
+       unsigned long dflags;
        size_t descs_size;
 
        count = gpiod_count(dev, con_id);
@@ -4788,7 +4805,7 @@ struct gpio_descs *__must_check gpiod_get_array(struct device *dev,
 
                descs->desc[descs->ndescs] = desc;
 
-               gc = gpiod_to_chip(desc);
+               gdev = gpiod_to_gpio_device(desc);
                /*
                 * If pin hardware number of array member 0 is also 0, select
                 * its chip as a candidate for fast bitmap processing path.
@@ -4796,8 +4813,8 @@ struct gpio_descs *__must_check gpiod_get_array(struct device *dev,
                if (descs->ndescs == 0 && gpio_chip_hwgpio(desc) == 0) {
                        struct gpio_descs *array;
 
-                       bitmap_size = BITS_TO_LONGS(gc->ngpio > count ?
-                                                   gc->ngpio : count);
+                       bitmap_size = BITS_TO_LONGS(gdev->ngpio > count ?
+                                                   gdev->ngpio : count);
 
                        array = krealloc(descs, descs_size +
                                         struct_size(array_info, invert_mask, 3 * bitmap_size),
@@ -4817,7 +4834,7 @@ struct gpio_descs *__must_check gpiod_get_array(struct device *dev,
 
                        array_info->desc = descs->desc;
                        array_info->size = count;
-                       array_info->chip = gc;
+                       array_info->gdev = gdev;
                        bitmap_set(array_info->get_mask, descs->ndescs,
                                   count - descs->ndescs);
                        bitmap_set(array_info->set_mask, descs->ndescs,
@@ -4830,7 +4847,7 @@ struct gpio_descs *__must_check gpiod_get_array(struct device *dev,
                        continue;
 
                /* Unmark array members which don't belong to the 'fast' chip */
-               if (array_info->chip != gc) {
+               if (array_info->gdev != gdev) {
                        __clear_bit(descs->ndescs, array_info->get_mask);
                        __clear_bit(descs->ndescs, array_info->set_mask);
                }
@@ -4853,9 +4870,10 @@ struct gpio_descs *__must_check gpiod_get_array(struct device *dev,
                                            array_info->set_mask);
                        }
                } else {
+                       dflags = READ_ONCE(desc->flags);
                        /* Exclude open drain or open source from fast output */
-                       if (gpiochip_line_is_open_drain(gc, descs->ndescs) ||
-                           gpiochip_line_is_open_source(gc, descs->ndescs))
+                       if (test_bit(FLAG_OPEN_DRAIN, &dflags) ||
+                           test_bit(FLAG_OPEN_SOURCE, &dflags))
                                __clear_bit(descs->ndescs,
                                            array_info->set_mask);
                        /* Identify 'fast' pins which require invertion */
@@ -4867,7 +4885,7 @@ struct gpio_descs *__must_check gpiod_get_array(struct device *dev,
        if (array_info)
                dev_dbg(dev,
                        "GPIO array info: chip=%s, size=%d, get_mask=%lx, set_mask=%lx, invert_mask=%lx\n",
-                       array_info->chip->label, array_info->size,
+                       array_info->gdev->label, array_info->size,
                        *array_info->get_mask, *array_info->set_mask,
                        *array_info->invert_mask);
        return descs;
index 83690f72f7e5cb35151a441cc40b913dcf4190e0..147156ec502b29e7c298c013b2112f22a065a5ed 100644 (file)
@@ -114,7 +114,7 @@ extern const char *const gpio_suffixes[];
  *
  * @desc:              Array of pointers to the GPIO descriptors
  * @size:              Number of elements in desc
- * @chip:              Parent GPIO chip
+ * @gdev:              Parent GPIO device
  * @get_mask:          Get mask used in fastpath
  * @set_mask:          Set mask used in fastpath
  * @invert_mask:       Invert mask used in fastpath
@@ -126,7 +126,7 @@ extern const char *const gpio_suffixes[];
 struct gpio_array {
        struct gpio_desc        **desc;
        unsigned int            size;
-       struct gpio_chip        *chip;
+       struct gpio_device      *gdev;
        unsigned long           *get_mask;
        unsigned long           *set_mask;
        unsigned long           invert_mask[];