泰晓科技 -- 聚焦 Linux - 追本溯源,见微知著!


LWN 532714: 内核中的 GPIO 子系统介绍

Wang Chen 创作于 2017/12/04

原文:GPIO in the kernel: an introduction 原创:By Jonathan Corbet @ Jan 16, 2013 翻译:By unicornx 校对:By cee1 & Zhang Fan of TinyLab.org

A GPIO (general-purpose I/O) device looks like the most boring sort of peripheral that a computer might offer. It is a single electrical signal that the CPU can either set to one of two values — zero or one, naturally — or read one of those values from (or both). Either way, a GPIO does not seem like a particularly expressive device. But, at their simplest, GPIOs can be used to control LEDs, reset lines, or pod-bay door locks. With additional “bit-banging” logic, GPIOs can be combined to implement higher-level protocols like i2c or DDC — a frequent occurrence on contemporary systems. GPIOs are thus useful in a lot of contexts.

GPIO(通用输入 / 输出)设备应该是计算机能够提供的最简单的外设了。对于 GPIO 的每一路端口,输出信号时,CPU 能够设置的值,要么是 0,要么是 1,输入时也是类似。无论哪种方式,GPIO 看上去都不是一个特别“富有表现力”的设备。即便如此,我们仍然可以利用它来控制 LED (的开与关),重设线路或开关吊舱门锁。再进一步通过复杂的组合和时序控制,我们甚至可以利用多个 GPIO 实现像 i2cDDC 这样的更高级别的接口协议 ,这些应用在当前的系统中经常出现。因此 GPIO 在很多情况下还是非常有用的。

GPIO lines seem to be especially prevalent in embedded systems; even so, there never seems to be enough of them. As one might expect, a system with dozens (or even hundreds) of GPIOs needs some sort of rational abstraction for managing them. The kernel has had such a mechanism since 2.6.21 (it was initially added by David Brownell). The API has changed surprisingly little since then, but that period of relative stasis may be about to come about to an end. The intended changes are best understood in the context of the existing API, though, so that is what this article will cover. Subsequent installments will look at how the GPIO API may evolve in the near future.

GPIO 端口在嵌入式系统中的应用非常普遍,且越来越多。想象一下,一个拥有几十(甚至几百)个 GPIO 引脚的系统,必然需要某种合理的软件抽象来管理。 内核在 2.6.21 版本,就由 David Brownell 引入了这样一套 GPIO API。该套 API 一直沿用至今,改动很小,但近来情况有所改变,一个重大的 API 演进即将诞生。为了更好地理解这次新的改进,再次回顾一下现有的 API 是非常有必要的,本文将关注 API 的现状。本主题的下一期将讨论 GPIO API 在未来的发展动态。

Naturally, there is an include file for working with GPIOs:

和使用其他子系统一样,使用 GPIO 的 API 需要包含一个头文件:

#include <linux/gpio.h>

In current kernels, every GPIO in the system is represented by a simple unsigned integer. There is no provision for somehow mapping a desired function (“the sensor power line for the first camera device,” say) onto a GPIO number; the code must come by that knowledge by other means. Often that is done through a long series of macro definitions; it is also possible to pass GPIO numbers through platform data or a device tree.

在当前的内核中,系统中的每个 GPIO 都由一个简单的无符号整数标识。在如何按照特定的功能(譬如“第一个摄像机设备的传感器电源线”)为 GPIO 编号这件事上并没有统一的规定; 在代码中一般根据实际情况以其他方式对其定义。譬如经常以宏的方式定义一长串数字常量;也可以通过平台数据(platform data)或者设备树(device tree)来传递 GPIO 编号。

GPIOs must be allocated before use, though the current implementation does not enforce this requirement. The basic allocation function is:

GPIO 必须在使用之前申请,尽管在目前的实现中这并不是一个强制性的要求。实现申请的函数是:

int gpio_request(unsigned int gpio, const char *label);

The gpio parameter indicates which GPIO is required, while label associates a string with it that can later appear in sysfs. The usual convention applies: a zero return code indicates success; otherwise the return value will be a negative error number. A GPIO can be returned to the system with:

参数 gpio 用于标识请求申请的 GPIO 引脚编号,而另一个参数 label 用于给定一个字符串,该字符串会出现在 sysfs 中。按惯例:函数返回零表示成功; 否则返回值将是一个以负整数形式表示的错误码。通过以下方式将已经申请的 GPIO 引脚归还给系统:

void gpio_free(unsigned int gpio);

There are some variants of these functions; gpio_request_one() can be used to set the initial configuration of the GPIO, and gpio_request_array() can request and configure a whole set of GPIOs with a single call. There are also “managed” versions (devm_gpio_request(), for example) that automatically handle cleanup if the developer forgets.

这些函数有一些变体,譬如使用 gpio_request_one() 可以在申请的同时对 GPIO 进行初始化配置,使用 gpio_request_array() 可以一次性申请和配置一组 GPIO。还有一些支持“资源管理”特性的版本(例如 devm_gpio_request()),可以自动执行清理动作,避免人为失误导致的资源泄漏。

Some GPIOs are used for output, others for input. A suitably-wired GPIO can be used in either mode, though only one direction is active at any given time. Kernel code must inform the GPIO core of how a line is to be used; that is done with these functions:

