12.2 platform设备驱动
12.2.1 platform总线、设备与驱动
在Linux 2.6以后的设备驱动模型中,需关心总线、设备和驱动这3个实体,总线将设备和驱动绑定。在系统每注册一个设备时,会寻找与之匹配的驱动;相反的,在系统每注册一个驱动时,会寻找与之匹配的设备,而匹配由总线完成。
一个现实的Linux设备和驱动通常都需要挂接在一种总线上,对于本身依附于PCI、USB、I2C、SPI等的设备来说,这自然不是问题,但是在嵌入式系统里面,在SoC系统中集成的独立外设控制器、挂接在SoC内存空间的外设等却不依附于此类总线。基于这一背景,Linux发明了一种虚拟的总线,称为platform总线,相应的设备称为platform_device,而驱动成为platform_driver。
注意:所谓的platform_device并不是与字符设备、块设备和网络设备并列的概念,而是Linux系统提供的一种附加手段,通常把在SoC内部集成的I2C、RTC、LCD、看门狗等控制器都归纳为platform_device,而它们本身就是字符设备。
platform_device结构体的定义如代码清单12.1所示。
#include <linux/platform_device.h>
struct platform_device {
const char *name;
int id;
bool id_auto;
struct device dev;
u32 num_resources;
struct resource *resource;
const struct platform_device_id *id_entry;
char *driver_override; /* Driver name to force a match */
/* MFD cell pointer */
struct mfd_cell *mfd_cell;
/* arch specific additions */
struct pdev_archdata archdata;
};
platform_driver结构体的定义如代码清单12.2所示。
#include <linux/platform_device.h>
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;
bool prevent_deferred_probe;
};
device_driver结构体的定义如代码清单12.3所示。
#include <linux/device.h>
struct device_driver {
const char *name;
struct bus_type *bus;
struct module *owner;
const char *mod_name; /* used for built-in modules */
bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
enum probe_type probe_type;
const struct of_device_id *of_match_table;
const struct acpi_device_id *acpi_match_table;
int (*probe) (struct device *dev);
int (*remove) (struct device *dev);
void (*shutdown) (struct device *dev);
int (*suspend) (struct device *dev, pm_message_t state);
int (*resume) (struct device *dev);
const struct attribute_group **groups;
const struct dev_pm_ops *pm;
struct driver_private *p;
};
与platform_driver地位对等的i2c_driver、spi_driver、usb_driver、pci_driver中都包含device_driver结构体实例成员。其实描述了各种xxx_driver(xxx是总线名)在驱动意义上的一些共性。
系统为platform总线定义了一个bus_type的实例platform_bus_type,定义位于drivers/base/platform.c,如代码清单12.4所示。
struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
EXPORT_SYMBOL_GPL(platform_bus_type);
重点关注其match()成员函数,这个成员函数确定platform_device和platform_driver之间是如何进行匹配的,如代码清单12.5所示。
代码清单12.5 platform_bus_type的match()成员函数
drivers/base/platform.c
/**
* platform_match - bind platform device to platform driver.
* @dev: device.
* @drv: driver.
*
* Platform device IDs are assumed to be encoded like this:
* "<name><instance>", where <name> is a short description of the type of
* device, like "pci" or "floppy", and <instance> is the enumerated
* instance of the device, like '0' or '42'. Driver IDs are simply
* "<name>". So, extract the <name> from the platform_device structure,
* and compare it against the name of the driver. Return whether they match
* or not.
*/
static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv);
/* When driver_override is set, only bind to the matching driver */
if (pdev->driver_override)
return !strcmp(pdev->driver_override, drv->name);
/* Attempt an OF style match first */
if (of_driver_match_device(dev, drv)) // 基于设备树风格的匹配
return 1;
/* Then try ACPI style match */
if (acpi_driver_match_device(dev, drv)) // 基于ACPI风格的匹配
return 1;
/* Then try to match against the id table */
if (pdrv->id_table)//匹配ID表(即platform_device设备名是否出现在platform_driver的ID表内)
return platform_match_id(pdrv->id_table, pdev) != NULL;
/* fall-back to driver name match */
return (strcmp(pdev->name, drv->name) == 0);//匹配platform_device设备名和驱动的名字
}
从代码清单12.5可以看出,匹配platform_device和platform_driver有4种可能性:
1)基于设备树风格的匹配。
2) 基于ACPI风格的匹配。
3)匹配ID表(即platform_device设备名是否出现在platform_driver的ID表内)。
4)匹配platform_device设备名和驱动的名字。
对于Linux 2.6ARM平台而言,对platform_device的定义通常在BSP的板文件中实现,在板文件中,将
platform_device归纳为一个数组,最终通过platform_add_devices()函数统一注册。
platform_add_devices()函数可以将平台设备添加到系统中,这个函数的原型为:
linux/platform_device.h
extern int platform_add_devices(struct platform_device **devs, int num);
该函数的第一个参数为平台设备的指针数组,第二个参数为平台设备的数量,函数内部调用
platform_device_register()函数以注册单个的平台设备。
函数定义:
drivers/base/platform.c
/**
* platform_add_devices - add a numbers of platform devices
* @devs: array of platform devices to add
* @num: number of platform devices in array
*/
int platform_add_devices(struct platform_device **devs, int num)
{
int i, ret = 0;
for (i = 0; i < num; i++) {
ret = platform_device_register(devs[i]); // 注册平台设备
if (ret) { // 出错处理
while (--i >= 0)
platform_device_unregister(devs[i]); // 注销平台设备
break;
}
}
return ret;
}
EXPORT_SYMBOL_GPL(platform_add_devices);
Linux 3.x之后,ARM Linux不太喜欢以编码的形式去填写platform_device和注册,而倾向于根据设备树中的内容自动展开platform_device。