lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Date: Mon, 10 Jun 2024 15:22:32 +0200
From: Bartosz Golaszewski <brgl@...ev.pl>
To: Andy Shevchenko <andy.shevchenko@...il.com>
Cc: Kent Gibson <warthog618@...il.com>, Linus Walleij <linus.walleij@...aro.org>, 
	linux-gpio@...r.kernel.org, linux-kernel@...r.kernel.org, 
	Bartosz Golaszewski <bartosz.golaszewski@...aro.org>
Subject: Re: [PATCH v7] gpio: virtuser: new virtual driver

On Wed, May 29, 2024 at 11:00 PM Andy Shevchenko
<andy.shevchenko@...il.com> wrote:
>
> Mon, May 27, 2024 at 04:40:54PM +0200, Bartosz Golaszewski kirjoitti:
> > From: Bartosz Golaszewski <bartosz.golaszewski@...aro.org>
> >
> > 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.
>
> ...
>
> > 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.

> ...
>
> > +#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>
>
> > +#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.

>
> > +#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>
>
> Implied by string_helpers.h
>

Yeah, but we still use symbols directly from string.h, we shouldn't
depend on implicit includes.

> > +#include <linux/string_helpers.h>
> > +#include <linux/sysfs.h>
> > +#include <linux/types.h>
>
> ...
>
> > +struct gpio_virtuser_line_array_data {
> > +     struct gpio_descs *descs;
> > +     struct kobject *kobj;
> > +     struct attribute_group *attr_group;
> > +};
> > +
> > +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;
> };
>
> ?
>
> ...
>
> > +struct gpio_virtuser_attr_ctx {
> > +     struct device_attribute dev_attr;
> > +     void *data;
> > +};
>
> struct dev_ext_attribute ?
>

Sounds good, I'll rework this.

> ...
>
> > +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.

> ...
>
> > +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?

> > +}
>
> ...
>
> > +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(...);

kstrtobool() accepts values we don't want here like [Tt]rue and [Ff]alse.

>                 if (ret)
>                         return ret;
>
>                 assign_bit(...); // btw, why atomic?
>
> > +             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()?
>

Because it parses hex, not binary.

> > +     return 0;
> > +}
>
> ...
>
> > +     unsigned long *values __free(bitmap) = bitmap_alloc(descs->ndescs,
> > +                                                         GFP_KERNEL);
>
> Perhaps
>
>         unsigned long *values __free(bitmap) =
>                  bitmap_alloc(descs->ndescs, GFP_KERNEL);
>
> ...
>
> > +     unsigned long *values __free(bitmap) = bitmap_zalloc(descs->ndescs,
> > +                                                          GFP_KERNEL);
>
> In the similar way?
>
> ...
>
> > +     unsigned long *values __free(bitmap) = bitmap_zalloc(descs->ndescs,
> > +                                                          GFP_KERNEL);
>
> Ditto.
>
> ...
>
> > +{
> > +     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)
>

These helpers take bool as argument. Hard to tell whether input or
output should correspond to true. I'd leave it as is.

> > +}
>
> ...
>
> > +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;
> > +     }
> > +
> > +     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
>

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()).

>         GPIO_VIRTUSER_OUT_LOW,
>         GPIO_VIRTUSER_OUT_HIGH,
>         GPIO_VIRTUSER_IN,
>
> (with it the string array can also be indexed).
>
> > +     return 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()?
>

I don't want to accept all the other strings kstrtobool() is fine with.

> > +     }
> > +
> > +     return value;
> > +}
>
> ...
>
> > +     ret = kstrtouint(buf, 10, &debounce);
>
> Why restrict to decimal?
>

Not sure what you gain from passing a period in hex?


> > +     if (ret)
> > +             return ret;
>
> ...
>
> > +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.
>

Ok.

