gpio: consumer: new virtual driver
Subsystems (2)
PATCH v2 1/1 gpio: consumer: new virtual driver
From: Bartosz Golaszewski <[email protected]>
The GPIO subsystem has a serious problem with undefined behavior and use-after-free bugs on hot-unplug of GPIO chips. This can be considered a corner-case by some as most GPIO controllers are enabled early in the boot process and live until the system goes down but most GPIO drivers do allow unbind over sysfs, many are loadable modules that can be (force) unloaded and there are also GPIO devices that can be dynamically detached, for instance CP2112 which is a USB GPIO expender.
Bugs can be triggered both from user-space as well as by in-kernel users. We have the means of testing it from user-space via the character device but the issues manifest themselves differently in the kernel.
This is a proposition of adding a new virtual driver - a configurable GPIO consumer that can be configured over configfs (similarly to gpio-sim).
The configfs interface allows users to create dynamic GPIO lookup tables that are registered with the GPIO subsystem. Every config group represents a consumer device. Every sub-group represents a single GPIO lookup. The device can work in three modes: just keeping the line active, toggling it every second or requesting its interrupt and reporting edges. Every lookup allows to specify the key, offset and flags as per the lookup struct defined in linux/gpio/machine.h.
The module together with gpio-sim allows to easily trigger kernel hot-unplug errors. A simple use-case is to create a simulated chip, setup the consumer to lookup one of its lines in 'monitor' mode, unbind the simulator, unbind the consumer and observe the fireworks in dmesg.
This driver is aimed as a helper in tackling the hot-unplug problem in GPIO as well as basis for future regression testing once the fixes are upstream.
---
RFC -> v2:
- add documentation
- fix various issues pointed out by Andy: use struct_size() where applicable, improve the logic when storing the 'live' property, improve log messages, remove commas in terminators, etc.
.../admin-guide/gpio/gpio-consumer.rst | 111 ++ drivers/gpio/Kconfig | 7 + drivers/gpio/Makefile | 1 + drivers/gpio/gpio-consumer.c | 1002 +++++++++++++++++ 4 files changed, 1121 insertions(+) create mode 100644 Documentation/admin-guide/gpio/gpio-consumer.rst create mode 100644 drivers/gpio/gpio-consumer.c
Documentation/admin-guide/gpio/gpio-consumer.rst new file +111 -0
@@ -0,0 +1,111 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +Virtual GPIO Consumer +===================== + +The virtual GPIO Consumer module allows users to create dynamic GPIO lookup +tables using the exposed configfs interface and instantiate virtual GPIO +consumer devices that retrieve and use GPIOs using these tables. + +Creating GPIO consumers +----------------------- + +The gpio-consumer module registers a configfs subsystem called +``'gpio-consumer'``. For details of the configfs filesystem, please refer to +the configfs documentation. + +The user can create a hierarchy of configfs groups and items as well as modify +values of exposed attributes. Once the consumer is instantiated, this hierarchy +will be translated to appropriate device properties. The general structure is: + +**Group:** ``/config/gpio-consumer`` + +This is the top directory of the gpio-consumer configfs tree. + +**Group:** ``/config/gpio-consumer/consumer-device`` + +**Attribute:** ``/config/gpio-consumer/gpio-device/function`` + +**Attribute:** ``/config/gpio-consumer/gpio-device/live`` + +This is a directory representing a GPIO consumer device. The ``'function'`` +attribute defines how the device will use requested GPIOs. It takes the +following values: ``'active'`` - GPIOs will be requested as output-high and +kept in this state until the device is unbound, ``'toggle'`` - GPIOs will will +be requested in output mode and then toggled periodically (every second) +between 1 and 0, ``'monitor'`` - GPIOs will be requested in input mode, then +requested as interrupts and their firing reported in the kernel log. + +The ``'live'`` attribute allows to trigger the actual creation of the device +once it's fully configured. The accepted values are: ``'1'`` to enable the +virtual device and ``'0'`` to disable and tear it down. + +Creating GPIO lookup tables +--------------------------- + +User can create a number of configfs groups under the device group with the +following properties: + +**Group:** ``/config/gpio-consumer/consumer-device/lookupX`` + +**Attribute:** ``/config/gpio-consumer/consumer-device/lookupX/key`` + +**Attribute:** ``/config/gpio-consumer/consumer-device/lookupX/offset`` + +**Attribute:** ``/config/gpio-consumer/consumer-device/lookupX/drive`` + +**Attribute:** ``/config/gpio-consumer/consumer-device/lookupX/pull`` + +**Attribute:** ``/config/gpio-consumer/consumer-device/lookupX/active_low`` + +**Attribute:** ``/config/gpio-consumer/consumer-device/lookupX/transitory`` + +This group represents a single entry in the GPIO lookup table. The name of the +sub-directory maps to the ``'con_id'`` field of ``'struct gpiod_lookup'``. The +``'key'`` attribute represents either the name of the chip this GPIO belongs to +or the GPIO line name. This depends on the value of the ``'offset'`` attribute: +if its value is >= 0, then ``'key'`` represents the label of the chip to lookup +while ``'offset'`` represents the offset of the line in that chip. If +``'offset'`` is < 0, then ``'key'`` represents the name of the line. + +The remaining attributes map to the ``'flags'`` field of the GPIO lookup struct. +The first two take string values as arguments: + +**``'drive'``:** ``'push-pull'``, ``'open-drain'``, ``'open-source'`` +**``'pull'``:** ``'pull-up'``, ``'pull-down'``, ``'pull-disabled'``, ``'as-is'`` + +``'active_low'`` and ``'transitory'`` are boolean attributes. + +Activating GPIO consumers +------------------------- + +Once the confiuration is complete, the ``'live'`` attribute must be set to 1 in +order to instantiate the consumer. It can be set back to 0 to destroy the +virtual devices. The module will synchronously wait for the new simulated device +to be successfully probed and if this doesn't happen, writing to ``'live'`` will +result in an error. + +Device-tree +----------- + +Virtual GPIO consumers can also be defined in device-tree. The compatible string +must be: ``"gpio-virtual-consumer"``. Supported properties are: + + ``"gpio-virtual-consumer,function"`` - function of the virtual device + ``"gpio-virtual-consumer,lines"`` - list of lookup keys + +An example device-tree code defining a virtual GPIO consumer: + +.. code-block :: none + + gpio-virt-consumer { + compatible = "gpio-virtual-consumer"; + + foo-gpios = <&gpio0 5 GPIO_ACTIVE_LOW>; + bar-gpios = <&gpio0 6 0>; + + gpio-virtual-consumer,function = "toggle"; + gpio-virtual-consumer,lines = "foo", "bar"; + }; + +.. SPDX-License-Identifier: GPL-2.0-or-later
drivers/gpio/Kconfig +7 -0
@@ -1762,6 +1762,13 @@ config GPIO_AGGREGATOR industrial control context, to be operated from userspace using the GPIO chardev interface. +config GPIO_CONSUMER + tristate "GPIO Consumer Testing Module" + select CONFIGFS_FS + help + This enables the configurable, configfs-based virtual GPIO consumer + testing driver. + config GPIO_LATCH tristate "GPIO latch driver" help
drivers/gpio/Makefile +1 -0
@@ -46,6 +46,7 @@ obj-$(CONFIG_GPIO_BT8XX) += gpio-bt8xx.o obj-$(CONFIG_GPIO_CADENCE) += gpio-cadence.o obj-$(CONFIG_GPIO_CLPS711X) += gpio-clps711x.o obj-$(CONFIG_GPIO_SNPS_CREG) += gpio-creg-snps.o +obj-$(CONFIG_GPIO_CONSUMER) += gpio-consumer.o obj-$(CONFIG_GPIO_CRYSTAL_COVE) += gpio-crystalcove.o obj-$(CONFIG_GPIO_CS5535) += gpio-cs5535.o obj-$(CONFIG_GPIO_DA9052) += gpio-da9052.o
drivers/gpio/gpio-consumer.c new file +1002 -0
1,002 lines not on this page · raw message
Signature · 1 line
2.39.2
Quoted 26 lines · Bartosz Golaszewski
From: Bartosz Golaszewski <[email protected]>
The GPIO subsystem has a serious problem with undefined behavior and use-after-free bugs on hot-unplug of GPIO chips. This can be considered a corner-case by some as most GPIO controllers are enabled early in the boot process and live until the system goes down but most GPIO drivers do allow unbind over sysfs, many are loadable modules that can be (force) unloaded and there are also GPIO devices that can be dynamically detached, for instance CP2112 which is a USB GPIO expender.
Bugs can be triggered both from user-space as well as by in-kernel users. We have the means of testing it from user-space via the character device but the issues manifest themselves differently in the kernel.
This is a proposition of adding a new virtual driver - a configurable GPIO consumer that can be configured over configfs (similarly to gpio-sim).
The configfs interface allows users to create dynamic GPIO lookup tables that are registered with the GPIO subsystem. Every config group represents a consumer device. Every sub-group represents a single GPIO lookup. The device can work in three modes: just keeping the line active, toggling it every second or requesting its interrupt and reporting edges. Every lookup allows to specify the key, offset and flags as per the lookup struct defined in
linux/gpio/machine.h.
Quoted 8 lines
The module together with gpio-sim allows to easily trigger kernel hot-unplug errors. A simple use-case is to create a simulated chip, setup the consumer to lookup one of its lines in 'monitor' mode, unbind the simulator, unbind the consumer and observe the fireworks in dmesg.
This driver is aimed as a helper in tackling the hot-unplug problem in GPIO as well as basis for future regression testing once the fixes are upstream.
I'll read documentation later. Some code comments below.
...
drivers/gpio/gpio-consumer.c line 93
Quoted 5 lines
drivers/gpio/gpio-consumer.c new file +4
-0+static void gpio_consumer_on_timer (struct timer_list *timer) +{ +struct gpio_consumer_timer_data *timer_data =to_timer_data (timer); + timer_data->val = timer_data->val ==0 ?1 :0 ;
Can be
timer_data->val = timer_data->val ? 0 : 1;
But again, why not
timer_data->val ^= 1;
?
Quoted 3 lines
drivers/gpio/gpio-consumer.c new file +3
-0+gpiod_set_value_cansleep (timer_data->desc, timer_data->val); +mod_timer (&timer_data->timer, jiffies +msecs_to_jiffies (1000 )); +}
...
drivers/gpio/gpio-consumer.c line 322
Quoted 6 lines
drivers/gpio/gpio-consumer.c new file +5
-0+ key =kstrndup (page, count,GFP_KERNEL ); +if (!key) +return -ENOMEM ; + stripped =strstrip (key); +memmove (key, stripped,strlen (stripped) +1 );
This can be avoided by
key = kstrndup(skip_spaces(page), count, GFP_KERNEL);
no?
...
drivers/gpio/gpio-consumer.c line 368
Quoted 5 lines
drivers/gpio/gpio-consumer.c new file +5
-0+ ret =kstrtoint (page,0 , &offset); +if (ret) +return ret; + +/* Use -1 to indicate lookup by name. */
This comment is unclear as offset can be -1 given by the user. What does above mean in that context?
drivers/gpio/gpio-consumer.c line 369
Quoted 1 line
drivers/gpio/gpio-consumer.c new file +1
-0+if (offset > (U16_MAX -1 ))
And how does it related to this -1 if related at all?
Quoted 1 line
(quoted diff) +1
-0+ return -EINVAL;
...
drivers/gpio/gpio-consumer.c line 932
Quoted 13 lines
drivers/gpio/gpio-consumer.c new file +13
-0+static struct config_group * +gpio_consumer_config_make_device_group (struct config_group *group, +const char *name) +{ +struct gpio_consumer_device *dev; + + dev =kzalloc (sizeof (*dev),GFP_KERNEL ); +if (!dev) +return ERR_PTR (-ENOMEM ); + + dev->id =ida_alloc (&gpio_consumer_ida,GFP_KERNEL ); +if (dev->id <0 ) { +kfree (dev);
Wondering if you can utilize cleanup.h.
Quoted 13 lines
drivers/gpio/gpio-consumer.c new file +13
-0+return ERR_PTR (dev->id); + } + +config_group_init_type_name (&dev->group, name, + &gpio_consumer_device_config_group_type); +mutex_init (&dev->lock); +INIT_LIST_HEAD (&dev->lookup_list); + dev->bus_notifier.notifier_call = gpio_consumer_bus_notifier_call; + dev->function =GPIO_CONSUMER_FUNCTION_ACTIVE ; +init_completion (&dev->probe_completion); + +return &dev->group; +}
Signature · 2 lines
With Best Regards, Andy Shevchenko
Quoted 45 lines · Andy Shevchenko
Quoted 26 lines · Bartosz Golaszewski
From: Bartosz Golaszewski <[email protected]>
The GPIO subsystem has a serious problem with undefined behavior and use-after-free bugs on hot-unplug of GPIO chips. This can be considered a corner-case by some as most GPIO controllers are enabled early in the boot process and live until the system goes down but most GPIO drivers do allow unbind over sysfs, many are loadable modules that can be (force) unloaded and there are also GPIO devices that can be dynamically detached, for instance CP2112 which is a USB GPIO expender.
Bugs can be triggered both from user-space as well as by in-kernel users. We have the means of testing it from user-space via the character device but the issues manifest themselves differently in the kernel.
This is a proposition of adding a new virtual driver - a configurable GPIO consumer that can be configured over configfs (similarly to gpio-sim).
The configfs interface allows users to create dynamic GPIO lookup tables that are registered with the GPIO subsystem. Every config group represents a consumer device. Every sub-group represents a single GPIO lookup. The device can work in three modes: just keeping the line active, toggling it every second or requesting its interrupt and reporting edges. Every lookup allows to specify the key, offset and flags as per the lookup struct defined in
linux/gpio/machine.h.Quoted 8 lines
The module together with gpio-sim allows to easily trigger kernel hot-unplug errors. A simple use-case is to create a simulated chip, setup the consumer to lookup one of its lines in 'monitor' mode, unbind the simulator, unbind the consumer and observe the fireworks in dmesg.
This driver is aimed as a helper in tackling the hot-unplug problem in GPIO as well as basis for future regression testing once the fixes are upstream.
I'll read documentation later. Some code comments below.
...
Quoted 3 lines
drivers/gpio/gpio-consumer.c new file +3
-0+static void gpio_consumer_on_timer (struct timer_list *timer) +{ +struct gpio_consumer_timer_data *timer_data =to_timer_data (timer);
Quoted 6 lines
Quoted 1 line
+
timer_data->val=timer_data->val== 0 ? 1 : 0;Can be
timer_data->val = timer_data->val ? 0 : 1;
This is still find, though it doesn't really save us even a single line of code.
Quoted 4 lines
But again, why not
timer_data->val ^= 1;
This is not ok in my book. If I need to think for more than a second about what it does, then it's worse. I put clarity over brevity.
Quoted 12 lines
?
Quoted 3 lines
drivers/gpio/gpio-consumer.c new file +3
-0+gpiod_set_value_cansleep (timer_data->desc, timer_data->val); +mod_timer (&timer_data->timer, jiffies +msecs_to_jiffies (1000 )); +}...
Quoted 3 lines
drivers/gpio/gpio-consumer.c new file +3
-0+ key =kstrndup (page, count,GFP_KERNEL ); +if (!key) +return -ENOMEM ;
Quoted 9 lines
drivers/gpio/gpio-consumer.c line 322
Quoted 2 lines
drivers/gpio/gpio-consumer.c new file +2
-0+ stripped =strstrip (key); +memmove (key, stripped,strlen (stripped) +1 );This can be avoided by
key = kstrndup(skip_spaces(page), count, GFP_KERNEL);no?
No, because we also want to remove the trailing spaces and newlines. But if you have a different suggestion with existing helpers, let me know. I didn't find any.
Quoted 11 lines
...
drivers/gpio/gpio-consumer.c line 368
Quoted 5 lines
drivers/gpio/gpio-consumer.c new file +5
-0+ ret =kstrtoint (page,0 , &offset); +if (ret) +return ret; + +/* Use -1 to indicate lookup by name. */ This comment is unclear as offset can be -1 given by the user. What does above mean in that context?
I added this to the documentation. Negative number means: lookup by line name, positive or zero - lookup offset in chip.
Quoted 4 lines
Quoted 1 line
+ if (offset > (
U16_MAX- 1))And how does it related to this -1 if related at all?
GPIOLIB interprets U16_MAX as "lookup by line name". So we can allow max (U16_MAX - 1). I will add a comment.
Quoted 4 lines
Quoted 1 line
+ return
-EINVAL;...
Quoted 16 lines
drivers/gpio/gpio-consumer.c line 932
Quoted 13 lines
drivers/gpio/gpio-consumer.c new file +13
-0+static struct config_group * +gpio_consumer_config_make_device_group (struct config_group *group, +const char *name) +{ +struct gpio_consumer_device *dev; + + dev =kzalloc (sizeof (*dev),GFP_KERNEL ); +if (!dev) +return ERR_PTR (-ENOMEM ); + + dev->id =ida_alloc (&gpio_consumer_ida,GFP_KERNEL ); +if (dev->id <0 ) { +kfree (dev);Wondering if you can utilize
cleanup.h.
Whooaah! In february this year I suggested basic C RAII during my talk at fosdem and here we are? I missed this one. Yeah, I will use it! Even better, I will abuse the cr*p out of it in gpio-sim as well! Thanks for bringing this to my attention. This may be the best thing that happened to kernel C code in years if people widely adopt it. (This paragraph was written by a fan of GLib's autopointer paradigm. :) )
Bartosz
Quoted 19 lines
drivers/gpio/gpio-consumer.c line 945
Quoted 13 lines
drivers/gpio/gpio-consumer.c new file +13
-0+return ERR_PTR (dev->id); + } + +config_group_init_type_name (&dev->group, name, + &gpio_consumer_device_config_group_type); +mutex_init (&dev->lock); +INIT_LIST_HEAD (&dev->lookup_list); + dev->bus_notifier.notifier_call = gpio_consumer_bus_notifier_call; + dev->function =GPIO_CONSUMER_FUNCTION_ACTIVE ; +init_completion (&dev->probe_completion); + +return &dev->group; +}--
With Best Regards,
Andy Shevchenko
Quoted 3 lines · Bartosz Golaszewski
Quoted 1 line · Andy Shevchenko
On Tue, Aug 08, 2023 at 04:56:05PM +0200, Bartosz Golaszewski wrote:
...
Quoted 6 lines
Quoted 3 lines
But again, why not
timer_data->val ^= 1;This is not ok in my book. If I need to think for more than a second about what it does, then it's worse. I put clarity over brevity.
Yes, this will require bit ops to check.
...
Quoted 10 lines
Quoted 6 lines
This can be avoided by
key = kstrndup(skip_spaces(page), count, GFP_KERNEL);no?
No, because we also want to remove the trailing spaces and newlines. But if you have a different suggestion with existing helpers, let me know. I didn't find any.
kstrto*() are newline friendly. The rest as you noted can be covered with sysfs_streq() / sysfs_match_string().
...
Quoted 11 lines
Quoted 8 lines
drivers/gpio/gpio-consumer.c line 368
Quoted 5 lines
drivers/gpio/gpio-consumer.c new file +5
-0+ ret =kstrtoint (page,0 , &offset); +if (ret) +return ret; + +/* Use -1 to indicate lookup by name. */ This comment is unclear as offset can be -1 given by the user. What does above mean in that context?
I added this to the documentation. Negative number means: lookup by line name, positive or zero - lookup offset in chip.
Then add a link to the documentation from here? Because reading this comment and the code is confusing.
Quoted 8 lines
Quoted 3 lines
Quoted 1 line
+ if (offset > (
U16_MAX- 1))And how does it related to this -1 if related at all?
GPIOLIB interprets
U16_MAXas "lookup by line name". So we can allow max (U16_MAX- 1). I will add a comment.Quoted 1 line
Quoted 1 line
+ return
-EINVAL;
...
Quoted 12 lines
Quoted 11 lines
drivers/gpio/gpio-consumer.c line 932
Quoted 9 lines
drivers/gpio/gpio-consumer.c new file +9
-0+struct gpio_consumer_device *dev; + + dev =kzalloc (sizeof (*dev),GFP_KERNEL ); +if (!dev) +return ERR_PTR (-ENOMEM ); + + dev->id =ida_alloc (&gpio_consumer_ida,GFP_KERNEL ); +if (dev->id <0 ) { +kfree (dev);Wondering if you can utilize
cleanup.h.
Quoted 7 lines
Whooaah! In february this year I suggested basic C RAII during my talk at fosdem and here we are? I missed this one. Yeah, I will use it! Even better, I will abuse the cr*p out of it in gpio-sim as well! Thanks for bringing this to my attention. This may be the best thing that happened to kernel C code in years if people widely adopt it. (This paragraph was written by a fan of GLib's autopointer paradigm. :) )
So let it be!
Signature · 2 lines
With Best Regards, Andy Shevchenko
Quoted 1 line · Andy Shevchenko
[snip]
Quoted 8 lines
Quoted 8 lines
Quoted 7 lines
This can be avoided by
key = kstrndup(skip_spaces(page), count, GFP_KERNEL);no?
Quoted 6 lines
Quoted 3 lines
No, because we also want to remove the trailing spaces and newlines. But if you have a different suggestion with existing helpers, let me know. I didn't find any.
kstrto*() are newline friendly. The rest as you noted can be covered with
sysfs_streq()/sysfs_match_string().
It's a kstrndup() not a kstrtosomething(). It's not newline friendly.
Bart
[snip]
Quoted 2 lines · Bartosz Golaszewski
On Thu, Aug 10, 2023 at 4:53 PM Andy Shevchenko
<[email protected]> wrote:
...
Quoted 7 lines
Quoted 7 lines
Quoted 7 lines
Quoted 6 lines
This can be avoided by
key = kstrndup(skip_spaces(page), count, GFP_KERNEL);no?
Quoted 8 lines
Quoted 6 lines
Quoted 3 lines
No, because we also want to remove the trailing spaces and newlines. But if you have a different suggestion with existing helpers, let me know. I didn't find any.
kstrto*() are newline friendly. The rest as you noted can be covered with
sysfs_streq()/sysfs_match_string().It's a
kstrndup()not akstrtosomething(). It's not newline friendly.
Right, I messed that up when replying.
Signature · 2 lines
With Best Regards, Andy Shevchenko