Commit 81570d6a authored by Bartosz Golaszewski's avatar Bartosz Golaszewski
Browse files

gpiolib: protect gpio_chip with SRCU in array_info paths in multi get/set

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: default avatarBartosz Golaszewski <bartosz.golaszewski@linaro.org>
parent 4e667a19
Loading
Loading
Loading
Loading
+33 −15
Original line number Diff line number Diff line
@@ -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);

		ret = gpio_chip_get_multiple(array_info->chip,
					     array_info->get_mask,
		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(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;
+2 −2
Original line number Diff line number Diff line
@@ -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[];