#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);
 }