大家好!我是大聪明-PLUS!
从内核版本 4.6-r1 开始,一个用于与内核 gpio 子系统交互的新接口已可用。现在有三种官方方法可以使用 gpio 并从中接收中断。没有必要详细讨论这个子系统的需求;对于少数人来说,这是一项严格的日常工作,而对于其他人来说,则是一项有趣的爱好。对于所有这些人来说,内核都提供了一种新的交互方式。
这篇文章具有流行性,因为我们不会涉及创新带来的主要优势,即在内核环境中简化使用 gpio 的工作。
新的 uapi gpio 接口
首先,gpiochip现在是一个实际的设备,在 devfs 中可以显示为gpiochipN ,其中 N 是 初始化期间 分配的芯片编号。其次,所有配置现在都通过ioctl完成。第三,令人惊讶的是,读写操作是通过 read/write 调用完成的。
gpio-模型
从内核版本 v4.9-rc1 开始(实际上只能在 v4.12-rc1 之前的版本中使用),虚拟gpio 设备已经可用,并支持通过debugfs进行状态管理。
我们通过一个例子来看一下用户空间中sysfs 和uapi之间的区别。
初始化 gpio-mockup
参数:
- gpio_mockup_ranges --- 用于初始化 gpiochips 的数字对,形式为"base,end",其中 base 是起始数字,end 是范围的结束。
- gpio_mockup_named_lines 是一个布尔参数,如果指定,则以 gpio-mockup-A..ZN 的形式为每条线路分配一个标签,其中 N 是组中线路的序列号。
# modprobe gpio-mockup gpio_mockup_ranges=0,8,8,16
通过此命令,我们创建了两个gpiochip ,每个芯片有 8 条线路,范围分别为 [0-8)、[8,16)。稍后我们将讨论gpio_mockup_named_lines 。
sysfs 和 uapi 的比较
使用新的驱动程序,我们将从用户的角度来看待这两个系统之间的差异。
关于 gpiochip 的信息
系统文件系统
` `# cat /sys/class/gpio/gpiochip*/base`
`0`
`8`
`# cat /sys/class/gpio/gpiochip*/ngpio`
`8`
`8`
`# cat /sys/class/gpio/gpiochip*/label`
gpio-mockup-A
gpio-mockup-B `
uapi
` struct gpiochip_info chip_info;
ioctl(fd, GPIO_GET_CHIPINFO_IOCTL, &chip_info); `
` `# ./lsgpio | grep GPIO\ chip `
GPIO chip: gpiochip1, `"gpio-mockup-B"`, `8` GPIO lines
GPIO chip: gpiochip0, `"gpio-mockup-A"`, `8` GPIO lines `
设置一条线作为输入并读取值
系统文件系统
` `# echo in > /sys/class/gpio/gpio0/direction `
`# cat /sys/class/gpio/gpio0/value
uapi
` struct gpiohandle_request req;
req.lineoffsets[0] = 0;
req.flags = GPIOHANDLE_REQUEST_INPUT;
req.lines = 1;
struct gpiohandle_data data;
ioctl(fd, GPIO_GET_LINEHANDLE_IOCTL, &req);
ioctl(req.fd, GPIOHANDLE_GET_LINE_VALUES_IOCTL, &data); `
将线路设置为输出
系统文件系统
` # echo high > /sys/class/gpio/gpio0/direction `
uapi
` struct gpiohandle_request req;
req.lineoffsets[0] = 0;
req.flags = GPIOHANDLE_REQUEST_OUTPUT;
req.default_values[0] = 1;
req.lines = 1;
ioctl(fd, GPIO_GET_LINEHANDLE_IOCTL, &req); `
边缘处理
系统文件系统
` `# echo both > /sys/class/gpio/gpio0/edge
uapi
` struct gpioevent_request ereq;
ereq.lineoffset = 0;
ereq.eventflags = GPIOEVENT_REQUEST_BOTH_EDGES;
ioctl(fd, GPIO_GET_LINEEVENT_IOCTL, &ereq); `
事件投票
sysfs 的内核文档指定使用EPOLLPRI 和EPOLLERR (或使用exceptfds进行选择),这对于任何对 sysfs_notify 的 调用来说都是典型的,但对于gpio子系统来说不一定如此。
对于 uapi,EPOLLIN就足够了。
` struct epoll_event event;
event.data.fd = ereq.fd;
event.events = EPOLLIN;
epoll_ctl(epollfd, EPOLL_CTL_ADD, ereq.fd, &event);`
我们读取一个带有时间戳和类型GPIOEVENT_EVENT_RISING_EDGE 或GPIOEVENT_EVENT_FALLING_EDGE的事件。
` struct gpioevent_data event;
read(pin->fd, &event, sizeof(event)); `
uapi 的EPOLLET按照 epoll 文档工作。
标签
关于 sysfs 的一个小注释
每个人都很习惯的联系人名称gpioN通常不是规范的,但如果联系人尚未分配名称(例如在设备树中),则会使用它。
` // drivers/gpio/gpiolib-sysfs.c
// int gpiod_export(struct gpio_desc *desc, bool direction_may_change)
offset = gpio_chip_hwgpio(desc);
if (chip->names && chip->names[offset])
ioname = chip->names[offset];
dev = device_create_with_groups(&gpio_class, &gdev->dev,
MKDEV(0, 0), data, gpio_groups,
ioname ? ioname : "gpio%u",
desc_to_gpio(desc)); `
让我们尝试使用gpio-mockup的 gpio_mockup_named_lines选项:
` `# modprobe gpio-mockup gpio_mockup_ranges=0,8,8,16 gpio_mockup_named_lines=1 `
`# echo 0 > /sys/class/gpio/export `
`# ls /sys/class/gpio/gpio-mockup-A-0 `
active_low device direction edge power subsystem uevent value `
我们可以看到,联系人名称采用了gpio_chip_label - gpio_offset 的形式,但这仅适用于gpio-mockup驱动程序。
` // drivers/gpio/gpio-mockup.c
// static int gpio_mockup_name_lines(struct device *dev, struct gpio_mockup_chip *chip)
for (i = 0; i < gc->ngpio; i++)
names[i] = devm_kasprintf(dev, GFP_KERNEL, "%s-%d", gc->label, i); `
如果不使用uapi ,就无法提前"猜测"联系人是否存在名称,并且如果事先不知道名称,则搜索导出的"命名"行会很困难(同样,如果知道名称,那么为了明确识别,我们需要已知gpichip上的名称和偏移量)。
uapi
uapi接口让我们无需初始化就能看到线路名称:
` `#./lsgpio | grep gpio-mockup-A `
GPIO chip: gpiochip0, `"gpio-mockup-A"`, `8` GPIO lines
line `0`: `"gpio-mockup-A-0"` unused [output]
...
line `7`: `"gpio-mockup-A-7"` unused [output] `
使用相应的设备树文件(示例取自内核文档):
` gpio-line-names = "MMC-CD", "MMC-WP", "VDD eth", "RST eth", "LED R",
"LED G", "LED B", "Col A", "Col B", "Col C", "Col D",
"Row A", "Row B", "Row C", "Row D", "NMI button",
"poweroff", "reset"; `
我们会在struct gpioline_info中看到每个引脚的名称,不幸的是,很少有人给引脚命名,即使是常见的 SBC 也是如此。
uapi 功能不适用于 sysfs
现在让我们列出旧界面所不具备的优点。
我认为主要的优势在于在中断处理程序的上半部分为事件分配时间戳。这对于需要事件之间精确计时的应用程序来说是必不可少的。
` le->timestamp = ktime_get_real_ns(); `
如果设备驱动程序允许,该线路还可以配置为开路集电极(GPIOLINE_FLAG_OPEN_DRAIN )或开路发射极(GPIOLINE_FLAG_OPEN_SOURCE ),这一创新可以轻松地转移到sysfs,但这不会发生,因为 Linus Waerle 反对它。
新的 API 还允许您在struct gpiohandle_request consumer_label field初始化期间为每个联系人分配自定义标签。
最后,它允许您一次"读取"和"写入"一组联系人状态。
比较结论
主观上,uapi 看起来比sysfs更麻烦,但我们不应忘记,我们通过标准 GNU cat 和 echo 实用程序和 C 代码比较了控制;如果我们比较每个接口的 C 代码,我们会发现它们的复杂性和体积大致相同。
重要的一点是,当使用sysfs时,该线路保持初始化状态,直到用户请求取消初始化或重新启动。uapi 在文件描述符关闭后立即释放该线路。
uapi 的优势
- 为我们节省了一个系统调用(不要忘记读取 gpio/value 后需要 lseek)。
- 初始化输入或输出数组。
- 读取或写入输入或输出数组。
- 漏极开路和源极开路
- 自定义标签
- 事件中传递的"实时纳秒时间戳"
对 UAPI 的批评
没有官方或非官方的批评,至少我还没找到。所以我只能说说自己的一些想法。
- 目前尚不清楚为什么推挽、去抖动、上拉、下拉被忽略。
- 最好向struct gpioevent_data添加一个带有当前行值的值参数。
对 sysfs gpio 的批评
最后,让我们以官方对 sysfs 接口的批评作为结束。Linus Waerle(内核中 gpio 和 pinctrl 子系统的维护者)在补丁评论中提出了以下几点:
- 无法通过一次呼叫同时打开和关闭多条线路。
- 为了使 sysfs 工作,必须在内核配置中启用相应的键。
- 如果应用程序崩溃,gpio 线路仍处于"初始化"状态
- 找到所需的线路很困难
- "Sysfs 严重损坏"©
总的来说,坦白地说,我认为 sysfs 完全可以胜任用户空间中分配给 gpio 的任务。这其中有一些浪漫之处,就像一个连电子工程基础知识都不熟悉的人,都能用echo来开灯一样。有了新的接口,这种直接的连接就消失了,因为现在需要额外的实用程序来进行交互。
但 GPIO 通常成组使用。举一个简单的例子(也是唯一的一个例子),考虑一对用作 I2C 总线的 GPIO;其中一条线处理数据,另一条线处理时钟。
我无法评论第一点;我从未遇到过这样的需求。毕竟,你可以在设备树 中立即将触点初始化为输入或输出。我知道这个功能对于那些需要在用户空间 进行位拆换的 人来说很有用,但有一个问题:在纯 Linux 上,位拆换仅适用于极低频信号,否则至少需要PREEMPT_RT补丁。
第二个论点也很奇怪;我无法想象为了节省空间,有必要禁用 sysfs。
第三个就更奇怪了,也许我们只是不想让应用程序"崩溃"?
关于第四点,我可以说,本质上没有什么变化。如果平台驱动程序或设备树中指定的 gpichip 标签正确,那么"搜索"就很容易。但如果它们的命名"很差",界面就根本帮不上忙。
总的来说,我找不到一个明确的答案。我并不反对新界面,甚至支持它,但如此彻底地埋葬旧界面,我个人觉得难以理解,也不太愉快。
公用事业
对于 uapi 来说,它们的数量远不及 sysfs 那么多。
Linux 内核 gpio 工具
- gpio-event-mon 是一个用于监控gpio线路事件的实用程序。
- gpio-hammer --- 以固定频率打开/关闭线路 n 次
- lsgpio - gpichip和线路的示例列表
libgpiod
来自gpio-mockup 和irq-sim驱动程序的作者, Bartosz Gołaszewski。
作者将其定位为一个通过新的 uapi 接口促进 gpio 工作的库,它还包含一组有用的实用程序。
- gpiodetect --- 列出gpichip的名称、标签和行数
- gpioinfo --- 列出gpiochip 的名称、偏移量、标签和线路状态
- gpioget --- 读取线路状态
- gpioset --- 设置线路状态,并在必要时保持线路忙碌,直到指定的时间、信号或用户输入到期
- gpiofind --- 通过名称查找线路并打印gpichipN设备和线路偏移量
- gpiomon --- 与 gpio-event-mon 相同