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
/value这个文件,频繁操作会造成大量的上下文切换的性能损耗,并且频率也上不去。 - 所有的gpio按照序号排序,没有很好的体现出gpiochip的模型。
- 在读取event中会出现上下文切换,如果在上下文切换时候再发生一个event可能会无法获取而丢失event。
- 没有详细的应用程序权限区分,只要是可以操作设备文件的特权用户都可操作任何gpio设备,可能会造成竞态问题。
- 如果使用GPIO的进程崩溃,GPIO还是会保持导出状态。
- 想要使用每个gpio都必须单独导出,并且使用的过程中如果想操作多个就需要导出多个。
- polling操作非常不好,容易丢失事件并且每个口都需要一个poll一个打开文件。
新的字符GPIO驱动接口
新的字符GPIO驱动接口说起来就是将单独的gpiochip变为一个/dev目录下的字符设备文件,之后用c语言的ioctl来申请和操作gpio口。
新的GPIO字符驱动接口在4.8版本的linux内核merge,使用line代表每个gpiochip上的线路,具有下面的特点:
- 每一个gpiochip对应一个/dev下的gpiochip<number>文件。
- 使用ioctl、poll和read等方法操作。
- 支持同时申请多个IO口、设置、获取多个IO口的值。
- 可以通过别名定位gpiochip和line。
- 可以为line增加open drain和open source标识。
- 每一个line都有一个consumer字符串,用于标识line的使用者。
- 支持uevent,更好的poll,不会丢失事件。
新字符驱动接口的C语言API
- 获取chip_info
- 获取line_info
- 申请lines以对其值进行操作
- 设置lines的值
- 获取lines的值
- 申请lines以产生事件
- polling事
- 读取事件
这里面的操作大多数都是通过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,包括三个部分:
- 更简单的gpio的C语言API
- 一系列命令行工具
- 测试工具
这个库已经被内核接收,源代码在https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/
yocto系统和fedora都直接支持这个库,非常方便。
tags: Linux - gpio