> > +     guard(mutex)(&data->consumer_lock);
> > +
> > +     ret = gpiod_set_consumer_name(data->desc, buf);
> > +     if (ret)
> > +             return ret;
> > +
> > +     sprintf(data->consumer, buf);
> > +
> > +     return len;
> > +}
>
> ...
>
> > +     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.

> > +     if (!data->attr_group->name)
> > +             return -ENOMEM;
>
> ...
>
> > +     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)
>

Ah we do now. This code dates back from early 2023 I think. I'll use it.

> > +             return ret;
>
> ...
>
> > +static int gpio_virtuser_prop_is_gpio(struct property *prop)
> > +{
> > +     char *dash = strpbrk(prop->name, "-");
>
> Why not strrchr() ?
>

Ok.

> > +     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.

> > +}
>
> ...
>
> > +/*
> > + * 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?
>

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.

> > + */
> > +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);
>
> > +     struct property *prop;
> > +     int ret = 0;
>
> > +     if (is_of_node(fwnode)) {
>
> Instead of this check...
>
>         if (np) {
>
> ...can be used.
>

Ok.

>
> > +             for_each_property_of_node(to_of_node(fwnode), prop) {
>
>         for_each_property_of_node(np, prop) {
>
> > +                     if (gpio_virtuser_prop_is_gpio(prop))
> > +                             ++ret;
>
> Why pre-increment?

Why not?

>
> > +             }
>
> > +             return ret;
> > +     }
>
> > +     return device_property_string_array_count(dev, "gpio-virtuser,ids");
> > +}
>
> ...
>
> > +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

> > +                     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.
>

Meh, this logic is fine as we know the range exactly. IMO kstrndup()
here would be overkill. I'd leave it for now.

> > +                     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);
> > +}
>
> ...
>
> > +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;
> > +     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.
>

Ok.

> > +     }
> > +
> > +     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;
> > +}
>
> ...
>
> > +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

> > +             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;
> > +}
>
> ...
>
> > +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.

> > +     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;
> > +}
>
> ...
>
> > +     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")) {
> > +             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.
> > +     }
> > +
> > +     return count;
>
> ...
>
> > +     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.

> ...
>
> > +     return sprintf(page, "%s\n", flags & GPIO_TRANSITORY ? "1" : "0");
>
> Ditto.
>
> ...
>
> > +     return sprintf(page, "%c\n", live ? '1' : '0');
>
> Wow! Third type of the same.
>
> ...
>
> > +     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).
>

Ok

> > +     if (!table->dev_id)
> > +             return -ENOMEM;
>
> ...
>
> > +                     curr->chip_hwnum = entry->offset < 0
> > +                                             ? U16_MAX : entry->offset;
>
> Can we leave ? on the previous line?
>
> ...
>
> > +                     ++i;
>
> Why pre-increment?
>
> ...
>
> > +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...
>
> > +     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.

> > +     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);
> > +}
>
> ...
>
> > +     guard(mutex)(&dev->lock);
> > +
> > +     if (live == gpio_virtuser_device_is_live(dev))
> > +             ret = -EPERM;
>
> With guard in place, just return directly, ...
>
> > +     else if (live)
>
> ...drop 'else'...
>
> > +             ret = gpio_virtuser_device_activate(dev);
> > +     else
>
> ...ditto...
>
> > +             gpio_virtuser_device_deactivate(dev);
> > +
> > +     return ret ?: count;
>
> ...and simply return count here.
>

Ok.

>
> ...
>
> > +     struct gpio_virtuser_device *dev __free(kfree) = kzalloc(sizeof(*dev),
> > +                                                              GFP_KERNEL);
>
>         struct gpio_virtuser_device *dev __free(kfree) =
>                 kzalloc(sizeof(*dev), GFP_KERNEL);
>
> > +     if (!dev)
> > +             return ERR_PTR(-ENOMEM);
>
> ...
>
> > +     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.

> > +             return ret;
> > +     }
>
> --
> With Best Regards,
> Andy Shevchenko
>
>

Thanks for the review!
Bart

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