Re: [PATCH v12 4/7] gpio: sim: new testing module

Bartosz Golaszewski
2021-12-06T09:48:17Z

On Fri, Dec 3, 2021 at 9:08 PM Andy Shevchenko
<andriy.shevchenko@linux.intel.com> wrote:
>
> On Fri, Dec 03, 2021 at 02:30:00PM +0100, Bartosz Golaszewski wrote:
> > Implement a new, modern GPIO testing module controlled by configfs
> > attributes instead of module parameters. The goal of this driver is
> > to provide a replacement for gpio-mockup that will be easily extensible
> > with new features and doesn't require reloading the module to change
> > the setup.
>
> ...
>
> > +**Group:** ``/config/gpio-sim/gpio-device``
> > +
> > +**Attribute:** ``/config/gpio-sim/gpio-device/dev_name``
> > +
> > +**Attribute:** ``/config/gpio-sim/gpio-device/live``
> > +
> > +This is a directory representing a GPIO platform device. The ``'dev_name'``
> > +attribute is read-only and allows the user-space to read the platform device
> > +name (e.g. ``'gpio-sim.0'``). 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 simulated device and ``'0'`` to disable and tear
> > +it down.
>
> Perhaps it makes sense to describe properties in the order you expect to be
> used, then it will be naturally to 'read and repeat' without jumping forward
> and backward through the documentation.
>

This is such order though. You naturally configure the device, then
bank, then lines, then hogs.

> ...
>
> > +**Group:** ``/config/gpio-sim/gpio-device/gpio-bankX``
> > +
> > +**Attribute:** ``/config/gpio-sim/gpio-device/gpio-bankX/chip_name``
>
> > +**Attribute:** ``/config/gpio-sim/gpio-device/gpio-bankX/num_lines``
>
> Why not to use the same name as in DT, i.e. ngpios?
>

This would be the only attribute that follows the DT naming, the
label, line names etc. wouldn't use the same name anyway. I don't see
any advantage of it as num_lines is actually more intuitive than
ngpios IMO.

> ...
>
> > +#include <linux/gpio/driver.h>
> > +#include <linux/gpio/machine.h>
>
> I would rather move this group below to emphasize that this is closer to GPIO
> then to other APIs.
>
> > +#include <linux/sysfs.h>
> > +
>
> ...here.
>

With the number of headers in this file, I'd stick with alphabetical order.

> > +#include "gpiolib.h"
>
> ...
>
> > +static int gpio_sim_apply_pull(struct gpio_sim_chip *chip,
> > + unsigned int offset, int value)
>
> I would use up to 100 here...
>
> > + if (test_bit(FLAG_REQUESTED, &desc->flags) &&
> > + !test_bit(FLAG_IS_OUT, &desc->flags)) {
>
> ...here and so on.
>
> But it's up to you.
>

Nah, the lines are broken just fine. Let's not overuse the limit.

> ...
>
> > + curr_val = !!test_bit(offset, chip->value_map);
> > + if (curr_val == value)
>
> Do you use curr_val anywhere else? Perhaps combine these two lines.
>
> > + goto set_pull;
>

Good point.

> ...
>
> > +static int gpio_sim_set_config(struct gpio_chip *gc,
> > + unsigned int offset, unsigned long config)
> > +{
> > + struct gpio_sim_chip *chip = gpiochip_get_data(gc);
> > +
> > + switch (pinconf_to_config_param(config)) {
> > + case PIN_CONFIG_BIAS_PULL_UP:
> > + return gpio_sim_apply_pull(chip, offset, 1);
> > + case PIN_CONFIG_BIAS_PULL_DOWN:
> > + return gpio_sim_apply_pull(chip, offset, 0);
> > + default:
>
> > + break;
> > + }
> > +
> > + return -ENOTSUPP;
>
> return directly from switch-case?
>

This may be a personal preference but I don't like returning functions
without a return at the end. Always looks wrong at first glance. I'd
like to keep it.

> > +}
>
> ...
>
> > +static ssize_t gpio_sim_sysfs_pull_show(struct device *dev,
> > + struct device_attribute *attr,
> > + char *buf)
> > +{
> > + struct gpio_sim_attribute *line_attr = to_gpio_sim_attr(attr);
> > + struct gpio_sim_chip *chip = dev_get_drvdata(dev);
> > + char *repr;
> > + int pull;
>
> int pull_up;
>
> ? Also, where is "pull-none"?
>

There isn't. If it's ever needed, we can extend the driver but so far
there hasn't been a need for it when testing from user-space.

> > + mutex_lock(&chip->lock);
> > + pull = !!test_bit(line_attr->offset, chip->pull_map);
> > + mutex_unlock(&chip->lock);
>
> > + if (pull)
> > + repr = "pull-up";
> > + else
> > + repr = "pull-down";
> > +
> > + return sysfs_emit(buf, "%s\n", repr);
>
> return sysfs_emit(buf, "%pull-s\n", pull_up ? "up" : "down");
>
> ?

Sure.

>
> > +}
>
> ...
>
> > +static ssize_t gpio_sim_sysfs_pull_store(struct device *dev,
> > + struct device_attribute *attr,
> > + const char *buf, size_t len)
> > +{
> > + struct gpio_sim_attribute *line_attr = to_gpio_sim_attr(attr);
> > + struct gpio_sim_chip *chip = dev_get_drvdata(dev);
> > + int ret, pull;
> > +
> > + if (sysfs_streq(buf, "pull-down"))
> > + pull = 0;
> > + else if (sysfs_streq(buf, "pull-up"))
> > + pull = 1;
> > + else
> > + return -EINVAL;
>
> sysfs_match_string() and use the very same string array in the above function
> to print them?
>
> Same question about "pull-none".
>
> > + ret = gpio_sim_apply_pull(chip, line_attr->offset, pull);
> > + if (ret)
> > + return ret;
> > +
> > + return len;
> > +}
>
> ...
>
> > + attr_group->name = devm_kasprintf(dev, GFP_KERNEL,
> > + "sim_gpio%u", i);
>
> Wondering if you can use devm_kasprintf_strarray().
>

I would need to do that in a separate loop and handle the new array, I
think it's an overkill here.

> > + if (!attr_group->name)
> > + return -ENOMEM;
>
> ...
>
> > + /* Default to input mode. */
> > + bitmap_fill(chip->direction_map, num_lines);
>
> More accurate is to use bitmap_set(). If we ever debug this it also helpful.
>

I'm not sure what you mean, this sets all bits to 1.

> ...
>
> > + ret = devm_add_action_or_reset(dev, gpio_sim_mutex_destroy,
> > + &chip->lock);
>
> It's 81, fine to have on one line.
>
> > + if (ret)
> > + return ret;
>
> ...
>
> > +static char *gpio_sim_strdup_trimmed(const char *str, size_t count)
> > +{
> > + char *dup, *trimmed, *ret;
> > +
> > + dup = kstrndup(str, count, GFP_KERNEL);
> > + if (!dup)
> > + return NULL;
> > +
> > + trimmed = strstrip(dup);
> > + ret = kstrdup(trimmed, GFP_KERNEL);
> > + kfree(dup);
> > + return ret;
>
> Why not memmove() instead of additional memory allocation?
>
> Or if you really want to save bytes, krealloc() after?
>
> char *dup, *start, *ret;
> size_t len;
>
> dup = kstrndup(str, count, GFP_KERNEL);
> if (!dup)
> return NULL;
>
> start = strstrip(dup);
> len = strlen(start) - (start - dup);
>
> memmove(dup, start, len + 1);
>
> ret = krealloc(dup, len + 1, GFP_KERNEL);
> if (ret)
> return ret;
>
> kfree(dup);
> return NULL;
>
> ?
>
> > +}
>
> ...
>
> > + return sprintf(page, "%c\n", live ? '1' : '0');
>
> return sprintf(page, "%d\n", live ? 1 : 0);
>
> ?
>
> ...
>
> > + list_for_each_entry(bank, &dev->bank_list, siblings) {
> > + list_for_each_entry(line, &bank->line_list, siblings) {
> > + if (line->hog)
> > + num_hogs++;
> > + }
> > + }
>
> > +
>
> No need to have a blank line here, but up to you.
>
> > + if (!num_hogs)
> > + return 0;
>
> ...
>
> > + list_for_each_entry(pos, &dev->bank_list, siblings) {
> > + if (this == pos || (!this->label || !pos->label))
>
> Too many parentheses.
>

No, this is the logic here. Skip either if both pointers point to the
same object or check if the labels are missing in at least one.

> > + continue;
> > +
> > + if (strcmp(this->label, pos->label) == 0)
> > + return true;
> > + }
>
> ...
>
> > + ret = kstrtouint(page, 10, &live);
>
> Why not kstrtobool() (according to the documentation)?
>

Sure.

> > + if (ret)
> > + return ret;
> > +
> > + mutex_lock(&dev->lock);
> > +
> > + if ((live == 0 && !gpio_sim_device_is_live_unlocked(dev)) ||
> > + (live == 1 && gpio_sim_device_is_live_unlocked(dev)))
> > + ret = -EPERM;
> > + else if (live == 1)
> > + ret = gpio_sim_device_activate_unlocked(dev);
> > + else if (live == 0)
> > + gpio_sim_device_deactivate_unlocked(dev);
>
> > + else
> > + ret = -EINVAL;
>
> This will gone if above is being applied.
>
> > + mutex_unlock(&dev->lock);
>
> ...
>
> > + mutex_lock(&dev->lock);
> > + ret = sprintf(page, "%s\n", bank->label ?: "");
>
> Don't we use "?" in the GPIO library for similar situations?
>

We use it in gpiolib to indicate a lack of label but here, it's the
configuration part. I think an empty string works fine.

> > + mutex_unlock(&dev->lock);
>
> ...
>
> > + ret = kstrtouint(page, 10, &num_lines);
>
> Why not allowing any digit base?
>

Sure.

> > + if (ret)
> > + return ret;
>
> ...
>
> > + switch (dir) {
> > + case GPIOD_IN:
> > + repr = "input";
> > + break;
> > + case GPIOD_OUT_HIGH:
> > + repr = "output-high";
> > + break;
> > + case GPIOD_OUT_LOW:
> > + repr = "output-low";
> > + break;
> > + default:
> > + /* This would be a programmer bug. */
> > + WARN(1, "Unexpected hog direction value: %d", dir);
> > + return -EINVAL;
> > + }
>
>
> > + if (strcmp(trimmed, "input") == 0)
> > + dir = GPIOD_IN;
> > + else if (strcmp(trimmed, "output-high") == 0)
> > + dir = GPIOD_OUT_HIGH;
> > + else if (strcmp(trimmed, "output-low") == 0)
> > + dir = GPIOD_OUT_LOW;
> > + else
> > + dir = -EINVAL;
>
>
> Same idea, i.e. static string array and use it above and here with help
> of match_string().
>

It would be great but GPIOD_IN etc. are bit flags and not sequence enums.

> ...
>
> > +static struct configfs_attribute *gpio_sim_hog_config_attrs[] = {
> > + &gpio_sim_hog_config_attr_name,
> > + &gpio_sim_hog_config_attr_direction,
>
> > + NULL,
>
> Comma is not needed.
>
> > +};
>
> ...
>
> > + id = ida_alloc(&gpio_sim_ida, GFP_KERNEL);
> > + if (id < 0) {
> > + kfree(dev);
> > + return ERR_PTR(id);
> > + }
> > +
> > + config_group_init_type_name(&dev->group, name,
> > + &gpio_sim_device_config_group_type);
> > + dev->id = id;
>
> If you group this assignment with above allocation it would look better.
>

Actually I think it looks better now with allocating the resources
first, then setting up the structure.

Bart

> --
> With Best Regards,
> Andy Shevchenko
>
>