知识杂货铺

不卖切糕

View on GitHub
15 August 2018 14:08

ELCE 2017 新的用户空间GPIO接口

by 宋强

这篇文章讲的并不是重构Linux的GPIO子系统,而是介绍了一种新的用户空间使用GPIO的接口。

旧的GPIO接口

旧的GPIO接口指的是使用sysfs按照gpio序号操作gpio的方法,目录为/sys/class/gpio,通常是使用export将指定序号的GPIO导出,产生一个gpio<number>的目录,其中有value、direction、active_state等设置和操作的接口。

这种GPIO接口是2008年merge的,但是实际设计中有许多问题:

新的字符GPIO驱动接口

新的字符GPIO驱动接口说起来就是将单独的gpiochip变为一个/dev目录下的字符设备文件,之后用c语言的ioctl来申请和操作gpio口。

新的GPIO字符驱动接口在4.8版本的linux内核merge,使用line代表每个gpiochip上的线路,具有下面的特点:

新字符驱动接口的C语言API

这里面的操作大多数都是通过ioctl来完成的。

获取chip_info

chip_info定义:

struct gpiochip_info {
        char name[32];
        char label[32];
        __u32 lines;
};

使用ioctl的 GPIO_GET_CHIPINFO_IOCTL命令获取这个结构体的值。

示例:

void get_chip_info(void)
{
        struct gpiochip_info info;
        int fd, rv;
        fd = open("/dev/gpiochip0", O_RDWR);
        rv = ioctl(fd, GPIO_GET_CHIPINFO_IOCTL, info);
}

获取line_info

line_info定义:

struct gpioline_info {
        __u32 line_offset;
        __u32 flags;
        char name[32];
        char consumer[32];
};

这里面的flags包括这几项:

#define GPIOLINE_FLAG_KERNEL (1UL << 0)
#define GPIOLINE_FLAG_IS_OUT (1UL << 1)
#define GPIOLINE_FLAG_ACTIVE_LOW (1UL << 2)
#define GPIOLINE_FLAG_OPEN_DRAIN (1UL << 3)
#define GPIOLINE_FLAG_OPEN_SOURCE (1UL << 4)

示例:

void get_line_info(void)
{
        struct gpioline_info info;
        memset(&info, 0, sizeof(info));
        info.line_offset = 3;
        rv = ioctl(fd, GPIO_GET_LINEINFO_IOCTL, &info);
}

申请lines以对其值进行操作

需要到的结构体:

struct gpiohandle_request {
        __u32 lineoffsets[GPIOHANDLES_MAX];
        __u32 flags;
        __u8 default_values[GPIOHANDLES_MAX];
        char consumer_label[32];
        __u32 lines;
        int fd;
};

#define GPIOHANDLES_MAX 64
#define GPIOHANDLE_REQUEST_INPUT (1UL << 0)
#define GPIOHANDLE_REQUEST_OUTPUT (1UL << 1)
#define GPIOHANDLE_REQUEST_ACTIVE_LOW (1UL << 2)
#define GPIOHANDLE_REQUEST_OPEN_DRAIN (1UL << 3)
#define GPIOHANDLE_REQUEST_OPEN_SOURCE (1UL << 4)

示例:

void request_output(void)
{
        struct gpiohandle_request req;
        int rv;
        req.flags |= GPIOHANDLE_REQUEST_OUTPUT;
        req.lines = 2;
        req.lineoffsets[0] = 3;
        req.lineoffsets[1] = 5;
        req.default_values[0] = 1;
        req.default_values[1] = 0;
        strcpy(req.consumer_label, "foobar");
        rv = ioctl(fd, GPIO_GET_LINEHANDLE_IOCTL, &req);
}

获取/设置GPIO的值

传递值使用gpiohandle_data结构体:

struct gpiohandle_data {
        __u8 values[GPIOHANDLES_MAX];
};

示例:

void set_values(void)
{
        struct gpiohandle_data data;
        int rv;
        data.values[0] = 0;
        data.values[1] = 1;
        rv = ioctl(req.fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &data);
}
 
void get_values(void)
{
        struct gpiohandle_data data;
        int rv;
        memset(&data, 0, sizeof(data));
        rv = ioctl(req.fd, GPIOHANDLE_GET_LINE_VALUES_IOCTL, &data);
}

申请线路用于产生事件

用到的结构体:

struct gpioevent_request {
        __u32 lineoffset;
        __u32 handleflags;
        __u32 eventflags;
        char consumer_label[32];
        int fd;
};
 
#define GPIOEVENT_REQUEST_RISING_EDGE (1UL << 0)
#define GPIOEVENT_REQUEST_FALLING_EDGE (1UL << 1)
#define GPIOEVENT_REQUEST_BOTH_EDGES ((1UL << 0) | (1UL << 1))

示例:

void request_event(void)
{
        struct gpioevent_request req;
        int rv;
        req.lineoffset = 4;
        req.handleflags = GPIOHANDLE_REQUEST_INPUT;
        req.eventflags = GPIOEVENT_REQUEST_BOTH_EDGES;
        strcpy(req.consumer_label, "foobar");
        rv = ioctl(fd, GPIO_GET_LINEEVENT_IOCTL, &req);
}

poll事件和读取事件

这里的事件使用gpioevent_data来传递:

struct gpioevent_data {
        __u64 timestamp;
        __u32 id;
};

id为事件类型:

#define GPIOEVENT_EVENT_RISING_EDGE 0x01
#define GPIOEVENT_EVENT_FALLING_EDGE 0x02

示例:

void recv_event(void)
{
        struct gpioevent_data event;
        struct pollfd pfd;
        ssize_t rd;
        int rv;
        pfd.fd = req.fd;
        pfd.events = POLLIN | POLLPRI;
        rv = poll(&pfd, 1, 1000);
        if (rv > 0)
                rd = read(req.fd, &event, sizeof(event));
}

libgpiod

由于作者觉得这个gpio的接口都需要c语言操作,而原来的sysfs接口可以直接通过命令行操作,命令行操作很多时候都是很方便的,所以作者就写了这样一个库libgpiod,包括三个部分:

这个库已经被内核接收,源代码在https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/

yocto系统和fedora都直接支持这个库,非常方便。

tags: Linux - gpio