一些 GPIO 专门用于输出,其他专门用于输入。通过适当的线路设计可以使单个 GPIO 端口既 支持输入又支持输出,但在任何一个给定的时刻只能保证一个方向有效。内核代码通过调用以下函数通知 GPIO 子系统设定某个 GPIO 端口的工作方式:

int gpio_direction_input(unsigned int gpio);
int gpio_direction_output(unsigned int gpio, int value);

In either case, gpio is the GPIO number. In the output case, the value of the GPIO (zero or one) must also be specified; the GPIO will be set accordingly as part of the call. For both functions, the return value is again zero or a negative error number. The direction of (suitably capable) GPIOs can be changed at any time.

在以上两个函数中,参数 gpio 都是 GPIO 的编号。在指定为输出模式情况下(译者注:指 gpio_direction_output() 函数 ),还必须指定缺省状态下 GPIO 引脚的电平信号值(零或一)。对于这两个函数,返回值依然为零表示成功或负的错误代码。如果硬件实现上支持,其输入输出模式可通过上述两个 API,在任意时刻进行指定。

For input GPIOs, the current value can be read with:

对于工作在输入模式下的 GPIO,可以通过如下函数读取当前值:

int gpio_get_value(unsigned int gpio);

This function returns the value of the provided gpio; it has no provision for returning an error code. It is assumed (correctly in almost all cases) that any errors will be found when gpio_direction_input() is called, so checking the return value from that function is important.

这个函数返回指定的 gpio 端口的值;它不会返回错误。因为根据假设(在几乎所有情况下这种假设都是正确的),任何错误在调用 gpio_direction_input() 时就会被发现 ,因此编程时应严格检查该函数(译者注:指 gpio_direction_input())的返回值。

Setting the value of output GPIOs can always be done using gpio_direction_output(), but, if the GPIO is known to be in output mode already, gpio_set_value() may be a bit more efficient:

如果我们要设置 GPIO 端口的输出值,通过使用 gpio_direction_output() 总是可以完成的,但是如果确定 GPIO 端口已经处于输出模式,那么直接调用 gpio_set_value() 会更高效一些:

void gpio_set_value(unsigned int gpio, int value);

Some GPIO controllers can generate interrupts when an input GPIO changes value. In such cases, code wishing to handle such interrupts should start by determining which IRQ number is associated with a given GPIO line:

对于某些 GPIO 控制器,当它的一个 GPIO 端口工作于输入模式并且其输入值发生变化时,会触发控制器产生中断。在这种情况下,希望处理这种中断的代码可以通过以下函数获取和该 GPIO 端口相关联的中断号 (IRQ number):

int gpio_to_irq(unsigned int gpio);

The given gpio must have been obtained with gpio_request() and put into the input mode first. If there is an associated interrupt number, it will be passed back as the return value from gpio_to_irq(); otherwise a negative error number will be returned. Once obtained in this manner, the interrupt number can be passed to request_irq() to set up the handling of the interrupt.

该函数中的 gpio 参数所对应的 GPIO 资源必须已经通过 gpio_request() 申请获得并已被设置为工作在输入模式。如果存在关联的中断号,它将作为 gpio_to_irq() 的返回值返回;否则该函数将返回一个负的错误码。一旦获取中断号成功,我们可以继续调用 request_irq() 并传入得到的中断编号来设置中断处理。

Finally, the GPIO subsystem is able to represent GPIO lines via a sysfs hierarchy, allowing user space to query (and possibly modify) them. Kernel code can cause a specific GPIO to appear in sysfs with:

最后,GPIO 子系统能够通过 sysfs 来展现 GPIO 端口信息,允许在用户空间级别对 GPIO 执行查询(甚至修改)操作。内核代码可通过以下函数向 sysfs 导出一个特定的 GPIO 端口信息:

int gpio_export(unsigned int gpio, bool direction_may_change);

The direction_may_change parameter controls whether user space is allowed to change the direction of the GPIO; in many cases, allowing that control would be asking for bad things to happen to the system as a whole. A GPIO can be removed from sysfs with gpio_unexport() or given another name with gpio_export_link().

参数 direction_may_change 用于控制是否允许通过用户空间的操作改变 GPIO 的输入输出模式;但要注意的是在很多情况下,允许这种控制对整个系统并不是什么好事。对于一个已经导出的 GPIO 端口,可以通过调用 gpio_unexport() 将其从 sysfs 中移除,或者通过调用 gpio_export_link() 给其创建符号链接。

And that is an overview of the kernel’s low-level GPIO interface. A number of details have naturally been left out; see Documentation/gpio.txt for a more thorough description. Also omitted is the low-level driver’s side of the API, by which GPIO lines can be made available to the GPIO subsystem; covering that API may be the subject of a future article. The next installment, though, will look at a couple of perceived deficiencies in the above-described API and how they might be remedied.

以上是内核中 GPIO 相关的底层接口的一个概述。很多细节被故意忽略了; 有关更详细的描述,请参阅 Documentation/gpio.txt。另外,本文也没介绍 “底层驱动通过哪些 APIs,向子系统注册实际的 GPIO 端口”; 这部分主题可能需要另外开启一篇文章来专门描述。本专题的下一篇将继续讨论上述 API 中的一些已知的缺陷以及如何对其进行改进。

Read Album:

Read Related:

Read Latest: