Skip to content

drivers/ioexpander: List the registered pins in /proc/gpio - #19878

Merged
xiaoxiang781216 merged 1 commit into
apache:masterfrom
Fishwaldo:upstream-gpio-procfs
Aug 18, 2026
Merged

drivers/ioexpander: List the registered pins in /proc/gpio#19878
xiaoxiang781216 merged 1 commit into
apache:masterfrom
Fishwaldo:upstream-gpio-procfs

Conversation

@Fishwaldo

Copy link
Copy Markdown
Contributor

Summary

Adds /proc/gpio, which lists every pin registered with the GPIO character
driver, with its type, value and counters in one place.

The pins are already visible in /dev and each can be read through its own
node with GPIOC_READ and GPIOC_PINTYPE. 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 ioctl at all. This is a quality
of life view of the same kind as /proc/pinctrl and /proc/reset.

gpio6        type:INPUT_PU           val:1 regs:0 ints:0 pad:SPI2_CS0_N port:A.6
gpio28       type:INT_FALLING        val:0 regs:0 ints:0 pad:GPIO28 port:A.28
gpio128      type:INPUT              val:0 regs:0 ints:0

Every line carries the same key:value tokens in the same order, so the file
can be parsed as well as read. The common fields all come from state the upper
half already holds — the pin type, the value through go_read(), and the
existing register_count and int_count — so nothing is added to any hot
path.

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 armed:

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 writes into a caller supplied buffer and the upper half owns the line, so a
lower half needs no procfs knowledge of its own. A lower half without it is
listed with the common tokens alone — the gpio128 line above is exactly that
case, a TCA6416 expander pin.

Two existing hazards in the paths this touches are fixed as well.
procfs_register() appends without checking for duplicates, so the entry is
now claimed once for the lifetime of the system rather than whenever the list
is empty — pins genuinely come and go at run time. And the pin type index is
bounded before use, since it comes from the lower half and the name table
cannot cover a type the enumeration does not define.

Impact

New feature, off by default. CONFIG_GPIO_PROCFS depends on
FS_PROCFS_REGISTER; with it unset the list, the lock and the procfs entry are
compiled out entirely.

struct gpio_operations_s gains one optional member at the end. Every existing
lower half initialises that structure by name or by position without reaching
it, so none need updating. One small allocation per registered pin when the
option is on.

Worth knowing: the renderer calls go_read() for every pin on every read(),
and procfs re-walks the list each time. For a memory mapped controller that is
a register read; for an I2C expander it is a bus transaction per pin per read.
Measured on the board below, with 32 of its 34 pins on TCA6416 expanders, one
cat of the file cost 144 I2C reads. That is inherent to the procfs
pattern rather than new here, and caching would trade staleness for speed in a
view whose whole purpose is to be current.

Testing

Host: macOS 26.5.1 (arm64), riscv-none-elf-gcc 15.2.0, Sphinx 6.2.1.

Build: sim:nsh with DEV_GPIO alone, and again with FS_PROCFS,
FS_PROCFS_REGISTER and GPIO_PROCFS added, each from a clean tree: both
compile drivers/ioexpander/gpio.c. The EIC7700 EVB configuration builds with
the option on and off. Documentation builds with no new warnings.

Hardware: ESWIN EIC7700 EVB (EIC7700X, 4 x RV64GC), 34 pins registered: two
from the SoC's own GPIO lower half, which implements go_describe, and 32 from
two TCA6416 I2C expanders, whose lower half does not.

nsh> cat /proc/gpio
gpio6        type:INPUT_PU           val:1 regs:0 ints:0 pad:SPI2_CS0_N port:A.6
gpio28       type:INT_FALLING        val:0 regs:0 ints:0 pad:GPIO28 port:A.28
gpio128      type:INPUT              val:0 regs:0 ints:0
gpio129      type:INPUT              val:1 regs:0 ints:0
gpio130      type:INPUT              val:0 regs:0 ints:0
...
gpio158      type:INPUT              val:0 regs:0 ints:0
gpio159      type:INPUT              val:0 regs:0 ints:0

Checked on that output:

  • all 34 registered pins listed, each exactly once — no duplicate or dropped
    line at any read boundary
  • every line carries the same four common tokens; an awk pass counting
    type:, val:, regs: and ints: finds no line that deviates
  • both cases covered on real hardware: gpio6 and gpio28 carry the SoC lower
    half's pad: and port: fields, gpio28 being an interrupt pin, while the
    expander pins from gpio128 up show the common tokens alone
  • pad:SPI2_CS0_N on gpio6 is correct: that pad is muxed to function 2,
    GPIO, which /proc/pinctrl reports independently

@github-actions github-actions Bot added Area: Drivers Drivers issues Size: M The size of the change in this PR is medium labels Aug 17, 2026
@github-actions

github-actions Bot commented Aug 17, 2026

Copy link
Copy Markdown

MemBrowse Memory Report

No memory changes detected for:

jerpelea
jerpelea previously approved these changes Aug 17, 2026
Comment thread drivers/ioexpander/gpio.c
@Fishwaldo
Fishwaldo force-pushed the upstream-gpio-procfs branch from d32e713 to 1735fa8 Compare August 17, 2026 12:23
@github-actions github-actions Bot added Size: L The size of the change in this PR is large and removed Size: M The size of the change in this PR is medium labels Aug 17, 2026
Comment thread drivers/ioexpander/gpio.c Outdated
Comment thread drivers/ioexpander/gpio.c Outdated
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 <justin@dynam.ac>
@Fishwaldo
Fishwaldo force-pushed the upstream-gpio-procfs branch from 1735fa8 to 79db3a6 Compare August 18, 2026 04:01
@xiaoxiang781216
xiaoxiang781216 merged commit 1528b47 into apache:master Aug 18, 2026
54 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area: Drivers Drivers issues Size: L The size of the change in this PR is large

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants