Skip to contentSkip to conversation

gpio: consumer: new virtual driver

PATCH Superseded Bartosz Golaszewski 2 participants 6 messages Aug 15, 2023 – Aug 17, 2023

Subsystems (2)

v5 of 7 Superseded Latest is v7 →

lore
Share & tools
lore
#0 lore raw

PATCH v5 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.

v2 -> v3:
- use cleanup.h interfaces
- add some clarifying commets
- more minor code tweaks

v3 -> v4:
- fix the toggle value assignment
- use guard(mutex)() wherever we can return directly from the subsequent function call
- use skip_spaces() + strim() to avoid having to do a memmove() when stripping strings off whitespaces
- DON'T try to save a couple LOC in ifdefs if that makes them less readable (Andy :) )

v4 -> v5:
- add the gpio-consumer docs to the admin-guide/gpio/ index (reported by kernel test robot <[email protected]>)

 .../admin-guide/gpio/gpio-consumer.rst        | 111 ++
 Documentation/admin-guide/gpio/index.rst      |   1 +
 drivers/gpio/Kconfig                          |   7 +
 drivers/gpio/Makefile                         |   1 +
 drivers/gpio/gpio-consumer.c                  | 954 ++++++++++++++++++
 5 files changed, 1074 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
new file mode 100644
index 000000000000..50ec57088e5a
@@ -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
+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
Documentation/admin-guide/gpio/index.rst +1 -0
index f6861ca16ffe..be89cb99c027 100644
@@ -8,6 +8,7 @@ gpio
     :maxdepth: 1
 
     gpio-aggregator
+    gpio-consumer
     sysfs
     gpio-mockup
     gpio-sim
drivers/gpio/Kconfig +7 -0
index e382dfebad7c..c152fcf6c54f 100644
@@ -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
index c3ac51d47aa9..268e8f65f3c8 100644
@@ -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 +954 -0

954 lines not on this page · raw message

Signature · 1 line
2.39.2
#1 lore raw
Quoted 34 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.

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.

...

Quoted 5 lines
drivers/gpio/gpio-consumer.c new file +5 -0
+	struct gpio_consumer_device *dev = lookup->parent;
+
+	guard(mutex)(&dev->lock);
+
+	return sprintf(page, "%s\n", lookup->key);

...

drivers/gpio/gpio-consumer.c line 344

Quoted 11 lines
drivers/gpio/gpio-consumer.c new file +11 -0
+static ssize_t
+gpio_consumer_lookup_config_offset_show(struct config_item *item, char *page)
+{
+	struct gpio_consumer_lookup *lookup = to_gpio_consumer_lookup(item);
+	struct gpio_consumer_device *dev = lookup->parent;
+	unsigned int offset;
+
+	scoped_guard(mutex, &dev->lock)
+		offset = lookup->offset;
+
+	return sprintf(page, "%d\n", offset);

Consistently it can be simplified same way

	guard(mutex)(&dev->lock);

	return sprintf(page, "%d\n", lookup->offset);

BUT. Thinking about this more. With guard() we put sprintf() inside the lock, which is suboptimal from runtime point of view. So, I think now that all these should actually use scoped_guard() rather than guard().

Quoted 1 line

+}

...

Quoted 3 lines
drivers/gpio/gpio-consumer.c new file +3 -0
+	guard(mutex)(&dev->lock);
+
+	return lookup->flags;

...

drivers/gpio/gpio-consumer.c line 538

Quoted 8 lines
drivers/gpio/gpio-consumer.c new file +7 -0
+static ssize_t
+gpio_consumer_lookup_config_transitory_show(struct config_item *item,
+					    char *page)
+{
 
+	enum gpio_lookup_flags flags;
+
+	flags = gpio_consumer_lookup_get_flags(item);

This is perfectly one line < 80 characters.

Quoted 2 lines
(quoted diff) +2 -0
+	return sprintf(page, "%s\n", flags & GPIO_TRANSITORY ? "1" : "0");
+}
Signature · 2 lines
With Best Regards,
Andy Shevchenko
#2 lore raw
Quoted 61 lines · Andy Shevchenko
Quoted 34 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.

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.

...

Quoted 5 lines
drivers/gpio/gpio-consumer.c new file +5 -0
+     struct gpio_consumer_device *dev = lookup->parent;
+
+     guard(mutex)(&dev->lock);
+
+     return sprintf(page, "%s\n", lookup->key);

