在未引进platform总线设备驱动模型之前,Linux驱动的代码都将硬件资源与寄存器的操作写死在代码中,这样的做法虽然能快速实现功能,但是可拓展性非常差,代码变得非常冗余,不便于后续的管理维护与二次开发。
为了解决这个问题,Linux内核引进了platform总线设备模型,该总线设备模型提取了设备操作的共有属性进行抽象,并将这部分抽象的共有属性放在Linux内核中实现,为添加设备(platform_ device)和驱动(platform_driver)的操作提供统一的接口,将硬件资源与驱动程序分离开来。
platform总线设备模型是一条虚拟的总线,platform_device表示设备,该设备相关的硬件资源配置放在platform_device中描述;platform_driver表示驱动,在驱动程序中获取platform_device中描述的硬件资源并操作硬件资源;在硬件资源发生变化时,只需要修改platform_device中的硬件资源信息即可,提高了安全性和可移植性。
platform总线设备模型框图如下:
platform总线设备模型框图
- platform_device :指定硬件资源
- platform_driver:在probe函数中获取硬件资源,并进行分配,设备,注册字符设备,创建设备节点等操作
重要的数据结构
- struct bus_type
- struct platform_device
- struct resource
- struct platform_driver
struct bus_type
// include/linux/device.h
struct bus_type {
const char *name; //总线名字
const char *dev_name;
struct device *dev_root;
......
int (*match)(struct device *dev, struct device_driver *drv);
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev);
int (*remove)(struct device *dev);
void (*shutdown)(struct device *dev);
......
/*
内核才能访问的私有数据,该数据结构有两条链表为struct klist klist_devices、
struct klist klist_drivers,分别存放注册成功的platform_device设备节点和
platform_driver设备节点
*/
struct subsys_private *p;
struct lock_class_key lock_key;
bool need_parent_lock;
};
struct platform_device
// include/linux/platform_device.h
struct platform_device {
const char *name; // 设备名称,要和platform_driver的name匹配
int id; // 插入总线下相同name的设备编号,一个驱动支持多个设备
bool id_auto;
/*该数据成员里面存放了总线设备链表的节点,与该设备匹配成功的platform_driver节点*/
struct device dev;
u64 dma_mask;
u32 num_resources; //资源个数,有多个资源一般使用ARRAY_SIZE()来算资源个数
struct resource *resource; // 设备资源,设备硬件信息
const struct platform_device_id *id_entry;
/*匹配的最高优先级,优先与platform_driver下的struct device_driver driver里的name匹配*/
char *driver_override; /* Driver name to force a match */
/* MFD cell pointer */
struct mfd_cell *mfd_cell;
/* arch specific additions */
struct pdev_archdata archdata;
};
struct resource
// include/linux/ioport.h
struct resource {
resource_size_t start; // 设备在CPU总线的线性起始物理地址
resource_size_t end; // 设备在CPU总线的线性结尾物理地址
const char *name; // 设备的名称
unsigned long flags; // 设备的标志位
unsigned long desc;
struct resource *parent, *sibling, *child;
};
struct platform_driver
// include/linux/platform_device.h
struct platform_driver {
/*当有platform_device设备与platform_driver匹配成功时,
则会调用probe函数分配、设置、注册file_operations、字符设备等;
获取platform_device设备里的硬件资源信息,从而操作硬件资源
*/
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 *);
/*
该数据成员里的name用于与设备匹配
*/
struct device_driver driver;
/*该数据成员用于与设备匹配*/
const struct platform_device_id *id_table;
bool prevent_deferred_probe;
};
struct platform_device_id
// include/linux/mod_devicetable.h
/*id_table是一个数组,可以支持一个或多个设备名;数组的最后一个成员应该为空;*/
struct platform_device_id {
char name[PLATFORM_NAME_SIZE];
kernel_ulong_t driver_data;
};
platform初始化
platform的初始化操作在内核启动时完成,不需要驱动开发者修改;下面是内核对platform的初始化的源码
// drivers/base/platform.c
int __init platform_bus_init(void)
{
int error;
early_platform_cleanup();
error = device_register(&platform_bus);
if (error)
return error;
error = bus_register(&platform_bus_type);
if (error)
device_unregister(&platform_bus);
of_platform_register_reconfig_notifier();
return error;
}
bus_register()
注册成功后可在sysfs下看到/sys/bus/platform;
// drivers/base/platform.c
struct device platform_bus = {
.init_name = "platform",
};
/*将platform_bus外部引用给其他模块使用*/
EXPORT_SYMBOL_GPL(platform_bus);
struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
/*将platform_bus_type外部引用给其他模块使用*/
EXPORT_SYMBOL_GPL(platform_bus_type);
注册过程
- platform_device_register函数
// drivers/base/platform.c
/**
* platform_device_register - add a platform-level device
* @pdev: platform device we're adding
*/
int platform_device_register(struct platform_device *pdev)
{
device_initialize(&pdev->dev);
setup_pdev_dma_masks(pdev);
return platform_device_add(pdev);
}
EXPORT_SYMBOL_GPL(platform_device_register);
函数调用流程
/*同一列表示同一级*/
platform_device_register
platform_device_add
device_add
bus_add_device // 放入链表
/*probe枚举设备,与platform_driver_list节点一一匹配,即找到匹配的(dev, drv)*/
bus_probe_device
device_initial_probe
__device_attach
bus_for_each_drv(...,__device_attach_driver,...)
__device_attach_driver
driver_match_device(drv, dev) // 是否匹配
driver_probe_device // 调用drv的probe
- platform_driver_register函数
// include/linux/platform_device.h
#define platform_driver_register(drv) \
__platform_driver_register(drv, THIS_MODULE)
extern int __platform_driver_register(struct platform_driver *,
struct module *);
// drivers/base/platform.c
/**
* __platform_driver_register - register a driver for platform-level devices
* @drv: platform driver structure
* @owner: owning module/driver
*/
int __platform_driver_register(struct platform_driver *drv,
struct module *owner)
{
drv->driver.owner = owner;
drv->driver.bus = &platform_bus_type;
drv->driver.probe = platform_drv_probe;
drv->driver.remove = platform_drv_remove;
drv->driver.shutdown = platform_drv_shutdown;
return driver_register(&drv->driver);
}
EXPORT_SYMBOL_GPL(__platform_driver_register);
函数调用流程
platform_driver_register
__platform_driver_register
driver_register
bus_add_driver // 放入链表
driver_attach(drv)
bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
__driver_attach
driver_match_device(drv, dev) // 是否匹配
driver_probe_device // 调用drv的probe
匹配流程
调用匹配函数 driver_match_device,最终会调用drivers\base\platform.c下的platform_match函数
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))
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_device. driver_override和platform_driver.driver.name
可以设置platform_device的driver_override,强制选择某个platform_driver。 - 用设备树节点进行匹配,这里不讲,详细可看另一篇设备树文章。(51条消息) Linux驱动-设备树_cola冲冲冲的博客-CSDN博客
-
然后比较:platform_device. name和platform_driver.id_table[i].name
Platform_driver.id_table是“platform_device_id”指针,表示该drv支持若干个device,它里面列出了各个device的{.name, .driver_data},其中的“name”表示该drv支持的设备的名字,driver_data是些提供给该device的私有数据 -
最后比较:platform_device.name和platform_driver.driver.name
platform_driver.id_table可能为空,
这时可以根据platform_driver.driver.name来寻找同名的platform_device。
调用probe函数
driver_probe_device()函数通过really_probe()函数,调用drv->bus->probe,就是指向platform_bus_type的probe函数指针,即platform_probe()函数,