Drv/LinuxGpioDriver/docs/sdd.md
The LinuxGpioDriver component provides a Linux-specific implementation of a General Purpose Input/Output (GPIO) pin driver. It implements the Drv.Gpio interface to enable reading, writing, and interrupt detection on a single GPIO line.
The component wraps the Linux GPIO character device ABI (/dev/gpiochip*), configuring and accessing a line through ioctl requests rather than the deprecated sysfs interface. Each component instance manages exactly one GPIO line, and that line is fixed to a single mode chosen at configuration time.
For more information on the driver interfaces see: Drv FPP Interfaces.
| Name | Description | Validation |
|---|---|---|
| LINUX-GPIO-COMP-001 | The LinuxGpioDriver component shall implement the Drv.Gpio interface | inspection |
| LINUX-GPIO-COMP-002 | The LinuxGpioDriver component shall configure a GPIO line as input, output, or interrupt using the Linux GPIO character device | inspection |
| LINUX-GPIO-COMP-003 | The LinuxGpioDriver component shall set a caller-supplied default state when a line is configured as output | inspection |
| LINUX-GPIO-COMP-004 | The LinuxGpioDriver component shall reject read and write requests that do not match the configured mode | inspection |
| LINUX-GPIO-COMP-005 | The LinuxGpioDriver component shall detect line transitions on a rising edge, a falling edge, or both edges as configured | inspection |
| LINUX-GPIO-COMP-006 | The LinuxGpioDriver component shall provide a dedicated thread for interrupt detection | inspection |
| LINUX-GPIO-COMP-007 | The LinuxGpioDriver component shall emit a timestamped interrupt on the gpioInterrupt output port when a configured transition occurs | inspection |
| LINUX-GPIO-COMP-008 | The LinuxGpioDriver component shall report configuration and runtime errors via events | inspection |
LinuxGpioDriver is a passive component implementing the design specified by the Drv.Gpio interface.
| Name | Type | Kind | Description |
|---|---|---|---|
| gpioRead | Drv.GpioRead | sync input | Reads the current logic state of the line |
| gpioWrite | Drv.GpioWrite | sync input | Sets the logic state of the line |
| gpioInterrupt | Svc.Cycle | output | Emits a timestamp when a configured line transition is detected |
The component declares no commands, telemetry channels, or parameters. Since the component is passive, gpioRead and gpioWrite execute on the caller's thread.
The component consists of the following key elements:
/dev/gpiochip* device, validates the requested line, and requests either a line handle or a line event from the kernelgpioRead and gpioWrite portsgpioInterrupt output porterrno values from the underlying system calls onto Os::File::Status and Drv::GpioStatusA line is configured once through the open() method, which selects one of five modes:
| Configuration | Direction | Supported Operations |
|---|---|---|
| GPIO_OUTPUT | Output | gpioWrite |
| GPIO_INPUT | Input | gpioRead |
| GPIO_INTERRUPT_RISING_EDGE | Input | gpioInterrupt on low-to-high transition |
| GPIO_INTERRUPT_FALLING_EDGE | Input | gpioInterrupt on high-to-low transition |
| GPIO_INTERRUPT_BOTH_RISING_AND_FALLING_EDGES | Input | gpioInterrupt on either transition |
open() performs the following steps:
GPIO_GET_LINEHANDLE_IOCTL) for the input and output modes, or a line event (GPIO_GET_LINEEVENT_IOCTL) for the interrupt modesThe component name is passed to the kernel as the consumer label for the line (when object names are enabled via FW_OBJECT_NAMES), so the owning component is identifiable in tools such as gpioinfo.
The read and write handlers are strictly gated on the configured mode:
gpioRead returns the line state only when the line is configured as GPIO_INPUTgpioWrite sets the line state only when the line is configured as GPIO_OUTPUTA request that does not match the configured mode returns Drv::GpioStatus::INVALID_MODE and performs no hardware access. Notably, the interrupt configurations do not support gpioRead; a line that must be both polled and interrupt-driven requires a separate component instance.
Interrupt detection runs on a dedicated thread started by start(). The thread is only available in the three interrupt configurations; calling start() in any other mode returns Drv::GpioStatus::INVALID_MODE and starts no thread.
The polling loop performs the following:
GPIO_POLL_TIMEOUT of 500 millisecondsOs::RawTimegpioInterrupt output port with that timestampBecause gpioInterrupt is invoked directly from the polling thread, the receiving component is responsible for any thread-safety and execution-time constraints on that path. Connecting the port to an async input port, such as CycleIn on Svc::ActiveRateGroup, keeps work off the polling thread.
The interrupt thread is controlled by three methods:
start(): sets the running flag and starts the polling task; returns Drv::GpioStatus::UNKNOWN_ERROR if the task fails to startstop(): clears the running flag, requesting shutdownjoin(): blocks until the polling task exitsThe running flag is guarded by a mutex. The poll timeout bounds how long the loop can block, so the thread observes a stop() request and exits within one timeout period. stop() must be called before join(), otherwise join() will not return.
Outside of the stubbed build, this component is restricted to Linux targets. When the FPRIME_USE_STUBBED_DRIVERS CMake option is set, a stub implementation is built instead, allowing topologies that instantiate this component to build on platforms without GPIO character device support. The stub returns Os::File::Status::NOT_SUPPORTED from open() and Drv::GpioStatus::UNKNOWN_ERROR from the port handlers, and its polling loop delays instead of accessing hardware.
The LinuxGpioDriver must be configured before use. Each instance drives exactly one GPIO line, so a topology using several lines instantiates several components.
The component should be instantiated in the FPP topology and configured using separate functions following F´ patterns:
// Configuration function - called during topology setup
void configureTopology() {
// Configure an output pin, driven low until written
Os::File::Status status = gpioLed.open("/dev/gpiochip0", // GPIO chip device
17, // Line number on that chip
Drv::LinuxGpioDriver::GPIO_OUTPUT, // Pin configuration
Fw::Logic::LOW); // Default output state
if (status != Os::File::Status::OP_OK) {
// Handle configuration error
}
// Configure an interrupt pin
status = gpioButton.open("/dev/gpiochip0", 27, Drv::LinuxGpioDriver::GPIO_INTERRUPT_RISING_EDGE);
if (status != Os::File::Status::OP_OK) {
// Handle configuration error
}
...
}
// Startup function - called when starting tasks
void setupTopology() {
// Start the interrupt thread; only valid for interrupt configurations
Drv::GpioStatus gpioStatus = gpioButton.start(GPIO_PRIORITY, // Thread priority
Os::Task::TASK_DEFAULT, // Thread stack size
Os::Task::TASK_DEFAULT); // Thread CPU affinity mask
if (gpioStatus != Drv::GpioStatus::OP_OK) {
// Handle startup error
}
}
// Shutdown function - called during teardown
void teardownTopology() {
gpioButton.stop();
gpioButton.join();
}
The read and write ports are connected to the component that uses the line, and an interrupt line is connected to a consumer of Svc.Cycle:
# In topology.fpp connections section
connections Gpio {
# A user component drives an output pin
ledManager.gpioWrite -> gpioLed.gpioWrite
# Interrupt pin drives a rate group
gpioButton.gpioInterrupt -> buttonRateGroup.CycleIn
}
| Parameter | Type | Description | Valid Values |
|---|---|---|---|
| device | const char* | Path to the GPIO chip device | Linux device path (e.g. "/dev/gpiochip0") |
| gpio | U32 | Line number on the given chip | Less than the line count reported by the chip |
| configuration | Drv::LinuxGpioDriver::GpioConfiguration | Pin mode | See pin configuration table |
| default_state | Fw::Logic | Initial state for output pins | Fw::Logic::LOW (default), Fw::Logic::HIGH |
The interrupt thread can be configured with:
| Parameter | Type | Default | Description |
|---|---|---|---|
| priority | FwTaskPriorityType | TASK_PRIORITY_DEFAULT | Thread priority |
| stackSize | FwSizeType | TASK_DEFAULT | Thread stack size |
| cpuAffinity | FwSizeType | TASK_DEFAULT | CPU affinity mask |
| identifier | FwTaskIdType | TASK_DEFAULT | Task identifier |
Port handlers return Drv::GpioStatus:
| Status | Meaning |
|---|---|
| OP_OK | Operation succeeded |
| NOT_OPENED | Pin was never opened |
| INVALID_MODE | Operation not permitted with the current configuration |
| UNKNOWN_ERROR | An unknown error occurred |
open() returns Os::File::Status, translated from the errno reported by the underlying system call. Note: in the current implementation, a request for a line number beyond the chip's line count logs OpenPinError but returns the preceding status (OP_OK); callers should not rely on open() returning an error in that case.
The component generates the following events:
| Event | Severity | Description |
|---|---|---|
| OpenChip | diagnostic | Chip and line configured successfully |
| OpenChipError | warning high | GPIO chip device could not be opened or queried |
| OpenPinError | warning high | GPIO line could not be configured |
| InterruptReadError | warning high | Interrupt event record read returned an unexpected size |
| PollingError | warning high | Interrupt polling returned an error |
| InterruptTimeError | warning high | Interrupt timestamp could not be read |