...

drivers/gpio/gpio-consumer.c line 344

Quoted 11 lines
drivers/gpio/gpio-consumer.c new file +11 -0
+static ssize_t
+gpio_consumer_lookup_config_offset_show(struct config_item *item, char *page)
+{
+     struct gpio_consumer_lookup *lookup = to_gpio_consumer_lookup(item);
+     struct gpio_consumer_device *dev = lookup->parent;
+     unsigned int offset;
+
+     scoped_guard(mutex, &dev->lock)
+             offset = lookup->offset;
+
+     return sprintf(page, "%d\n", offset);

Consistently it can be simplified same way

Quoted 8 lines
        guard(mutex)(&dev->lock);

        return sprintf(page, "%d\n", lookup->offset);

BUT. Thinking about this more. With guard() we put sprintf() inside the lock, which is suboptimal from runtime point of view. So, I think now that all these should actually use scoped_guard() rather than guard().

Precisely why I used a scoped guard here. Same elsewhere.

Quoted 15 lines
Quoted 1 line

+}

...

Quoted 3 lines
drivers/gpio/gpio-consumer.c new file +3 -0
+     guard(mutex)(&dev->lock);
+
+     return lookup->flags;

...

Quoted 4 lines
drivers/gpio/gpio-consumer.c new file +4 -0
+static ssize_t
+gpio_consumer_lookup_config_transitory_show(struct config_item *item,
+                                         char *page)
+{
Quoted 9 lines

drivers/gpio/gpio-consumer.c line 399

Quoted 3 lines
drivers/gpio/gpio-consumer.c new file +3 -0
+     enum gpio_lookup_flags flags;
+
+     flags = gpio_consumer_lookup_get_flags(item);

This is perfectly one line < 80 characters.

Quoted 2 lines
(quoted diff) +2 -0
+     return sprintf(page, "%s\n", flags & GPIO_TRANSITORY ? "1" : "0");
+}

There's nothing wrong with setting the variable on another line though.

Bart

Quoted 5 lines

--
With Best Regards,
Andy Shevchenko

#3 lore raw
Quoted 3 lines · Bartosz Golaszewski
Quoted 1 line · Andy Shevchenko

On Tue, Aug 15, 2023 at 08:56:50PM +0200, Bartosz Golaszewski wrote:

...

Quoted 5 lines
Quoted 5 lines

drivers/gpio/gpio-consumer.c line 304

Quoted 5 lines
drivers/gpio/gpio-consumer.c new file +5 -0
+     struct gpio_consumer_device *dev = lookup->parent;
+
+     guard(mutex)(&dev->lock);
+
+     return sprintf(page, "%s\n", lookup->key);

(1)

...

Quoted 16 lines
Quoted 16 lines

drivers/gpio/gpio-consumer.c line 344

Quoted 11 lines
drivers/gpio/gpio-consumer.c new file +11 -0
+static ssize_t
+gpio_consumer_lookup_config_offset_show(struct config_item *item, char *page)
+{
+     struct gpio_consumer_lookup *lookup = to_gpio_consumer_lookup(item);
+     struct gpio_consumer_device *dev = lookup->parent;
+     unsigned int offset;
+
+     scoped_guard(mutex, &dev->lock)
+             offset = lookup->offset;
+
+     return sprintf(page, "%d\n", offset);

Consistently it can be simplified same way

        guard(mutex)(&dev->lock);
Quoted 8 lines
Quoted 6 lines
        return sprintf(page, "%d\n", lookup->offset);

BUT. Thinking about this more. With guard() we put sprintf() inside the lock, which is suboptimal from runtime point of view. So, I think now that all these should actually use scoped_guard() rather than guard().

Precisely why I used a scoped guard here. Same elsewhere.

So the 1) has to be amended then.

Quoted 1 line
Quoted 1 line
Quoted 1 line

+}

...

Quoted 7 lines
Quoted 5 lines

drivers/gpio/gpio-consumer.c line 399

Quoted 3 lines
drivers/gpio/gpio-consumer.c new file +3 -0
+     enum gpio_lookup_flags flags;
+
+     flags = gpio_consumer_lookup_get_flags(item);

This is perfectly one line < 80 characters.

