mbox series

[v2,00/11] gpiolib: work towards removing gpiochip_find()

Message ID 20230912100727.23197-1-brgl@bgdev.pl
Headers show
Series gpiolib: work towards removing gpiochip_find() | expand

Message

Bartosz Golaszewski Sept. 12, 2023, 10:07 a.m. UTC
From: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>

This is a reduced subset of patches from the initial sumbission[1]
limited only to changes inside GPIOLIB. Once this is upstream, we can
then slowly merge patches for other subsystems (like HTE) and then
eventually remove gpiochip_find() entirely.

The GPIO subsystem does not handle hot-unplug events very well. We have
recently patched the user-space part of it so that at least a rouge user
cannot crash the kernel but in-kernel users are still affected by a lot of
issues: from incorrect locking or lack thereof to using structures that are
private to GPIO drivers. Since almost all GPIO controllers can be unbound,
not to mention that we have USB devices registering GPIO expanders as well as
I2C-on-USB HID devices on which I2C GPIO expanders can live, various media
gadgets etc., we really need to make GPIO hotplug/unplug friendly.

Before we can even get to fixing the locking, we need to address a serious
abuse of the GPIO driver API - accessing struct gpio_chip by anyone who isn't
the driver owning this object. This structure is owned by the GPIO provider
and its lifetime is tied to that of that provider. It is destroyed when the
device is unregistered and this may happen at any moment. struct gpio_device
is the opaque, reference counted interface to struct gpio_chip (which is the
low-level implementation) and all access should pass through it.

The end-goal is to make all gpio_device manipulators check the existence of
gdev->chip and then lock it for the duration of any of the calls using SRCU.
Before we can get there, we need to first provide a set of functions that will
replace any gpio_chip functions and convert all in-kernel users.

This series adds several new helpers to the public GPIO API and uses
them across the core GPIO code.

Note that this does not make everything correct just yet. Especially the
GPIOLIB internal users release the reference returned by the lookup function
after getting the descriptor of interest but before requesting it. This will
eventually be addressed. This is not a regression either.

[1] https://lore.kernel.org/lkml/20230905185309.131295-1-brgl@bgdev.pl/T/

v1 -> v2:
- drop all non-GPIOLIB patches
- collect tags
- fix kernel docs
- use gpio_device_get_chip() and gpio_device_get_desc() where applicable

Bartosz Golaszewski (11):
  gpiolib: make gpio_device_get() and gpio_device_put() public
  gpiolib: add support for scope-based management to gpio_device
  gpiolib: provide gpio_device_find()
  gpiolib: provide gpio_device_find_by_label()
  gpiolib: provide gpio_device_get_desc()
  gpiolib: reluctantly provide gpio_device_get_chip()
  gpiolib: replace find_chip_by_name() with gpio_device_find_by_label()
  gpio: of: replace gpiochip_find_* with gpio_device_find_*
  gpio: acpi: replace gpiochip_find() with gpio_device_find()
  gpio: swnode: replace gpiochip_find() with gpio_device_find_by_label()
  gpio: sysfs: drop the mention of gpiochip_find() from sysfs code

 drivers/gpio/gpiolib-acpi.c   |  12 +-
 drivers/gpio/gpiolib-of.c     |  32 +++---
 drivers/gpio/gpiolib-swnode.c |  33 +++---
 drivers/gpio/gpiolib-sysfs.c  |   2 +-
 drivers/gpio/gpiolib.c        | 201 ++++++++++++++++++++++++++--------
 drivers/gpio/gpiolib.h        |  10 --
 include/linux/gpio/driver.h   |  13 +++
 7 files changed, 211 insertions(+), 92 deletions(-)

Comments

Andy Shevchenko Sept. 12, 2023, 11 a.m. UTC | #1
On Tue, Sep 12, 2023 at 12:07:21PM +0200, Bartosz Golaszewski wrote:
> From: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
> 
> Getting the GPIO descriptor directly from the gpio_chip struct is
> dangerous as we don't take the reference to the underlying GPIO device.
> In order to start working towards removing gpiochip_get_desc(), let's
> provide a safer variant that works with an existing reference to struct
> gpio_device.

