platform总线、设备与驱动
在linux2.6以后的设备驱动模型中,总线将设备与驱动绑定,它们的匹配由总线完成。
一个现实的linux设备和驱动通常都需要挂接在一种总线上,对本身依附于PCI,USB,I2C,SPI等的设备而言,这自然不是问题。但在嵌入式系统里,SOC系统中集成的独立外设控制器、挂接在soc内存空间的外设等却不依附于此类总线。为此,linux发明了一种虚拟的总线,成为platform总线,响应的设备成为platform_device,驱动称为platform_driver.
(1)platform_device 位于 /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;
/* MFD cell pointer */
struct mfd_cell *mfd_cell;
/* arch specific additions */
struct pdev_archdata archdata;
};
(2)platform_driver 位于 /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;
};
platform_driver结构体中包含 probe(),remove(),一个device_driver实例,电源管理函数suspend()、resume()。
device_driver结构体,位于/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 */
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_drvier地位对等的i2c_driver、spi_driver、usb_driver、pci_driver中都包含了device_driver结构体实例成员。它其实描述了各种xxx_driver(xxx是总线名)在驱动意义上的一些共性。
系统为platform总线定义了一个bus_type的实例platform_bus_type,位于drivers/base/paltform.c下,
struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
重点关注match()成员函数,次函数确定了platform_device和platform_driver之间是如何进行匹配,platform_match函数代码:
static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev = to_platform_device(dev);//获得platform_device 结构体地址
struct platform_driver *pdrv = to_platform_driver(drv);//获得platform_driver 结构体地址
/* 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))
return 1;
/* Then try to match against the id table */
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;
/* fall-back to driver name match */
return (strcmp(pdev->name, drv->name) == 0);
}
从上面代码中可以看出,匹配platform_driver 和platform_device有4中可能性,一是基于设备树风格的匹配,二十基于ACPI风格的匹配,三是匹配ID表,四是匹配platform_device设备名和驱动的名字。
(3)platform_device的定义通常在BSP的板文件中实现,在板文件中,将platform_device归纳为一个数组,最终通过platform_add_devices()函数同一注册
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;
}
第一个参数为平台设备数组指针,第二个参数为平台设备的数量,它内被调用了platform_device_register()函数以注册单个的平台设备。
Linux3.x之后,ARM Linux不太喜欢人们以编码的形式去填充platform_device和注册,而倾向于根据设备树中的内容自动展开platform_device。