Commit 6766f590 authored by Bartosz Golaszewski's avatar Bartosz Golaszewski
Browse files

gpio: sysfs: fix chip removal with GPIOs exported over sysfs

Currently if we export a GPIO over sysfs and unbind the parent GPIO
controller, the exported attribute will remain under /sys/class/gpio
because once we remove the parent device, we can no longer associate the
descriptor with it in gpiod_unexport() and never drop the final
reference.

Rework the teardown code: provide an unlocked variant of
gpiod_unexport() and remove all exported GPIOs with the sysfs_lock taken
before unregistering the parent device itself. This is done to prevent
any new exports happening before we unregister the device completely.

Cc: stable@vger.kernel.org
Fixes: 1cd53df7 ("gpio: sysfs: don't look up exported lines as class devices")
Link: https://patch.msgid.link/20260212133505.81516-1-bartosz.golaszewski@oss.qualcomm.com


Signed-off-by: default avatarBartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
parent ff91965a
Loading
Loading
Loading
Loading
+55 −51
Original line number Diff line number Diff line
@@ -919,24 +919,12 @@ int gpiod_export_link(struct device *dev, const char *name,
}
EXPORT_SYMBOL_GPL(gpiod_export_link);

/**
 * gpiod_unexport - reverse effect of gpiod_export()
 * @desc: GPIO to make unavailable
 *
 * This is implicit on gpiod_free().
 */
void gpiod_unexport(struct gpio_desc *desc)
static void gpiod_unexport_unlocked(struct gpio_desc *desc)
{
	struct gpiod_data *tmp, *desc_data = NULL;
	struct gpiodev_data *gdev_data;
	struct gpio_device *gdev;

	if (!desc) {
		pr_warn("%s: invalid GPIO\n", __func__);
		return;
	}

	scoped_guard(mutex, &sysfs_lock) {
	if (!test_bit(GPIOD_FLAG_EXPORT, &desc->flags))
		return;

@@ -971,11 +959,28 @@ void gpiod_unexport(struct gpio_desc *desc)

	sysfs_remove_groups(desc_data->parent,
			    desc_data->chip_attr_groups);
	}

	mutex_destroy(&desc_data->mutex);
	kfree(desc_data);
}

/**
 * gpiod_unexport - reverse effect of gpiod_export()
 * @desc: GPIO to make unavailable
 *
 * This is implicit on gpiod_free().
 */
void gpiod_unexport(struct gpio_desc *desc)
{
	if (!desc) {
		pr_warn("%s: invalid GPIO\n", __func__);
		return;
	}

	guard(mutex)(&sysfs_lock);

	gpiod_unexport_unlocked(desc);
}
EXPORT_SYMBOL_GPL(gpiod_unexport);

int gpiochip_sysfs_register(struct gpio_device *gdev)
@@ -1054,29 +1059,28 @@ void gpiochip_sysfs_unregister(struct gpio_device *gdev)
	struct gpio_desc *desc;
	struct gpio_chip *chip;

	scoped_guard(mutex, &sysfs_lock) {
	guard(mutex)(&sysfs_lock);

	data = gdev_get_data(gdev);
	if (!data)
		return;

#if IS_ENABLED(CONFIG_GPIO_SYSFS_LEGACY)
		device_unregister(data->cdev_base);
#endif /* CONFIG_GPIO_SYSFS_LEGACY */
		device_unregister(data->cdev_id);
		kfree(data);
	}

	guard(srcu)(&gdev->srcu);

	chip = srcu_dereference(gdev->chip, &gdev->srcu);
	if (!chip)
		return;

	/* unregister gpiod class devices owned by sysfs */
	for_each_gpio_desc_with_flag(chip, desc, GPIOD_FLAG_SYSFS) {
		gpiod_unexport(desc);
		gpiod_unexport_unlocked(desc);
		gpiod_free(desc);
	}

#if IS_ENABLED(CONFIG_GPIO_SYSFS_LEGACY)
	device_unregister(data->cdev_base);
#endif /* CONFIG_GPIO_SYSFS_LEGACY */
	device_unregister(data->cdev_id);
	kfree(data);
}

/*