gpio: virtuser: new virtual driver
Subsystems (2)
PATCH v7 1/1 gpio: virtuser: new virtual driver
From: Bartosz Golaszewski <[email protected]>
The GPIO subsystem used to have 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) or described on the device-tree.
This driver is aimed as a helper in spotting any regressions in hot-unplug handling in GPIOLIB.
---
v6 -> v7:
- this is a complete rewrite of the original idea, the entire interface has changed so it warrants a new round of reviews
Link to v6: https://lore.kernel.org/linux-gpio/[email protected]/
v5 -> v6:
- initialize the flags temp variables at declaration and hopefully make Andy happy finally :)
v4 -> v5:
- add the gpio-consumer docs to the admin-guide/gpio/ index (reported by kernel test robot <[email protected]>)
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 :) )
v2 -> v3:
- use cleanup.h interfaces
- add some clarifying commets
- more minor code tweaks
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-virtuser.rst | 176 ++ Documentation/admin-guide/gpio/index.rst | 1 + drivers/gpio/Kconfig | 8 + drivers/gpio/Makefile | 1 + drivers/gpio/gpio-virtuser.c | 1809 +++++++++++++++++ 5 files changed, 1995 insertions(+) create mode 100644 Documentation/admin-guide/gpio/gpio-virtuser.rst create mode 100644 drivers/gpio/gpio-virtuser.c
Documentation/admin-guide/gpio/gpio-virtuser.rst new file +176 -0
@@ -0,0 +1,176 @@ +.. SPDX-License-Identifier: GPL-2.0-only + +Virtual GPIO Consumer +===================== + +The virtual GPIO Consumer module allows users to instantiate virtual devices +that request GPIOs and then control their behavior over sysfs. Virtual consumer +devices can be instantiated from device-tree or over configfs. + +A virtual consumer uses the driver-facing GPIO APIs and allows to cover it with +automated tests driven by user-space. The GPIOs are requested using +``gpiod_get_array()`` and so we support multiple GPIOs per connector ID. + +Creating GPIO consumers +----------------------- + +The gpio-consumer module registers a configfs subsystem called +``'gpio-virtuser'``. 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-virtuser`` + +This is the top directory of the gpio-consumer configfs tree. + +**Group:** ``/config/gpio-consumer/example-name`` + +**Attribute:** ``/config/gpio-consumer/example-name/live`` + +**Attribute:** ``/config/gpio-consumer/example-name/dev_name`` + +This is a directory representing a GPIO consumer device. + +The read-only ``dev_name`` attribute exposes the name of the device as it will +appear in the system on the platform bus. This is useful for locating the +associated sysfs group under ``/sys/bus/platform/devices/$dev_name``. + +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 +--------------------------- + +Users can create a number of configfs groups under the device group: + +**Group:** ``/config/gpio-consumer/example-name/con_id`` + +The ``'con_id'`` directory represents a single GPIO lookup and its value maps +to the ``'con_id'`` argument of the ``gpiod_get()`` function. For example: +``con_id`` == ``'reset'`` maps to the ``reset-gpios`` device property. + +Users can assign a number of GPIOs to each lookup. Each GPIO is a sub-directory +with a user-defined name under the ``'con_id'`` group. + +**Attribute:** ``/config/gpio-consumer/example-name/con_id/0/key`` + +**Attribute:** ``/config/gpio-consumer/example-name/con_id/0/offset`` + +**Attribute:** ``/config/gpio-consumer/example-name/con_id/0/drive`` + +**Attribute:** ``/config/gpio-consumer/example-name/con_id/0/pull`` + +**Attribute:** ``/config/gpio-consumer/example-name/con_id/0/active_low`` + +**Attribute:** ``/config/gpio-consumer/example-name/con_id/0/transitory`` + +This is a group describing a single GPIO in the ``con_id-gpios`` property. + +For virtual consumers created using configfs we use machine lookup tables so +this group can be considered as a mapping between the filesystem and the fields +of a single entry in ``'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 device. 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-virtuser"`` with at least one property following the +standardized GPIO pattern. + +An example device-tree code defining a virtual GPIO consumer: + +.. code-block :: none + + gpio-virt-consumer { + compatible = "gpio-virtuser"; + + foo-gpios = <&gpio0 5 GPIO_ACTIVE_LOW>, <&gpio1 2 0>; + bar-gpios = <&gpio0 6 0>; + }; + +Controlling virtual GPIO consumers +---------------------------------- + +Once active, the device will export sysfs attributes for controlling GPIO +arrays as well as each requested GPIO line separately. Let's consider the +following device property: ``foo-gpios = <&gpio0 0 0>, <&gpio0 4 0>;``. + +The following sysfs attribute groups will be created: + +**Group:** ``/sys/bus/platform/device/$dev_name/gpiod:foo/`` + +This is the group that will contain the attributes for the entire GPIO array. + +**Attribute:** ``/sys/bus/platform/device/$dev_name/gpiod:foo/values`` + +**Attribute:** ``/sys/bus/platform/device/$dev_name/gpiod:foo/values_atomic`` + +Both attributes allow to read and set arrays of GPIO values. User must pass +exactly the number of values that the array contains in the form of a string +containing zeroes and ones representing inactive and active GPIO states +respectively. In this example: ``echo 11 > values``. + +The ``values_atomic`` attribute works the same as ``values`` but the kernel +will execute the GPIO driver callbacks in interrupt context. + +**Group:** ``/sys/bus/platform/device/$dev_name/gpiod:foo:$index/`` + +This is a group that represents a single GPIO with ``$index`` being its offset +in the array. + +**Attribute:** ``/sys/bus/platform/device/$dev_name/gpiod:foo:$index/consumer`` + +Allows to set and read the consumer label of the GPIO line. + +**Attribute:** ``/sys/bus/platform/device/$dev_name/gpiod:foo:$index/debounce`` + +Allows to set and read the debounce period of the GPIO line. + +**Attribute:** ``/sys/bus/platform/device/$dev_name/gpiod:foo:$index/direction`` + +**Attribute:** ``/sys/bus/platform/device/$dev_name/gpiod:foo:$index/direction_atomic`` + +These two attributes allow to set the direction of the GPIO line. They accept +"input" and "output" as values. The atomic variant executes the driver callback +in interrupt context. + +**Attribute:** ``/sys/bus/platform/device/$dev_name/gpiod:foo:$index/interrupts`` + +If the line is requested in input mode, writing ``1`` to this attribute will +make the module listen for edge interrupts on the GPIO. Writing ``0`` disables +the monitoring. Reading this attribute returns the current number of registered +interrupts (both edges). + +**Attribute:** ``/sys/bus/platform/device/$dev_name/gpiod:foo:$index/value`` + +**Attribute:** ``/sys/bus/platform/device/$dev_name/gpiod:foo:$index/value_atomic`` + +Both attributes allow to read and set values of individual requested GPIO lines. +They accept the following values: ``active``, ``1``, ``inactive`` and ``0``.
Documentation/admin-guide/gpio/index.rst +1 -0
@@ -10,6 +10,7 @@ GPIO Character Device Userspace API <../../userspace-api/gpio/chardev> gpio-aggregator gpio-sim + gpio-virtuser Obsolete APIs <obsolete> .. only:: subproject and html
drivers/gpio/Kconfig +8 -0
@@ -1889,6 +1889,14 @@ config GPIO_SIM This enables the GPIO simulator - a configfs-based GPIO testing driver. +config GPIO_VIRTUSER + tristate "GPIO Virtual User Testing Module" + select CONFIGFS_FS + select IRQ_WORK + help + This enables the configurable, configfs-based virtual GPIO consumer + testing driver. + endmenu endif
drivers/gpio/Makefile +1 -0
@@ -182,6 +182,7 @@ obj-$(CONFIG_GPIO_UNIPHIER) += gpio-uniphier.o obj-$(CONFIG_GPIO_VF610) += gpio-vf610.o obj-$(CONFIG_GPIO_VIPERBOARD) += gpio-viperboard.o obj-$(CONFIG_GPIO_VIRTIO) += gpio-virtio.o +obj-$(CONFIG_GPIO_VIRTUSER) += gpio-virtuser.o obj-$(CONFIG_GPIO_VISCONTI) += gpio-visconti.o obj-$(CONFIG_GPIO_VX855) += gpio-vx855.o obj-$(CONFIG_GPIO_WCD934X) += gpio-wcd934x.o
drivers/gpio/gpio-virtuser.c new file +1809 -0
1,809 lines not on this page · raw message
Signature · 1 line
2.40.1
drivers/gpio/gpio-virtuser.c line 593
Quoted 9 lines · Bartosz Golaszewski
drivers/gpio/gpio-virtuser.c new file +9
-0+static ssize_t +gpio_virtuser_sysfs_consumer_store (struct device *dev, +struct device_attribute *attr, +const char *buf,size_t len) +{ +struct gpio_virtuser_line_data *data =to_gpio_virtuser_data (attr); +int ret; + +if (strlen (buf) >GPIO_CONSUMER_NAME_MAX_LEN )
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This is off by one. strlen() doesn't count the NUL terminator.
drivers/gpio/gpio-virtuser.c line 602
Quoted 9 lines
drivers/gpio/gpio-virtuser.c new file +9
-0+return -EINVAL ; + +guard (mutex)(&data->consumer_lock); + + ret =gpiod_set_consumer_name (data->desc, buf); +if (ret) +return ret; + +sprintf (data->consumer, buf);
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
It results in a buffer overflow here.
drivers/gpio/gpio-virtuser.c line 218
Quoted 3 lines
drivers/gpio/gpio-virtuser.c new file +3
-0+ +return len; +}
regards,
dan carpenter
Mon, May 27, 2024 at 04:40:54PM +0200, Bartosz Golaszewski kirjoitti:
Quoted 20 lines
From: Bartosz Golaszewski <[email protected]>
The GPIO subsystem used to have 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) or described on the device-tree.
This driver is aimed as a helper in spotting any regressions in hot-unplug handling in GPIOLIB.
...
Quoted 1 line
User must pass exactly the number of values that the array contains
Can't we assume non-active values for the rest if less than needed were provided? For more than that, why do we care?
...
Quoted 7 lines
drivers/gpio/gpio-virtuser.c new file +7
-0+#include <linux/atomic.h> +#include <linux/bitmap.h> +#include <linux/cleanup.h> +#include <linux/completion.h> +#include <linux/configfs.h> +#include <linux/device.h> +#include <linux/err.h>
drivers/gpio/gpio-virtuser.c line 23
Quoted 10 lines
drivers/gpio/gpio-virtuser.c new file +7
-0+#include <linux/gpio/consumer.h> +#include <linux/gpio/driver.h> +#include <linux/gpio/machine.h> +#include <linux/idr.h> +#include <linux/interrupt.h> +#include <linux/irq_work.h> +#include <linux/kernel.h>
Do you need this?
Quoted 5 lines
drivers/gpio/gpio-virtuser.c new file +5
-0+#include <linux/limits.h> +#include <linux/list.h> +#include <linux/lockdep.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h>
drivers/gpio/gpio-virtuser.c line 37
Quoted 10 lines
drivers/gpio/gpio-virtuser.c new file +9
-0+#include <linux/mutex.h> +#include <linux/notifier.h> +#include <linux/of.h> +#include <linux/overflow.h> +#include <linux/platform_device.h> +#include <linux/printk.h> +#include <linux/property.h> +#include <linux/slab.h> +#include <linux/string.h>
Implied by string_helpers.h
Quoted 3 lines
drivers/gpio/gpio-virtuser.c new file +3
-0+#include <linux/string_helpers.h> +#include <linux/sysfs.h> +#include <linux/types.h>
...
Quoted 6 lines
drivers/gpio/gpio-virtuser.c new file +6
-0+struct gpio_virtuser_line_array_data { +struct gpio_descs *descs; +struct kobject *kobj; +struct attribute_group *attr_group; +}; +
drivers/gpio/gpio-virtuser.c line 78
Quoted 10 lines
drivers/gpio/gpio-virtuser.c new file +10
-0+struct gpio_virtuser_line_data { +struct gpio_desc *desc; +struct kobject *kobj; +struct attribute_group *attr_group; +char consumer[GPIO_CONSUMER_NAME_MAX_LEN ]; +struct mutex consumer_lock; +unsigned int debounce; +atomic_t irq; +atomic_t irq_count; +};
Maybe
struct gpio_virtuser_sysfs_data {
union {
struct gpio_desc *desc;
struct gpio_descs *descs;
};
struct kobject *kobj;
struct attribute_group *attr_group;
};struct gpio_virtuser_line_array_data {
struct gpio_virtuser_sysfs_data sd;
};struct gpio_virtuser_line_data {
struct gpio_virtuser_sysfs_data sd;
char consumer[GPIO_CONSUMER_NAME_MAX_LEN];
struct mutex consumer_lock;
unsigned int debounce;
atomic_t irq;
atomic_t irq_count;
};?
...
drivers/gpio/gpio-virtuser.c line 83
Quoted 4 lines
drivers/gpio/gpio-virtuser.c new file +4
-0+struct gpio_virtuser_attr_ctx { +struct device_attribute dev_attr; +void *data; +};
struct dev_ext_attribute ?
...
drivers/gpio/gpio-virtuser.c line 103
Quoted 6 lines
drivers/gpio/gpio-virtuser.c new file +6
-0+struct gpio_virtuser_attr_descr { +const char *name; +ssize_t (*show)(struct device *,struct device_attribute *,char *); +ssize_t (*store)(struct device *,struct device_attribute *, +const char *,size_t ); +};
struct device_attribute ? (Yes, I know that that one is a bit bigger but benefit is that we have some code that you may reuse)
...
drivers/gpio/gpio-virtuser.c line 150
Quoted 11 lines
drivers/gpio/gpio-virtuser.c new file +11
-0+static ssize_t gpio_virtuser_sysfs_emit_value_array (char *buf, +unsigned long *values, +size_t num_values) +{ +ssize_t len =0 ; +size_t i; + +for (i =0 ; i < num_values; i++) + len +=sysfs_emit_at (buf, len,"%d" , +test_bit (i, values) ?1 :0 ); +return len +sysfs_emit_at (buf, len,"\n" );
Why not use %pb?
Quoted 1 line
+}
...
drivers/gpio/gpio-virtuser.c line 158
Quoted 6 lines
drivers/gpio/gpio-virtuser.c new file +6
-0+static int gpio_virtuser_sysfs_parse_value_array (const char *buf,size_t len, +unsigned long *values) +{ +size_t i; + +for (i =0 ; i < len; i++) {
Perhaps
bool val; int ret; ret = kstrtobool(...); if (ret) return ret; assign_bit(...); // btw, why atomic?
drivers/gpio/gpio-virtuser.c line 165
Quoted 8 lines
drivers/gpio/gpio-virtuser.c new file +7
-0+if (buf[i] =='0' ) +clear_bit (i, values); +else if (buf[i] =='1' ) +set_bit (i, values); +else +return -EINVAL ; + }
BUT, why not bitmap_parse()?
Quoted 2 lines
(quoted diff) +2
-0+ return 0; +}
...
Review
Quoted 2 lines
(quoted diff) +2
-0+ unsigned long *values __free(bitmap) = bitmap_alloc(descs->ndescs, + GFP_KERNEL);
Perhaps
unsigned long *values __free(bitmap) = bitmap_alloc(descs->ndescs, GFP_KERNEL);
...
Review
Quoted 2 lines
(quoted diff) +2
-0+ unsigned long *values __free(bitmap) = bitmap_zalloc(descs->ndescs, + GFP_KERNEL);
In the similar way?
...
Review
Quoted 2 lines
(quoted diff) +2
-0+ unsigned long *values __free(bitmap) = bitmap_zalloc(descs->ndescs, + GFP_KERNEL);
Ditto.
...
drivers/gpio/gpio-virtuser.c line 304
Quoted 3 lines
drivers/gpio/gpio-virtuser.c new file +3
-0+{ +return sysfs_emit (buf,"%s\n" , + dir ==GPIO_LINE_DIRECTION_IN ?"input" :"output" );
I think this maybe transformed to something like str_input_output() in string_choices.h (and you don't even need to include that as it's implied by string_helpers.h)
Quoted 1 line
+}
...
Quoted 5 lines
drivers/gpio/gpio-virtuser.c new file +5
-0+static int gpio_virtuser_parse_direction (const char *buf,int *dir,int *val) +{ +if (sysfs_streq (buf,"input" )) { + *dir =GPIO_LINE_DIRECTION_IN ; +return 0 ;
drivers/gpio/gpio-virtuser.c line 329
Quoted 10 lines
drivers/gpio/gpio-virtuser.c new file +10
-0+ } + +if (sysfs_streq (buf,"output-high" )) + *val =1 ; +else if (sysfs_streq (buf,"output-low" )) + *val =0 ; +else +return -EINVAL ; + + *dir =GPIO_LINE_DIRECTION_OUT ;
This can be transformed to use sysfs_match_string() with
static const char * const dirs[] = { "output-low", "output-high", "input" };
int ret; ret = sysfs_match_string(...); if (ret < 0) return ret; *val = ret; *dir = ret == 2 ? GPIO_LINE_DIRECTION_IN : GPIO_LINE_DIRECTION_OUT;
And with this approach it even not clear why do you need dir and val to be separated here (esp. if we add a enum like
GPIO_VIRTUSER_OUT_LOW,
GPIO_VIRTUSER_OUT_HIGH,
GPIO_VIRTUSER_IN,
(with it the string array can also be indexed).
Quoted 2 lines
(quoted diff) +2
-0+ return 0; +}
...
drivers/gpio/gpio-virtuser.c line 448
Quoted 13 lines
drivers/gpio/gpio-virtuser.c new file +12
-0+static int gpio_virtuser_parse_value (const char *buf) +{ +int value, ret; + + value =sysfs_match_string (gpio_virtuser_sysfs_value_strings, buf); +if (value <0 ) { +/* Can be 0 or 1 too. */ + ret =kstrtoint (buf,0 , &value); +if (ret) +return ret; +if (value !=0 && value !=1 ) +return -EINVAL ;
Why not kstrtobool()?
Quoted 4 lines
drivers/gpio/gpio-virtuser.c new file +4
-0+ } + +return value; +}
...
drivers/gpio/gpio-virtuser.c line 561
Quoted 1 line
drivers/gpio/gpio-virtuser.c new file +1
-0+ ret =kstrtouint (buf,10 , &debounce);
Why restrict to decimal?
Quoted 2 lines
(quoted diff) +2
-0+ if (ret) + return ret;
...
drivers/gpio/gpio-virtuser.c line 594
Quoted 10 lines
drivers/gpio/gpio-virtuser.c new file +9
-0+static ssize_t +gpio_virtuser_sysfs_consumer_store (struct device *dev, +struct device_attribute *attr, +const char *buf,size_t len) +{ +struct gpio_virtuser_line_data *data =to_gpio_virtuser_data (attr); +int ret; +if (strlen (buf) >GPIO_CONSUMER_NAME_MAX_LEN ) +return -EINVAL ;
You don't need this if you use strscpy() below and check its returned value.
Quoted 10 lines
drivers/gpio/gpio-virtuser.c new file +10
-0+guard (mutex)(&data->consumer_lock); + + ret =gpiod_set_consumer_name (data->desc, buf); +if (ret) +return ret; + +sprintf (data->consumer, buf); + +return len; +}
...
drivers/gpio/gpio-virtuser.c line 769
Quoted 2 lines
drivers/gpio/gpio-virtuser.c new file +2
-0+ data->attr_group->name =devm_kasprintf (dev,GFP_KERNEL , +"gpiod:%s" , id);
Why two lines?
Quoted 2 lines
(quoted diff) +2
-0+ if (!data->attr_group->name) + return -ENOMEM;
...
drivers/gpio/gpio-virtuser.c line 824
Quoted 2 lines
drivers/gpio/gpio-virtuser.c new file +2
-0+ ret =devm_add_action_or_reset (dev, gpio_virtuser_mutex_destroy, + &data->consumer_lock);
Don't we have devm_mutex_init() (git tag --contains shows v6.10-rc1 to me)
Quoted 1 line
(quoted diff) +1
-0+ return ret;
...
drivers/gpio/gpio-virtuser.c line 861
Quoted 3 lines
drivers/gpio/gpio-virtuser.c new file +3
-0+static int gpio_virtuser_prop_is_gpio (struct property *prop) +{ +char *dash =strpbrk (prop->name,"-" );
Why not strrchr() ?
drivers/gpio/gpio-virtuser.c line 863
Quoted 1 line
drivers/gpio/gpio-virtuser.c new file +1
-0+return dash &&strcmp (dash,"-gpios" ) ==0 ;
Can't we reuse the suffix from the array from the gpiolib internal header? Also I don't like the form of '-' in the line. "gpios" is good and chance that linker deduplicates the same string if it occurs somewhere else in the binary (in case this goes with =y in .config).
Quoted 1 line
+}
...
Quoted 5 lines
+/*
+ * If this is an OF-based system, then we iterate over properties and consider
+ * all whose names end in "-gpios". For configfs we expect an additional string
+ * array property - "gpio-virtuser,ids" - containing the list of all GPIO IDs
+ * to request.
Why not any other system? What's wrong for having this available for ACPI, for example? Okay, I see that this is probably due to absence of API.
OTOH the last call in the function assumes non-OF cases. Why can't we have the same approach in both?
drivers/gpio/gpio-virtuser.c line 874
Quoted 4 lines
+ */
drivers/gpio/gpio-virtuser.c new file +3
-0+static int gpio_virtuser_count_ids (struct device *dev) +{ +struct fwnode_handle *fwnode =dev_fwnode (dev);
Why? This function is mostly OF one, make it simpler.
struct device_node *np = dev_of_node(dev);
drivers/gpio/gpio-virtuser.c line 878
Quoted 4 lines
drivers/gpio/gpio-virtuser.c new file +3
-0+struct property *prop; +int ret =0 ; +if (is_of_node (fwnode)) {
Instead of this check...
if (np) {...can be used.
Review
Quoted 1 line
(quoted diff) +1
-0+ for_each_property_of_node(to_of_node(fwnode), prop) {
for_each_property_of_node(np, prop) {drivers/gpio/gpio-virtuser.c line 881
Quoted 2 lines
drivers/gpio/gpio-virtuser.c new file +2
-0+if (gpio_virtuser_prop_is_gpio (prop)) + ++ret;
Why pre-increment?
Quoted 7 lines
drivers/gpio/gpio-virtuser.c new file +5
-0+ } +return ret; + } +return device_property_string_array_count (dev,"gpio-virtuser,ids" ); +}
...
drivers/gpio/gpio-virtuser.c line 899
Quoted 10 lines
drivers/gpio/gpio-virtuser.c new file +10
-0+static int gpio_virtuser_get_ids (struct device *dev,const char **ids, +int num_ids) +{ +struct fwnode_handle *fwnode =dev_fwnode (dev); +struct property *prop; +size_t pos =0 , diff; +char *dash, *tmp; + +if (is_of_node (fwnode)) { +for_each_property_of_node (to_of_node (fwnode), prop) {
As per above function.
drivers/gpio/gpio-virtuser.c line 907
Quoted 8 lines
drivers/gpio/gpio-virtuser.c new file +8
-0+if (!gpio_virtuser_prop_is_gpio (prop)) +continue ; + + dash =strpbrk (prop->name,"-" ); + diff = dash - prop->name; + + tmp =devm_kmemdup (dev, prop->name, diff +1 , +GFP_KERNEL );
devm_kstrndup() is not okay? Okay, we don't have it (yet?), but at least I would rather expect wrapped kstrndup() than this.
Quoted 13 lines
drivers/gpio/gpio-virtuser.c new file +13
-0+if (!tmp) +return -ENOMEM ; + + tmp[diff] ='\0' ; + ids[pos++] = tmp; + } + +return 0 ; + } + +return device_property_read_string_array (dev,"gpio-virtuser,ids" , + ids, num_ids); +}
...
Quoted 6 lines
drivers/gpio/gpio-virtuser.c new file +6
-0+static int gpio_virtuser_probe (struct platform_device *pdev) +{ +struct device *dev = &pdev->dev; +struct gpio_descs *descs; +int ret, num_ids =0 , i; +const char **ids;
drivers/gpio/gpio-virtuser.c line 937
Quoted 10 lines
drivers/gpio/gpio-virtuser.c new file +10
-0+unsigned int j; + + num_ids =gpio_virtuser_count_ids (dev); +if (num_ids <0 ) +return dev_err_probe (dev, num_ids, +"Failed to get the number of GPIOs to request\n" ); + +if (num_ids ==0 ) { +dev_err (dev,"No GPIO IDs specified\n" ); +return -EINVAL ;
It's okay to
return dev_err_probe(...);
with know error code.
Quoted 38 lines
drivers/gpio/gpio-virtuser.c new file +38
-0+ } + + ids =devm_kcalloc (dev, num_ids,sizeof (*ids),GFP_KERNEL ); +if (!ids) +return -ENOMEM ; + + ret =gpio_virtuser_get_ids (dev, ids, num_ids); +if (ret <0 ) +return dev_err_probe (dev, ret, +"Failed to get the IDs of GPIOs to request\n" ); + +for (i =0 ; i < num_ids; i++) { + descs =devm_gpiod_get_array (dev, ids[i],GPIOD_ASIS ); +if (IS_ERR (descs)) +return dev_err_probe (dev,PTR_ERR (descs), +"Failed to request the '%s' GPIOs\n" , + ids[i]); + + ret =gpio_virtuser_sysfs_init_line_array_attrs (dev, descs, + ids[i]); +if (ret) +return dev_err_probe (dev, ret, +"Failed to setup the sysfs array interface for the '%s' GPIOs\n" , + ids[i]); + +for (j =0 ; j < descs->ndescs; j++) { + ret =gpio_virtuser_sysfs_init_line_attrs (dev, + descs->desc[j], + ids[i], j); +if (ret) +return dev_err_probe (dev, ret, +"Failed to setup the sysfs line interface for the '%s' GPIOs\n" , + ids[i]); + } + } + +return 0 ; +}
...
drivers/gpio/gpio-virtuser.c line 1017
Quoted 11 lines
drivers/gpio/gpio-virtuser.c new file +11
-0+static int gpio_virtuser_bus_notifier_call (struct notifier_block *nb, +unsigned long action,void *data) +{ +struct gpio_virtuser_device *vdev; +struct device *dev = data; +char devname[32 ]; + + vdev = container_of(nb, struct gpio_virtuser_device, bus_notifier); +snprintf (devname,sizeof (devname),"gpio-virtuser.%d" , vdev->id); + +if (strcmp (dev_name (dev), devname))
if (!device_match_name(...))
Quoted 16 lines
drivers/gpio/gpio-virtuser.c new file +16
-0+return NOTIFY_DONE ; + +switch (action) { +case BUS_NOTIFY_BOUND_DRIVER : + vdev->driver_bound = true; +break ; +case BUS_NOTIFY_DRIVER_NOT_BOUND : + vdev->driver_bound = false; +break ; +default : +return NOTIFY_DONE ; + } + +complete (&vdev->probe_completion); +return NOTIFY_OK ; +}
...
drivers/gpio/gpio-virtuser.c line 1110
Quoted 9 lines
drivers/gpio/gpio-virtuser.c new file +9
-0+static ssize_t +gpio_virtuser_lookup_entry_config_key_store (struct config_item *item, +const char *page,size_t count) +{ +struct gpio_virtuser_lookup_entry *entry = +to_gpio_virtuser_lookup_entry (item); +struct gpio_virtuser_device *dev = entry->parent->parent; + +char *key =kstrndup (skip_spaces (page), count,GFP_KERNEL );
Missing __free() ?
Quoted 15 lines
drivers/gpio/gpio-virtuser.c new file +13
-0+if (!key) +return -ENOMEM ; +strim (key); +guard (mutex)(&dev->lock); + +if (gpio_virtuser_device_is_live (dev)) +return -EBUSY ; + +kfree (entry->key); + entry->key =no_free_ptr (key); + +return count; +}
...
Quoted 4 lines
drivers/gpio/gpio-virtuser.c new file +4
-0+if (sysfs_streq (page,"pull-up" )) { + entry->flags &= ~(GPIO_PULL_DOWN |GPIO_PULL_DISABLE ); + entry->flags |=GPIO_PULL_UP ; + }else if (sysfs_streq (page,"pull-down" )) {
drivers/gpio/gpio-virtuser.c line 1282
Quoted 10 lines
drivers/gpio/gpio-virtuser.c new file +10
-0+ entry->flags &= ~(GPIO_PULL_UP |GPIO_PULL_DISABLE ); + entry->flags |=GPIO_PULL_DOWN ; + }else if (sysfs_streq (page,"pull-disabled" )) { + entry->flags &= ~(GPIO_PULL_UP |GPIO_PULL_DOWN ); + entry->flags |=GPIO_PULL_DISABLE ; + }else if (sysfs_streq (page,"as-is" )) { + entry->flags &= ~(GPIO_PULL_UP |GPIO_PULL_DOWN | +GPIO_PULL_DISABLE ); + }else { + count = -EINVAL ;
return -EINVAL won't (ab)use count semantics.
Quoted 3 lines
drivers/gpio/gpio-virtuser.c new file +3
-0+ } + +return count;
...
drivers/gpio/gpio-virtuser.c line 1296
Quoted 1 line
drivers/gpio/gpio-virtuser.c new file +1
-0+return sprintf (page,"%s\n" , flags &GPIO_ACTIVE_LOW ?"1" :"0" );
Somewhere above you used %d for very similar situation, why %s here? Or why "5d" there?
...
drivers/gpio/gpio-virtuser.c line 1335
Quoted 1 line
drivers/gpio/gpio-virtuser.c new file +1
-0+return sprintf (page,"%s\n" , flags &GPIO_TRANSITORY ?"1" :"0" );
Ditto.
...
drivers/gpio/gpio-virtuser.c line 1405
Quoted 1 line
drivers/gpio/gpio-virtuser.c new file +1
-0+return sprintf (page,"%c\n" , live ?'1' :'0' );
Wow! Third type of the same.
...
drivers/gpio/gpio-virtuser.c line 1439
Quoted 7 lines
drivers/gpio/gpio-virtuser.c new file +6
-0+ struct gpiod_lookup_table *table __free(kfree) = +kzalloc (struct_size (table, table, num_entries +1 ),GFP_KERNEL ); +if (!table) +return -ENOMEM ; + table->dev_id =kasprintf (GFP_KERNEL ,"gpio-virtuser.%d" , + dev->id);
Perfectly one line in comparison with the few lines above).
Quoted 2 lines
drivers/gpio/gpio-virtuser.c new file +2
-0+if (!table->dev_id) +return -ENOMEM ;
...
drivers/gpio/gpio-virtuser.c line 1451
Quoted 2 lines
drivers/gpio/gpio-virtuser.c new file +2
-0+ curr->chip_hwnum = entry->offset <0 + ?U16_MAX : entry->offset;
Can we leave ? on the previous line?
...
drivers/gpio/gpio-virtuser.c line 1453
Quoted 1 line
drivers/gpio/gpio-virtuser.c new file +1
-0+ ++i;
Why pre-increment?
...
drivers/gpio/gpio-virtuser.c line 1469
Quoted 7 lines
drivers/gpio/gpio-virtuser.c new file +7
-0+static struct fwnode_handle * +gpio_virtuser_make_device_swnode (struct gpio_virtuser_device *dev) +{ +struct property_entry properties[2 ]; +struct gpio_virtuser_lookup *lookup; +size_t num_ids; +int i =0 ;
Why signed? And in all this kind of case, I would split assignment...
drivers/gpio/gpio-virtuser.c line 1478
Quoted 8 lines
drivers/gpio/gpio-virtuser.c new file +8
-0+memset (properties,0 ,sizeof (properties)); + + num_ids =list_count_nodes (&dev->lookup_list); + char **ids __free(kfree) = kcalloc(num_ids + 1, sizeof(*ids), +GFP_KERNEL ); +if (!ids) +return ERR_PTR (-ENOMEM ); +
To be here, that the reader will see immediately (close enough) what is the initial values. Moreover this code will be robuse against changes in between (if i become reusable).
Quoted 8 lines
drivers/gpio/gpio-virtuser.c new file +8
-0+list_for_each_entry (lookup, &dev->lookup_list, siblings) + ids[i++] = lookup->con_id; + + properties[0 ] =PROPERTY_ENTRY_STRING_ARRAY_LEN ("gpio-virtuser,ids" , + ids, num_ids); + +return fwnode_create_software_node (properties,NULL ); +}
...
drivers/gpio/gpio-virtuser.c line 1571
Quoted 4 lines
drivers/gpio/gpio-virtuser.c new file +4
-0+guard (mutex)(&dev->lock); + +if (live ==gpio_virtuser_device_is_live (dev)) + ret = -EPERM ;
With guard in place, just return directly, ...
drivers/gpio/gpio-virtuser.c line 1572
Quoted 1 line
drivers/gpio/gpio-virtuser.c new file +1
-0+else if (live)
...drop 'else'...
drivers/gpio/gpio-virtuser.c line 1574
Quoted 2 lines
drivers/gpio/gpio-virtuser.c new file +2
-0+ ret =gpio_virtuser_device_activate (dev); + else
...ditto...
drivers/gpio/gpio-virtuser.c line 1577
Quoted 3 lines
drivers/gpio/gpio-virtuser.c new file +3
-0+gpio_virtuser_device_deactivate (dev); + +return ret ?: count;
...and simply return count here.
...
drivers/gpio/gpio-virtuser.c line 1736
Quoted 2 lines
drivers/gpio/gpio-virtuser.c new file +2
-0+ struct gpio_virtuser_device *dev __free(kfree) = kzalloc(sizeof(*dev), +GFP_KERNEL );
struct gpio_virtuser_device *dev __free(kfree) = kzalloc(sizeof(*dev), GFP_KERNEL);
Quoted 2 lines
drivers/gpio/gpio-virtuser.c new file +2
-0+if (!dev) +return ERR_PTR (-ENOMEM );
...
drivers/gpio/gpio-virtuser.c line 1779
Quoted 5 lines
drivers/gpio/gpio-virtuser.c new file +4
-0+ ret =platform_driver_register (&gpio_virtuser_driver); +if (ret) { +pr_err ("Failed to register the platform driver: %d\n" , + ret);
I would keep one line.
Quoted 2 lines
(quoted diff) +2
-0+ return ret; + }
Signature · 2 lines
With Best Regards, Andy Shevchenko
Quoted 23 lines · Andy Shevchenko
Mon, May 27, 2024 at 04:40:54PM +0200, Bartosz Golaszewski kirjoitti:
Quoted 14 lines
From: Bartosz Golaszewski <[email protected]>
The GPIO subsystem used to have 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.
Quoted 6 lines
This is a proposition of adding a new virtual driver - a configurable GPIO consumer that can be configured over configfs (similarly to gpio-sim) or described on the device-tree.
This driver is aimed as a helper in spotting any regressions in hot-unplug handling in GPIOLIB.
Quoted 7 lines
...
Quoted 1 line
User must pass exactly the number of values that the array contains
Can't we assume non-active values for the rest if less than needed were provided? For more than that, why do we care?
Honestly, what good would it do? It would just be more confusing IMO.
Quoted 13 lines
...
Quoted 10 lines
drivers/gpio/gpio-virtuser.c new file +10
-0+#include <linux/atomic.h> +#include <linux/bitmap.h> +#include <linux/cleanup.h> +#include <linux/completion.h> +#include <linux/configfs.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/gpio/consumer.h> +#include <linux/gpio/driver.h> +#include <linux/gpio/machine.h>
Quoted 8 lines
drivers/gpio/gpio-virtuser.c line 23
Quoted 6 lines
drivers/gpio/gpio-virtuser.c new file +4
-0+#include <linux/idr.h> +#include <linux/interrupt.h> +#include <linux/irq_work.h> +#include <linux/kernel.h> Do you need this?
ARRAY_SIZE() used to live here when I first wrote this but it was since moved. I'll drop this.
Quoted 15 lines
Quoted 14 lines
drivers/gpio/gpio-virtuser.c new file +13
-0+#include <linux/limits.h> +#include <linux/list.h> +#include <linux/lockdep.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/notifier.h> +#include <linux/of.h> +#include <linux/overflow.h> +#include <linux/platform_device.h> +#include <linux/printk.h> +#include <linux/property.h> +#include <linux/slab.h>
Quoted 4 lines
Quoted 1 line
+#include <
linux/string.h>Implied by
string_helpers.h
Yeah, but we still use symbols directly from string.h, we shouldn't depend on implicit includes.
Quoted 49 lines
Quoted 3 lines
drivers/gpio/gpio-virtuser.c new file +3
-0+#include <linux/string_helpers.h> +#include <linux/sysfs.h> +#include <linux/types.h> ...
Quoted 6 lines
drivers/gpio/gpio-virtuser.c new file +6
-0+struct gpio_virtuser_line_array_data { +struct gpio_descs *descs; +struct kobject *kobj; +struct attribute_group *attr_group; +}; +drivers/gpio/gpio-virtuser.c line 78
Quoted 10 lines
drivers/gpio/gpio-virtuser.c new file +10
-0+struct gpio_virtuser_line_data { +struct gpio_desc *desc; +struct kobject *kobj; +struct attribute_group *attr_group; +char consumer[GPIO_CONSUMER_NAME_MAX_LEN ]; +struct mutex consumer_lock; +unsigned int debounce; +atomic_t irq; +atomic_t irq_count; +};Maybe
struct gpio_virtuser_sysfs_data { union { struct gpio_desc *desc; struct gpio_descs *descs; }; struct kobject *kobj; struct attribute_group *attr_group; };struct gpio_virtuser_line_array_data { struct gpio_virtuser_sysfs_data sd; };struct gpio_virtuser_line_data { struct gpio_virtuser_sysfs_data sd; char consumer[GPIO_CONSUMER_NAME_MAX_LEN]; struct mutex consumer_lock; unsigned int debounce; atomic_t irq; atomic_t irq_count; };?
Quoted 9 lines
...
drivers/gpio/gpio-virtuser.c line 83
Quoted 4 lines
drivers/gpio/gpio-virtuser.c new file +4
-0+struct gpio_virtuser_attr_ctx { +struct device_attribute dev_attr; +void *data; +};struct
dev_ext_attribute?
Sounds good, I'll rework this.
Quoted 12 lines
...
drivers/gpio/gpio-virtuser.c line 103
Quoted 6 lines
drivers/gpio/gpio-virtuser.c new file +6
-0+struct gpio_virtuser_attr_descr { +const char *name; +ssize_t (*show)(struct device *,struct device_attribute *,char *); +ssize_t (*store)(struct device *,struct device_attribute *, +const char *,size_t ); +};struct
device_attribute? (Yes, I know that that one is a bit bigger but benefit is that we have some code that you may reuse)
Not sure what you mean here, these are callbacks for sysfs.
Quoted 16 lines
...
drivers/gpio/gpio-virtuser.c line 150
Quoted 11 lines
drivers/gpio/gpio-virtuser.c new file +11
-0+static ssize_t gpio_virtuser_sysfs_emit_value_array (char *buf, +unsigned long *values, +size_t num_values) +{ +ssize_t len =0 ; +size_t i; + +for (i =0 ; i < num_values; i++) + len +=sysfs_emit_at (buf, len,"%d" , +test_bit (i, values) ?1 :0 ); +return len +sysfs_emit_at (buf, len,"\n" );Why not use %pb?
Because it outputs hex? I want to output binary, can I do it?
Quoted 13 lines
Quoted 1 line
+}
...
drivers/gpio/gpio-virtuser.c line 158
Quoted 6 lines
drivers/gpio/gpio-virtuser.c new file +6
-0+static int gpio_virtuser_sysfs_parse_value_array (const char *buf,size_t len, +unsigned long *values) +{ +size_t i; + +for (i =0 ; i < len; i++) {Perhaps
Quoted 4 lines
bool val;
int ret;
ret = kstrtobool(...);kstrtobool() accepts values we don't want here like [Tt]rue and [Ff]alse.
Quoted 12 lines
if (ret) return ret; assign_bit(...); // btw, why atomic?Quoted 6 lines
drivers/gpio/gpio-virtuser.c new file +6
-0+if (buf[i] =='0' ) +clear_bit (i, values); +else if (buf[i] =='1' ) +set_bit (i, values); +else +return -EINVAL ;
Quoted 4 lines
Quoted 1 line
+ }
BUT, why not
bitmap_parse()?
Because it parses hex, not binary.
Quoted 29 lines
Quoted 2 lines
(quoted diff) +2
-0+ return 0; +}...
Review
Quoted 2 lines
(quoted diff) +2
-0+ unsigned long *values __free(bitmap) = bitmap_alloc(descs->ndescs, + GFP_KERNEL);Perhaps
unsigned long *values
__free(bitmap)=
bitmap_alloc(descs->ndescs, GFP_KERNEL);...
Review
Quoted 2 lines
(quoted diff) +2
-0+ unsigned long *values __free(bitmap) = bitmap_zalloc(descs->ndescs, + GFP_KERNEL);In the similar way?
...
Review
Quoted 2 lines
(quoted diff) +2
-0+ unsigned long *values __free(bitmap) = bitmap_zalloc(descs->ndescs, + GFP_KERNEL);Ditto.
...
Quoted 8 lines
drivers/gpio/gpio-virtuser.c line 304
Quoted 3 lines
drivers/gpio/gpio-virtuser.c new file +3
-0+{ +return sysfs_emit (buf,"%s\n" , + dir ==GPIO_LINE_DIRECTION_IN ?"input" :"output" );I think this maybe transformed to something like
str_input_output()instring_choices.h(and you don't even need to include that as it's implied bystring_helpers.h)
These helpers take bool as argument. Hard to tell whether input or output should correspond to true. I'd leave it as is.
Quoted 33 lines
Quoted 1 line
+}
...
Quoted 5 lines
drivers/gpio/gpio-virtuser.c new file +5
-0+static int gpio_virtuser_parse_direction (const char *buf,int *dir,int *val) +{ +if (sysfs_streq (buf,"input" )) { + *dir =GPIO_LINE_DIRECTION_IN ; +return 0 ;drivers/gpio/gpio-virtuser.c line 329
Quoted 10 lines
drivers/gpio/gpio-virtuser.c new file +10
-0+ } + +if (sysfs_streq (buf,"output-high" )) + *val =1 ; +else if (sysfs_streq (buf,"output-low" )) + *val =0 ; +else +return -EINVAL ; + + *dir =GPIO_LINE_DIRECTION_OUT ;This can be transformed to use
sysfs_match_string()withstatic const char * const dirs[] = { "output-low", "output-high", "input" };
int ret; ret = sysfs_match_string(...); if (ret < 0) return ret; *val = ret; *dir = ret == 2 ? GPIO_LINE_DIRECTION_IN : GPIO_LINE_DIRECTION_OUT;
Quoted 3 lines
And with this approach it even not clear why do you need dir and val to be separated here (esp. if we add a enum like
We do want them to be separated not for better UX but to be able to test all kernel APIs (gpiod_direction_input|output() and gpiod_set_value()).
Quoted 22 lines
GPIO_VIRTUSER_OUT_LOW,
GPIO_VIRTUSER_OUT_HIGH,
GPIO_VIRTUSER_IN,(with it the string array can also be indexed).
Quoted 2 lines
(quoted diff) +2
-0+ return 0; +}...
Quoted 10 lines
drivers/gpio/gpio-virtuser.c new file +10
-0+static int gpio_virtuser_parse_value (const char *buf) +{ +int value, ret; + + value =sysfs_match_string (gpio_virtuser_sysfs_value_strings, buf); +if (value <0 ) { +/* Can be 0 or 1 too. */ + ret =kstrtoint (buf,0 , &value); +if (ret) +return ret;
Quoted 5 lines
drivers/gpio/gpio-virtuser.c line 448
Quoted 2 lines
drivers/gpio/gpio-virtuser.c new file +2
-0+if (value !=0 && value !=1 ) +return -EINVAL ;Why not
kstrtobool()?
I don't want to accept all the other strings kstrtobool() is fine with.
Quoted 11 lines
Quoted 4 lines
drivers/gpio/gpio-virtuser.c new file +4
-0+ } + +return value; +}...
Quoted 1 line
+ ret =
kstrtouint(buf, 10, &debounce);Why restrict to decimal?
Not sure what you gain from passing a period in hex?
Quoted 13 lines
Quoted 2 lines
(quoted diff) +2
-0+ if (ret) + return ret;...
Quoted 7 lines
drivers/gpio/gpio-virtuser.c new file +7
-0+static ssize_t +gpio_virtuser_sysfs_consumer_store (struct device *dev, +struct device_attribute *attr, +const char *buf,size_t len) +{ +struct gpio_virtuser_line_data *data =to_gpio_virtuser_data (attr); +int ret;
Quoted 5 lines
drivers/gpio/gpio-virtuser.c line 594
Quoted 2 lines
drivers/gpio/gpio-virtuser.c new file +2
-0+if (strlen (buf) >GPIO_CONSUMER_NAME_MAX_LEN ) +return -EINVAL ;You don't need this if you use
strscpy()below and check its returned value.
Ok.
Quoted 13 lines
Quoted 10 lines
drivers/gpio/gpio-virtuser.c new file +10
-0+guard (mutex)(&data->consumer_lock); + + ret =gpiod_set_consumer_name (data->desc, buf); +if (ret) +return ret; + +sprintf (data->consumer, buf); + +return len; +}...
Quoted 5 lines
drivers/gpio/gpio-virtuser.c line 769
Quoted 2 lines
drivers/gpio/gpio-virtuser.c new file +2
-0+ data->attr_group->name =devm_kasprintf (dev,GFP_KERNEL , +"gpiod:%s" , id);Why two lines?
It's not even longer than previously with the new struct layout so it warrants a break.
Quoted 10 lines
Quoted 2 lines
(quoted diff) +2
-0+ if (!data->attr_group->name) + return -ENOMEM;...
drivers/gpio/gpio-virtuser.c line 824
Quoted 2 lines
drivers/gpio/gpio-virtuser.c new file +2
-0+ ret =devm_add_action_or_reset (dev, gpio_virtuser_mutex_destroy, + &data->consumer_lock);Don't we have
devm_mutex_init()(git tag --containsshows v6.10-rc1 to me)
Ah we do now. This code dates back from early 2023 I think. I'll use it.
Quoted 10 lines
Quoted 1 line
+ return ret;
...
drivers/gpio/gpio-virtuser.c line 861
Quoted 3 lines
drivers/gpio/gpio-virtuser.c new file +3
-0+static int gpio_virtuser_prop_is_gpio (struct property *prop) +{ +char *dash =strpbrk (prop->name,"-" );Why not
strrchr()?
Ok.
Quoted 7 lines
Quoted 1 line
+ return dash &&
strcmp(dash, "-gpios")== 0;Can't we reuse the suffix from the array from the gpiolib internal header? Also I don't like the form of '-' in the line. "gpios" is good and chance that linker deduplicates the same string if it occurs somewhere else in the binary (in case this goes with =y in .config).
I'm not sure I follow what you're saying here. Please rephrase.
Quoted 10 lines
Quoted 1 line
+}
...
Quoted 5 lines
+/*
+ * If this is an OF-based system, then we iterate over properties and consider
+ * all whose names end in "-gpios". For configfs we expect an additional string
+ * array property - "gpio-virtuser,ids" - containing the list of all GPIO IDs
+ * to request.
Quoted 6 lines
Why not any other system? What's wrong for having this available for ACPI, for example? Okay, I see that this is probably due to absence of API.
OTOH the last call in the function assumes non-OF cases. Why can't we have the same approach in both?
Again: I have no idea what you mean. We support device-tree and configfs as sources of configuration for these virtual consumers. If you want to add something more, be my guest once it's upstream.
The reason to use a different approach is to not require the "gpio-virtuser,ids" property in device-tree.
Quoted 12 lines
drivers/gpio/gpio-virtuser.c line 874
Quoted 4 lines
+ */
drivers/gpio/gpio-virtuser.c new file +3
-0+static int gpio_virtuser_count_ids (struct device *dev) +{ +struct fwnode_handle *fwnode =dev_fwnode (dev);Why? This function is mostly OF one, make it simpler.
struct device_node *np = dev_of_node(dev);Quoted 2 lines
drivers/gpio/gpio-virtuser.c new file +2
-0+struct property *prop; +int ret =0 ;
Quoted 8 lines
Quoted 1 line
+ if (
is_of_node(fwnode)) {Instead of this check...
if (np) {...can be used.
Ok.
Quoted 9 lines
Quoted 2 lines
+
for_each_property_of_node(to_of_node(fwnode), prop){for_each_property_of_node(np, prop) {drivers/gpio/gpio-virtuser.c line 881
Quoted 2 lines
drivers/gpio/gpio-virtuser.c new file +2
-0+if (gpio_virtuser_prop_is_gpio (prop)) + ++ret;Why pre-increment?
Why not?
Quoted 11 lines
Quoted 8 lines
drivers/gpio/gpio-virtuser.c new file +5
-0+ } +return ret; + } +return device_property_string_array_count (dev,"gpio-virtuser,ids" ); +}...
Quoted 13 lines
drivers/gpio/gpio-virtuser.c line 899
Quoted 10 lines
drivers/gpio/gpio-virtuser.c new file +10
-0+static int gpio_virtuser_get_ids (struct device *dev,const char **ids, +int num_ids) +{ +struct fwnode_handle *fwnode =dev_fwnode (dev); +struct property *prop; +size_t pos =0 , diff; +char *dash, *tmp; + +if (is_of_node (fwnode)) { +for_each_property_of_node (to_of_node (fwnode), prop) {As per above function.
Ok
Quoted 12 lines
drivers/gpio/gpio-virtuser.c line 907
Quoted 8 lines
drivers/gpio/gpio-virtuser.c new file +8
-0+if (!gpio_virtuser_prop_is_gpio (prop)) +continue ; + + dash =strpbrk (prop->name,"-" ); + diff = dash - prop->name; + + tmp =devm_kmemdup (dev, prop->name, diff +1 , +GFP_KERNEL );
devm_kstrndup()is not okay? Okay, we don't have it (yet?), but at least I would rather expect wrappedkstrndup()than this.
Meh, this logic is fine as we know the range exactly. IMO kstrndup() here would be overkill. I'd leave it for now.
Quoted 35 lines
Quoted 13 lines
drivers/gpio/gpio-virtuser.c new file +13
-0+if (!tmp) +return -ENOMEM ; + + tmp[diff] ='\0' ; + ids[pos++] = tmp; + } + +return 0 ; + } + +return device_property_read_string_array (dev,"gpio-virtuser,ids" , + ids, num_ids); +}...
Quoted 6 lines
drivers/gpio/gpio-virtuser.c new file +6
-0+static int gpio_virtuser_probe (struct platform_device *pdev) +{ +struct device *dev = &pdev->dev; +struct gpio_descs *descs; +int ret, num_ids =0 , i; +const char **ids;drivers/gpio/gpio-virtuser.c line 937
Quoted 10 lines
drivers/gpio/gpio-virtuser.c new file +10
-0+unsigned int j; + + num_ids =gpio_virtuser_count_ids (dev); +if (num_ids <0 ) +return dev_err_probe (dev, num_ids, +"Failed to get the number of GPIOs to request\n" ); + +if (num_ids ==0 ) { +dev_err (dev,"No GPIO IDs specified\n" ); +return -EINVAL ;It's okay to
Quoted 4 lines
return dev_err_probe(...);with know error code.
Ok.
Quoted 41 lines
Quoted 38 lines
drivers/gpio/gpio-virtuser.c new file +38
-0+ } + + ids =devm_kcalloc (dev, num_ids,sizeof (*ids),GFP_KERNEL ); +if (!ids) +return -ENOMEM ; + + ret =gpio_virtuser_get_ids (dev, ids, num_ids); +if (ret <0 ) +return dev_err_probe (dev, ret, +"Failed to get the IDs of GPIOs to request\n" ); + +for (i =0 ; i < num_ids; i++) { + descs =devm_gpiod_get_array (dev, ids[i],GPIOD_ASIS ); +if (IS_ERR (descs)) +return dev_err_probe (dev,PTR_ERR (descs), +"Failed to request the '%s' GPIOs\n" , + ids[i]); + + ret =gpio_virtuser_sysfs_init_line_array_attrs (dev, descs, + ids[i]); +if (ret) +return dev_err_probe (dev, ret, +"Failed to setup the sysfs array interface for the '%s' GPIOs\n" , + ids[i]); + +for (j =0 ; j < descs->ndescs; j++) { + ret =gpio_virtuser_sysfs_init_line_attrs (dev, + descs->desc[j], + ids[i], j); +if (ret) +return dev_err_probe (dev, ret, +"Failed to setup the sysfs line interface for the '%s' GPIOs\n" , + ids[i]); + } + } + +return 0 ; +}...
Quoted 14 lines
drivers/gpio/gpio-virtuser.c line 1017
Quoted 11 lines
drivers/gpio/gpio-virtuser.c new file +11
-0+static int gpio_virtuser_bus_notifier_call (struct notifier_block *nb, +unsigned long action,void *data) +{ +struct gpio_virtuser_device *vdev; +struct device *dev = data; +char devname[32 ]; + + vdev = container_of(nb, struct gpio_virtuser_device, bus_notifier); +snprintf (devname,sizeof (devname),"gpio-virtuser.%d" , vdev->id); + +if (strcmp (dev_name (dev), devname))if (!device_match_name(...))
Ok
Quoted 19 lines
Quoted 16 lines
drivers/gpio/gpio-virtuser.c new file +16
-0+return NOTIFY_DONE ; + +switch (action) { +case BUS_NOTIFY_BOUND_DRIVER : + vdev->driver_bound = true; +break ; +case BUS_NOTIFY_DRIVER_NOT_BOUND : + vdev->driver_bound = false; +break ; +default : +return NOTIFY_DONE ; + } + +complete (&vdev->probe_completion); +return NOTIFY_OK ; +}...
Quoted 12 lines
drivers/gpio/gpio-virtuser.c line 1110
Quoted 9 lines
drivers/gpio/gpio-virtuser.c new file +9
-0+static ssize_t +gpio_virtuser_lookup_entry_config_key_store (struct config_item *item, +const char *page,size_t count) +{ +struct gpio_virtuser_lookup_entry *entry = +to_gpio_virtuser_lookup_entry (item); +struct gpio_virtuser_device *dev = entry->parent->parent; + +char *key =kstrndup (skip_spaces (page), count,GFP_KERNEL );Missing
__free()?
Right.
Quoted 38 lines
Quoted 15 lines
drivers/gpio/gpio-virtuser.c new file +13
-0+if (!key) +return -ENOMEM ; +strim (key); +guard (mutex)(&dev->lock); + +if (gpio_virtuser_device_is_live (dev)) +return -EBUSY ; + +kfree (entry->key); + entry->key =no_free_ptr (key); + +return count; +}...
Quoted 4 lines
drivers/gpio/gpio-virtuser.c new file +4
-0+if (sysfs_streq (page,"pull-up" )) { + entry->flags &= ~(GPIO_PULL_DOWN |GPIO_PULL_DISABLE ); + entry->flags |=GPIO_PULL_UP ; + }else if (sysfs_streq (page,"pull-down" )) {drivers/gpio/gpio-virtuser.c line 1282
Quoted 10 lines
drivers/gpio/gpio-virtuser.c new file +10
-0+ entry->flags &= ~(GPIO_PULL_UP |GPIO_PULL_DISABLE ); + entry->flags |=GPIO_PULL_DOWN ; + }else if (sysfs_streq (page,"pull-disabled" )) { + entry->flags &= ~(GPIO_PULL_UP |GPIO_PULL_DOWN ); + entry->flags |=GPIO_PULL_DISABLE ; + }else if (sysfs_streq (page,"as-is" )) { + entry->flags &= ~(GPIO_PULL_UP |GPIO_PULL_DOWN | +GPIO_PULL_DISABLE ); + }else { + count = -EINVAL ;return
-EINVALwon't (ab)use count semantics.drivers/gpio/gpio-virtuser.c line 1124
Quoted 3 lines
drivers/gpio/gpio-virtuser.c new file +3
-0+ } + +return count;
Quoted 7 lines
...
Quoted 1 line
+ return
sprintf(page, "%s\n", flags & GPIO_ACTIVE_LOW ? "1" : "0");Somewhere above you used %d for very similar situation, why %s here? Or why "5d" there?
No reason, I'll unify it.
Quoted 19 lines
...
Quoted 1 line
+ return
sprintf(page, "%s\n", flags & GPIO_TRANSITORY ? "1" : "0");Ditto.
...
Quoted 1 line
+ return
sprintf(page, "%c\n", live ? '1' : '0');Wow! Third type of the same.
...
Quoted 4 lines
drivers/gpio/gpio-virtuser.c new file +4
-0+ struct gpiod_lookup_table *table __free(kfree) = +kzalloc (struct_size (table, table, num_entries +1 ),GFP_KERNEL ); +if (!table) +return -ENOMEM ;
Quoted 5 lines
drivers/gpio/gpio-virtuser.c line 1439
Quoted 2 lines
drivers/gpio/gpio-virtuser.c new file +2
-0+ table->dev_id =kasprintf (GFP_KERNEL ,"gpio-virtuser.%d" , + dev->id);Perfectly one line in comparison with the few lines above).
Ok
Quoted 28 lines
Quoted 2 lines
drivers/gpio/gpio-virtuser.c new file +2
-0+if (!table->dev_id) +return -ENOMEM ;...
drivers/gpio/gpio-virtuser.c line 1451
Quoted 2 lines
drivers/gpio/gpio-virtuser.c new file +2
-0+ curr->chip_hwnum = entry->offset <0 + ?U16_MAX : entry->offset;Can we leave ? on the previous line?
...
Quoted 1 line
+ ++i;
Why pre-increment?
...
drivers/gpio/gpio-virtuser.c line 1469
Quoted 7 lines
drivers/gpio/gpio-virtuser.c new file +7
-0+static struct fwnode_handle * +gpio_virtuser_make_device_swnode (struct gpio_virtuser_device *dev) +{ +struct property_entry properties[2 ]; +struct gpio_virtuser_lookup *lookup; +size_t num_ids; +int i =0 ;Why signed? And in all this kind of case, I would split assignment...
Quoted 13 lines
drivers/gpio/gpio-virtuser.c line 1478
Quoted 8 lines
drivers/gpio/gpio-virtuser.c new file +8
-0+memset (properties,0 ,sizeof (properties)); + + num_ids =list_count_nodes (&dev->lookup_list); + char **ids __free(kfree) = kcalloc(num_ids + 1, sizeof(*ids), +GFP_KERNEL ); +if (!ids) +return ERR_PTR (-ENOMEM ); +To be here, that the reader will see immediately (close enough) what is the initial values. Moreover this code will be robuse against changes in between (if i become reusable).
Sorry, I can't parse it.
Quoted 25 lines
Quoted 8 lines
drivers/gpio/gpio-virtuser.c new file +8
-0+list_for_each_entry (lookup, &dev->lookup_list, siblings) + ids[i++] = lookup->con_id; + + properties[0 ] =PROPERTY_ENTRY_STRING_ARRAY_LEN ("gpio-virtuser,ids" , + ids, num_ids); + +return fwnode_create_software_node (properties,NULL ); +}...
drivers/gpio/gpio-virtuser.c line 1571
Quoted 4 lines
drivers/gpio/gpio-virtuser.c new file +4
-0+guard (mutex)(&dev->lock); + +if (live ==gpio_virtuser_device_is_live (dev)) + ret = -EPERM ;With guard in place, just return directly, ...
Quoted 1 line
+ else if (live)
...drop 'else'...
drivers/gpio/gpio-virtuser.c line 1574
Quoted 2 lines
drivers/gpio/gpio-virtuser.c new file +2
-0+ ret =gpio_virtuser_device_activate (dev); + else
Quoted 8 lines
...ditto...
drivers/gpio/gpio-virtuser.c line 1577
Quoted 3 lines
drivers/gpio/gpio-virtuser.c new file +3
-0+gpio_virtuser_device_deactivate (dev); + +return ret ?: count;...and simply return count here.
Ok.
Quoted 14 lines
...
drivers/gpio/gpio-virtuser.c line 1736
Quoted 2 lines
drivers/gpio/gpio-virtuser.c new file +2
-0+ struct gpio_virtuser_device *dev __free(kfree) = kzalloc(sizeof(*dev), +GFP_KERNEL );struct
gpio_virtuser_device*dev__free(kfree)=
kzalloc(sizeof(*dev), GFP_KERNEL);Quoted 2 lines
drivers/gpio/gpio-virtuser.c new file +2
-0+if (!dev) +return ERR_PTR (-ENOMEM );...
Quoted 8 lines
drivers/gpio/gpio-virtuser.c line 1779
Quoted 5 lines
drivers/gpio/gpio-virtuser.c new file +4
-0+ ret =platform_driver_register (&gpio_virtuser_driver); +if (ret) { +pr_err ("Failed to register the platform driver: %d\n" , + ret);I would keep one line.
Ok.
Quoted 8 lines
Review
Quoted 2 lines
(quoted diff) +2
-0+ return ret; + }--
With Best Regards,
Andy Shevchenko
Thanks for the review!
Bart
Mon, Jun 10, 2024 at 03:22:32PM +0200, Bartosz Golaszewski kirjoitti:
Quoted 3 lines
Quoted 1 line · Andy Shevchenko
Mon, May 27, 2024 at 04:40:54PM +0200, Bartosz Golaszewski kirjoitti:
...
Quoted 6 lines
Quoted 4 lines
Quoted 1 line
User must pass exactly the number of values that the array contains
Can't we assume non-active values for the rest if less than needed were provided? For more than that, why do we care?
Honestly, what good would it do? It would just be more confusing IMO.
Let's say you can leave documentation as is, but relax the code. That's the benefit, less complex checks in the code.
...
Quoted 16 lines
Quoted 16 lines
Quoted 15 lines
drivers/gpio/gpio-virtuser.c new file +13
-0+#include <linux/atomic.h> +#include <linux/bitmap.h> +#include <linux/cleanup.h> +#include <linux/completion.h> +#include <linux/configfs.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/gpio/consumer.h> +#include <linux/gpio/driver.h> +#include <linux/gpio/machine.h> +#include <linux/idr.h> +#include <linux/interrupt.h> +#include <linux/irq_work.h>
Quoted 6 lines
Quoted 3 lines
Quoted 1 line
+#include <
linux/kernel.h>Do you need this?
ARRAY_SIZE()used to live here when I first wrote this but it was since moved. I'll drop this.
Rather replace with array_size.h.
Quoted 14 lines
Quoted 14 lines
Quoted 13 lines
drivers/gpio/gpio-virtuser.c new file +13
-0+#include <linux/limits.h> +#include <linux/list.h> +#include <linux/lockdep.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/notifier.h> +#include <linux/of.h> +#include <linux/overflow.h> +#include <linux/platform_device.h> +#include <linux/printk.h> +#include <linux/property.h> +#include <linux/slab.h>
Quoted 6 lines
Quoted 3 lines
Quoted 1 line
+#include <
linux/string.h>Implied by
string_helpers.hYeah, but we still use symbols directly from
string.h, we shouldn't depend on implicit includes.
string_helpers.h is and will continue guranteening inclusion of string.h. It's the same as we drop bits.h when we include, for instance, bitmap.h.
Quoted 3 lines
Quoted 3 lines
Quoted 3 lines
drivers/gpio/gpio-virtuser.c new file +3
-0+#include <linux/string_helpers.h> +#include <linux/sysfs.h> +#include <linux/types.h>
...
Quoted 11 lines
Quoted 9 lines
drivers/gpio/gpio-virtuser.c line 103
Quoted 6 lines
drivers/gpio/gpio-virtuser.c new file +6
-0+struct gpio_virtuser_attr_descr { +const char *name; +ssize_t (*show)(struct device *,struct device_attribute *,char *); +ssize_t (*store)(struct device *,struct device_attribute *, +const char *,size_t ); +};struct
device_attribute? (Yes, I know that that one is a bit bigger but benefit is that we have some code that you may reuse)Not sure what you mean here, these are callbacks for sysfs.
I mean to replace your custom data type with the existing device_attribute.
...
Quoted 14 lines
Quoted 5 lines
Quoted 5 lines
drivers/gpio/gpio-virtuser.c new file +5
-0+static ssize_t gpio_virtuser_sysfs_emit_value_array (char *buf, +unsigned long *values, +size_t num_values) +{ +ssize_t len =0 ;Quoted 8 lines
drivers/gpio/gpio-virtuser.c line 150
Quoted 6 lines
drivers/gpio/gpio-virtuser.c new file +6
-0+size_t i; + +for (i =0 ; i < num_values; i++) + len +=sysfs_emit_at (buf, len,"%d" , +test_bit (i, values) ?1 :0 ); +return len +sysfs_emit_at (buf, len,"\n" );Why not use %pb?
Quoted 1 line
Because it outputs hex? I want to output binary, can I do it?
But why do you need that? You can also print a list of numbers of bits that set (%pbl).
We have a few ABIs in the kernel that works nice and people are familiar with (CPU sets, IRQ affinity masks, etc). Why to reinvent the wheel?
Quoted 1 line
Quoted 1 line
Quoted 1 line
+}
...
Quoted 9 lines
Quoted 9 lines
drivers/gpio/gpio-virtuser.c line 158
Quoted 6 lines
drivers/gpio/gpio-virtuser.c new file +6
-0+static int gpio_virtuser_sysfs_parse_value_array (const char *buf,size_t len, +unsigned long *values) +{ +size_t i; + +for (i =0 ; i < len; i++) {Perhaps
Quoted 6 lines
Quoted 4 lines
bool val; int ret; ret = kstrtobool(...);
kstrtobool()accepts values we don't want here like [Tt]rue and [Ff]alse.
Yes, see below.
Quoted 12 lines
Quoted 12 lines
if (ret) return ret; assign_bit(...); // btw, why atomic?Quoted 6 lines
drivers/gpio/gpio-virtuser.c new file +6
-0+if (buf[i] =='0' ) +clear_bit (i, values); +else if (buf[i] =='1' ) +set_bit (i, values); +else +return -EINVAL ;
Quoted 5 lines
Quoted 3 lines
Quoted 1 line
+ }
BUT, why not
bitmap_parse()?Because it parses hex, not binary.
So, why do we reinvent a wheel? Wouldn't be better that users may apply the knowledge they familiar with (and I believe the group of the users who know about bitmaps is much bigger than those who will use this driver).
Quoted 2 lines
Quoted 2 lines
Quoted 2 lines
(quoted diff) +2
-0+ return 0; +}
...
Quoted 9 lines
Quoted 6 lines
drivers/gpio/gpio-virtuser.c line 304
Quoted 2 lines
drivers/gpio/gpio-virtuser.c new file +2
-0+return sysfs_emit (buf,"%s\n" , + dir ==GPIO_LINE_DIRECTION_IN ?"input" :"output" );I think this maybe transformed to something like
str_input_output()instring_choices.h(and you don't even need to include that as it's implied bystring_helpers.h)These helpers take bool as argument. Hard to tell whether input or output should correspond to true. I'd leave it as is.
There is a convention: str_TRUE_FALSE().
...
Quoted 29 lines
Quoted 29 lines
Quoted 5 lines
drivers/gpio/gpio-virtuser.c new file +5
-0+static int gpio_virtuser_parse_direction (const char *buf,int *dir,int *val) +{ +if (sysfs_streq (buf,"input" )) { + *dir =GPIO_LINE_DIRECTION_IN ; +return 0 ;drivers/gpio/gpio-virtuser.c line 329
Quoted 10 lines
drivers/gpio/gpio-virtuser.c new file +10
-0+ } + +if (sysfs_streq (buf,"output-high" )) + *val =1 ; +else if (sysfs_streq (buf,"output-low" )) + *val =0 ; +else +return -EINVAL ; + + *dir =GPIO_LINE_DIRECTION_OUT ;This can be transformed to use
sysfs_match_string()withstatic const char * const dirs[] = { "output-low", "output-high", "input" };
int ret; ret = sysfs_match_string(...); if (ret < 0) return ret; *val = ret; *dir = ret == 2 ? GPIO_LINE_DIRECTION_IN : GPIO_LINE_DIRECTION_OUT;
Quoted 6 lines
Quoted 2 lines
And with this approach it even not clear why do you need dir and val to be separated here (esp. if we add a enum like
We do want them to be separated not for better UX but to be able to test all kernel APIs (gpiod_direction_input|output() and
gpiod_set_value()).
Still you can do some optimisations I proposed above.
Quoted 8 lines
Quoted 8 lines
GPIO_VIRTUSER_OUT_LOW,
GPIO_VIRTUSER_OUT_HIGH,
GPIO_VIRTUSER_IN,(with it the string array can also be indexed).
Quoted 2 lines
(quoted diff) +2
-0+ return 0; +}
...
Quoted 11 lines
Quoted 11 lines
Quoted 10 lines
drivers/gpio/gpio-virtuser.c new file +10
-0+static int gpio_virtuser_parse_value (const char *buf) +{ +int value, ret; + + value =sysfs_match_string (gpio_virtuser_sysfs_value_strings, buf); +if (value <0 ) { +/* Can be 0 or 1 too. */ + ret =kstrtoint (buf,0 , &value); +if (ret) +return ret;
Quoted 6 lines
Quoted 4 lines
drivers/gpio/gpio-virtuser.c line 448
Quoted 2 lines
drivers/gpio/gpio-virtuser.c new file +2
-0+if (value !=0 && value !=1 ) +return -EINVAL ;Why not
kstrtobool()?I don't want to accept all the other strings
kstrtobool()is fine with.
What's wrong with other strings?
At bare minumum you can reduce the range by using kstrtou8().
Quoted 4 lines
Quoted 4 lines
Quoted 4 lines
drivers/gpio/gpio-virtuser.c new file +4
-0+ } + +return value; +}
...
Quoted 5 lines
Quoted 3 lines
Quoted 1 line
+ ret =
kstrtouint(buf, 10, &debounce);Why restrict to decimal?
Not sure what you gain from passing a period in hex?
For example, if I compare this to the real HW, I might be able to do something like 0x1234 (let's say it's debounce step) and shifting it by 4 bits will give me something I want. But despite that quite unlikely case the restriction here doesn't bring us much.
Quoted 2 lines
Quoted 2 lines
Quoted 2 lines
(quoted diff) +2
-0+ if (ret) + return ret;
...
Quoted 8 lines
Quoted 6 lines
Quoted 1 line
+ return dash &&
strcmp(dash, "-gpios")== 0;Can't we reuse the suffix from the array from the gpiolib internal header? Also I don't like the form of '-' in the line. "gpios" is good and chance that linker deduplicates the same string if it occurs somewhere else in the binary (in case this goes with =y in .config).
I'm not sure I follow what you're saying here. Please rephrase.
Do strcmp() against one from the gpio_suffixes array.
...
Quoted 12 lines
Quoted 11 lines
Quoted 5 lines
+/*
+ * If this is an OF-based system, then we iterate over properties and consider
+ * all whose names end in "-gpios". For configfs we expect an additional string
+ * array property - "gpio-virtuser,ids" - containing the list of all GPIO IDs
+ * to request.Why not any other system? What's wrong for having this available for ACPI, for example? Okay, I see that this is probably due to absence of API.
OTOH the last call in the function assumes non-OF cases. Why can't we have the same approach in both?
Quoted 6 lines
Again: I have no idea what you mean. We support device-tree and configfs as sources of configuration for these virtual consumers. If you want to add something more, be my guest once it's upstream.
The reason to use a different approach is to not require the "gpio-virtuser,ids" property in device-tree.
Yes, and I'm asking why can't we unify and require it there as well? But okay, I might give up on the trying of the DT/ACPI property unification.
Quoted 1 line
Quoted 1 line
Quoted 1 line
+ */
...
Quoted 6 lines
Quoted 4 lines
drivers/gpio/gpio-virtuser.c line 881
Quoted 2 lines
drivers/gpio/gpio-virtuser.c new file +2
-0+if (gpio_virtuser_prop_is_gpio (prop)) + ++ret;Why pre-increment?
Why not?
Because we have a pattern. Pre-increment adds into additional questioning "why?". I.e. What does make this case special? When I read such a code I need more brain power to parse it.
...
Quoted 1 line
Quoted 1 line
Quoted 1 line
+ dash =
strpbrk(prop->name, "-");
Btw, don't you want strrchr() here? (Note 'r' variant).
Quoted 5 lines
Quoted 5 lines
Quoted 4 lines
drivers/gpio/gpio-virtuser.c new file +4
-0+ diff = dash - prop->name; + + tmp =devm_kmemdup (dev, prop->name, diff +1 , +GFP_KERNEL );
Quoted 10 lines
Quoted 2 lines
devm_kstrndup()is not okay? Okay, we don't have it (yet?), but at least I would rather expect wrappedkstrndup()than this.Meh, this logic is fine as we know the range exactly. IMO
kstrndup()here would be overkill. I'd leave it for now.Quoted 4 lines
Quoted 4 lines
drivers/gpio/gpio-virtuser.c new file +3
-0+if (!tmp) +return -ENOMEM ; + tmp[diff] ='\0' ;
This line will gone with kstrndup(). I think we will benefit from it.
...
Quoted 3 lines
Quoted 3 lines
Quoted 1 line
+ int i = 0;
Why signed? And in all this kind of case, I would split assignment...
(1)
Quoted 13 lines
Quoted 4 lines
Quoted 4 lines
drivers/gpio/gpio-virtuser.c new file +4
-0+memset (properties,0 ,sizeof (properties)); + + num_ids =list_count_nodes (&dev->lookup_list); + char **ids __free(kfree) = kcalloc(num_ids + 1, sizeof(*ids),Quoted 8 lines
drivers/gpio/gpio-virtuser.c line 1478
Quoted 4 lines
drivers/gpio/gpio-virtuser.c new file +4
-0+ GFP_KERNEL); +if (!ids) +return ERR_PTR (-ENOMEM ); +To be here, that the reader will see immediately (close enough) what is the initial values. Moreover this code will be robuse against changes in between (if i become reusable).
Quoted 1 line
Sorry, I can't parse it.
I meant to see here
i = 0;
instead of the above (1).
Quoted 2 lines
Quoted 2 lines
Quoted 2 lines
(quoted diff) +2
-0+ list_for_each_entry(lookup, &dev->lookup_list, siblings) + ids[i++] = lookup->con_id;
Signature · 2 lines
With Best Regards, Andy Shevchenko
Quoted 10 lines · Andy Shevchenko
Mon, Jun 10, 2024 at 03:22:32PM +0200, Bartosz Golaszewski kirjoitti:
Quoted 3 lines
Quoted 1 line · Andy Shevchenko
Mon, May 27, 2024 at 04:40:54PM +0200, Bartosz Golaszewski kirjoitti:
...
Quoted 2 lines
Quoted 2 lines
Quoted 1 line
User must pass exactly the number of values that the array contains
Quoted 8 lines
Quoted 4 lines
Quoted 2 lines
Can't we assume non-active values for the rest if less than needed were provided? For more than that, why do we care?
Honestly, what good would it do? It would just be more confusing IMO.
Let's say you can leave documentation as is, but relax the code. That's the benefit, less complex checks in the code.
Actually this is more ambiguity in the code. I'm against it.
Quoted 18 lines
...
Quoted 16 lines
Quoted 16 lines
Quoted 15 lines
drivers/gpio/gpio-virtuser.c new file +13
-0+#include <linux/atomic.h> +#include <linux/bitmap.h> +#include <linux/cleanup.h> +#include <linux/completion.h> +#include <linux/configfs.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/gpio/consumer.h> +#include <linux/gpio/driver.h> +#include <linux/gpio/machine.h> +#include <linux/idr.h> +#include <linux/interrupt.h> +#include <linux/irq_work.h>
Quoted 9 lines
Quoted 6 lines
Quoted 3 lines
Quoted 1 line
+#include <
linux/kernel.h>Do you need this?
ARRAY_SIZE()used to live here when I first wrote this but it was since moved. I'll drop this.Rather replace with
array_size.h.
Yeah this is what I did.
Quoted 16 lines
Quoted 16 lines
Quoted 16 lines
Quoted 15 lines
drivers/gpio/gpio-virtuser.c new file +14
-0+#include <linux/limits.h> +#include <linux/list.h> +#include <linux/lockdep.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/notifier.h> +#include <linux/of.h> +#include <linux/overflow.h> +#include <linux/platform_device.h> +#include <linux/printk.h> +#include <linux/property.h> +#include <linux/slab.h> +#include <linux/string.h>
Quoted 8 lines
Quoted 4 lines
Quoted 1 line
Implied by
string_helpers.hYeah, but we still use symbols directly from
string.h, we shouldn't depend on implicit includes.
string_helpers.his and will continue guranteening inclusion ofstring.h. It's the same as we dropbits.hwhen we include, for instance,bitmap.h.
Whatever, ok.
Quoted 16 lines
Quoted 3 lines
Quoted 3 lines
Quoted 3 lines
drivers/gpio/gpio-virtuser.c new file +3
-0+#include <linux/string_helpers.h> +#include <linux/sysfs.h> +#include <linux/types.h> ...
Quoted 10 lines
Quoted 9 lines
drivers/gpio/gpio-virtuser.c line 103
Quoted 6 lines
drivers/gpio/gpio-virtuser.c new file +6
-0+struct gpio_virtuser_attr_descr { +const char *name; +ssize_t (*show)(struct device *,struct device_attribute *,char *); +ssize_t (*store)(struct device *,struct device_attribute *, +const char *,size_t ); +};struct
device_attribute? (Yes, I know that that one is a bit bigger but benefit is that we have some code that you may reuse)
Quoted 4 lines
Quoted 1 line
Not sure what you mean here, these are callbacks for sysfs.
I mean to replace your custom data type with the existing
device_attribute.
Doesn't make sense here. struct device_attribute has lots of cruft we don't need here. It's just the name and the callback pointers.
Quoted 16 lines
...
Quoted 14 lines
Quoted 13 lines
drivers/gpio/gpio-virtuser.c line 150
Quoted 11 lines
drivers/gpio/gpio-virtuser.c new file +11
-0+static ssize_t gpio_virtuser_sysfs_emit_value_array (char *buf, +unsigned long *values, +size_t num_values) +{ +ssize_t len =0 ; +size_t i; + +for (i =0 ; i < num_values; i++) + len +=sysfs_emit_at (buf, len,"%d" , +test_bit (i, values) ?1 :0 ); +return len +sysfs_emit_at (buf, len,"\n" );Why not use %pb?
Quoted 7 lines
Quoted 1 line
Because it outputs hex? I want to output binary, can I do it?
But why do you need that? You can also print a list of numbers of bits that set (%pbl).
We have a few ABIs in the kernel that works nice and people are familiar with (CPU sets, IRQ affinity masks, etc). Why to reinvent the wheel?
If I see "11001011" as output, I can immediately convert that to pins in my head. If I see 0xcb, I need to use a calculator.
Quoted 93 lines
Quoted 2 lines
Quoted 2 lines
Quoted 2 lines
+}
...
Quoted 9 lines
Quoted 9 lines
drivers/gpio/gpio-virtuser.c line 158
Quoted 6 lines
drivers/gpio/gpio-virtuser.c new file +6
-0+static int gpio_virtuser_sysfs_parse_value_array (const char *buf,size_t len, +unsigned long *values) +{ +size_t i; + +for (i =0 ; i < len; i++) {Perhaps
Quoted 6 lines
Quoted 4 lines
bool val; int ret; ret = kstrtobool(...);
kstrtobool()accepts values we don't want here like [Tt]rue and [Ff]alse.Yes, see below.
Quoted 12 lines
Quoted 12 lines
if (ret) return ret; assign_bit(...); // btw, why atomic?Quoted 6 lines
drivers/gpio/gpio-virtuser.c new file +6
-0+if (buf[i] =='0' ) +clear_bit (i, values); +else if (buf[i] =='1' ) +set_bit (i, values); +else +return -EINVAL ;Quoted 5 lines
Quoted 3 lines
Quoted 1 line
+ }
BUT, why not
bitmap_parse()?Because it parses hex, not binary.
So, why do we reinvent a wheel? Wouldn't be better that users may apply the knowledge they familiar with (and I believe the group of the users who know about bitmaps is much bigger than those who will use this driver).
Quoted 2 lines
Quoted 2 lines
Quoted 2 lines
(quoted diff) +2
-0+ return 0; +}...
Quoted 9 lines
Quoted 6 lines
drivers/gpio/gpio-virtuser.c line 304
Quoted 2 lines
drivers/gpio/gpio-virtuser.c new file +2
-0+return sysfs_emit (buf,"%s\n" , + dir ==GPIO_LINE_DIRECTION_IN ?"input" :"output" );I think this maybe transformed to something like
str_input_output()instring_choices.h(and you don't even need to include that as it's implied bystring_helpers.h)These helpers take bool as argument. Hard to tell whether input or output should correspond to true. I'd leave it as is.
There is a convention:
str_TRUE_FALSE()....
Quoted 29 lines
Quoted 29 lines
Quoted 5 lines
drivers/gpio/gpio-virtuser.c new file +5
-0+static int gpio_virtuser_parse_direction (const char *buf,int *dir,int *val) +{ +if (sysfs_streq (buf,"input" )) { + *dir =GPIO_LINE_DIRECTION_IN ; +return 0 ;drivers/gpio/gpio-virtuser.c line 329
Quoted 10 lines
drivers/gpio/gpio-virtuser.c new file +10
-0+ } + +if (sysfs_streq (buf,"output-high" )) + *val =1 ; +else if (sysfs_streq (buf,"output-low" )) + *val =0 ; +else +return -EINVAL ; + + *dir =GPIO_LINE_DIRECTION_OUT ;This can be transformed to use
sysfs_match_string()withstatic const char * const dirs[] = { "output-low", "output-high", "input" };
int ret; ret = sysfs_match_string(...); if (ret < 0) return ret; *val = ret; *dir = ret == 2 ? GPIO_LINE_DIRECTION_IN : GPIO_LINE_DIRECTION_OUT;
Quoted 9 lines
Quoted 6 lines
Quoted 2 lines
And with this approach it even not clear why do you need dir and val to be separated here (esp. if we add a enum like
We do want them to be separated not for better UX but to be able to test all kernel APIs (gpiod_direction_input|output() and
gpiod_set_value()).Still you can do some optimisations I proposed above.
IMO they are much less readable and you don't gain anything. NAK on this one.
Quoted 22 lines
Quoted 8 lines
Quoted 8 lines
GPIO_VIRTUSER_OUT_LOW,
GPIO_VIRTUSER_OUT_HIGH,
GPIO_VIRTUSER_IN,(with it the string array can also be indexed).
Quoted 2 lines
(quoted diff) +2
-0+ return 0; +}...
Quoted 11 lines
Quoted 11 lines
Quoted 10 lines
drivers/gpio/gpio-virtuser.c new file +10
-0+static int gpio_virtuser_parse_value (const char *buf) +{ +int value, ret; + + value =sysfs_match_string (gpio_virtuser_sysfs_value_strings, buf); +if (value <0 ) { +/* Can be 0 or 1 too. */ + ret =kstrtoint (buf,0 , &value); +if (ret) +return ret;
Quoted 9 lines
Quoted 6 lines
Quoted 4 lines
drivers/gpio/gpio-virtuser.c line 448
Quoted 2 lines
drivers/gpio/gpio-virtuser.c new file +2
-0+if (value !=0 && value !=1 ) +return -EINVAL ;Why not
kstrtobool()?I don't want to accept all the other strings
kstrtobool()is fine with.What's wrong with other strings?
A line is False? I mean sure, you can map that to inactive but it's not a naming convention associated with GPIOs very often.
Quoted 2 lines
At bare minumum you can reduce the range by using
kstrtou8().
As opposed to just checking '0' and '1'? Meh...
Quoted 9 lines
Quoted 4 lines
Quoted 4 lines
Quoted 4 lines
drivers/gpio/gpio-virtuser.c new file +4
-0+ } + +return value; +}...
Quoted 2 lines
Quoted 2 lines
Quoted 1 line
+ ret =
kstrtouint(buf, 10, &debounce);
Quoted 9 lines
Quoted 3 lines
Quoted 1 line
Why restrict to decimal?
Not sure what you gain from passing a period in hex?
For example, if I compare this to the real HW, I might be able to do something like
0x1234(let's say it's debounce step) and shifting it by 4 bits will give me something I want. But despite that quite unlikely case the restriction here doesn't bring us much.
Ok, whatever.
Quoted 7 lines
Quoted 2 lines
Quoted 2 lines
Quoted 2 lines
(quoted diff) +2
-0+ if (ret) + return ret;...
Quoted 2 lines
Quoted 2 lines
Quoted 1 line
+ return dash &&
strcmp(dash, "-gpios")== 0;
Quoted 9 lines
Quoted 6 lines
Quoted 4 lines
Can't we reuse the suffix from the array from the gpiolib internal header? Also I don't like the form of '-' in the line. "gpios" is good and chance that linker deduplicates the same string if it occurs somewhere else in the binary (in case this goes with =y in .config).
I'm not sure I follow what you're saying here. Please rephrase.
Do
strcmp()against one from thegpio_suffixesarray.
I don't want to accept the "gpio" suffix. Also: I don't want to include gpiolib.h.
Quoted 31 lines
...
Quoted 12 lines
Quoted 11 lines
Quoted 5 lines
+/*
+ * If this is an OF-based system, then we iterate over properties and consider
+ * all whose names end in "-gpios". For configfs we expect an additional string
+ * array property - "gpio-virtuser,ids" - containing the list of all GPIO IDs
+ * to request.Why not any other system? What's wrong for having this available for ACPI, for example? Okay, I see that this is probably due to absence of API.
OTOH the last call in the function assumes non-OF cases. Why can't we have the same approach in both?
Quoted 6 lines
Again: I have no idea what you mean. We support device-tree and configfs as sources of configuration for these virtual consumers. If you want to add something more, be my guest once it's upstream.
The reason to use a different approach is to not require the "gpio-virtuser,ids" property in device-tree.
Yes, and I'm asking why can't we unify and require it there as well? But okay, I might give up on the trying of the DT/ACPI property unification.
Quoted 1 line
Quoted 1 line
Quoted 1 line
+ */
...
Quoted 3 lines
Quoted 3 lines
Quoted 2 lines
drivers/gpio/gpio-virtuser.c new file +2
-0+if (gpio_virtuser_prop_is_gpio (prop)) + ++ret;
Quoted 8 lines
Quoted 3 lines
Quoted 1 line
Why pre-increment?
Why not?
Because we have a pattern. Pre-increment adds into additional questioning "why?". I.e. What does make this case special? When I read such a code I need more brain power to parse it.
Ok whatever.
Quoted 5 lines
...
Quoted 1 line
Quoted 1 line
Quoted 1 line
+ dash =
strpbrk(prop->name, "-");Btw, don't you want
strrchr()here? (Note 'r' variant).
Already changed.
Quoted 12 lines
Quoted 12 lines
Quoted 8 lines
drivers/gpio/gpio-virtuser.c line 907
Quoted 5 lines
drivers/gpio/gpio-virtuser.c new file +4
-0+ diff = dash - prop->name; + + tmp =devm_kmemdup (dev, prop->name, diff +1 , +GFP_KERNEL );
devm_kstrndup()is not okay? Okay, we don't have it (yet?), but at least I would rather expect wrappedkstrndup()than this.Meh, this logic is fine as we know the range exactly. IMO
kstrndup()here would be overkill. I'd leave it for now.
Quoted 7 lines
Quoted 4 lines
Quoted 4 lines
drivers/gpio/gpio-virtuser.c line 911
Quoted 4 lines
drivers/gpio/gpio-virtuser.c new file +3
-0+if (!tmp) +return -ENOMEM ; + tmp[diff] ='\0' ;This line will gone with
kstrndup(). I think we will benefit from it.
I'll allow myself to keep my version here.
Quoted 21 lines
...
Quoted 3 lines
Quoted 3 lines
Quoted 1 line
+ int i = 0;
Why signed? And in all this kind of case, I would split assignment...
(1)
Quoted 13 lines
Quoted 12 lines
drivers/gpio/gpio-virtuser.c line 1478
Quoted 8 lines
drivers/gpio/gpio-virtuser.c new file +8
-0+memset (properties,0 ,sizeof (properties)); + + num_ids =list_count_nodes (&dev->lookup_list); + char **ids __free(kfree) = kcalloc(num_ids + 1, sizeof(*ids), +GFP_KERNEL ); +if (!ids) +return ERR_PTR (-ENOMEM ); +To be here, that the reader will see immediately (close enough) what is the initial values. Moreover this code will be robuse against changes in between (if i become reusable).
Quoted 8 lines
Quoted 1 line
Sorry, I can't parse it.
I meant to see here
i = 0;instead of the above (1).
Why? I see no good reason.
Quoted 8 lines
Quoted 2 lines
Quoted 2 lines
Review
Quoted 2 lines
(quoted diff) +2
-0+ list_for_each_entry(lookup, &dev->lookup_list, siblings) + ids[i++] = lookup->con_id;--
With Best Regards,
Andy Shevchenko
Bart
Quoted 6 lines · Bartosz Golaszewski
Quoted 4 lines · Andy Shevchenko
Mon, Jun 10, 2024 at 03:22:32PM +0200, Bartosz Golaszewski kirjoitti:
Quoted 3 lines
Quoted 1 line · Andy Shevchenko
Mon, May 27, 2024 at 04:40:54PM +0200, Bartosz Golaszewski kirjoitti:
...
Quoted 16 lines
Quoted 16 lines
Quoted 15 lines
Quoted 5 lines
Quoted 5 lines
drivers/gpio/gpio-virtuser.c new file +5
-0+static ssize_t gpio_virtuser_sysfs_emit_value_array (char *buf, +unsigned long *values, +size_t num_values) +{ +ssize_t len =0 ;Quoted 8 lines
drivers/gpio/gpio-virtuser.c line 150
Quoted 6 lines
drivers/gpio/gpio-virtuser.c new file +6
-0+size_t i; + +for (i =0 ; i < num_values; i++) + len +=sysfs_emit_at (buf, len,"%d" , +test_bit (i, values) ?1 :0 ); +return len +sysfs_emit_at (buf, len,"\n" );Why not use %pb?
Because it outputs hex? I want to output binary, can I do it?
Quoted 8 lines
Quoted 5 lines
But why do you need that? You can also print a list of numbers of bits that set (%pbl).
We have a few ABIs in the kernel that works nice and people are familiar with (CPU sets, IRQ affinity masks, etc). Why to reinvent the wheel?
If I see "11001011" as output, I can immediately convert that to pins in my head. If I see
0xcb, I need to use a calculator.
From my experience I see the opposite, as the hex values are condensed, it will be easy to get lost in binary > 8 bits. Despite that, I am really against Yet Another Parsers in the kernel. Here I do not see a good justification for one more. As I pointed out the kernel has very known bitmap ABIs that users are familiar with. Are you creating a driver for yourself or for a wider audience?
Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line
Quoted 1 line
+}
...
Quoted 32 lines
Quoted 32 lines
Quoted 9 lines
Quoted 9 lines
drivers/gpio/gpio-virtuser.c line 158
Quoted 6 lines
drivers/gpio/gpio-virtuser.c new file +6
-0+static int gpio_virtuser_sysfs_parse_value_array (const char *buf,size_t len, +unsigned long *values) +{ +size_t i; + +for (i =0 ; i < len; i++) {Perhaps
Quoted 6 lines
Quoted 4 lines
bool val; int ret; ret = kstrtobool(...);
kstrtobool()accepts values we don't want here like [Tt]rue and [Ff]alse.Yes, see below.
Quoted 14 lines
Quoted 14 lines
if (ret) return ret; assign_bit(...); // btw, why atomic?Quoted 8 lines
drivers/gpio/gpio-virtuser.c new file +7
-0+if (buf[i] =='0' ) +clear_bit (i, values); +else if (buf[i] =='1' ) +set_bit (i, values); +else +return -EINVAL ; + }
Quoted 7 lines
Quoted 7 lines
Quoted 3 lines
Quoted 1 line
BUT, why not
bitmap_parse()?Because it parses hex, not binary.
So, why do we reinvent a wheel? Wouldn't be better that users may apply the knowledge they are familiar with (and I believe the group of the users who know about bitmaps is much bigger than those who will use this driver).
You see, you ignored this comment.
Quoted 2 lines
Quoted 2 lines
Quoted 2 lines
Quoted 2 lines
Quoted 2 lines
(quoted diff) +2
-0+ return 0; +}
...
Quoted 3 lines
Quoted 1 line
At bare minumum you can reduce the range by using
kstrtou8().As opposed to just checking '0' and '1'? Meh...
Okay, can you explain why you need to take bigger numbers when it's going to be used only in a very reduced range? Esp. negative ones. Whatever, your choice.
...
Quoted 21 lines
Quoted 21 lines
Quoted 3 lines
Quoted 3 lines
Quoted 1 line
+ int i = 0;
Why signed? And in all this kind of case, I would split assignment...
(1)
Quoted 14 lines
Quoted 4 lines
Quoted 4 lines
drivers/gpio/gpio-virtuser.c new file +4
-0+memset (properties,0 ,sizeof (properties)); + + num_ids =list_count_nodes (&dev->lookup_list); + char **ids __free(kfree) = kcalloc(num_ids + 1, sizeof(*ids),Quoted 8 lines
drivers/gpio/gpio-virtuser.c line 1478
Quoted 4 lines
drivers/gpio/gpio-virtuser.c new file +4
-0+ GFP_KERNEL); +if (!ids) +return ERR_PTR (-ENOMEM ); +To be here, that the reader will see immediately (close enough) what is the initial values. Moreover this code will be robuse against changes in between (if i become reusable).
Sorry, I can't parse it.
Quoted 7 lines
Quoted 5 lines
I meant to see here
i = 0;instead of the above (1).
Why? I see no good reason.
There are two:
1) readability as explained above;
2) maintenance.
The second one is good in case the same variable (that's the very often case for such as "i" is going to be reused in the future by some new code. But okay, in this case you probably will be the only one for maintenance.
Quoted 2 lines
Quoted 2 lines
Quoted 2 lines
Quoted 2 lines
Quoted 2 lines
(quoted diff) +2
-0+ list_for_each_entry(lookup, &dev->lookup_list, siblings) + ids[i++] = lookup->con_id;
Signature · 2 lines
With Best Regards, Andy Shevchenko