GPIO Control
1. GPIO Subsystem
In Linux, GPIOs are managed by a dedicated GPIO subsystem based on the kernel's GPIO framework (gpiolib), which provides a standardized abstraction for GPIO hardware. Through this framework, the kernel uniformly wraps GPIO controllers from different platforms so that user space and kernel drivers can access GPIO resources in a consistent way.
There are two ways to control GPIOs:
- Export to user space via the sysfs file system, and then operate on GPIO pins by reading and writing files. (The legacy GPIO interface, located at
/sys/class/gpio/; the Linux kernel no longer recommends its use.) - Use the character-device-based GPIO interface (gpiochip). (The new GPIO interface, located at
/dev/gpiochipN; the new method officially recommended by Linux.)

GPIO number calculation
Each GPIO group has 8 GPIO pins. The GPIO number equals GPIO group * 8 + offset within the group. For example, the GPIO number of GPIO4_2 is 4 * 8 + 2 = 34. When using the character-device driver, for GPIO1_4: chip_path corresponds to /dev/gpiochip1, and line_offset corresponds to 4.
2. Character GPIO
2.1 Terminology
libgpiod
libgpiod is the standard library for accessing GPIOs (general-purpose input/output) from the Linux user space, and is the official user-space interface to the modern GPIO subsystem (gpiolib character device interface).
ioctl
ioctl is a device-control system call interface. Beyond read/write, it interacts with device drivers through a command code (request) and a structured parameter (arg) to implement device configuration, status query, and special control functions.
Note
libgpiod is the official Linux GPIO user-space reference library. It wraps the GPIO character device ioctl interface and provides unified APIs for requesting, controlling, and receiving events on GPIO lines. Functionally, it is the user-space abstraction layer for GPIO, but it still relies on ioctl to talk to the kernel's gpiolib.
Also: users can bypass libgpiod entirely and implement their own GPIO abstraction layer directly on ioctl.
2.2 Using the GPIO ioctl Interface
2.2.1 Header File
linux/gpio.h
This header defines common structures and ioctls:
- struct gpiohandle_request
- struct gpiohandle_data
- struct gpioevent_request
and these ioctl commands:
- GPIO_GET_CHIPINFO_IOCTL
- GPIO_GET_LINEINFO_IOCTL
- GPIO_GET_LINEHANDLE_IOCTL
- GPIO_GET_LINEEVENT_IOCTL
- GPIOHANDLE_GET_LINE_VALUES_IOCTL
- GPIOHANDLE_SET_LINE_VALUES_IOCTL
2.2.2 Related Structures
gpiohandle_request
Used to request a normal input/output GPIO line.
Key members:
- lineoffsets[]: line numbers
- flags: input or output
- default_values[]: default output value
- consumer_label: consumer name shown to the kernel
- fd: returns the line handle on success
gpiohandle_data
Used to read and write line levels.
Key members:
- values[]: value of each line
gpioevent_request
Used to request an interrupt event line.
Key members:
- lineoffset: line number
- handleflags: usually input
- eventflags: rising edge / falling edge / both edges
- fd: returns the event fd on success
2.3 Implementing a Breathing-LED Application
See Quick Start / Deploying Your First Application.
Create and write gpio_hal.c
#include "gpio_hal.h"
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
int gpio_handle_init(gpio_handle_t *gpio_handle)
{
struct gpiohandle_request req;
if (gpio_handle == NULL || gpio_handle->chip_path == NULL) {
errno = EINVAL;
return -1;
}
gpio_handle->chip_fd = -1;
gpio_handle->line_fd = -1;
gpio_handle->chip_fd = open(gpio_handle->chip_path, O_RDONLY);
if (gpio_handle->chip_fd < 0) {
perror("open gpiochip");
return -1;
}
memset(&req, 0, sizeof(req));
req.lineoffsets[0] = gpio_handle->line_offset;
req.flags = gpio_handle->gpio_mode;
req.default_values[0] = gpio_handle->default_value ? 1 : 0;
req.lines = 1;
if (gpio_handle->consumer_label[0] != '\0') {
strncpy(req.consumer_label,
gpio_handle->consumer_label,
sizeof(req.consumer_label) - 1);
req.consumer_label[sizeof(req.consumer_label) - 1] = '\0';
} else {
strncpy(req.consumer_label, "gpio-led", sizeof(req.consumer_label) - 1);
req.consumer_label[sizeof(req.consumer_label) - 1] = '\0';
}
if (ioctl(gpio_handle->chip_fd, GPIO_GET_LINEHANDLE_IOCTL, &req) < 0) {
perror("GPIO_GET_LINEHANDLE_IOCTL");
close(gpio_handle->chip_fd);
gpio_handle->chip_fd = -1;
return -1;
}
gpio_handle->line_fd = req.fd;
return 0;
}
int gpio_set_value(gpio_handle_t *gpio_handle, int value)
{
struct gpiohandle_data data;
if (gpio_handle == NULL || gpio_handle->line_fd < 0) {
errno = EINVAL;
return -1;
}
memset(&data, 0, sizeof(data));
data.values[0] = value ? 1 : 0;
if (ioctl(gpio_handle->line_fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &data) < 0) {
perror("GPIOHANDLE_SET_LINE_VALUES_IOCTL");
return -1;
}
return 0;
}
void gpio_handle_close(gpio_handle_t *gpio_handle)
{
if (gpio_handle == NULL)
return;
if (gpio_handle->line_fd >= 0) {
close(gpio_handle->line_fd);
gpio_handle->line_fd = -1;
}
if (gpio_handle->chip_fd >= 0) {
close(gpio_handle->chip_fd);
gpio_handle->chip_fd = -1;
}
}Create and write gpio_hal.h
#ifndef GPIO_HAL_H
#define GPIO_HAL_H
#ifdef __cplusplus
extern "C" {
#endif
#include <linux/gpio.h>
#define GPIO_CONSUMER_LABEL_LEN 32
typedef struct gpio_handle_t {
int chip_fd;
const char *chip_path;
unsigned int line_offset;
unsigned int gpio_mode;
int default_value;
int line_fd;
char consumer_label[GPIO_CONSUMER_LABEL_LEN];
} gpio_handle_t;
int gpio_handle_init(gpio_handle_t *gpio_handle);
int gpio_set_value(gpio_handle_t *gpio_handle, int value);
void gpio_handle_close(gpio_handle_t *gpio_handle);
#ifdef __cplusplus
}
#endif
#endifCreate and write main.c
#include "gpio_hal.h"
#include <linux/gpio.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#define LED_CHIP_PATH "/dev/gpiochip1"
#define LED_LINE 6
int main(void)
{
gpio_handle_t led_handle;
memset(&led_handle, 0, sizeof(led_handle));
led_handle.chip_path = LED_CHIP_PATH;
led_handle.line_offset = LED_LINE;
led_handle.gpio_mode = GPIOHANDLE_REQUEST_OUTPUT;
led_handle.default_value = 0;
snprintf(led_handle.consumer_label, sizeof(led_handle.consumer_label), "led-gpio");
if (gpio_handle_init(&led_handle) < 0) {
perror("gpio_handle_init");
return 1;
}
printf("LED GPIO set high\n");
if (gpio_set_value(&led_handle, 1) < 0) {
gpio_handle_close(&led_handle);
return 1;
}
sleep(5);
printf("LED GPIO set low\n");
if (gpio_set_value(&led_handle, 0) < 0) {
gpio_handle_close(&led_handle);
return 1;
}
gpio_handle_close(&led_handle);
return 0;
}Create and write the Makefile
SDK_DIR := $(shell cd $(shell pwd)/../../.. && /bin/pwd)
include $(SDK_DIR)/build/base.mk
TARGET := gpio
SRCS := main.c gpio_hal.c
OBJS := $(SRCS:.c=.o)
.PHONY: all clean install
all: $(TARGET)
$(TARGET): $(OBJS)
$(CC) $(SDK_LD_CFLAGS) -o $@ $^
%.o: %.c
$(CC) $(SDK_USR_CFLAGS) -c -o $@ $<
install: all
@mkdir -p $(XMEDIA_ROOTFS_DIR)/usr/bin
@cp -f $(TARGET) $(XMEDIA_ROOTFS_DIR)/usr/bin/$(TARGET)
@chmod 755 $(XMEDIA_ROOTFS_DIR)/usr/bin/$(TARGET)
@echo "Installed $(TARGET) to $(XMEDIA_ROOTFS_DIR)/usr/bin/$(TARGET)"
clean:
rm -f $(TARGET) $(OBJS)Build the application into the rootfs, log in to Pico-G1, and run the application
Expected result: The LED lights up for 5 seconds then turns off
Note
If the expected result is not achieved, the GPIO used by the LED may have been muxed to another function. Run the following commands:
xmmd.l 0x11980018 # View the value of the GPIO1_6 register
xmmm 0x11980018 0x00001000 # Modify the register valueAfter modifying, run the application again.
3 Controlling GPIO via sysfs
3.1 Export a GPIO to User Space
In the /sys/class/gpio directory on the board, each GPIO device has its own folder. These folder names are gpio plus the pin number, for example /sys/class/gpio/gpio14 represents pin number 14, which is GPIO1_6. Users can view them with the following command:
~ # echo 14 > /sys/class/gpio/export
~ # ls /sys/class/gpio/
export gpiochip0 gpiochip24 gpiochip40 gpiochip56 gpiochip8
gpio14 gpiochip16 gpiochip32 gpiochip48 gpiochip64 unexportNote
echo 14 > /sys/class/gpio exports GPIO14 to user space; after export, /sys/class/gpio/gpio14 is created. Use echo 14 > /sys/class/gpio/unexport to unexport it.
3.2 GPIO Control Directory
The control directory of an exported GPIO (that is, /sys/class/gpio/gpio14) usually contains the following files:
~ # ls /sys/class/gpio/gpio14/
active_low direction power uevent
device edge subsystem valueactive_low
Used to set whether the GPIO level logic is inverted. Example:
echo 1 > /sys/class/gpio/gpio14/active_low # Inverts the level logic of GPIO14direction
Used to set the GPIO direction, i.e. whether it is input or output. Example:
echo in > /sys/class/gpio/gpio14/direction # Input
echo out > /sys/class/gpio/gpio14/direction # Outputpower
Power-management-related directory/files; usually not operated on directly.
uevent
Related to device events; usually not the most common file for daily manual GPIO control.
device
Indicates the device information that this GPIO belongs to.
edge
echo both > /sys/class/gpio/gpio14/edgeConfigures the interrupt trigger edge. Common values:
- none: no interrupt
- rising: interrupt on rising edge
- falling: interrupt on falling edge
- both: interrupt on both edges
subsystem
Indicates which subsystem this GPIO belongs to.
value
Used to read or set the GPIO level value.
cat /sys/class/gpio/gpio14/value # Read the level
echo 1 > /sys/class/gpio/gpio14/value # Set to high level
echo 0 > /sys/class/gpio/gpio14/value # Set to low level