By Jonathan Corbet
January 16, 2013
GPIO(通用 I/O)设备看起来是计算机可能提供的最无聊的外设。它是一个单一的电信号,CPU 可以将其设置为两个值中的一个(自然是 0 或 1),或者读取其中一个值(或者两个都读取)。无论哪种方式,GPIO 似乎都不是一个特别有表现力的设备。但从最简单的角度看,GPIO 可用于控制 LED、复位线路或 pod 舱门锁。通过额外的 “比特撞击 ”逻辑,GPIO 可以组合起来实现更高级别的协议,如 i2c 或 DDC,这在当代系统中经常出现。因此,GPIO 在很多情况下都非常有用。
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 的系统需要某种合理的抽象来管理它们。内核从 2.6.21 开始就有了这样的机制(最初由 David Brownell 添加)。从那时起,API 的变化出奇的小,但这种相对静止的状态可能即将结束。不过,最好还是在现有 API 的背景下理解预期的更改,这也是本文要介绍的内容。后续文章将探讨 GPIO API 在不久的将来会如何发展。
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 的包含文件:
Naturally, there is an include file for working with GPIOs:
#include <linux/gpio.h>
在当前的内核中,系统中的每个 GPIO 都由一个简单的无符号整数表示。没有规定以某种方式将所需功能(例如 “第一个摄像头设备的传感器电源线”)映射到 GPIO 编号上;代码必须通过其他方式获得该知识。通常是通过一长串宏定义来实现;也可以通过平台数据或设备树来传递 GPIO 编号。
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 必须在使用前分配,但目前的实现并不强制执行这一要求。基本分配功能如下:
GPIOs must be allocated before use, though the current implementation does not enforce this requirement. The basic allocation function is:
int gpio_request(unsigned int gpio, const char *label);
gpio 参数表示所需的 GPIO,而 label 则关联了一个字符串,该字符串随后会出现在 sysfs 中。通常的惯例是:返回代码为 0 表示成功,否则返回值将是一个负的错误数字。GPIO 可以通过以下方式返回给系统:
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:
void gpio_free(unsigned int gpio);
这些函数有一些变体:gpio_request_one() 可用于设置 GPIO 的初始配置,而 gpio_request_array() 只需一次调用即可请求并配置一整套 GPIO。还有一些 “托管 ”版本(例如 devm_gpio_request()),可以在开发人员忘记时自动进行清理。
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 用于输出,有些则用于输入。接线合适的 GPIO 可用于任何一种模式,但在任何时候都只有一个方向处于活动状态。内核代码必须通知 GPIO 内核如何使用某条线路;这可以通过这些函数来实现:
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:
int gpio_direction_input(unsigned int gpio);
int gpio_direction_output(unsigned int gpio, int value);
无论哪种情况,gpio 都是 GPIO 的编号。在输出情况下,还必须指定 GPIO 的值(0 或 1);GPIO 将在调用时相应设置。这两个函数的返回值都是零或负的错误数字。GPIO 的方向可以随时更改。
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,可通过以下方式读取当前值:
For input GPIOs, the current value can be read with:
int gpio_get_value(unsigned int gpio);
该函数返回所提供 gpio 的值;但不返回错误代码。我们假设(几乎在所有情况下都是正确的),任何错误都会在调用 gpio_direction_input() 时发现,因此检查该函数的返回值非常重要。
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_output(),但如果已知 GPIO 已处于输出模式,gpio_set_value() 可能会更有效一些:
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:
void gpio_set_value(unsigned int gpio, int value);
当输入 GPIO 的值发生变化时,某些 GPIO 控制器会产生中断。在这种情况下,希望处理此类中断的代码应首先确定与给定 GPIO 线路相关联的 IRQ 编号:
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:
int gpio_to_irq(unsigned int gpio);
给定的 gpio 必须先通过 gpio_request()获取并进入输入模式。如果有相关的中断号,它将作为 gpio_to_irq() 的返回值传回;否则将返回一个负的错误号。通过这种方式获得中断号后,就可以将其传递给 request_irq(),以设置中断处理。
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 子系统能够通过 sysfs 层次结构表示 GPIO 线路,允许用户空间查询(并可能修改)它们。内核代码可以通过以下方式使特定 GPIO 出现在 sysfs 中:
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:
int gpio_export(unsigned int gpio, bool direction_may_change);
direction_may_change 参数控制是否允许用户空间改变 GPIO 的方向;在许多情况下,允许用户空间改变 GPIO 的方向会导致整个系统出现问题。可以使用 gpio_unexport()将 GPIO 从 sysfs 中移除,或使用 gpio_export_link()赋予其另一个名称。
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().
以上就是内核底层 GPIO 接口的概述。其中有许多细节自然没有提及,请参阅Documentation/gpio.txt 以获得更全面的描述。此外,本文还忽略了该 API 的底层驱动程序部分,通过该部分,GPIO 线路可以被 GPIO 子系统使用; 有关该 API 的内容可能会在今后的文章中介绍。不过,下一篇文章将探讨上述 API 中存在的一些缺陷,以及如何弥补这些缺陷。
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.
参考: