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


关注 @泰晓科技
原创服务 | 实验云台 | 共享书籍 | 分享桌面 | 直播回放

LWN 448499: 平台设备 API

Wang Chen 创作于 2017/10/10

原文:The platform device API 原创:By Jonathan Corbet @ June 21, 2011 翻译:By Unicornx of TinyLab.org @ Oct 10, 2017

In the very early days, Linux users often had to tell the kernel where specific devices were to be found before their systems would work. In the absence of this information, the driver could not know which I/O ports and interrupt line(s) the device was configured to use. Happily, we now live in the days of busses like PCI which have discoverability built into them; any device sitting on a PCI bus can tell the system what sort of device it is and where its resources are. So the kernel can, at boot time, enumerate the devices available and everything Just Works.

在过去,Linux 开发人员经常需要通过编码配置内核具体设备的位置。缺少这些信息的话,驱动无从得知设备的 I/O 端口和中断线的信息从而无法配置和使用设备。幸运的是,现在存在像 PCI 这样的总线设备支持自动发现设备的功能,PCI 总线上的设备可以通过总线告诉系统自己的类型并申请所需的资源信息。所以内核可以在系统引导阶段就枚举这些设备。

Alas, life is not so simple; there are plenty of devices which are still not discoverable by the CPU. In the embedded and system-on-chip world, non-discoverable devices are, if anything, increasing in number. So the kernel still needs to provide ways to be told about the hardware that is actually present. “Platform devices” have long been used in this role in the kernel. This article will describe the interface for platform devices; it is meant as needed background material for a following article on integration with device trees.

可惜,生活并不总是那么简单;事实上依然存在大量的设备不可以被处理器检测到。在嵌入式系统和片上系统的世界里,无法在启动阶段被检测到的设备正变得越来越多。所以我们仍然需要提供一种方法来告诉内核这些硬件设备的连接情况。内核把这一类硬件设备称之为 “平台设备” (“Platform devices”)。本文将描述开发平台设备中的调用接口;这些也是继续后继文章所必须的背景知识。

平台驱动 (Platform drivers)

A platform device is represented by struct platform_device, which, like the rest of the relevant declarations, can be found in <linux/platform_device.h>. These devices are deemed to be connected to a virtual “platform bus”; drivers of platform devices must thus register themselves as such with the platform bus code. This registration is done by way of a platform_driver structure:

内核用结构体类型 struct platform_device 来表示一个平台设备,该定义和本文其他相关的类型定义都在 <linux/platform_device.h> 中。内核假设这些平台设备都连接在一个虚拟的“平台总线”上;对应的平台驱动负责将其注册到平台总线上。注册的行为需要通过定义一个 platform_driver 类型的结构体来完成。

