#include "dev-sync-probe.h"
#define AGGREGATOR_MAX_GPIOS 512
+#define AGGREGATOR_LEGACY_PREFIX "_sysfs"
/*
* GPIO Aggregator sysfs interface
return aggr->probe_data.pdev && platform_get_drvdata(aggr->probe_data.pdev);
}
+/* Only aggregators created via legacy sysfs can be "activating". */
+static bool gpio_aggregator_is_activating(struct gpio_aggregator *aggr)
+{
+ lockdep_assert_held(&aggr->lock);
+
+ return aggr->probe_data.pdev && !platform_get_drvdata(aggr->probe_data.pdev);
+}
+
static size_t gpio_aggregator_count_lines(struct gpio_aggregator *aggr)
{
lockdep_assert_held(&aggr->lock);
list_del(&line->entry);
}
+static void gpio_aggregator_free_lines(struct gpio_aggregator *aggr)
+{
+ struct gpio_aggregator_line *line, *tmp;
+
+ list_for_each_entry_safe(line, tmp, &aggr->list_head, entry) {
+ configfs_unregister_group(&line->group);
+ /*
+ * Normally, we acquire aggr->lock within the configfs
+ * callback. However, in the legacy sysfs interface case,
+ * calling configfs_(un)register_group while holding
+ * aggr->lock could cause a deadlock. Fortunately, this is
+ * unnecessary because the new_device/delete_device path
+ * and the module unload path are mutually exclusive,
+ * thanks to an explicit try_module_get. That's why this
+ * minimal scoped_guard suffices.
+ */
+ scoped_guard(mutex, &aggr->lock)
+ gpio_aggregator_line_del(aggr, line);
+ kfree(line->key);
+ kfree(line->name);
+ kfree(line);
+ }
+}
+
/*
* GPIO Forwarder
guard(mutex)(&aggr->lock);
- if (gpio_aggregator_is_active(aggr))
+ if (gpio_aggregator_is_activating(aggr) ||
+ gpio_aggregator_is_active(aggr))
return -EBUSY;
kfree(line->key);
guard(mutex)(&aggr->lock);
- if (gpio_aggregator_is_active(aggr))
+ if (gpio_aggregator_is_activating(aggr) ||
+ gpio_aggregator_is_active(aggr))
return -EBUSY;
kfree(line->name);
guard(mutex)(&aggr->lock);
- if (gpio_aggregator_is_active(aggr))
+ if (gpio_aggregator_is_activating(aggr) ||
+ gpio_aggregator_is_active(aggr))
return -EBUSY;
line->offset = offset;
if (!try_module_get(THIS_MODULE))
return -ENOENT;
- if (live)
+ if (live && !aggr->init_via_sysfs)
gpio_aggregator_lockup_configfs(aggr, true);
scoped_guard(mutex, &aggr->lock) {
- if (live == gpio_aggregator_is_active(aggr))
+ if (gpio_aggregator_is_activating(aggr) ||
+ (live == gpio_aggregator_is_active(aggr)))
ret = -EPERM;
else if (live)
ret = gpio_aggregator_activate(aggr);
* Undepend is required only if device disablement (live == 0)
* succeeds or if device enablement (live == 1) fails.
*/
- if (live == !!ret)
+ if (live == !!ret && !aggr->init_via_sysfs)
gpio_aggregator_lockup_configfs(aggr, false);
module_put(THIS_MODULE);
struct gpio_aggregator *aggr = to_gpio_aggregator(item);
/*
- * If the aggregator is active, this code wouldn't be reached,
+ * At this point, aggr is neither active nor activating,
* so calling gpio_aggregator_deactivate() is always unnecessary.
*/
gpio_aggregator_free(aggr);
if (ret != 1 || nchar != strlen(name))
return ERR_PTR(-EINVAL);
+ if (aggr->init_via_sysfs)
+ /*
+ * Aggregators created via legacy sysfs interface are exposed as
+ * default groups, which means rmdir(2) is prohibited for them.
+ * For simplicity, and to avoid confusion, we also prohibit
+ * mkdir(2).
+ */
+ return ERR_PTR(-EPERM);
+
guard(mutex)(&aggr->lock);
if (gpio_aggregator_is_active(aggr))
struct gpio_aggregator *aggr;
int ret;
+ /*
+ * "_sysfs" prefix is reserved for auto-generated config group
+ * for devices create via legacy sysfs interface.
+ */
+ if (strncmp(name, AGGREGATOR_LEGACY_PREFIX,
+ sizeof(AGGREGATOR_LEGACY_PREFIX)) == 0)
+ return ERR_PTR(-EINVAL);
+
/* arg space is unneeded */
ret = gpio_aggregator_alloc(&aggr, 0);
if (ret)
static int gpio_aggregator_parse(struct gpio_aggregator *aggr)
{
char *args = skip_spaces(aggr->args);
+ struct gpio_aggregator_line *line;
+ char name[CONFIGFS_ITEM_NAME_LEN];
char *key, *offsets, *p;
unsigned int i, n = 0;
int error = 0;
p = get_options(offsets, 0, &error);
if (error == 0 || *p) {
/* Named GPIO line */
+ scnprintf(name, sizeof(name), "line%u", n);
+ line = gpio_aggregator_line_alloc(aggr, n, key, -1);
+ if (!line) {
+ error = -ENOMEM;
+ goto err;
+ }
+ config_group_init_type_name(&line->group, name,
+ &gpio_aggregator_line_type);
+ error = configfs_register_group(&aggr->group,
+ &line->group);
+ if (error)
+ goto err;
+ scoped_guard(mutex, &aggr->lock)
+ gpio_aggregator_line_add(aggr, line);
+
error = gpio_aggregator_add_gpio(aggr, key, U16_MAX, &n);
if (error)
- return error;
+ goto err;
key = offsets;
continue;
}
for_each_set_bit(i, bitmap, AGGREGATOR_MAX_GPIOS) {
+ scnprintf(name, sizeof(name), "line%u", n);
+ line = gpio_aggregator_line_alloc(aggr, n, key, i);
+ if (!line) {
+ error = -ENOMEM;
+ goto err;
+ }
+ config_group_init_type_name(&line->group, name,
+ &gpio_aggregator_line_type);
+ error = configfs_register_group(&aggr->group,
+ &line->group);
+ if (error)
+ goto err;
+ scoped_guard(mutex, &aggr->lock)
+ gpio_aggregator_line_add(aggr, line);
+
error = gpio_aggregator_add_gpio(aggr, key, i, &n);
if (error)
- return error;
+ goto err;
}
args = next_arg(args, &key, &p);
if (!n) {
pr_err("No GPIOs specified\n");
- return -EINVAL;
+ goto err;
}
return 0;
+
+err:
+ gpio_aggregator_free_lines(aggr);
+ return error;
}
static ssize_t gpio_aggregator_new_device_store(struct device_driver *driver,
const char *buf, size_t count)
{
+ char name[CONFIGFS_ITEM_NAME_LEN];
struct gpio_aggregator *aggr;
struct platform_device *pdev;
int res;
goto free_table;
}
- res = gpio_aggregator_parse(aggr);
+ scnprintf(name, sizeof(name), "%s.%d", AGGREGATOR_LEGACY_PREFIX, aggr->id);
+ config_group_init_type_name(&aggr->group, name, &gpio_aggregator_device_type);
+
+ /*
+ * Since the device created by sysfs might be toggled via configfs
+ * 'live' attribute later, this initialization is needed.
+ */
+ dev_sync_probe_init(&aggr->probe_data);
+
+ /* Expose to configfs */
+ res = configfs_register_group(&gpio_aggregator_subsys.su_group,
+ &aggr->group);
if (res)
goto free_dev_id;
+ res = gpio_aggregator_parse(aggr);
+ if (res)
+ goto unregister_group;
+
gpiod_add_lookup_table(aggr->lookups);
pdev = platform_device_register_simple(DRV_NAME, aggr->id, NULL, 0);
remove_table:
gpiod_remove_lookup_table(aggr->lookups);
+unregister_group:
+ configfs_unregister_group(&aggr->group);
free_dev_id:
kfree(aggr->lookups->dev_id);
free_table:
static void gpio_aggregator_destroy(struct gpio_aggregator *aggr)
{
- gpio_aggregator_deactivate(aggr);
+ scoped_guard(mutex, &aggr->lock) {
+ if (gpio_aggregator_is_activating(aggr) ||
+ gpio_aggregator_is_active(aggr))
+ gpio_aggregator_deactivate(aggr);
+ }
+ gpio_aggregator_free_lines(aggr);
+ configfs_unregister_group(&aggr->group);
kfree(aggr);
}