Sim GPIO Chip Driver (Linux Host GPIO)

Overview

The Sim GPIO Chip driver provides a mechanism for NuttX simulation (sim) to access the Linux host’s GPIO chip devices (/dev/gpiochipN). This allows NuttX applications running in simulation mode to interact with real hardware GPIO pins connected to the Linux host system.

This driver is particularly useful for:

  • Testing GPIO-based applications in a simulated environment with real hardware

  • Interfacing with USB-to-GPIO adapters (e.g., CH341A) from NuttX simulation

  • Developing and debugging GPIO drivers without dedicated embedded hardware

Host Prepare

Preparation required on the host side:

Architecture

The driver consists of two layers:

  1. NuttX Layer (sim_gpiochip.c): Implements the NuttX ioexpander_dev_s interface, providing standard GPIO operations to upper-layer NuttX drivers.

  2. Host Layer (sim_linux_gpiochip.c): Interfaces directly with Linux kernel’s GPIO character device (/dev/gpiochipN) using the GPIO v2 ABI.

+---------------------+
|  NuttX Application  |
+---------------------+
        |
        v
+---------------------+
|  GPIO Lower Half    |
|  (gpio_lower_half)  |
+---------------------+
        |
        v
+---------------------+
|   sim_gpiochip.c    |  <-- NuttX ioexpander interface
| (ioexpander_dev_s)  |
+---------------------+
        |
        v
+---------------------+
| sim_linux_gpiochip.c|  <-- Linux host GPIO interface
|  (GPIO v2 ABI)      |
+---------------------+
        |
        v
+---------------------+
|  /dev/gpiochipN     |  <-- Linux GPIO character device
+---------------------+

Header Files

  • arch/sim/src/sim/sim_hostgpiochip.h: Host GPIO chip interface definitions and function prototypes.

  • include/nuttx/ioexpander/ioexpander.h: Standard NuttX IO expander interface.

  • include/nuttx/ioexpander/gpio.h: NuttX GPIO interface definitions.

Configuration Options

The following configuration options are relevant to this driver:

  • CONFIG_SIM_GPIOCHIP: Enable the sim GPIO chip driver.

  • CONFIG_IOEXPANDER_NPINS: Maximum number of GPIO pins supported (default: 64).

  • CONFIG_IOEXPANDER_INT_ENABLE: Enable interrupt support for GPIO pins.

Supported Operations

The driver supports the following GPIO operations:

Direction Control

int sim_gpiochip_direction(struct ioexpander_dev_s *dev,
                           uint8_t pin, int direction);

Set GPIO pin direction. Supported directions:

  • IOEXPANDER_DIRECTION_IN: Configure as input

  • IOEXPANDER_DIRECTION_OUT: Configure as output

  • IOEXPANDER_DIRECTION_OUT_OPENDRAIN: Configure as open-drain output

Read/Write Pin

int sim_gpiochip_readpin(struct ioexpander_dev_s *dev, uint8_t pin,
                         bool *value);
int sim_gpiochip_writepin(struct ioexpander_dev_s *dev, uint8_t pin,
                          bool value);

Read or write the value of a GPIO pin.

Interrupt Configuration

int sim_gpiochip_option(struct ioexpander_dev_s *dev, uint8_t pin,
                        int option, void *val);

Configure GPIO pin options. Supported interrupt edge configurations:

  • IOEXPANDER_VAL_RISING: Trigger on rising edge

  • IOEXPANDER_VAL_FALLING: Trigger on falling edge

  • IOEXPANDER_VAL_BOTH: Trigger on both edges

  • IOEXPANDER_VAL_DISABLE: Disable interrupt

Interrupt Callback

void *sim_gpiochip_attach(struct ioexpander_dev_s *dev,
                          ioe_pinset_t pinset,
                          ioe_callback_t callback,
                          void *arg);
int sim_gpiochip_detach(struct ioexpander_dev_s *dev, void *handle);

Attach or detach an interrupt callback function for GPIO pins.

Host Layer API

The host layer (sim_linux_gpiochip.c) provides the following functions:

/* Allocate and initialize a host GPIO chip device */
struct host_gpiochip_dev *host_gpiochip_alloc(const char *filename);

/* Free a host GPIO chip device */
void host_gpiochip_free(struct host_gpiochip_dev *dev);

/* Set GPIO pin direction */
int host_gpiochip_direction(struct host_gpiochip_dev *dev,
                            uint8_t pin, bool input);

/* Read GPIO pin value */
int host_gpiochip_readpin(struct host_gpiochip_dev *dev,
                          uint8_t pin, bool *value);

/* Write GPIO pin value */
int host_gpiochip_writepin(struct host_gpiochip_dev *dev,
                           uint8_t pin, bool value);

/* Request GPIO interrupt */
int host_gpiochip_irq_request(struct host_gpiochip_dev *dev,
                              uint8_t pin, uint16_t cfgset);

/* Check if GPIO interrupt is active */
bool host_gpiochip_irq_active(struct host_gpiochip_dev *dev, uint8_t pin);

/* Get GPIO line information */
int host_gpiochip_get_line(struct host_gpiochip_dev *priv,
                           uint8_t pin, bool *input);

Linux Kernel Version Requirements

The driver uses Linux GPIO v2 ABI, which requires:

  • Linux kernel >= 6.8.0: Full functionality with GPIO v2 API support.

  • Linux kernel < 6.8.0: The driver compiles but provides stub implementations that return 0 or NULL.

Usage Example

Initialization

#include <nuttx/ioexpander/gpio.h>
#include "sim_internal.h"

int board_gpio_initialize(void)
{
  struct ioexpander_dev_s *ioe;
  int ret;

  /* Initialize the GPIO chip device */
  ioe = sim_gpiochip_initialize("/dev/gpiochip0");
  if (ioe == NULL)
    {
      return -ENODEV;
    }

  /* Register GPIO pins using gpio_lower_half */
  ret = gpio_lower_half(ioe, 0, GPIO_INPUT_PIN, 60);  /* Pin 0 as input, minor 60 */
  if (ret < 0)
    {
      return ret;
    }

  ret = gpio_lower_half(ioe, 1, GPIO_OUTPUT_PIN, 61); /* Pin 1 as output, minor 61 */
  if (ret < 0)
    {
      return ret;
    }

  return OK;
}

Application Usage

After initialization, GPIO pins can be accessed through standard NuttX GPIO interface:

#include <fcntl.h>
#include <sys/ioctl.h>
#include <nuttx/ioexpander/gpio.h>

int main(void)
{
  int fd;
  bool value;

  /* Open GPIO device */
  fd = open("/dev/gpio60", O_RDWR);
  if (fd < 0)
    {
      return -1;
    }

  /* Read GPIO value */
  ioctl(fd, GPIOC_READ, &value);
  printf("GPIO value: %d\n", value);

  close(fd);
  return 0;
}

Interrupt Handling

The driver uses a work queue to poll for GPIO events. The polling interval is defined by SIM_GPIOCHIP_WORK_DELAY (default: 500 microseconds).

When an interrupt event is detected on a GPIO pin, the registered callback function is invoked with the pin number and user-provided argument.

static int gpio_interrupt_handler(struct ioexpander_dev_s *dev,
                                  ioe_pinset_t pinset, void *arg)
{
  printf("GPIO interrupt on pin %d\n", pinset);
  return OK;
}

/* Attach interrupt handler */
void *handle = IOEP_ATTACH(ioe, (1 << pin), gpio_interrupt_handler, NULL);

/* Configure interrupt edge */
IOEP_SETOPTION(ioe, pin, IOEXPANDER_OPTION_INTCFG,
               (void *)IOEXPANDER_VAL_RISING);

Files

  • arch/sim/src/sim/sim_gpiochip.c: NuttX IO expander implementation

  • arch/sim/src/sim/posix/sim_linux_gpiochip.c: Linux host GPIO interface

  • arch/sim/src/sim/sim_hostgpiochip.h: Host GPIO chip header file

Limitations

  1. Polling-based interrupts: Due to simulation constraints, interrupts are implemented using polling rather than true hardware interrupts.

  2. Linux kernel version: Full functionality requires Linux kernel >= 6.8.0.

  3. Pin count: Limited by CONFIG_IOEXPANDER_NPINS configuration.

  4. Invert option: The IOEXPANDER_OPTION_INVERT option is not yet implemented.

See Also