What the BSPs actually do with GPIO, and what the new API cannot say
Scope and method
Every bsps/ directory was scanned: 16 architectures, 128 BSP directories.
Thirteen BSP families expose a GPIO or pad-configuration interface, plus one
I2C expander driver and the four drivers written against the proposed API.
The scan keyed on register names and public function names, then every
interesting result was read. Two deliberate exclusions: vendor SDK imports
(imxrt/mcux-sdk/, altera-cyclone-v/socal/) are in the tree but are not
RTEMS interfaces, and spi-gpio.c / imx-spi-gpio.c are consumers of GPIO
rather than providers.
A scan that keys on names misses a BSP that does something without using the
usual words for it. Treat absence in the tables below as “not found”, not as
“not there”.
Inventory
| BSP family | interface | lines |
|---|---|---|
arm/beagle |
old generic API (bsp/gpio.h) |
525 |
arm/raspberrypi |
old generic API | 372 + 138 |
aarch64/raspberrypi |
its own, 4 functions | 110 |
arm/efm32gg11 |
its own | 242 |
arm/lpc176x |
its own | 396 |
arm/lm3s69xx |
pin config at start-up | 146 |
arm/stm32f4 |
pin config at start-up | 262 |
arm/lpc24xx |
pin config at start-up | 608 |
arm/imx (shared) |
its own, FDT-driven | 409 |
arm/tms570 |
pinmux only | 280 |
microblaze/microblaze_fpga |
its own, AXI GPIO | 361 |
sparc GRLIB |
gpiolib + grgpio |
308 + 488 |
riscv/esp32 |
new API | 578 |
shared zynq-gpio |
new API | 1073 |
shared ti-gpio |
new API | 1089 |
shared gpio-virtual |
new API | 512 |
dev/i2c/gpio-nxp-pca9535 |
its own ioctls | 188 |
shared gpio-support.c |
the old generic API | 2037 |
Nothing is shared. Eleven of the thirteen families invented their own
vocabulary, and the two that did not — beagle and raspberrypi — are the two
the old generic API was written for.
Feature matrix
Y = present in the interface. - = absent. ~ = reachable but unnamed.
| dir | pull | open drain | drive str | slew | debounce | edge irq | level irq | shared irq | mask/unmask | ack | pending | bulk | analog/off | mux | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| beagle | Y | Y | - | - | Y | Y¹ | Y | Y | Y | - | - | - | Y | ~² | Y |
| raspberrypi (arm) | Y | Y | - | - | - | Y¹ | Y | Y | Y | - | - | - | Y | - | Y |
| raspberrypi (a64) | Y | Y | - | - | - | - | - | - | - | - | - | - | - | - | Y |
| efm32gg11 | ~ | - | - | - | - | - | Y | - | - | Y | - | - | Y | ~³ | ~ |
| lpc176x | Y | - | - | - | - | - | Y | - | - | - | - | - | - | - | Y |
| lm3s69xx | Y | Y | Y | Y | Y | - | - | - | - | - | - | - | - | Y | Y |
| stm32f4 | Y | Y | Y | - | Y | - | - | - | - | - | - | - | - | Y | Y |
| lpc24xx | Y | Y | Y | - | Y | - | - | - | - | - | - | - | - | ~ | Y |
| imx | Y | - | - | - | - | - | Y | Y | - | Y | Y | Y | - | - | Y |
| tms570 | - | - | - | - | - | - | - | - | - | - | - | - | - | - | Y |
| microblaze_fpga | Y | - | - | - | - | - | Y⁴ | - | - | Y | Y | Y | Y | - | - |
GRLIB gpiolib |
Y | - | - | - | - | - | Y | Y | - | Y | Y | - | - | - | - |
| pca9535 | Y | - | - | - | - | - | - | - | - | - | - | - | Y | - | - |
| old generic API | Y | Y | - | - | - | Y¹ | Y | Y | Y | - | - | - | Y | - | Y |
| proposed API | Y | Y | Y | Y | - | Y | Y | Y | - | - | - | - | Y | - | n/a |
¹ software debounce, in clock ticks — not a hardware debouncer.
² RXACTIVE in the AM335x pad config; bbb-gpio.c never touches it.
³ efm32gg11_gpio_mode() takes a raw nibble and EFM32 mode 0 is DISABLED.
⁴ per channel, not per pin.
Where the new API is ahead
Nothing else in the tree has drive strength as a first-class setting except
lm3s69xx, and nothing has open source at all. Capability reporting is
unique to the new API — every other interface makes you ask and find out.
Hardware debounce in real units (µs) exists nowhere else; the old API’s
debounce_clock_tick_interval is a software delay measured in ticks.
Atomic clear-and-set is covered. pca9535 has
clear_and_set_output(fd, clear, set) and efm32gg11 has
gpio_clear_set(port, clear, set); rtems_gpio_pin_set_multiple() with a mask
and a values bitmap is the same operation. No gap.
Gaps, ranked
1. Shared interrupt handlers — a regression, not a gap
The old API has rtems_gpio_handler_flag { SHARED_HANDLER, UNIQUE_HANDLER },
and gpio-support.c implements it properly: handler_chain is an
rtems_chain_control walked in the ISR, and a handler returns
rtems_gpio_irq_state { IRQ_HANDLED, IRQ_NONE } so it can say “not mine”.
That is exactly the RTEMS_INTERRUPT_SHARED behaviour asked for in review, and
RTEMS has had it since 2014. The proposed API has one handler per pin, and the
ESP32-C3 driver silently overwrites it on a second irq_enable
(esp32c3-gpio.c:509) — the second caller steals the line and the first is
never told.
Whatever is decided about sharing, the silent overwrite is a defect.
2. Mask and unmask, distinct from register and unregister
Three BSPs separate “stop delivering this interrupt” from “forget the handler”:
- GRLIB:
gpiolib_irq_mask()/gpiolib_irq_unmask()and separately
gpiolib_irq_enable()/gpiolib_irq_disable()and separately
gpiolib_irq_register(). Five operations where the new API has two. efm32gg11_gpio_int_enable(which, bool)— keyed on the interrupt number, so
the handler registration survives.imx_gpio_int_enable()/imx_gpio_int_disable(), independent of the
handler.
In the new API rtems_gpio_pin_irq_disable() drops the handler, so re-arming
means passing it again. More seriously, it takes the controller mutex, so it
cannot be called from the handler at all — which is what MR !12 addresses with
rtems_gpio_pin_irq_disable_isr().
3. Explicit acknowledge
gpiolib_irq_clear(), imx_gpio_clear_isr(pin, clr),
microblaze_gpio_interrupt_clear(). Three independent BSPs expose it.
The new API has nothing: drivers clear the status before calling the handler
and the caller never sees it. That is fine for edge triggers and wrong for
level triggers, which is open question 2 of the RFC — a level-triggered handler
that does not remove its cause is re-entered, and Linux masks the line and
unmasks on ack precisely to avoid that.
4. Pending status
imx_gpio_get_isr(), microblaze_gpio_interrupt_get_status(),
microblaze_gpio_interrupt_get_enabled(). No equivalent in the new API, so a
caller cannot poll instead of taking an interrupt, and a shared handler could
not work out which pin fired even if sharing existed.
5. Threaded handlers — also a regression
The old API’s rtems_gpio_interrupt_configuration carries
bool threaded_interrupts, and gpio-support.c routes handlers either to
interrupt context or to a server task accordingly (per bank, not per pin).
The new API has no choice: the handler runs wherever the driver raises it, and
rtems_gpio_get_info() only reports which that is. The blocking-ioctl
model discussed in review would cover the same ground more cleanly, but until
it exists this is capability that was in the tree and is not in the proposal.
6. Finite interrupt-line pools
efm32gg11_gpio_int_register() searches a pool of 16 external interrupt lines,
and a pin may only use the four lines in its group (pin / 4). It returns -1
when they are all taken.
So on that part a pin’s ability to interrupt is dynamic and contended. The
new API’s RTEMS_GPIO_CAP_EDGE_* are static bits from pin_get_info: a pin can
truthfully advertise edge capability and still fail to arm. irq_enable can
return an errno, so it is expressible — but no errno is documented for it and
there is no way to ask in advance. EBUSY would be the honest answer and it
should be written down.
7. Bank-level interrupts
AXI GPIO interrupts per channel, not per pin
(microblaze_gpio_interrupt_enable(ctx, channel)). The new API is per-pin only,
so such a driver must either demultiplex by reading the data register and
diffing, or report no interrupt capability at all.
8. Slew rate
lm3s69xx (slr field, LM3S69XX_GPIO_SLEW_RATE_CONTROL), beagle
(BBB_SLEWCTRL), lpc24xx. Three BSPs. The new rtems_gpio_config has
direction, bias, drive, trigger, flags, drive_strength, debounce
and initial_value — no slew, and no capability bit for one. Bits 15 to 31 of
the capability word are free.
9. Analog / disconnected pad state
lm3s69xx keeps two bits — digital (the den register) and analog
(amsel) — plus a runtime setter. stm32f4 has STM32F4_GPIO_MODE_ANALOG and
STM32F4_GPIO_CNF_IN_ANALOG. Versal/ZynqMP has PM_PINCTRL_CONFIG_TRI_STATE.
mpc55xx has APC and IBE. The input-buffer lever is wider still: AM335x
RXACTIVE, ESP32-C3 FUN_IE — which our own new driver already writes.
RTEMS_GPIO_DIRECTION_NONE cannot absorb this: it is documented as the state
before configure and after release, a lifecycle state. Note the old API has the
identical conflation, reached independently — NOT_USED in
rtems_gpio_function is bookkeeping, and asking for it returns
RTEMS_NOT_DEFINED (gpio-support.c:1407).
Because the four BSPs decompose it four different ways, one enumerator and one
capability bit is the right granularity. Modelling which kind of disconnected
would be wrong on at least one part.
10. Software debounce
The old API’s debounce is a software delay in clock ticks and works on any
part. The new API’s is hardware and in µs, so raspberrypi and beagle — the two
BSPs that use the old API today — get ENOTSUP where they used to get
debounce. Better layering, real capability loss, and a migration plan should
say so rather than discover it.
11. Hardware polarity inversion
pca9535 has a polarity inversion register
(GPIO_NXP_PCA9535_SET_POL_INV). The proposal deliberately removed
CAP_INVERT and applies RTEMS_GPIO_FLAG_ACTIVE_LOW above the driver, so a
pca9535 driver written against the new API cannot use the hardware and must be
careful not to invert twice. By design, worth stating.
12. Alternate function selection
aarch64/raspberrypi’s only configuration call is
raspberrypi_gpio_set_function(), whose enum mixes GPIO_INPUT, GPIO_OUTPUT
and GPIO_AF0 through GPIO_AF5. The old API had BSP_SPECIFIC plus an
io_function and a void *bsp_specific escape.
The new API excludes muxing on purpose — it is the BSP’s. That is the right
call, and it means porting raspberrypi is not a translation: the mux half of
its public interface has to go somewhere else first. Which is the pad
arbitration question.
Our own four drivers
zynq-gpio and ti-gpio report all fifteen capability bits. esp32c3-gpio
reports thirteen, omitting DEBOUNCE and OPEN_SOURCE, which the part does
not have. gpio-virtual reports three, which is right for a software pin.
No under-reporting found.
Recommendations
- Define what a second
irq_enableon one pin does, before merge. Today it
is undefined by omission and silently wrong in one driver. - Carry an options field in
rtems_gpio_pin_irqnow. It is already the
ioctl payload struct, so addinguint32_t optionscosts a line before merge
and an ABI break after.RTEMS_GPIO_IRQ_SHAREDcan returnENOTSUPuntil a
driver implements it. - Add
RTEMS_GPIO_DIRECTION_DISCONNECTEDand a capability bit. Four BSPs
already model it; Zephyr’sGPIO_DISCONNECTEDis the precedent. - Add slew rate — a field and a bit, three BSPs, free capability bits.
- Document an errno for “no interrupt resource” (
EBUSY), for parts with
a line pool. - Settle acknowledge and level re-entrancy once, in the API rather than
per driver. Three BSPs expose an explicit clear. - Say what happens to
threaded_interruptsand software debounce in the
deprecation plan for the old API. Both are capability the tree has today.
Items 1 and 2 are pre-merge. The rest can follow, and items 3 to 6 are each
small enough to be one commit with a gpio01 test.