In Linux, GPIOs are managed by a dedicated GPIO subsystem built on the kernel's GPIO framework (gpiolib), which provides a standardized abstraction over GPIO hardware. The framework wraps the GPIO controllers of different platforms in a uniform way, so user space or kernel drivers can access GPIO resources consistently. There are two ways to control GPIOs:
/sys/class/gpio/; the Linux kernel no longer recommends it.)/dev/gpiochipN; the officially recommended approach.)
Calculating GPIO numbers
Each GPIO group has 8 pins, and the GPIO number equals
group * 8 + offset within the group. For example GPIO4_2 has GPIO number4*8+2=34. When using the character device driver, forGPIO1_4thechip_pathis/dev/gpiochip1andline_offsetis4.
Note
libgpiod is the GPIO user-space reference library provided by Linux. It wraps the GPIO character device ioctl interface and offers a unified API for requesting, controlling, and handling events on GPIO lines. Functionally it is a user-space abstraction layer over GPIO, but underneath it still relies on ioctl to talk to the kernel gpiolib.
That said, you can bypass libgpiod entirely and build your own GPIO abstraction directly on ioctl.
linux/gpio.h
This header defines the 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
gpiohandle_request
Requests ordinary input/output GPIO lines.
Key members:
- lineoffsets[]: line numbers
- flags: input or output
- default_values[]: default output values
- consumer_label: the owner name shown to the kernel
- fd: returns the line handle on success
gpiohandle_data
Reads and writes line levels.
Key members:
- values[]: the value of each line
gpioevent_request
Requests an interrupt event line.
Key members:
- lineoffset: line number
- handleflags: usually input
- eventflags: rising / falling / both edges
- fd: returns the event fd on success
See Quick Start / Deploying Your First Application.
#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;
}
}
#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
#endif
#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;
}
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)
Important
If the result does not match, the GPIO driving the LED may be multiplexed to another function. Run:
xmmd.l 0x11980018 # read the value of the GPIO1_6 register xmmm 0x11980018 0x00001000 # write the registerThen run the application again.
Under /sys/class/gpio, each GPIO has its own folder, named gpio plus the pin number. For example /sys/class/gpio/gpio14 is pin number 14, i.e. GPIO1_6. To check:
~ # echo 14 > /sys/class/gpio/export
~ # ls /sys/class/gpio/
export gpiochip0 gpiochip24 gpiochip40 gpiochip56 gpiochip8
gpio14 gpiochip16 gpiochip32 gpiochip48 gpiochip64 unexport
Note
echo 14 > /sys/class/gpio/exportexports GPIO14 to user space, which creates/sys/class/gpio/gpio14. Useecho 14 > /sys/class/gpio/unexportto undo the export.
The exported GPIO's control directory (/sys/class/gpio/gpio14) usually contains these files:
~ # ls /sys/class/gpio/gpio14/
active_low direction power uevent
device edge subsystem value
Inverts the GPIO level logic:
echo 1 > /sys/class/gpio/gpio14/active_low # invert the logic level of GPIO14
Sets the GPIO direction, input or output:
echo in > /sys/class/gpio/gpio14/direction # input
echo out > /sys/class/gpio/gpio14/direction # output
Power-management related; normally not manipulated directly.
Related to device events; not usually needed for everyday manual GPIO control.
Information about the device this GPIO belongs to.
echo both > /sys/class/gpio/gpio14/edge
Configures interrupt trigger edges. Common values:
Which subsystem this GPIO belongs to.
Reads or sets the GPIO level:
cat /sys/class/gpio/gpio14/value # read the level
echo 1 > /sys/class/gpio/gpio14/value # drive high
echo 0 > /sys/class/gpio/gpio14/value # drive low