...

> +struct gpio_desc *
> +gpio_device_get_desc(struct gpio_device *gdev, unsigned int hwnum)
> +{
> +	struct gpio_chip *gc = gdev->chip;

I prefer

	struct gpio_chip *gc;

> +	/*
> +	 * FIXME: This will be locked once we protect gdev->chip everywhere
> +	 * with SRCU.
> +	 */

	gc = gdev->chip;

as it is more robust against changes in between and easier to read and
understand in the code what's going on. With decoupled assignment in this case
it's harder to see at the flash glance if the gc is parameter of the function
or being derived from somewhere else.

> +	if (!gc)
> +		return ERR_PTR(-ENODEV);
>  
>  	if (hwnum >= gdev->ngpio)
>  		return ERR_PTR(-EINVAL);
>  
>  	return &gdev->descs[hwnum];
>  }
Andy Shevchenko Sept. 12, 2023, 11:16 a.m. UTC | #2
On Tue, Sep 12, 2023 at 12:07:23PM +0200, Bartosz Golaszewski wrote:
> From: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
> 
> Remove all remaining uses of find_chip_by_name() (and subsequently:
> gpiochip_find()) from gpiolib.c and use the new
> gpio_device_find_by_label() instead.

...

>  	for (p = &table->table[0]; p->key; p++) {
> -		struct gpio_chip *gc;
> +		struct gpio_device *gdev __free(gpio_device_put) = NULL;

> +		gc = gpio_device_get_chip(gdev);

What the heck is this, btw? You have gdev NULL here.

>  		/* idx must always match exactly */
>  		if (p->idx != idx)
> @@ -4004,9 +3996,8 @@ static struct gpio_desc *gpiod_find(struct device *dev, const char *con_id,
>  			return ERR_PTR(-EPROBE_DEFER);
>  		}
>  
> -		gc = find_chip_by_name(p->key);
> -
> -		if (!gc) {
> +		gdev = gpio_device_find_by_label(p->key);
> +		if (!gdev) {

...

>  		if (gc->ngpio <= p->chip_hwnum) {
>  			dev_err(dev,
>  				"requested GPIO %u (%u) is out of range [0..%u] for chip %s\n",
> -				idx, p->chip_hwnum, gc->ngpio - 1,
> +				idx, p->chip_hwnum, gdev->chip->ngpio - 1,

In other patch you use wrapper to get gdev->chip, why not here?

>  				gc->label);

Is this gc is different to gdev->chip?

>  			return ERR_PTR(-EINVAL);
>  		}

...

Sorry, but this patch seems to me as WIP. Please, revisit it, make sure all
things are done consistently.
Andy Shevchenko Sept. 12, 2023, 11:33 a.m. UTC | #3
On Tue, Sep 12, 2023 at 12:07:18PM +0200, Bartosz Golaszewski wrote:
> From: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
> 
> As the few users that need to get the reference to the GPIO device often
> release it right after inspecting its properties, let's add support for
> the automatic reference release to struct gpio_device.

...

> +DEFINE_FREE(gpio_device_put, struct gpio_device *, if (_T) gpio_device_put(_T));

Looks like this should be

	if (!IS_ERR_OR_NULL(_T))

(with linux/err.h included).
Bartosz Golaszewski Sept. 12, 2023, 11:35 a.m. UTC | #4
On Tue, Sep 12, 2023 at 1:16 PM Andy Shevchenko
<andriy.shevchenko@linux.intel.com> wrote:
>
> On Tue, Sep 12, 2023 at 12:07:23PM +0200, Bartosz Golaszewski wrote:
> > From: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
> >
> > Remove all remaining uses of find_chip_by_name() (and subsequently:
> > gpiochip_find()) from gpiolib.c and use the new
> > gpio_device_find_by_label() instead.
>
> ...
>
> >       for (p = &table->table[0]; p->key; p++) {
> > -             struct gpio_chip *gc;
> > +             struct gpio_device *gdev __free(gpio_device_put) = NULL;
>
> > +             gc = gpio_device_get_chip(gdev);
>
> What the heck is this, btw? You have gdev NULL here.
>

Gah! Thanks. I relied on tests succeeding and no KASAN warnings, I
need to go through this line-by-line again.

Bart

> >               /* idx must always match exactly */
> >               if (p->idx != idx)
> > @@ -4004,9 +3996,8 @@ static struct gpio_desc *gpiod_find(struct device *dev, const char *con_id,
> >                       return ERR_PTR(-EPROBE_DEFER);
> >               }
> >
> > -             gc = find_chip_by_name(p->key);
> > -
> > -             if (!gc) {
> > +             gdev = gpio_device_find_by_label(p->key);
> > +             if (!gdev) {
>
> ...
>
> >               if (gc->ngpio <= p->chip_hwnum) {
> >                       dev_err(dev,
> >                               "requested GPIO %u (%u) is out of range [0..%u] for chip %s\n",
> > -                             idx, p->chip_hwnum, gc->ngpio - 1,
> > +                             idx, p->chip_hwnum, gdev->chip->ngpio - 1,
>
> In other patch you use wrapper to get gdev->chip, why not here?
>
> >                               gc->label);
>
> Is this gc is different to gdev->chip?
>
> >                       return ERR_PTR(-EINVAL);
> >               }
>
> ...
>
> Sorry, but this patch seems to me as WIP. Please, revisit it, make sure all
> things are done consistently.
>
> --
> With Best Regards,
> Andy Shevchenko
>
>
Bartosz Golaszewski Sept. 15, 2023, 9:44 a.m. UTC | #5
On Tue, Sep 12, 2023 at 1:35 PM Bartosz Golaszewski <brgl@bgdev.pl> wrote:
>
> On Tue, Sep 12, 2023 at 1:16 PM Andy Shevchenko
> <andriy.shevchenko@linux.intel.com> wrote:
> >
> > On Tue, Sep 12, 2023 at 12:07:23PM +0200, Bartosz Golaszewski wrote:
> > > From: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
> > >
> > > Remove all remaining uses of find_chip_by_name() (and subsequently:
> > > gpiochip_find()) from gpiolib.c and use the new
> > > gpio_device_find_by_label() instead.
> >
> > ...
> >
> > >       for (p = &table->table[0]; p->key; p++) {
> > > -             struct gpio_chip *gc;
> > > +             struct gpio_device *gdev __free(gpio_device_put) = NULL;
> >
> > > +             gc = gpio_device_get_chip(gdev);
> >
> > What the heck is this, btw? You have gdev NULL here.
> >
>
> Gah! Thanks. I relied on tests succeeding and no KASAN warnings, I
> need to go through this line-by-line again.
>

Fortunately, this was just an unused leftover. I fixed it for v3.

Bart

> Bart
>
> > >               /* idx must always match exactly */
> > >               if (p->idx != idx)
> > > @@ -4004,9 +3996,8 @@ static struct gpio_desc *gpiod_find(struct device *dev, const char *con_id,
> > >                       return ERR_PTR(-EPROBE_DEFER);
> > >               }
> > >
> > > -             gc = find_chip_by_name(p->key);
> > > -
> > > -             if (!gc) {
> > > +             gdev = gpio_device_find_by_label(p->key);
> > > +             if (!gdev) {
> >
> > ...
> >
> > >               if (gc->ngpio <= p->chip_hwnum) {
> > >                       dev_err(dev,
> > >                               "requested GPIO %u (%u) is out of range [0..%u] for chip %s\n",
> > > -                             idx, p->chip_hwnum, gc->ngpio - 1,
> > > +                             idx, p->chip_hwnum, gdev->chip->ngpio - 1,
> >
> > In other patch you use wrapper to get gdev->chip, why not here?
> >
> > >                               gc->label);
> >
> > Is this gc is different to gdev->chip?
> >
> > >                       return ERR_PTR(-EINVAL);
> > >               }
> >
> > ...
> >
> > Sorry, but this patch seems to me as WIP. Please, revisit it, make sure all
> > things are done consistently.
> >
> > --
> > With Best Regards,
> > Andy Shevchenko
> >
> >