struct platform_driver {
	int (*probe)(struct platform_device *);
	int (*remove)(struct platform_device *);
	void (*shutdown)(struct platform_device *);
	int (*suspend)(struct platform_device *, pm_message_t state);
	int (*resume)(struct platform_device *);
	struct device_driver driver;
	const struct platform_device_id *id_table;

At a minimum, the probe() and remove() callbacks must be supplied; the other callbacks have to do with power management and should be provided if they are relevant.

定义平台驱动时至少需要定义 probe()remove() 这两个回调函数;其他的回调函数和电源管理有关,仅在需要时提供。

The other thing the driver must provide is a way for the bus code to bind actual devices to the driver; there are two mechanisms which can be used for that purpose. The first is the id_table argument; the relevant structure is:

驱动需要做的另外一件事情就是提供方法给总线调用用于将实际的设备和该驱动绑定起来;目前有两种机制实现该功能。第一种和 struct platform_driver 结构体中的 id_table 成员有关,其类型定义如下:

struct platform_device_id {
	kernel_ulong_t driver_data;

If an ID table is present, the platform bus code will scan through it every time it has to find a driver for a new platform device. If the device’s name matches the name in an ID table entry, the device will be given to the driver for management; a pointer to the matching ID table entry will be made available to the driver as well. As it happens, though, most platform drivers do not provide an ID table at all; they simply provide a name for the driver itself in the driver field. As an example, the i2c-gpio driver turns two GPIO lines into an i2c bus; it sets itself up as a platform device with:

总线的代码在为新发现的平台设备搜索对应驱动时会检查系统已经注册的所有驱动,检查驱动中是否提供了该 ID 表,如果该 ID 表存在,则继续检查是否设备的名字和该驱动的 ID 表的 name 字段匹配, 如果匹配则设备和驱动之间的绑定完成;同时驱动得到一个指向该 ID 表的指针。但在实际代码实现中,大部分的平台驱动并没有提供 ID 表;而是为 struct platform_driverdriver 成员提供了一个名字。下面是一个实际的例子,i2c-gpio 驱动利用 GPIO 的两个管脚实现了 I2C 功能总线;为此其定义了一个平台驱动如下:

static struct platform_driver i2c_gpio_driver = {
	.driver 	= {
		.name	= "i2c-gpio",
		.owner	= THIS_MODULE,
	.probe		= i2c_gpio_probe,
	.remove 	= __devexit_p(i2c_gpio_remove),

With this setup, any device identifying itself as “i2c-gpio” will be bound to this driver; no ID table is needed.

基于该驱动,所有将自己标识为 “i2c-gpio” 的设备会被绑定在该驱动上,使用了该方法就不用定义 ID 表了。

Platform drivers make themselves known to the kernel with:

定义好一个平台驱动后可以通过下面的 API 向内核进行注册:

int platform_driver_register(struct platform_driver *driver);

As soon as this call succeeds, the driver’s probe() function can be called with new devices. That function gets as an argument a platform_device pointer describing the device to be instantiated:

注册成功后,一旦内核检测到对应的设备存在就会调用驱动的 probe() 函数。该回调函数会传入一个类型为 platform_device 的指针指向绑定的设备实例。

struct platform_device {
	const char	*name;
	int		id;
	struct device	dev;
	u32		num_resources;
	struct resource *resource;
	const struct platform_device_id *id_entry;
	/* Others omitted */

The dev structure can be used in contexts where it is needed - the DMA mapping API, for example. If the device was matched using an ID table entry, id_entry will point to the specific entry matched. The resource array can be used to learn where various resources, including memory-mapped I/O registers and interrupt lines, can be found. There are a number of helper functions for getting data out of the resource array; these include:

dev 字段可以用于 DMA 映射。如果该设备是通过 ID 表绑定得到,则 id_entry 会指向对应的 ID 表。 resource 字段是一个指向数组的指针,用于保存设备会访问的资源信息,包括内存映射 I/O 寄存器和中断线等。内核还提供了一些辅助函数用于从 resource 数组中获取更详细的信息,它们是:

struct resource *platform_get_resource(struct platform_device *pdev,
					   unsigned int type, unsigned int n);
struct resource *platform_get_resource_byname(struct platform_device *pdev,
					   unsigned int type, const char *name);
int platform_get_irq(struct platform_device *pdev, unsigned int n);

The “n” parameter says which resource of that type is desired, with zero indicating the first one. Thus, for example, a driver could find its second MMIO region with:

其中参数 “n” 用于表示某种类型资源的第 n 个实例,以 0 序。下面这个例子是驱动获取其第二个 MMIO 资源。

r = platform_get_resource(pdev, IORESOURCE_MEM, 1);

Assuming the probe() function finds the information it needs, it should verify the device’s existence to the extent possible, register the “real” devices associated with the platform device, and return zero.

probe()得到了其所需的必要信息后,应该尽可能地验证设备存在的合法性,然后将“真实”的设备和平台设备关联起来,该回调函数必须返回 0 表示成功。

平台设备 (Platform devices)

So now we have a driver for a platform device, but no actual devices yet. As was noted at the beginning, platform devices are inherently not discoverable, so there must be another way to tell the kernel about their existence. That is typically done with the creation of a static platform_device structure providing, at a minimum, a name which is used to find the associated driver. So, for example, a simple (fictional) device might be set up this way:

现在我们有了一个平台设备的驱动,但还没有定义实际的设备。正如前面所述,平台设备理论上是不可以自动被检测到的,所以必须采用另外一种方法让内核能够意识到其存在。典型地我们可以编码定义一个静态的 platform_device 类型变量,并至少提供一个名字以便内核为其绑定驱动。下面是一个简单的假想设备,定义如下:

static struct resource foomatic_resources[] = {
		.start	= 0x10000000,
		.end	= 0x10001000,
		.flags	= IORESOURCE_MEM,
		.name	= "io-memory"
		.start	= 20,
		.end	= 20,
		.flags	= IORESOURCE_IRQ,
		.name	= "irq",

static struct platform_device my_foomatic = {
	.name		= "foomatic",
	.resource	= foomatic_resources,
	.num_resources	= ARRAY_SIZE(foomatic_resources),

These declarations describe a “foomatic” device with a one-page MMIO region starting at 0x10000000 and using IRQ 20. The device is made known to the system with:

代码创建了一个名字叫做 “foomatic” 的设备,该设备需要配置一页 MMIO,起始地址是 0x10000000,该设备使用 20 号中断。通过调用下面的 API 来向内核注册该设备。

int platform_device_register(struct platform_device *pdev);

Once both a platform device and an associated driver have been registered, the driver’s probe() function will be called and the device will be instantiated. Registration of device and driver are usually done in different places and can happen in either order. A call to platform_device_unregister() can be used to remove a platform device.

一旦一个平台设备和相关的驱动被注册后,驱动的 probe() 函数就会被回调,设备就会被初始化。设备和驱动的注册通常发生在不同的地方,注册的顺序并无要求。如果要删除一个平台设备可以调用另一个 API platform_device_unregister()

平台设备数据 (Platform data)

The above information is adequate to instantiate a simple platform device, but many devices are more complex than that. Even the simple i2c-gpio driver described above needs two additional pieces of information: the numbers of the GPIO lines to be used as i2c clock and data lines. The mechanism used to pass this information is called “platform data”; in short, one defines a structure containing the specific information needed and passes it in the platform device’s dev.platform_data field.

以上信息对于实例化一个简单的平台设备应该是足够的了,但实际的设备比它要复杂得多。甚至假想的 i2c-gpio 驱动也需要补充更多的信息才可以工作,譬如实际的 GPIO 管脚信息,用于实现 i2c 的时钟线和数据线。用于注册这些信息的数据结构称之为 “平台数据”;该数据结构通过 platform_device 结构体的 dev.platform_data 传给驱动。

With the i2c-gpio example, a full configuration looks like this:

仍然使用 i2c-gpio 为例,一个完整的配置代码如下:

#include <linux/i2c-gpio.h>

static struct i2c_gpio_platform_data my_i2c_plat_data = {
	.scl_pin	= 100,
	.sda_pin	= 101,

static struct platform_device my_gpio_i2c = {
	.name		= "i2c-gpio",
	.id		= 0,
	.dev = {
		.platform_data = &my_i2c_plat_data,

When the driver’s probe() function is called, it can fetch the platform_data pointer and use it to obtain the rest of the information it needs.

当驱动的 probe() 函数被回调时,它可以通过 platform_data 指针得到相应的数据信息。

Not everybody in the kernel community is enamored with platform devices; they seem like a bit of a hack used to encode information about specific hardware platforms into the kernel. Additionally, the platform data mechanism lacks any sort of type checking; drivers must simply assume that they have been passed a structure of the expected type. Even so, platform devices are heavily used, and that’s unlikely to change, though the means by which they are created and discovered is changing. The way of the future appears to be device trees, which will be described in the following article.


Read Album:

Read Related:

Read Latest: