@@ -9,10 +9,13 @@
#include <linux/bitmap.h>
#include <linux/bitops.h>
+#include <linux/completion.h>
+#include <linux/configfs.h>
#include <linux/ctype.h>
#include <linux/delay.h>
#include <linux/idr.h>
#include <linux/kernel.h>
+#include <linux/list.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/mutex.h>
@@ -33,9 +36,22 @@
* GPIO Aggregator sysfs interface
*/
+struct gpio_aggregator_line;
+
struct gpio_aggregator {
+ struct config_group group;
struct gpiod_lookup_table *lookups;
struct platform_device *pdev;
+ int id;
+ struct mutex lock;
+
+ struct notifier_block bus_notifier;
+ struct completion probe_completion;
+ bool driver_bound;
+
+ unsigned int num_lines;
+ struct gpio_aggregator_line *lines;
+
char args[];
};
@@ -474,6 +490,25 @@ static int gpiochip_fwd_setup_delay_line(struct device *dev, struct gpio_chip *c
}
#endif /* !CONFIG_OF_GPIO */
+static int gpiochip_fwd_line_names(struct device *dev, const char **names, int len)
+{
+ struct device_node *of_node = dev_of_node(dev);
+ int num;
+
+ if (of_node)
+ /* not supported */
+ return 0;
+
+ num = device_property_string_array_count(dev, "gpio-line-names");
+ if (num > len) {
+ pr_warn("gpio-line-names contains %d lines while %d expected",
+ num, len);
+ num = len;
+ }
+ return device_property_read_string_array(dev, "gpio-line-names",
+ names, num);
+}
+
/**
* gpiochip_fwd_create() - Create a new GPIO forwarder
* @dev: Parent device pointer
@@ -496,6 +531,7 @@ static struct gpiochip_fwd *gpiochip_fwd_create(struct device *dev,
{
const char *label = dev_name(dev);
struct gpiochip_fwd *fwd;
+ const char **line_names;
struct gpio_chip *chip;
unsigned int i;
int error;
@@ -507,6 +543,14 @@ static struct gpiochip_fwd *gpiochip_fwd_create(struct device *dev,
chip = &fwd->chip;
+ line_names = devm_kcalloc(dev, sizeof(*line_names), ngpios, GFP_KERNEL);
+ if (!line_names)
+ return ERR_PTR(-ENOMEM);
+
+ error = gpiochip_fwd_line_names(dev, line_names, ngpios);
+ if (error < 0)
+ return ERR_PTR(-ENOMEM);
+
/*
* If any of the GPIO lines are sleeping, then the entire forwarder
* will be sleeping.
@@ -538,6 +582,7 @@ static struct gpiochip_fwd *gpiochip_fwd_create(struct device *dev,
chip->to_irq = gpio_fwd_to_irq;
chip->base = -1;
chip->ngpio = ngpios;
+ chip->names = line_names;
fwd->descs = descs;
if (chip->can_sleep)
@@ -581,8 +626,20 @@ static int gpio_aggregator_probe(struct platform_device *pdev)
for (i = 0; i < n; i++) {
descs[i] = devm_gpiod_get_index(dev, NULL, i, GPIOD_ASIS);
- if (IS_ERR(descs[i]))
+ if (IS_ERR(descs[i])) {
+ /*
+ * Deferred probing is not suitable when the aggregator
+ * is created by userspace. They should just retry
+ * later whenever they like.
+ * .prevent_deferred_probe is kept unset for other
+ * cases.
+ */
+ if (!dev_of_node(dev) && descs[i] == ERR_PTR(-EPROBE_DEFER)) {
+ pr_warn("Deferred probe canceled for creation by userspace.\n");
+ return -ENODEV;
+ }
return PTR_ERR(descs[i]);
+ }
}
features = (uintptr_t)device_get_match_data(dev);
@@ -616,14 +673,626 @@ static struct platform_driver gpio_aggregator_driver = {
},
};
+static int gpio_aggregator_bus_notifier_call(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ struct gpio_aggregator *aggr;
+ struct device *dev = data;
+
+ aggr = container_of(nb, struct gpio_aggregator, bus_notifier);
+ if (!device_match_name(dev, aggr->lookups->dev_id))
+ return NOTIFY_DONE;
+
+ switch (action) {
+ case BUS_NOTIFY_BOUND_DRIVER:
+ aggr->driver_bound = true;
+ break;
+ case BUS_NOTIFY_DRIVER_NOT_BOUND:
+ aggr->driver_bound = false;
+ break;
+ default:
+ return NOTIFY_DONE;
+ }
+
+ complete(&aggr->probe_completion);
+ return NOTIFY_OK;
+}
+
+static struct gpio_aggregator *
+to_gpio_aggregator(struct config_item *item)
+{
+ struct config_group *group = to_config_group(item);
+
+ return container_of(group, struct gpio_aggregator, group);
+}
+
+static bool
+gpio_aggregator_is_live(struct gpio_aggregator *aggr)
+{
+ lockdep_assert_held(&aggr->lock);
+
+ return !!aggr->pdev;
+}
+
+struct gpio_aggregator_line {
+ struct config_group group;
+
+ struct gpio_aggregator *parent;
+ int idx;
+
+ char *name;
+ char *key;
+ /* Can be negative to indicate lookup by name. */
+ int offset;
+ enum gpio_lookup_flags flags;
+};
+
+static void
+gpio_aggregator_line_init(struct gpio_aggregator_line *line)
+{
+ memset(line, 0, sizeof(*line));
+ line->idx = -1;
+}
+
+static struct gpio_aggregator_line *
+to_gpio_aggregator_line(struct config_item *item)
+{
+ struct config_group *group = to_config_group(item);
+
+ return container_of(group, struct gpio_aggregator_line, group);
+}
+
+static ssize_t
+gpio_aggregator_line_config_key_show(struct config_item *item, char *page)
+{
+ struct gpio_aggregator_line *line = to_gpio_aggregator_line(item);
+ struct gpio_aggregator *aggr = line->parent;
+
+ guard(mutex)(&aggr->lock);
+
+ return sprintf(page, "%s\n", line->key ?: "");
+}
+
+static ssize_t
+gpio_aggregator_line_config_key_store(struct config_item *item,
+ const char *page, size_t count)
+{
+ struct gpio_aggregator_line *line = to_gpio_aggregator_line(item);
+ struct gpio_aggregator *aggr = line->parent;
+
+ char *key __free(kfree) = kstrndup(skip_spaces(page), count,
+ GFP_KERNEL);
+ if (!key)
+ return -ENOMEM;
+
+ strim(key);
+
+ guard(mutex)(&aggr->lock);
+
+ if (gpio_aggregator_is_live(aggr))
+ return -EBUSY;
+
+ kfree(line->key);
+ line->key = no_free_ptr(key);
+
+ return count;
+}
+
+CONFIGFS_ATTR(gpio_aggregator_line_config_, key);
+
+static ssize_t
+gpio_aggregator_line_config_name_show(struct config_item *item, char *page)
+{
+ struct gpio_aggregator_line *line =
+ to_gpio_aggregator_line(item);
+ struct gpio_aggregator *aggr = line->parent;
+
+ guard(mutex)(&aggr->lock);
+
+ return sprintf(page, "%s\n", line->name ?: "");
+}
+
+static ssize_t
+gpio_aggregator_line_config_name_store(struct config_item *item,
+ const char *page, size_t count)
+{
+ struct gpio_aggregator_line *line =
+ to_gpio_aggregator_line(item);
+ struct gpio_aggregator *aggr = line->parent;
+
+ char *name __free(kfree) = kstrndup(skip_spaces(page), count,
+ GFP_KERNEL);
+ if (!name)
+ return -ENOMEM;
+
+ strim(name);
+
+ guard(mutex)(&aggr->lock);
+
+ if (gpio_aggregator_is_live(aggr))
+ return -EBUSY;
+
+ kfree(line->name);
+ line->name = no_free_ptr(name);
+
+ return count;
+}
+
+CONFIGFS_ATTR(gpio_aggregator_line_config_, name);
+
+static ssize_t
+gpio_aggregator_line_config_offset_show(struct config_item *item,
+ char *page)
+{
+ struct gpio_aggregator_line *line =
+ to_gpio_aggregator_line(item);
+ struct gpio_aggregator *aggr = line->parent;
+ unsigned int offset;
+
+ scoped_guard(mutex, &aggr->lock)
+ offset = line->offset;
+
+ return sprintf(page, "%d\n", offset);
+}
+
+static ssize_t
+gpio_aggregator_line_config_offset_store(struct config_item *item,
+ const char *page, size_t count)
+{
+ struct gpio_aggregator_line *line =
+ to_gpio_aggregator_line(item);
+ struct gpio_aggregator *aggr = line->parent;
+ int offset, ret;
+
+ ret = kstrtoint(page, 0, &offset);
+ if (ret)
+ return ret;
+
+ /*
+ * Negative number here means: 'key' represents a line name to lookup.
+ * Non-negative means: 'key' represents the label of the chip with
+ * the 'offset' value representing the line within that chip.
+ *
+ * GPIOLIB uses the U16_MAX value to indicate lookup by line name so
+ * the greatest offset we can accept is (U16_MAX - 1).
+ */
+ if (offset > (U16_MAX - 1))
+ return -EINVAL;
+
+ guard(mutex)(&aggr->lock);
+
+ if (gpio_aggregator_is_live(aggr))
+ return -EBUSY;
+
+ line->offset = offset;
+
+ return count;
+}
+
+CONFIGFS_ATTR(gpio_aggregator_line_config_, offset);
+
+static struct configfs_attribute *gpio_aggregator_line_config_attrs[] = {
+ &gpio_aggregator_line_config_attr_key,
+ &gpio_aggregator_line_config_attr_name,
+ &gpio_aggregator_line_config_attr_offset,
+ NULL
+};
+
+static struct fwnode_handle *
+gpio_aggregator_make_device_swnode(struct gpio_aggregator *aggr)
+{
+ struct property_entry properties[2];
+ int i;
+
+ memset(properties, 0, sizeof(properties));
+
+ char **line_names __free(kfree);
+ line_names = kcalloc(aggr->num_lines, sizeof(*line_names), GFP_KERNEL);
+ if (!line_names)
+ return ERR_PTR(-ENOMEM);
+
+ for (i = 0; i < aggr->num_lines; i++)
+ line_names[i] = aggr->lines[i].name ?: "";
+
+ properties[0] = PROPERTY_ENTRY_STRING_ARRAY_LEN(
+ "gpio-line-names",
+ line_names, aggr->num_lines);
+
+ return fwnode_create_software_node(properties, NULL);
+}
+
+static int
+gpio_aggregator_activate(struct gpio_aggregator *aggr)
+{
+ struct platform_device_info pdevinfo;
+ struct gpio_aggregator_line *line;
+ struct fwnode_handle *swnode;
+ struct platform_device *pdev;
+ unsigned int n = 0;
+ int i, ret = 0;
+
+ aggr->lookups = kzalloc(struct_size(aggr->lookups, table, 1),
+ GFP_KERNEL);
+ if (!aggr->lookups)
+ return -ENOMEM;
+
+ swnode = gpio_aggregator_make_device_swnode(aggr);
+ if (IS_ERR(swnode))
+ goto err_remove_lookups;
+
+ memset(&pdevinfo, 0, sizeof(pdevinfo));
+ pdevinfo.name = DRV_NAME;
+ pdevinfo.id = aggr->id;
+ pdevinfo.fwnode = swnode;
+
+ for (i = 0; i < aggr->num_lines; i++) {
+ line = &aggr->lines[i];
+ if (line->idx < 0)
+ goto err_remove_swnode;
+ if (line->offset < 0)
+ ret = aggr_add_gpio(aggr, line->key, U16_MAX, &n);
+ else
+ ret = aggr_add_gpio(aggr, line->key, line->offset, &n);
+ if (ret)
+ goto err_remove_swnode;
+ }
+
+ aggr->lookups->dev_id = kasprintf(GFP_KERNEL, "%s.%d", DRV_NAME, aggr->id);
+
+ gpiod_add_lookup_table(aggr->lookups);
+
+ reinit_completion(&aggr->probe_completion);
+ aggr->driver_bound = false;
+ aggr->bus_notifier.notifier_call = gpio_aggregator_bus_notifier_call;
+ bus_register_notifier(&platform_bus_type, &aggr->bus_notifier);
+
+ pdev = platform_device_register_full(&pdevinfo);
+ if (IS_ERR(pdev)) {
+ ret = PTR_ERR(pdev);
+ bus_unregister_notifier(&platform_bus_type, &aggr->bus_notifier);
+ goto err_remove_lookup_table;
+ }
+
+ wait_for_completion(&aggr->probe_completion);
+ bus_unregister_notifier(&platform_bus_type, &aggr->bus_notifier);
+
+ if (!aggr->driver_bound) {
+ ret = -ENXIO;
+ goto err_unregister_pdev;
+ }
+
+ aggr->pdev = pdev;
+ return 0;
+
+err_unregister_pdev:
+ platform_device_unregister(pdev);
+err_remove_lookup_table:
+ gpiod_remove_lookup_table(aggr->lookups);
+err_remove_swnode:
+ fwnode_remove_software_node(swnode);
+err_remove_lookups:
+ kfree(aggr->lookups);
+
+ return ret;
+}
+
+static void
+gpio_aggregator_deactivate(struct gpio_aggregator *aggr)
+{
+ platform_device_unregister(aggr->pdev);
+ gpiod_remove_lookup_table(aggr->lookups);
+ kfree(aggr->lookups->dev_id);
+ kfree(aggr->lookups);
+ aggr->pdev = NULL;
+}
+
+static void
+gpio_aggregator_lockup_configfs(struct gpio_aggregator *aggr, bool lock)
+{
+ struct configfs_subsystem *subsys = aggr->group.cg_subsys;
+ struct gpio_aggregator_line *line;
+ int i;
+
+ /*
+ * The device only needs to depend on leaf lookups. This is
+ * sufficient to lock up all the configfs entries that the
+ * instantiated, alive device depends on.
+ */
+ for (i = 0; i < aggr->num_lines; i++) {
+ line = &aggr->lines[i];
+ if (lock)
+ WARN_ON(configfs_depend_item_unlocked(
+ subsys, &line->group.cg_item));
+ else
+ configfs_undepend_item_unlocked(
+ &line->group.cg_item);
+ }
+}
+
+static ssize_t
+gpio_aggregator_device_config_dev_name_show(struct config_item *item,
+ char *page)
+{
+ struct gpio_aggregator *aggr = to_gpio_aggregator(item);
+ struct platform_device *pdev;
+
+ guard(mutex)(&aggr->lock);
+
+ pdev = aggr->pdev;
+ if (pdev)
+ return sprintf(page, "%s\n", dev_name(&pdev->dev));
+
+ return sprintf(page, "%s.%d\n", DRV_NAME, aggr->id);
+}
+
+CONFIGFS_ATTR_RO(gpio_aggregator_device_config_, dev_name);
+
+static ssize_t gpio_aggregator_device_config_live_show(struct config_item *item,
+ char *page)
+{
+ struct gpio_aggregator *aggr = to_gpio_aggregator(item);
+ bool live;
+
+ scoped_guard(mutex, &aggr->lock)
+ live = gpio_aggregator_is_live(aggr);
+
+ return sprintf(page, "%c\n", live ? '1' : '0');
+}
+
+static ssize_t
+gpio_aggregator_device_config_live_store(struct config_item *item,
+ const char *page, size_t count)
+{
+ struct gpio_aggregator *aggr = to_gpio_aggregator(item);
+ int ret = 0;
+ bool live;
+
+ ret = kstrtobool(page, &live);
+ if (ret)
+ return ret;
+
+ if (live)
+ gpio_aggregator_lockup_configfs(aggr, true);
+
+ scoped_guard(mutex, &aggr->lock) {
+ if (live == gpio_aggregator_is_live(aggr))
+ ret = -EPERM;
+ else if (live)
+ ret = gpio_aggregator_activate(aggr);
+ else
+ gpio_aggregator_deactivate(aggr);
+ }
+
+ /*
+ * Undepend is required only if device disablement (live == 0)
+ * succeeds or if device enablement (live == 1) fails.
+ */
+ if (live == !!ret)
+ gpio_aggregator_lockup_configfs(aggr, false);
+
+ return ret ?: count;
+}
+
+CONFIGFS_ATTR(gpio_aggregator_device_config_, live);
+
+static ssize_t
+gpio_aggregator_device_config_num_lines_show(struct config_item *item,
+ char *page)
+{
+ struct gpio_aggregator *aggr = to_gpio_aggregator(item);
+
+ guard(mutex)(&aggr->lock);
+
+ return sprintf(page, "%u\n", aggr->num_lines);
+}
+
+static ssize_t
+gpio_aggregator_device_config_num_lines_store(struct config_item *item,
+ const char *page, size_t count)
+{
+ struct gpio_aggregator *aggr = to_gpio_aggregator(item);
+ struct gpio_aggregator_line *line, *lines;
+ unsigned int num_lines;
+ int i, ret;
+
+ ret = kstrtouint(page, 0, &num_lines);
+ if (ret)
+ return ret;
+
+ if (num_lines == 0)
+ return -EINVAL;
+
+ guard(mutex)(&aggr->lock);
+
+ if (gpio_aggregator_is_live(aggr))
+ return -EBUSY;
+
+ if (aggr->num_lines > num_lines) {
+ for (i = aggr->num_lines - 1; i >= num_lines; i--) {
+ line = &aggr->lines[i];
+ if (line->idx >= 0)
+ return -EBUSY;
+ }
+ }
+
+ lines = krealloc(aggr->lines, sizeof(*line) * num_lines, GFP_KERNEL);
+ for (i = aggr->num_lines; i < num_lines; i++)
+ gpio_aggregator_line_init(&lines[i]);
+
+ aggr->lines = lines;
+ aggr->num_lines = num_lines;
+
+ return count;
+}
+
+CONFIGFS_ATTR(gpio_aggregator_device_config_, num_lines);
+
+static struct configfs_attribute *gpio_aggregator_device_config_attrs[] = {
+ &gpio_aggregator_device_config_attr_dev_name,
+ &gpio_aggregator_device_config_attr_live,
+ &gpio_aggregator_device_config_attr_num_lines,
+ NULL
+};
+
+static void
+gpio_aggregator_line_config_release(struct config_item *item)
+{
+ struct gpio_aggregator_line *line =
+ to_gpio_aggregator_line(item);
+ struct gpio_aggregator *aggr = line->parent;
+
+ guard(mutex)(&aggr->lock);
+
+ kfree(line->key);
+ kfree(line->name);
+ gpio_aggregator_line_init(line);
+}
+
+static struct
+configfs_item_operations gpio_aggregator_line_config_item_ops = {
+ .release = gpio_aggregator_line_config_release,
+};
+
+static const struct config_item_type gpio_aggregator_line_config_type = {
+ .ct_item_ops = &gpio_aggregator_line_config_item_ops,
+ .ct_attrs = gpio_aggregator_line_config_attrs,
+ .ct_owner = THIS_MODULE,
+};
+
+static void gpio_aggregator_device_config_release(struct config_item *item)
+{
+ struct gpio_aggregator *aggr = to_gpio_aggregator(item);
+
+ guard(mutex)(&aggr->lock);
+
+ if (gpio_aggregator_is_live(aggr))
+ gpio_aggregator_deactivate(aggr);
+
+ mutex_destroy(&aggr->lock);
+ idr_remove(&gpio_aggregator_idr, aggr->id);
+ kfree(aggr);
+}
+
+static struct configfs_item_operations gpio_aggregator_device_config_item_ops = {
+ .release = gpio_aggregator_device_config_release,
+};
+
+static struct config_group *
+gpio_aggregator_device_config_make_group(struct config_group *group,
+ const char *name)
+{
+ struct gpio_aggregator *aggr =
+ to_gpio_aggregator(&group->cg_item);
+ struct gpio_aggregator_line *line;
+ unsigned int offset;
+ int ret, nchar;
+
+ ret = sscanf(name, "line%u%n", &offset, &nchar);
+ if (ret != 1 || nchar != strlen(name))
+ return ERR_PTR(-EINVAL);
+
+ guard(mutex)(&aggr->lock);
+
+ if (gpio_aggregator_is_live(aggr))
+ return ERR_PTR(-EBUSY);
+
+ line = &aggr->lines[offset];
+ if (line->idx >= 0)
+ return ERR_PTR(-EBUSY);
+
+ config_group_init_type_name(&line->group, name,
+ &gpio_aggregator_line_config_type);
+ line->flags = GPIO_LOOKUP_FLAGS_DEFAULT;
+ line->parent = aggr;
+ line->idx = offset;
+
+ return &no_free_ptr(line)->group;
+}
+
+static struct
+configfs_group_operations gpio_aggregator_device_config_group_ops = {
+ .make_group = gpio_aggregator_device_config_make_group,
+};
+
+static const struct config_item_type gpio_aggregator_device_config_type = {
+ .ct_group_ops = &gpio_aggregator_device_config_group_ops,
+ .ct_item_ops = &gpio_aggregator_device_config_item_ops,
+ .ct_attrs = gpio_aggregator_device_config_attrs,
+ .ct_owner = THIS_MODULE,
+};
+
+static struct config_group *
+gpio_aggregator_config_make_group(struct config_group *group,
+ const char *name)
+{
+ /* no arg space is needed */
+ struct gpio_aggregator *aggr __free(kfree) = kzalloc(sizeof(*aggr),
+ GFP_KERNEL);
+ if (!aggr)
+ return ERR_PTR(-ENOMEM);
+
+ mutex_lock(&gpio_aggregator_lock);
+ aggr->id = idr_alloc(&gpio_aggregator_idr, aggr, 0, 0, GFP_KERNEL);
+ mutex_unlock(&gpio_aggregator_lock);
+
+ if (aggr->id < 0)
+ return ERR_PTR(aggr->id);
+
+ mutex_init(&aggr->lock);
+ config_group_init_type_name(&aggr->group, name,
+ &gpio_aggregator_device_config_type);
+ aggr->bus_notifier.notifier_call = gpio_aggregator_bus_notifier_call;
+ init_completion(&aggr->probe_completion);
+
+ return &no_free_ptr(aggr)->group;
+}
+
+static struct configfs_group_operations gpio_aggregator_config_group_ops = {
+ .make_group = gpio_aggregator_config_make_group,
+};
+
+static const struct config_item_type gpio_aggregator_config_type = {
+ .ct_group_ops = &gpio_aggregator_config_group_ops,
+ .ct_owner = THIS_MODULE,
+};
+
+static struct configfs_subsystem gpio_aggregator_config_subsys = {
+ .su_group = {
+ .cg_item = {
+ .ci_namebuf = DRV_NAME,
+ .ci_type = &gpio_aggregator_config_type,
+ },
+ },
+};
+
static int __init gpio_aggregator_init(void)
{
- return platform_driver_register(&gpio_aggregator_driver);
+ int ret = 0;
+
+ ret = platform_driver_register(&gpio_aggregator_driver);
+ if (ret) {
+ pr_err("Failed to register the platform driver: %d\n", ret);
+ return ret;
+ }
+
+ config_group_init(&gpio_aggregator_config_subsys.su_group);
+ mutex_init(&gpio_aggregator_config_subsys.su_mutex);
+ ret = configfs_register_subsystem(&gpio_aggregator_config_subsys);
+ if (ret) {
+ pr_err("Failed to register the '%s' configfs subsystem: %d\n",
+ gpio_aggregator_config_subsys.su_group.cg_item.ci_namebuf,
+ ret);
+ mutex_destroy(&gpio_aggregator_config_subsys.su_mutex);
+ platform_driver_unregister(&gpio_aggregator_driver);
+ }
+
+ return ret;
}
module_init(gpio_aggregator_init);
static void __exit gpio_aggregator_exit(void)
{
+ configfs_unregister_subsystem(&gpio_aggregator_config_subsys);
gpio_aggregator_remove_all();
platform_driver_unregister(&gpio_aggregator_driver);
}
The existing 'new_device' interface has several limitations: * No way to determine when GPIO aggregator creation is complete. * No way to retrieve errors when creating a GPIO aggregator. * No way to trace a GPIO line of an aggregator back to its corresponding physical device. * The 'new_device' echo does not indicate which virtual gpiochip.<N> was created. * No way to assign names to GPIO lines exported through an aggregator. Introduce configfs-based interface for gpio-aggregator to address these issues. It provides a more streamlined, modern, and extensible configuration method. For backward compatibility, the 'new_device' interface is retained for now. Signed-off-by: Koichiro Den <koichiro.den@canonical.com> --- drivers/gpio/gpio-aggregator.c | 673 ++++++++++++++++++++++++++++++++- 1 file changed, 671 insertions(+), 2 deletions(-)