This is an automated email from the ASF dual-hosted git repository.
xiaoxiang781216 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx.git
The following commit(s) were added to refs/heads/master by this push:
new 1528b470b9e drivers/ioexpander: List the registered pins in /proc/gpio.
1528b470b9e is described below
commit 1528b470b9e3e490830e739926055ded9a7f127b
Author: Justin Hammond <[email protected]>
AuthorDate: Sun Aug 2 18:54:39 2026 +0800
drivers/ioexpander: List the registered pins in /proc/gpio.
The pins a board publishes are visible in /dev and each can be read
through its own node, but surveying a whole board that way means an open
and two ioctls per pin, and the signal and interrupt counters the upper
half keeps are not reachable through any of them.
Adds a list of registered pins and publishes it as /proc/gpio, behind
GPIO_PROCFS: a quality of life view of the same kind as /proc/pinctrl
and /proc/reset. Every common field comes from state the upper half
already holds: the pin type, the value through go_read(), how many times
the pin has been registered for signals, and how many interrupts it has
taken. Lines carry the same key:value tokens in the same order, so the
file is machine parseable.
Lower halves may supply an optional go_describe() adding what only they
can say, such as which pad carries the line or how its trigger is
configured. It writes text into a caller supplied buffer and the upper
half owns the line, so a lower half needs no procfs knowledge. A lower
half without it is listed with the common tokens alone.
The pin type index is bounded before use: it comes from the lower half
and the name table cannot cover a type the enumeration does not define.
A pin that cannot be read reports val:- rather than a zero that would
read as a real level.
procfs_register() appends without checking for duplicates, so the entry
is claimed once for the lifetime of the system rather than whenever the
list is empty; pins come and go at run time.
The name is held in a buffer as long as the one gpio_pin_register()
accepts, so a listing always names the same string as /dev.
The pin type name table is declared without an explicit size so that the
assertion beside it compares against the enumeration and can fail; sized
as [GPIO_NPINTYPES] it would have been tautological.
Documents the entry, its tokens, and how a lower half describes a pin.
Off by default: with GPIO_PROCFS unset the list, the lock and the procfs
entry are compiled out, and go_describe() is one more member at the end
of a structure existing lower halves do not reach.
Assisted-by: Claude:claude-opus-5
Signed-off-by: Justin Hammond <[email protected]>
---
.../components/drivers/character/gpio.rst | 59 ++-
drivers/ioexpander/Kconfig | 17 +
drivers/ioexpander/gpio.c | 446 ++++++++++++++++++++-
include/nuttx/ioexpander/gpio.h | 17 +
4 files changed, 537 insertions(+), 2 deletions(-)
diff --git a/Documentation/components/drivers/character/gpio.rst
b/Documentation/components/drivers/character/gpio.rst
index fe3bf735ce4..53f3ee6e5aa 100644
--- a/Documentation/components/drivers/character/gpio.rst
+++ b/Documentation/components/drivers/character/gpio.rst
@@ -166,6 +166,62 @@ An example application can be found in ``nuttx-apps``
repository under
path ``examples/gpio``. It is an example application that allows you
to read, write or configure GPIO pins.
+/proc/gpio
+==========
+
+The pins are visible in ``/dev`` and each can be read through its own node
+with the commands above, but doing that for a whole board means an open and
+two ioctls per pin, and the signal and interrupt counters are not exposed
+that way at all. ``CONFIG_GPIO_PROCFS`` adds ``/proc/gpio``, which puts
+every registered pin in one place:
+
+.. code-block:: text
+
+ gpio0 type:INPUT val:1 regs:0 ints:0 pad:GPIO27 port:A.27
+ gpio1 type:OUTPUT val:0 regs:0 ints:0 pad:GPIO28 port:A.28
+ gpio2 type:INT_BOTH val:1 regs:1 ints:42 pad:GPIO29
port:A.29 armed:both
+
+Every line carries the same ``key:value`` tokens in the same order, so the
+file can be parsed as well as read:
+
+ ========== =============================================================
+ Token Meaning
+ ========== =============================================================
+ ``type`` The pin type, from ``enum gpio_pintype_e``
+ ``val`` The pin's current value, or ``-`` if it cannot be read
+ ``regs`` How many times the pin has been registered for signals
+ ``ints`` How many interrupts the pin has taken
+ ========== =============================================================
+
+Anything after those comes from the pin's lower half.
+
+Describing a pin from the lower half
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The upper half already knows a pin's type, value and counters, so a lower
+half only supplies what it alone can say: which pad carries the line, how
+the trigger is configured, and so on. That is the optional
+``go_describe`` method, which writes further ``key:value`` fields and
+leaves the rest of the line to the renderer:
+
+.. code-block:: c
+
+ static int mychip_describe(FAR struct gpio_dev_s *dev, FAR char *extra,
+ size_t len)
+ {
+ FAR struct mychip_gpio_s *priv = (FAR struct mychip_gpio_s *)dev;
+
+ snprintf(extra, len, "pad:%u port:%c.%u", priv->pad,
+ 'A' + priv->port, priv->pin);
+ return OK;
+ }
+
+It is optional: a pin whose lower half omits it is listed with the common
+tokens and nothing more. A lower half needs no procfs knowledge of its
+own, since the renderer owns the line.
+
+The option depends on ``FS_PROCFS_REGISTER`` and is off by default.
+
Configuration
=============
@@ -176,7 +232,8 @@ GPIO peripheral is enabled by ``CONFIG_DEV_GPIO``. Option
``CONFIG_DEV_NPOLLWAITERS`` is used to specify the maximum number of threads
that can be waiting on poll with default set to one. It is also possible
to register signals with the GPIO driver. The number of allowed signals
-is configured with ``CONFIG_DEV_NSIGNALS``.
+is configured with ``CONFIG_DEV_NSIGNALS``. ``CONFIG_GPIO_PROCFS`` adds
+``/proc/gpio``, described above.
IO Expander Device Drivers
==========================
diff --git a/drivers/ioexpander/Kconfig b/drivers/ioexpander/Kconfig
index 0de2ee4e56e..489ca1b0c7c 100644
--- a/drivers/ioexpander/Kconfig
+++ b/drivers/ioexpander/Kconfig
@@ -615,6 +615,23 @@ config DEV_GPIO
Enables a simple GPIO input/output driver to support
application-
space testing of hardware.
+config GPIO_PROCFS
+ bool "GPIO procfs entry"
+ default n
+ depends on DEV_GPIO && FS_PROCFS && FS_PROCFS_REGISTER
+ ---help---
+ Create /proc/gpio, listing every registered pin with its type,
+ its current value, and how many times it has been registered for
+ signals and taken an interrupt.
+
+ The pins are visible in /dev and each can be read through its
own
+ node, but surveying a board that way means an open and two
ioctls
+ per pin, and the counters are not reachable through any of them.
+
+ A lower half that supplies the optional go_describe method also
+ contributes whatever only it can say, such as which pad carries
+ the line.
+
config DEV_GPIO_NPOLLWAITERS
int "Max number of polls"
default 1
diff --git a/drivers/ioexpander/gpio.c b/drivers/ioexpander/gpio.c
index 512a0e19f41..e00d6a60a74 100644
--- a/drivers/ioexpander/gpio.c
+++ b/drivers/ioexpander/gpio.c
@@ -27,6 +27,7 @@
#include <nuttx/config.h>
#include <sys/types.h>
+#include <inttypes.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
@@ -40,8 +41,43 @@
#include <nuttx/spinlock.h>
#include <nuttx/ioexpander/gpio.h>
+#ifdef CONFIG_GPIO_PROCFS
+# include <sys/stat.h>
+# include <sys/param.h>
+# include <fcntl.h>
+# include <nuttx/kmalloc.h>
+# include <nuttx/list.h>
+# include <nuttx/mutex.h>
+# include <nuttx/fs/procfs.h>
+#endif
+
#ifdef CONFIG_DEV_GPIO
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+#ifdef CONFIG_GPIO_PROCFS
+
+/* One registered pin. struct gpio_dev_s belongs to the lower half, which
+ * usually embeds it in a larger private structure of its own, so the list
+ * node lives here rather than being added to it.
+ */
+
+/* Long enough for any name gpio_pin_register() accepts: it builds
+ * "/dev/" plus the name in a 32 byte buffer.
+ */
+
+#define GPIO_PROCFS_NAMELEN 28
+
+struct gpio_entry_s
+{
+ struct list_node node;
+ FAR struct gpio_dev_s *dev;
+ char name[GPIO_PROCFS_NAMELEN];
+};
+#endif
+
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
@@ -55,6 +91,21 @@ static ssize_t gpio_write(FAR struct file *filep, FAR const
char *buffer,
static off_t gpio_seek(FAR struct file *filep, off_t offset, int whence);
static int gpio_ioctl(FAR struct file *filep, int cmd,
unsigned long arg);
+#ifdef CONFIG_GPIO_PROCFS
+static int gpio_procfs_open(FAR struct file *filep,
+ FAR const char *relpath,
+ int oflags, mode_t mode);
+static int gpio_procfs_close(FAR struct file *filep);
+static ssize_t gpio_procfs_read(FAR struct file *filep, FAR char *buffer,
+ size_t buflen);
+static int gpio_procfs_dup(FAR const struct file *oldp,
+ FAR struct file *newp);
+static int gpio_procfs_stat(FAR const char *relpath,
+ FAR struct stat *buf);
+static void gpio_procfs_add(FAR struct gpio_dev_s *dev,
+ FAR const char *pinname);
+static void gpio_procfs_remove(FAR struct gpio_dev_s *dev);
+#endif
static int gpio_poll(FAR struct file *filep,
FAR struct pollfd *fds, bool setup);
@@ -75,10 +126,390 @@ static const struct file_operations g_gpio_drvrops =
gpio_poll, /* poll */
};
+#ifdef CONFIG_GPIO_PROCFS
+
+static struct list_node g_gpio_list = LIST_INITIAL_VALUE(g_gpio_list);
+static mutex_t g_gpio_lock = NXMUTEX_INITIALIZER;
+static bool g_gpio_procfs_added;
+
+/* Indexed by enum gpio_pintype_e. The enum's own comment warns that a
+ * table like this has to be extended with it; the assertion below turns
+ * forgetting into a build error rather than a pin type with no name.
+ */
+
+static const FAR char *g_gpio_typename[] =
+{
+ "INPUT", /* GPIO_INPUT_PIN */
+ "INPUT_PU", /* GPIO_INPUT_PIN_PULLUP */
+ "INPUT_PD", /* GPIO_INPUT_PIN_PULLDOWN */
+ "OUTPUT", /* GPIO_OUTPUT_PIN */
+ "OUTPUT_OD", /* GPIO_OUTPUT_PIN_OPENDRAIN */
+ "INT", /* GPIO_INTERRUPT_PIN */
+ "INT_HIGH", /* GPIO_INTERRUPT_HIGH_PIN */
+ "INT_LOW", /* GPIO_INTERRUPT_LOW_PIN */
+ "INT_RISING", /* GPIO_INTERRUPT_RISING_PIN */
+ "INT_FALLING", /* GPIO_INTERRUPT_FALLING_PIN */
+ "INT_BOTH", /* GPIO_INTERRUPT_BOTH_PIN */
+ "INT_WAKE", /* GPIO_INTERRUPT_PIN_WAKEUP */
+ "INT_HIGH_WAKE", /* GPIO_INTERRUPT_HIGH_PIN_WAKEUP */
+ "INT_LOW_WAKE", /* GPIO_INTERRUPT_LOW_PIN_WAKEUP */
+ "INT_RISING_WAKE", /* GPIO_INTERRUPT_RISING_PIN_WAKEUP */
+ "INT_FALLING_WAKE", /* GPIO_INTERRUPT_FALLING_PIN_WAKEUP */
+ "INT_BOTH_WAKE", /* GPIO_INTERRUPT_BOTH_PIN_WAKEUP */
+};
+
+static_assert(nitems(g_gpio_typename) == GPIO_NPINTYPES,
+ "pin type name table does not match enum gpio_pintype_e");
+
+static const struct procfs_operations g_gpio_procfs_ops =
+{
+ gpio_procfs_open, /* open */
+ gpio_procfs_close, /* close */
+ gpio_procfs_read, /* read */
+ NULL, /* write */
+ NULL, /* poll */
+
+ gpio_procfs_dup, /* dup */
+
+ NULL, /* opendir */
+ NULL, /* closedir */
+ NULL, /* readdir */
+ NULL, /* rewinddir */
+
+ gpio_procfs_stat, /* stat */
+};
+
+static const struct procfs_entry_s g_gpio_procfs =
+{
+ "gpio", &g_gpio_procfs_ops, PROCFS_FILE_TYPE
+};
+
+#endif /* CONFIG_GPIO_PROCFS */
+
/****************************************************************************
* Private Functions
****************************************************************************/
+#ifdef CONFIG_GPIO_PROCFS
+
+/****************************************************************************
+ * Name: gpio_procfs_open
+ *
+ * Description:
+ * Open /proc/gpio. The entry is read only, and holds no state of its
+ * own beyond the position accounting procfs does for every file.
+ *
+ * Input Parameters:
+ * filep - The file structure to attach the open file to
+ * relpath - The path below /proc being opened
+ * oflags - Open flags; anything but read only is refused
+ * mode - Ignored, the entry cannot be created
+ *
+ * Returned Value:
+ * Zero on success, or a negated errno on failure.
+ *
+ ****************************************************************************/
+
+static int gpio_procfs_open(FAR struct file *filep, FAR const char *relpath,
+ int oflags, mode_t mode)
+{
+ FAR struct procfs_file_s *priv;
+
+ if ((oflags & O_ACCMODE) != O_RDONLY)
+ {
+ return -EACCES;
+ }
+
+ priv = kmm_zalloc(sizeof(struct procfs_file_s));
+ if (priv == NULL)
+ {
+ return -ENOMEM;
+ }
+
+ filep->f_priv = priv;
+ return OK;
+}
+
+/****************************************************************************
+ * Name: gpio_procfs_close
+ *
+ * Description:
+ * Close /proc/gpio and free what open() allocated.
+ *
+ * Input Parameters:
+ * filep - The open file
+ *
+ * Returned Value:
+ * Zero on success, or a negated errno on failure.
+ *
+ ****************************************************************************/
+
+static int gpio_procfs_close(FAR struct file *filep)
+{
+ kmm_free(filep->f_priv);
+ filep->f_priv = NULL;
+ return OK;
+}
+
+/****************************************************************************
+ * Name: gpio_procfs_read
+ *
+ * Description:
+ * List every registered pin, in registration order, with what the upper
+ * half knows about it. A lower half that supplies go_describe adds its
+ * own fields to the same line.
+ *
+ * Each read renders from the start and skips what earlier reads already
+ * returned, so a file longer than the caller's buffer still comes out
+ * whole across successive reads.
+ *
+ * Input Parameters:
+ * filep - The open file, carrying the offset reached so far
+ * buffer - Where to return the text
+ * buflen - Size of buffer
+ *
+ * Returned Value:
+ * The number of bytes returned, zero at end of file, or a negated errno
+ * on failure.
+ *
+ ****************************************************************************/
+
+static ssize_t gpio_procfs_read(FAR struct file *filep, FAR char *buffer,
+ size_t buflen)
+{
+ FAR struct gpio_entry_s *entry;
+ size_t remaining = buflen;
+ FAR char *dest = buffer;
+ off_t pos = filep->f_pos;
+ char extra[48];
+ char line[128];
+ bool value;
+ size_t n;
+ int ret;
+
+ ret = nxmutex_lock(&g_gpio_lock);
+ if (ret < 0)
+ {
+ return ret;
+ }
+
+ list_for_every_entry(&g_gpio_list, entry, struct gpio_entry_s, node)
+ {
+ FAR const char *type = "-";
+
+ if (remaining == 0)
+ {
+ break;
+ }
+
+ /* A lower half is free to invent a pin type this table has never
+ * heard of, so the index is bounded here rather than trusted.
+ */
+
+ if (entry->dev->gp_pintype < nitems(g_gpio_typename))
+ {
+ type = g_gpio_typename[entry->dev->gp_pintype];
+ }
+
+ /* A pin that cannot be read reports -, so that a failed read is
+ * not shown as a low level.
+ */
+
+ if (entry->dev->gp_ops->go_read != NULL &&
+ entry->dev->gp_ops->go_read(entry->dev, &value) >= 0)
+ {
+ n = snprintf(line, sizeof(line), "%-12s type:%-18s val:%u",
+ entry->name, type, value);
+ }
+ else
+ {
+ n = snprintf(line, sizeof(line), "%-12s type:%-18s val:-",
+ entry->name, type);
+ }
+
+ n += snprintf(line + n, sizeof(line) - n,
+ " regs:%u ints:%" PRIuPTR,
+ entry->dev->register_count, entry->dev->int_count);
+
+ extra[0] = '\0';
+ if (entry->dev->gp_ops->go_describe != NULL)
+ {
+ entry->dev->gp_ops->go_describe(entry->dev, extra, sizeof(extra));
+ extra[sizeof(extra) - 1] = '\0';
+ }
+
+ if (extra[0] != '\0')
+ {
+ n += snprintf(line + n, sizeof(line) - n, " %s", extra);
+ }
+
+ n += snprintf(line + n, sizeof(line) - n, "\n");
+
+ /* snprintf() reports the length it wanted, so a line longer than the
+ * buffer would otherwise carry n past it.
+ */
+
+ if (n >= sizeof(line))
+ {
+ n = sizeof(line) - 1;
+ line[n - 1] = '\n';
+ }
+
+ n = procfs_memcpy(line, n, dest, remaining, &pos);
+ dest += n;
+ remaining -= n;
+ }
+
+ nxmutex_unlock(&g_gpio_lock);
+
+ filep->f_pos += (dest - buffer);
+ return dest - buffer;
+}
+
+/****************************************************************************
+ * Name: gpio_procfs_dup
+ *
+ * Description:
+ * Duplicate an open /proc/gpio, copying the position reached so that the
+ * new file continues where the old one had got to.
+ *
+ * Input Parameters:
+ * oldp - The open file being duplicated
+ * newp - The file structure to attach the duplicate to
+ *
+ * Returned Value:
+ * Zero on success, or a negated errno on failure.
+ *
+ ****************************************************************************/
+
+static int gpio_procfs_dup(FAR const struct file *oldp,
+ FAR struct file *newp)
+{
+ FAR struct procfs_file_s *priv;
+
+ priv = kmm_zalloc(sizeof(struct procfs_file_s));
+ if (priv == NULL)
+ {
+ return -ENOMEM;
+ }
+
+ memcpy(priv, oldp->f_priv, sizeof(struct procfs_file_s));
+ newp->f_priv = priv;
+ return OK;
+}
+
+/****************************************************************************
+ * Name: gpio_procfs_stat
+ *
+ * Description:
+ * Report /proc/gpio as a read only regular file.
+ *
+ * Input Parameters:
+ * relpath - The path below /proc being queried
+ * buf - Where to return the status
+ *
+ * Returned Value:
+ * Zero on success, or a negated errno on failure.
+ *
+ ****************************************************************************/
+
+static int gpio_procfs_stat(FAR const char *relpath, FAR struct stat *buf)
+{
+ buf->st_mode = S_IFREG | S_IROTH | S_IRGRP | S_IRUSR;
+ buf->st_size = 0;
+ buf->st_blksize = 0;
+ buf->st_blocks = 0;
+ return OK;
+}
+
+/****************************************************************************
+ * Name: gpio_procfs_add
+ *
+ * Description:
+ * Remember a pin, and create /proc/gpio when the first one appears.
+ * procfs_register() has to run before procfs is mounted, which holds
+ * for the pins a board registers during start up. The entry is claimed
+ * once for the lifetime of the system, since procfs_register() appends
+ * without checking for duplicates.
+ *
+ * Input Parameters:
+ * dev - The pin being registered
+ * pinname - The name it was registered under, used as the /proc/gpio
+ * label; the caller's string must outlive the pin
+ *
+ * Returned Value:
+ * None. A pin that cannot be listed is still a working pin, so a
+ * failure here does not fail the registration.
+ *
+ ****************************************************************************/
+
+static void gpio_procfs_add(FAR struct gpio_dev_s *dev,
+ FAR const char *pinname)
+{
+ FAR struct gpio_entry_s *entry;
+
+ entry = kmm_zalloc(sizeof(struct gpio_entry_s));
+ if (entry == NULL)
+ {
+ return;
+ }
+
+ entry->dev = dev;
+ strlcpy(entry->name, pinname, sizeof(entry->name));
+
+ nxmutex_lock(&g_gpio_lock);
+
+ /* procfs_register() appends without checking for a duplicate, so the
+ * entry is claimed once for the lifetime of the system rather than
+ * whenever the list is empty. Pins come and go at run time.
+ */
+
+ if (!g_gpio_procfs_added)
+ {
+ procfs_register(&g_gpio_procfs);
+ g_gpio_procfs_added = true;
+ }
+
+ list_add_tail(&g_gpio_list, &entry->node);
+ nxmutex_unlock(&g_gpio_lock);
+}
+
+/****************************************************************************
+ * Name: gpio_procfs_remove
+ *
+ * Description:
+ * Forget a pin. /proc/gpio stays, since procfs has no way to withdraw
+ * an entry, and lists nothing once the last pin has gone.
+ *
+ * Input Parameters:
+ * dev - The pin being unregistered
+ *
+ * Returned Value:
+ * None.
+ *
+ ****************************************************************************/
+
+static void gpio_procfs_remove(FAR struct gpio_dev_s *dev)
+{
+ FAR struct gpio_entry_s *entry;
+
+ nxmutex_lock(&g_gpio_lock);
+
+ list_for_every_entry(&g_gpio_list, entry, struct gpio_entry_s, node)
+ {
+ if (entry->dev == dev)
+ {
+ list_delete(&entry->node);
+ kmm_free(entry);
+ break;
+ }
+ }
+
+ nxmutex_unlock(&g_gpio_lock);
+}
+
+#endif /* CONFIG_GPIO_PROCFS */
+
/****************************************************************************
* Name: gpio_handler
*
@@ -791,7 +1222,16 @@ int gpio_pin_register_byname(FAR struct gpio_dev_s *dev,
gpioinfo("Registering %s\n", devname);
- return register_driver(devname, &g_gpio_drvrops, 0600, dev);
+ ret = register_driver(devname, &g_gpio_drvrops, 0600, dev);
+
+#ifdef CONFIG_GPIO_PROCFS
+ if (ret >= 0)
+ {
+ gpio_procfs_add(dev, pinname);
+ }
+#endif
+
+ return ret;
}
/****************************************************************************
@@ -851,6 +1291,10 @@ int gpio_pin_unregister_byname(FAR struct gpio_dev_s *dev,
gpioinfo("Unregistering %s\n", devname);
+#ifdef CONFIG_GPIO_PROCFS
+ gpio_procfs_remove(dev);
+#endif
+
return unregister_driver(devname);
}
diff --git a/include/nuttx/ioexpander/gpio.h b/include/nuttx/ioexpander/gpio.h
index cd85ae888bd..a34d3b2ecd9 100644
--- a/include/nuttx/ioexpander/gpio.h
+++ b/include/nuttx/ioexpander/gpio.h
@@ -156,6 +156,23 @@ struct gpio_operations_s
CODE int (*go_setdebounce)(FAR struct gpio_dev_s *gpio,
unsigned long duration);
CODE int (*go_setmask)(FAR struct gpio_dev_s *dev, bool enable);
+
+ /* Describe whatever only this pin's controller can say, for
+ * /proc/gpio: which pad carries the line, how its trigger is
+ * configured, anything the upper half has no way to ask for.
+ *
+ * Optional. A pin whose lower half omits it is still listed with
+ * everything the upper half knows: its type, its value, and how many
+ * times it has been registered for signals and taken an interrupt.
+ *
+ * Write at most len bytes into extra as further key:value fields, in
+ * the form the renderer uses, and return OK. The renderer owns the
+ * line and appends this to it, so a lower half needs no procfs
+ * knowledge of its own.
+ */
+
+ CODE int (*go_describe)(FAR struct gpio_dev_s *dev, FAR char *extra,
+ size_t len);
};
/* Signal information */