There's nothing wrong with setting the variable on another line though.

Why do we need 3 LoCs instead of a single one? Do you increase your line statistics? :-) I really would like to know the rationale behind this.

Signature · 2 lines
With Best Regards,
Andy Shevchenko
#4 lore raw
Quoted 36 lines · Andy Shevchenko
Quoted 3 lines · Bartosz Golaszewski
Quoted 1 line · Andy Shevchenko

On Tue, Aug 15, 2023 at 08:56:50PM +0200, Bartosz Golaszewski wrote:

...

Quoted 5 lines
Quoted 5 lines

drivers/gpio/gpio-consumer.c line 304

Quoted 5 lines
drivers/gpio/gpio-consumer.c new file +5 -0
+     struct gpio_consumer_device *dev = lookup->parent;
+
+     guard(mutex)(&dev->lock);
+
+     return sprintf(page, "%s\n", lookup->key);

(1)

...

Quoted 18 lines
Quoted 18 lines

drivers/gpio/gpio-consumer.c line 344

Quoted 11 lines
drivers/gpio/gpio-consumer.c new file +11 -0
+static ssize_t
+gpio_consumer_lookup_config_offset_show(struct config_item *item, char *page)
+{
+     struct gpio_consumer_lookup *lookup = to_gpio_consumer_lookup(item);
+     struct gpio_consumer_device *dev = lookup->parent;
+     unsigned int offset;
+
+     scoped_guard(mutex, &dev->lock)
+             offset = lookup->offset;
+
+     return sprintf(page, "%d\n", offset);

Consistently it can be simplified same way

        guard(mutex)(&dev->lock);

        return sprintf(page, "%d\n", lookup->offset);
Quoted 8 lines
Quoted 6 lines
Quoted 4 lines

BUT. Thinking about this more. With guard() we put sprintf() inside the lock, which is suboptimal from runtime point of view. So, I think now that all these should actually use scoped_guard() rather than guard().

Precisely why I used a scoped guard here. Same elsewhere.

So the 1) has to be amended then.

No! lookup->key is a string stored in the lookup struct BUT protected by the consumer device's lock (it must be or else the lookup could get removed while it's being modified). Unless you want to duplicate the string in order to release the mutex earlier, it has to be locked until sprintf() returns.

Quoted 9 lines
Quoted 2 lines
Quoted 2 lines
Quoted 2 lines

+}

...

Quoted 4 lines
Quoted 4 lines
Quoted 3 lines
drivers/gpio/gpio-consumer.c new file +3 -0
+     enum gpio_lookup_flags flags;
+
+     flags = gpio_consumer_lookup_get_flags(item);
Quoted 7 lines
Quoted 3 lines
Quoted 1 line

This is perfectly one line < 80 characters.

There's nothing wrong with setting the variable on another line though.

Why do we need 3 LoCs instead of a single one? Do you increase your line statistics? :-) I really would like to know the rationale behind this.

No reason. It's just that churning out new patch versions changing minute coding style details is not conducive to "getting stuff done". At some point the code is *good enough*. It will never be perfect.

Bart

#5 lore raw
Quoted 6 lines · Bartosz Golaszewski
Quoted 4 lines · Andy Shevchenko
Quoted 3 lines · Bartosz Golaszewski
Quoted 1 line · Andy Shevchenko

On Tue, Aug 15, 2023 at 08:56:50PM +0200, Bartosz Golaszewski wrote:

...

Quoted 7 lines
Quoted 7 lines
Quoted 5 lines
Quoted 5 lines

drivers/gpio/gpio-consumer.c line 304

Quoted 5 lines
drivers/gpio/gpio-consumer.c new file +5 -0
+     struct gpio_consumer_device *dev = lookup->parent;
+
+     guard(mutex)(&dev->lock);
+
+     return sprintf(page, "%s\n", lookup->key);

(1)

...

Quoted 7 lines
Quoted 1 line

So the 1) has to be amended then.

No! lookup->key is a string stored in the lookup struct BUT protected by the consumer device's lock (it must be or else the lookup could get removed while it's being modified). Unless you want to duplicate the string in order to release the mutex earlier, it has to be locked until sprintf() returns.

Ah, good point, otherwise we would need a copy...

Signature · 2 lines
With Best Regards,
Andy Shevchenko