From nobody Mon Feb 9 00:55:45 2026 Received: from smtp-relay-internal-1.canonical.com (smtp-relay-internal-1.canonical.com [185.125.188.123]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C36A51DE4C7 for ; Wed, 29 Jan 2025 15:56:05 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.125.188.123 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1738166169; cv=none; b=cSgWpFzzPvZJTiiQh4tdiBj2+Mb6wsExUPlroFN52qfj2D994yQ4mQh2nuI6GhLZExjIwqx3WGLztdyCsSHCiAIxMkZs+y3N5GkEydOda0cxWYHO7033HN7a4l88G6IyPH+rFncAjgGBxtEntYgAv0Iz8dDF0cUmJmdbO3LVBxw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1738166169; c=relaxed/simple; bh=5+OrJzj0VE962N5YwRs/JWP6s0he08iKxAz6WvoXRMI=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=JZwiBtZbxHDtcMdn1pwuXJ5HKK7M09Dw5R2kUmbLFhowIPhNfad3IuxDrdTtqI/EYOzu6dqGsl48tJruxWQlg1YRnaXmJwPH1xqtddTaJzdg18btGw7zXS20o/HDi2efh2IQpX6zrnbVlc38D+HbXeI8NWKet2ygziGyvO+X/Gc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=canonical.com; spf=pass smtp.mailfrom=canonical.com; dkim=pass (2048-bit key) header.d=canonical.com header.i=@canonical.com header.b=lUwCnXAs; arc=none smtp.client-ip=185.125.188.123 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=canonical.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=canonical.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=canonical.com header.i=@canonical.com header.b="lUwCnXAs" Received: from mail-pl1-f198.google.com (mail-pl1-f198.google.com [209.85.214.198]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by smtp-relay-internal-1.canonical.com (Postfix) with ESMTPS id BAA1F3F1E8 for ; Wed, 29 Jan 2025 15:56:03 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=canonical.com; s=20210705; t=1738166163; bh=yi0QNwrD3iE1YqyCu0HWM2nWr2NBX0dPTj1NHEUytGE=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=lUwCnXAslWab//n15ISsflwApGq2849kaE3senEhOHxxEGy2tezUZHX0X05FB4iAF cU3Tp5Sqg9Z1dbvChdevLlTd1IuFhf77Vd7jozEQZJqkgoFzpS2rIRCmQ0UfmMOtiR wk9eEaD9m339gnACbH4WCuIF2c/0+VF+Tp/hr81f7Tmq9y2gOIu8skNd0ch5K5zSJu Wp5FLneGDP3OSuHALrJbTY6vxVZGkSm8e6G5yv4YOkTRGsPbIgAxjPPjdsfLeSNh3Z cZPTTLxhzXdoLDfi680UDtRKDbOqioGKRQSmeE8o6X1BbEij3ooECe+udibx4+hNpD kAvNHQvNPOmWw== Received: by mail-pl1-f198.google.com with SMTP id d9443c01a7336-2166464e236so207714775ad.1 for ; Wed, 29 Jan 2025 07:56:03 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1738166162; x=1738770962; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=yi0QNwrD3iE1YqyCu0HWM2nWr2NBX0dPTj1NHEUytGE=; b=LHMayLp0tMXBqHnQe59ODEAykIaJw4xVEom4HCi0AM96Xb9RwEEwoY8TalKgZLrPJP BzScGRzx2ztpgpCQyTUN6WXGDzVJRFOlNDuwvc4kXhBIwtpKeY1j+0TbchLiXMLufHCP DIQSdQdpa/0d8bygal9tRDC7G3XfLRjG6tRQCz0G2sjxlrNG5SE2tI0o2HVDpm8YcyIu ZaKmCGye+TVkutdS+r9fyG5cx0Dc0R7PKzaTiMRjZMc5xuB/Fi8pdLlVT9UDX0dOWVfS i+m2L8CEvjxhFXTABsrBrJgnDHGRtH/nQnz7H0IkSI9C3eTGqhYDUuWz/y1fkIiEs11J mNJw== X-Forwarded-Encrypted: i=1; AJvYcCWp2d2WHSeGsDXPguJfNvLz4XnaYsyytnPOgOlhCXtPJoMFbweawFTj4E1BWMgHKZGEwCOR99SQkcaZOeA=@vger.kernel.org X-Gm-Message-State: AOJu0YwPm0eseazwo2FTxhzhVNodVsnn2yzG4mi+Ck+3CRQCg+aTD3Nx uWWN//bqH10ZQ9nsYNycvLyQNvxAx8qPiticrf9pqkgVV13XUHdvkGF5kpK+Qynu7H8cROM/+W0 rlj/IeJfMQwLjyBq5Xtgn3pc8g2vKnI6r538Pvsx0jPnP1Tv86s555Ko0aC1g8fluTr9meJyfbl Cyag== X-Gm-Gg: ASbGncuBAF3myPZCml+5IYwh+aJoQmNaw5WjyeNI4J+wrDl6c+D9FtXIirHvmExXrIS mYSDSBk+WLsavnwEyfPHzu3C1OLm9ZDIPfQTB0nzuaRe4wXqI175Mrk/dgIlUXSTxnbhWXboNFQ uoy4migBYLZk967XodcyEs6GRr3aRJ6I/dzG9LgY83ZjFZ3thlIx1Z2FHJJ4leImxydHwbrHKt5 pmyBr4BVBRyZKcNGejkqpXwYf+ehsy2Q31HF+lcqLLfIi9UvD8O8qvWywHm05brcsASO1QHrvZI p+2Qp04uGzKjpoqRjefV3cY= X-Received: by 2002:a05:6a20:3d94:b0:1e0:cc4a:caab with SMTP id adf61e73a8af0-1ed7a530d19mr5466134637.19.1738166162175; Wed, 29 Jan 2025 07:56:02 -0800 (PST) X-Google-Smtp-Source: AGHT+IGpfHj1m4TnkAKMydkV2iBmNzUa8E8y/DH2MR033ztsysfZOHgaIw3ltZ9PzVLVhobWf0yHbg== X-Received: by 2002:a05:6a20:3d94:b0:1e0:cc4a:caab with SMTP id adf61e73a8af0-1ed7a530d19mr5466097637.19.1738166161646; Wed, 29 Jan 2025 07:56:01 -0800 (PST) Received: from localhost.localdomain ([240f:74:7be:1:daf2:7ab4:ebc3:f450]) by smtp.gmail.com with ESMTPSA id d2e1a72fcca58-72f8a77c4fasm11674432b3a.132.2025.01.29.07.55.59 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 29 Jan 2025 07:56:01 -0800 (PST) From: Koichiro Den To: linux-gpio@vger.kernel.org Cc: geert+renesas@glider.be, linus.walleij@linaro.org, brgl@bgdev.pl, maciej.borzecki@canonical.com, linux-kernel@vger.kernel.org Subject: [RFC PATCH 1/2] gpio: aggregator: Introduce configfs interface Date: Thu, 30 Jan 2025 00:55:24 +0900 Message-ID: <20250129155525.663780-2-koichiro.den@canonical.com> X-Mailer: git-send-email 2.45.2 In-Reply-To: <20250129155525.663780-1-koichiro.den@canonical.com> References: <20250129155525.663780-1-koichiro.den@canonical.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" 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. 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 --- drivers/gpio/gpio-aggregator.c | 673 ++++++++++++++++++++++++++++++++- 1 file changed, 671 insertions(+), 2 deletions(-) diff --git a/drivers/gpio/gpio-aggregator.c b/drivers/gpio/gpio-aggregator.c index 65f41cc3eafc..717a2bee3e8b 100644 --- a/drivers/gpio/gpio-aggregator.c +++ b/drivers/gpio/gpio-aggregator.c @@ -9,10 +9,13 @@ =20 #include #include +#include +#include #include #include #include #include +#include #include #include #include @@ -33,9 +36,22 @@ * GPIO Aggregator sysfs interface */ =20 +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[]; }; =20 @@ -474,6 +490,25 @@ static int gpiochip_fwd_setup_delay_line(struct device= *dev, struct gpio_chip *c } #endif /* !CONFIG_OF_GPIO */ =20 +static int gpiochip_fwd_line_names(struct device *dev, const char **names,= int len) +{ + struct device_node *of_node =3D dev_of_node(dev); + int num; + + if (of_node) + /* not supported */ + return 0; + + num =3D 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 =3D 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 =3D 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, =20 chip =3D &fwd->chip; =20 + line_names =3D devm_kcalloc(dev, sizeof(*line_names), ngpios, GFP_KERNEL); + if (!line_names) + return ERR_PTR(-ENOMEM); + + error =3D 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 =3D gpio_fwd_to_irq; chip->base =3D -1; chip->ngpio =3D ngpios; + chip->names =3D line_names; fwd->descs =3D descs; =20 if (chip->can_sleep) @@ -581,8 +626,20 @@ static int gpio_aggregator_probe(struct platform_devic= e *pdev) =20 for (i =3D 0; i < n; i++) { descs[i] =3D 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] =3D=3D ERR_PTR(-EPROBE_DEFER)) { + pr_warn("Deferred probe canceled for creation by userspace.\n"); + return -ENODEV; + } return PTR_ERR(descs[i]); + } } =20 features =3D (uintptr_t)device_get_match_data(dev); @@ -616,14 +673,626 @@ static struct platform_driver gpio_aggregator_driver= =3D { }, }; =20 +static int gpio_aggregator_bus_notifier_call(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct gpio_aggregator *aggr; + struct device *dev =3D data; + + aggr =3D 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 =3D true; + break; + case BUS_NOTIFY_DRIVER_NOT_BOUND: + aggr->driver_bound =3D 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 =3D 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 =3D -1; +} + +static struct gpio_aggregator_line * +to_gpio_aggregator_line(struct config_item *item) +{ + struct config_group *group =3D 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 =3D to_gpio_aggregator_line(item); + struct gpio_aggregator *aggr =3D 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 =3D to_gpio_aggregator_line(item); + struct gpio_aggregator *aggr =3D line->parent; + + char *key __free(kfree) =3D 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 =3D 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 =3D + to_gpio_aggregator_line(item); + struct gpio_aggregator *aggr =3D 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 =3D + to_gpio_aggregator_line(item); + struct gpio_aggregator *aggr =3D line->parent; + + char *name __free(kfree) =3D 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 =3D 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 =3D + to_gpio_aggregator_line(item); + struct gpio_aggregator *aggr =3D line->parent; + unsigned int offset; + + scoped_guard(mutex, &aggr->lock) + offset =3D 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 =3D + to_gpio_aggregator_line(item); + struct gpio_aggregator *aggr =3D line->parent; + int offset, ret; + + ret =3D 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 =3D offset; + + return count; +} + +CONFIGFS_ATTR(gpio_aggregator_line_config_, offset); + +static struct configfs_attribute *gpio_aggregator_line_config_attrs[] =3D { + &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 =3D kcalloc(aggr->num_lines, sizeof(*line_names), GFP_KERNEL); + if (!line_names) + return ERR_PTR(-ENOMEM); + + for (i =3D 0; i < aggr->num_lines; i++) + line_names[i] =3D aggr->lines[i].name ?: ""; + + properties[0] =3D 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 =3D 0; + int i, ret =3D 0; + + aggr->lookups =3D kzalloc(struct_size(aggr->lookups, table, 1), + GFP_KERNEL); + if (!aggr->lookups) + return -ENOMEM; + + swnode =3D gpio_aggregator_make_device_swnode(aggr); + if (IS_ERR(swnode)) + goto err_remove_lookups; + + memset(&pdevinfo, 0, sizeof(pdevinfo)); + pdevinfo.name =3D DRV_NAME; + pdevinfo.id =3D aggr->id; + pdevinfo.fwnode =3D swnode; + + for (i =3D 0; i < aggr->num_lines; i++) { + line =3D &aggr->lines[i]; + if (line->idx < 0) + goto err_remove_swnode; + if (line->offset < 0) + ret =3D aggr_add_gpio(aggr, line->key, U16_MAX, &n); + else + ret =3D aggr_add_gpio(aggr, line->key, line->offset, &n); + if (ret) + goto err_remove_swnode; + } + + aggr->lookups->dev_id =3D kasprintf(GFP_KERNEL, "%s.%d", DRV_NAME, aggr->= id); + + gpiod_add_lookup_table(aggr->lookups); + + reinit_completion(&aggr->probe_completion); + aggr->driver_bound =3D false; + aggr->bus_notifier.notifier_call =3D gpio_aggregator_bus_notifier_call; + bus_register_notifier(&platform_bus_type, &aggr->bus_notifier); + + pdev =3D platform_device_register_full(&pdevinfo); + if (IS_ERR(pdev)) { + ret =3D 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 =3D -ENXIO; + goto err_unregister_pdev; + } + + aggr->pdev =3D 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 =3D NULL; +} + +static void +gpio_aggregator_lockup_configfs(struct gpio_aggregator *aggr, bool lock) +{ + struct configfs_subsystem *subsys =3D 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 =3D 0; i < aggr->num_lines; i++) { + line =3D &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 =3D to_gpio_aggregator(item); + struct platform_device *pdev; + + guard(mutex)(&aggr->lock); + + pdev =3D 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 =3D to_gpio_aggregator(item); + bool live; + + scoped_guard(mutex, &aggr->lock) + live =3D 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 =3D to_gpio_aggregator(item); + int ret =3D 0; + bool live; + + ret =3D kstrtobool(page, &live); + if (ret) + return ret; + + if (live) + gpio_aggregator_lockup_configfs(aggr, true); + + scoped_guard(mutex, &aggr->lock) { + if (live =3D=3D gpio_aggregator_is_live(aggr)) + ret =3D -EPERM; + else if (live) + ret =3D gpio_aggregator_activate(aggr); + else + gpio_aggregator_deactivate(aggr); + } + + /* + * Undepend is required only if device disablement (live =3D=3D 0) + * succeeds or if device enablement (live =3D=3D 1) fails. + */ + if (live =3D=3D !!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 =3D 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 =3D to_gpio_aggregator(item); + struct gpio_aggregator_line *line, *lines; + unsigned int num_lines; + int i, ret; + + ret =3D kstrtouint(page, 0, &num_lines); + if (ret) + return ret; + + if (num_lines =3D=3D 0) + return -EINVAL; + + guard(mutex)(&aggr->lock); + + if (gpio_aggregator_is_live(aggr)) + return -EBUSY; + + if (aggr->num_lines > num_lines) { + for (i =3D aggr->num_lines - 1; i >=3D num_lines; i--) { + line =3D &aggr->lines[i]; + if (line->idx >=3D 0) + return -EBUSY; + } + } + + lines =3D krealloc(aggr->lines, sizeof(*line) * num_lines, GFP_KERNEL); + for (i =3D aggr->num_lines; i < num_lines; i++) + gpio_aggregator_line_init(&lines[i]); + + aggr->lines =3D lines; + aggr->num_lines =3D num_lines; + + return count; +} + +CONFIGFS_ATTR(gpio_aggregator_device_config_, num_lines); + +static struct configfs_attribute *gpio_aggregator_device_config_attrs[] = =3D { + &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 =3D + to_gpio_aggregator_line(item); + struct gpio_aggregator *aggr =3D 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 =3D { + .release =3D gpio_aggregator_line_config_release, +}; + +static const struct config_item_type gpio_aggregator_line_config_type =3D { + .ct_item_ops =3D &gpio_aggregator_line_config_item_ops, + .ct_attrs =3D gpio_aggregator_line_config_attrs, + .ct_owner =3D THIS_MODULE, +}; + +static void gpio_aggregator_device_config_release(struct config_item *item) +{ + struct gpio_aggregator *aggr =3D 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 =3D { + .release =3D 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 =3D + to_gpio_aggregator(&group->cg_item); + struct gpio_aggregator_line *line; + unsigned int offset; + int ret, nchar; + + ret =3D sscanf(name, "line%u%n", &offset, &nchar); + if (ret !=3D 1 || nchar !=3D strlen(name)) + return ERR_PTR(-EINVAL); + + guard(mutex)(&aggr->lock); + + if (gpio_aggregator_is_live(aggr)) + return ERR_PTR(-EBUSY); + + line =3D &aggr->lines[offset]; + if (line->idx >=3D 0) + return ERR_PTR(-EBUSY); + + config_group_init_type_name(&line->group, name, + &gpio_aggregator_line_config_type); + line->flags =3D GPIO_LOOKUP_FLAGS_DEFAULT; + line->parent =3D aggr; + line->idx =3D offset; + + return &no_free_ptr(line)->group; +} + +static struct +configfs_group_operations gpio_aggregator_device_config_group_ops =3D { + .make_group =3D gpio_aggregator_device_config_make_group, +}; + +static const struct config_item_type gpio_aggregator_device_config_type = =3D { + .ct_group_ops =3D &gpio_aggregator_device_config_group_ops, + .ct_item_ops =3D &gpio_aggregator_device_config_item_ops, + .ct_attrs =3D gpio_aggregator_device_config_attrs, + .ct_owner =3D 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) =3D kzalloc(sizeof(*aggr), + GFP_KERNEL); + if (!aggr) + return ERR_PTR(-ENOMEM); + + mutex_lock(&gpio_aggregator_lock); + aggr->id =3D 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 =3D 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 = =3D { + .make_group =3D gpio_aggregator_config_make_group, +}; + +static const struct config_item_type gpio_aggregator_config_type =3D { + .ct_group_ops =3D &gpio_aggregator_config_group_ops, + .ct_owner =3D THIS_MODULE, +}; + +static struct configfs_subsystem gpio_aggregator_config_subsys =3D { + .su_group =3D { + .cg_item =3D { + .ci_namebuf =3D DRV_NAME, + .ci_type =3D &gpio_aggregator_config_type, + }, + }, +}; + static int __init gpio_aggregator_init(void) { - return platform_driver_register(&gpio_aggregator_driver); + int ret =3D 0; + + ret =3D 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 =3D 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); =20 static void __exit gpio_aggregator_exit(void) { + configfs_unregister_subsystem(&gpio_aggregator_config_subsys); gpio_aggregator_remove_all(); platform_driver_unregister(&gpio_aggregator_driver); } --=20 2.45.2 From nobody Mon Feb 9 00:55:45 2026 Received: from smtp-relay-internal-0.canonical.com (smtp-relay-internal-0.canonical.com [185.125.188.122]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id CE5CD1DE897 for ; Wed, 29 Jan 2025 15:56:07 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.125.188.122 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1738166171; cv=none; b=uLwDrvCcg+gyzd2nav8Z5+fYZ5ttVsANTxyB3YhH/PCPUyPfPRgfK7oI+ODRjwOZZfcvwUZfU+bibbKUqKak5mZibPm8L/xSCJ6o0F3pg34wdxryHBU3O96nW5fsUcX16sao7oJ8LVpijNe+5dVlQ537O3ihNSPtbnnmwmx+ohM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1738166171; c=relaxed/simple; bh=JgsbT/iITDHrPwbLtAepZLcicNYXO1cKBa+nTq8RAs4=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=W//CD/IZddn5ljPdgJkGhURqp+Z2Ms9M+RXSNkzgZ+vBUalPLj9Va7Ww9v/AjNcj286pfoRYNJp3I0tUR1VLdmd8fmVmn2UUVnfMEvCuNCKFe9nm6OGqgNYF/9JlLJ7rJgHNu/toy1EZR8p+WCxYeZRFhVaOJ+uW329YyLcLFKw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=canonical.com; spf=pass smtp.mailfrom=canonical.com; dkim=pass (2048-bit key) header.d=canonical.com header.i=@canonical.com header.b=Ht0+o813; arc=none smtp.client-ip=185.125.188.122 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=canonical.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=canonical.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=canonical.com header.i=@canonical.com header.b="Ht0+o813" Received: from mail-pj1-f70.google.com (mail-pj1-f70.google.com [209.85.216.70]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by smtp-relay-internal-0.canonical.com (Postfix) with ESMTPS id 3ECF63F875 for ; Wed, 29 Jan 2025 15:56:06 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=canonical.com; s=20210705; t=1738166166; bh=LAv/ebokFHKYkiIjsLayeeaNB6exbRKSL1D3ireWnTc=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Ht0+o813p3VwDufgm44lyT6VORPYCKf3/iR1kIWS1yGnJu78g5keiGPKU4KOCfVWd yVM2VnJOhchuh1TxCgvUsSP0YX8miC5QkHrE6eD+ztPJ4/aici9EpwRwk4dzVDU+Xe PydTD4T+xyKylqkhiMpg3KVsDhfd+LYxNmNcIeBJSM9JEJkWowM5n8Q1v++YIzxdp+ FeN0dooHJrmZp000mhcb3HNeBlrZ3W+m7iLI+hHpvKM7vmdb7m4GACvTv9iqrxpk8j GJul4xdGzq8nubadtDYTQdrI39wzP8ElmgSmZjMxLHVavOavXlU85nEsHr4bTnuZl7 4tEFVJPWAwd1A== Received: by mail-pj1-f70.google.com with SMTP id 98e67ed59e1d1-2f2a9f056a8so13849219a91.2 for ; Wed, 29 Jan 2025 07:56:06 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1738166165; x=1738770965; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=LAv/ebokFHKYkiIjsLayeeaNB6exbRKSL1D3ireWnTc=; b=N/hcsigHIKADNBYx6DnNaCePAl/iK1XZIYzYpnwZ8yGjE0ZaB5vi21Cr14nMWLqK3b BYgLFOTWbYHJt4HWs0WuNaDoRL4vtT3wjkCwf0rm9KTxIXrRknku9yBB0uru2ChG3KIJ 0QNvEiCfCczUnjr/NO6GdzQi+va29qmGHD2/UikQZAxeKEkU/cI4qCYSxHU5WnunXE4N xtcCvu6Uu+LfZs+TMAGBNjMk9QrRS8cx99OlHDCVFVExyZmYNLTffBTbTr0H/rbObwCC 4JLMj/aL0d5lQEYLp34hJccA6To20/QOV53X7Oq1aJmYdpnb/4m5H7A3337YEEFHy9zz ImJw== X-Forwarded-Encrypted: i=1; AJvYcCWOEzrKIkP4MntBr/BYRN4vvy9yggw+onuJxV/WveiRfz4GsTrauT+U+CUdE8VVpEUJk3ggdr5NKCqoMv0=@vger.kernel.org X-Gm-Message-State: AOJu0Ywk+Pf90Sl1GC2SPXFXSAkX3I2y1Q2BOG1kIIezTYln7bPocdSm EzxmzjBY4RXh+nSBp4OGyzY3WY84m0vTi86IBuHZ+gJ44Fd1QqHNlpSXJI2oDZ5KM3/ycFv66g0 603cdEKf/KkNK4EoZ8cd588qA/+hChbGvuXsBn1ymQJyK/1BIhd815WsPJQFbgT2p7lVqM/nIdJ agfw== X-Gm-Gg: ASbGncu4Q+jL66Rxno84MBxmcescdjSPCLXCqa/NY316jf/7pxyUBO6pvLLsRzNnho+ hjYB4jEG5M2U7MFm1WayKmwDNhBOEEHvRmfr1yBk33WHQJMj5PbLsNNiWTY7/mhpn+va4aGz2yl LLi87PJxFUEEO+OSEncCP9rWML6QLFkGO2Ks8TUjWygtuuEtRQ8/AUMNrKkazbJ9Hv4dFXHSIJN OsNObqlJcKrytW6CUdkiPXVchrzzMBXwyy8X+qdrNSBMabAa2wKSRw4rPNXJpP032zv9MrrqlwT YKDqvQKOBYC+XaQZDX8JfyI= X-Received: by 2002:a05:6a20:7345:b0:1e1:c03c:b420 with SMTP id adf61e73a8af0-1ed7a6b820dmr6530644637.31.1738166164675; Wed, 29 Jan 2025 07:56:04 -0800 (PST) X-Google-Smtp-Source: AGHT+IEsGbo3W8SlmzBnN5syShd527EpxYUjNOwu5RzjXkAS9t/vhb7pHZnFzhygBAw/LN3di47JNA== X-Received: by 2002:a05:6a20:7345:b0:1e1:c03c:b420 with SMTP id adf61e73a8af0-1ed7a6b820dmr6530601637.31.1738166164355; Wed, 29 Jan 2025 07:56:04 -0800 (PST) Received: from localhost.localdomain ([240f:74:7be:1:daf2:7ab4:ebc3:f450]) by smtp.gmail.com with ESMTPSA id d2e1a72fcca58-72f8a77c4fasm11674432b3a.132.2025.01.29.07.56.02 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 29 Jan 2025 07:56:04 -0800 (PST) From: Koichiro Den To: linux-gpio@vger.kernel.org Cc: geert+renesas@glider.be, linus.walleij@linaro.org, brgl@bgdev.pl, maciej.borzecki@canonical.com, linux-kernel@vger.kernel.org Subject: [RFC PATCH 2/2] Documentation: gpio: document configfs interface for gpio-aggregator Date: Thu, 30 Jan 2025 00:55:25 +0900 Message-ID: <20250129155525.663780-3-koichiro.den@canonical.com> X-Mailer: git-send-email 2.45.2 In-Reply-To: <20250129155525.663780-1-koichiro.den@canonical.com> References: <20250129155525.663780-1-koichiro.den@canonical.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add documentation for the newly added configfs-based interface for GPIO aggregator. Signed-off-by: Koichiro Den --- .../admin-guide/gpio/gpio-aggregator.rst | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/Documentation/admin-guide/gpio/gpio-aggregator.rst b/Documenta= tion/admin-guide/gpio/gpio-aggregator.rst index 5cd1e7221756..219ef2bb70ab 100644 --- a/Documentation/admin-guide/gpio/gpio-aggregator.rst +++ b/Documentation/admin-guide/gpio/gpio-aggregator.rst @@ -69,6 +69,92 @@ write-only attribute files in sysfs. $ echo gpio-aggregator.0 > delete_device =20 =20 +Aggregating GPIOs using Configfs +-------------------------------- + +**Group:** ``/config/gpio-aggregator`` + + This is the root directory of the gpio-aggregator configfs tree. + +**Group:** ``/config/gpio-aggregator/`` + + This directory represents a GPIO aggregator device. You can assign any + name to ```` (e.g., ``agg0``). + +**Attribute:** ``/config/gpio-aggregator//live`` + + 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 + virtual device and ``'0'`` to disable and tear it down. + +**Attribute:** ``/config/gpio-aggregator//dev_name`` + + The read-only ``dev_name`` attribute exposes the name of the device as= it + will appear in the system on the platform bus (e.g. ``gpio-aggregator.= 0``). + This is useful for identifying a character device for the newly created + aggregator. If it's ``gpio-aggregator.0``, + ``/sys/devices/platform/gpio-aggregator.0/gpiochipX`` path tells you t= hat the + GPIO device id is ``X``. + +**Attribute:** ``/config/gpio-aggregator//num_lines`` + + The ``num_lines`` attribute sets the number of lines to aggregate. It + must be set to a value greater than 0 before proceeding with line + configuration. For a value of Y+1 (where Y>=3D0), you must create exac= tly + ``line0``, ``line1``, ..., ``lineY`` subdirectories and configure all + lines before activating the device by setting ``live`` to 1. + +**Group:** ``/config/gpio-aggregator///`` + + This directory represents a GPIO line to include in the aggregator. + +**Attribute:** ``/config/gpio-aggregator///key`` + +**Attribute:** ``/config/gpio-aggregator///offset`` + + If ``offset`` is >=3D 0: + * ``key`` specifies the name of the chip this GPIO belongs to + * ``offset`` specifies the line offset within that chip. + If ``offset`` is <0: + * ``key`` needs to specify the GPIO line name. + +**Attribute:** ``/config/gpio-aggregator///name`` + + The ``name`` attribute sets a custom name for lineY. If left unset, the + line will remain unnamed. + +Once the configuration is done, the ``'live'`` attribute must be set to 1 +in order to instantiate the aggregator device. It can be set back to 0 to +destroy the virtual device. The module will synchronously wait for the new +aggregator device to be successfully probed and if this doesn't happen, wr= iting +to ``'live'`` will result in an error. This is a different behaviour from = the +case when you create it using sysfs ``new_device`` interface. + +Sample configuration commands +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: sh + + # Create a directory for an aggregator device + $ mkdir /sys/kernel/config/gpio-aggregator/agg0 + + # Set how many lines to aggregate beforehand + $ echo 2 > /sys/kernel/config/gpio-aggregator/agg0/num_lines + + # Configure each line + $ mkdir /sys/kernel/config/gpio-aggregator/agg0/line0 + $ echo gpiochip0 > /sys/kernel/config/gpio-aggregator/agg0/line0/key + $ echo 6 > /sys/kernel/config/gpio-aggregator/agg0/line0/offset + $ echo test0 > /sys/kernel/config/gpio-aggregator/agg0/line0/name + $ mkdir /sys/kernel/config/gpio-aggregator/agg0/line1 + $ echo gpiochip0 > /sys/kernel/config/gpio-aggregator/agg0/line1/key + $ echo 7 > /sys/kernel/config/gpio-aggregator/agg0/line1/offset + $ echo test1 > /sys/kernel/config/gpio-aggregator/agg0/line1/name + + # Activate the aggregator device + $ echo 1 > /sys/kernel/config/gpio-aggregator/agg0/live + + Generic GPIO Driver ------------------- =20 --=20 2.45.2