platform总线、设备、驱动模型
前言
设备驱动模型中,关心的是总线,设备和驱动这3个实体,总线将设备与驱动绑定。
在系统总线注册(挂接)一个设备的时候,程序会自动寻找与之匹配的驱动;相反,在系统总线注册(挂接)一个驱动的时候,也会寻找与之匹配的设备。这个匹配动作是有总线完成的。
一个现实的LINUX设备和驱动,通常都需要挂接在一种总线上,对于本身依附于PCI,USB,IIC,SPI总线等的设备而言,这自然不是问题。
但是,在嵌入式系统里,soc(现在大多数处理器芯片都是soc)系统中集成的独立的外设控制器(如串口控制器)却不依附于此类总线。
基于这一背景,LINUX发明了一种虚拟的总线,称为platform总线。相应的设备称为platform_device,而驱动称为platform_driver。
注意,所谓的platform_device并不是与字符设备,块设备和网络设备并列的概念,而是LINUX系统提供的一种附加手段。例如,在s3c2440处理器中,把内部集成的iic,rtc,spi,lcd,uart等控制器都归纳为platform_device,而它们本身就是字符设备。
一、总线
1、设备结构:
struct device platform_bus = {
.init_name = "platform",
};
platform总线本身就是是一种设备
2、总线结构体:
struct bus_type platform_bus_type = {
.name = "platform",
.dev_attrs = platform_dev_attrs,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
其中,成员说明:
platform_dev_attrs: 设备属性
platform_match: match函数,当属于platform的设备或者驱动注册到
内核时就会被调用,完成设备与驱动的匹配工作。
platform_uevent: 热插拔操作函数
3、内核总线初始化
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);
return error;
}
这个函数向内核注册了一种总线。他首先由/drivers/base/init.c中的
driver_init函数调用,driver_init函数由/init/main.c中的do_basic_setup
函数调用,do_basic_setup这个函数由kernel_init调用,所以platform总线是
在内核初始化的时候就注册进了内核。
二、platform设备
1、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;
/* arch specific additions */
struct pdev_archdata archdata;
};
platform_device结构体中有一个struct resource结构,
是设备占用系统的资源
struct resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
struct resource *parent, *sibling, *child;
};
num_resources 占用系统资源的数目,一般设备都占用两种资源,
io内存(寄存器的物理地址都被映射为虚拟地址,IO空间了)和中断信号线。
2、platform_device_register设备注册
int platform_device_register(struct platform_device *pdev)
{
device_initialize(&pdev->dev);
return platform_device_add(pdev);
}
首先初始化了platform_device的device结构,然后调用
platform_device_add,这个是注册函数的关键。
mxs_add_device(pdev, 3);是platform_device_register的简单封装
3、自己注册设备流程
单设备:
通过定义一个platform_device实体,并进初始化,再将实体赋值给looup数组,最后通过mxs_get_device得到设备实体的指针 再进行注册。
多设备:
定义实体数组,并初始化,再将实体赋值给looup数组,在通过mxs_get_devices 获得lookup数组的指针,再遍历注册设备
三、platform驱动
1.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;
};
如果要将所写的驱动程序注册成platform驱动,那么所做的工作就是初始化一个
platform_driver,然后调用platform_driver_register进行注册。
上面主要实现probe和remove,当驱动与设备匹配到就调用probe函数进行驱动的
初始配置,类似于驱动初始化init。remove同exit函数
2、platform_driver_register驱动注册
int platform_driver_register(struct platform_driver *drv)
{
drv->driver.bus = &platform_bus_type;
if (drv->probe)
drv->driver.probe = platform_drv_probe;
if (drv->remove)
drv->driver.remove = platform_drv_remove;
if (drv->shutdown)
drv->driver.shutdown = platform_drv_shutdown;
return driver_register(&drv->driver);
}
3、设备与驱动如何联系起来?
每当注册一个platform驱动的时候就会调用driver_register,这个函数的调用会遍历设备驱动所属总线上的所有设备,并对每个设备调用总线的match函数。
platform驱动是属于platform_bus_type总线,所以调用platform_match函数。
如下所示:
platform_driver_register -> driver_register ->bus_add_driver ->
driver_attach -> __driver_attach ->
driver_match_device(){return drv->bus->match ? drv->bus->match(dev, drv) : 1;}
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);
/* match against the id table first */
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与platform_driver是通过platform_match联系起
来的。进一步说,是通过相同的name来匹配的。
4、驱动模板
当设备与驱动通过name匹配到后调用探测函数(probe)
static int mxs_key_probe(struct platform_device *pdev)
{
.....
return 0;
}
static int mxs_rtc_remove(struct platform_device *dev)
{
.....
return 0;
}
static struct platform_driver mxs_keydrv = {
.probe = mxs_rtc_probe,
.remove = mxs_rtc_remove,
.driver = {
.name = "mxs-key",
.owner = THIS_MODULE,
},
};
static int __init mxs_key_init(void)
{
printk("mxs_key_init \n");
return platform_driver_register(&mxs_keydrv);
}
static void __exit mxs_key_exit(void)
{
printk("mxs_key_exit \n");
platform_driver_unregister(&mxs_keydrv);
}
module_exit(mxs_key_exit);
module_init(mxs_key_init);
那如果先调用platform_driver_register进行驱动注册,再调用platform_device_register进行设备注册,是否可以找到相应的驱动呢?
答案是肯定的!
platform_device_register -> platform_device_add -> device_add -> bus_probe_device -> device_attach -> __device_attach
static int __device_attach(struct device_driver *drv, void *data)
{
struct device *dev = data;
if (!driver_match_device(drv, dev))
return 0;
return driver_probe_device(drv, dev);
}
可以看出,通过如上流程,设备找到了相应的驱动,并调用了相应的probe探测函数。
总结
1、设备注册: 在arch/arm/plat-mxs/device.c里通过platform_device_register进行设备注册,
2、驱动注册:在drivers/key/key.c里通过platform_driver_register进行驱动注册。
3、然后设备与驱动通过相同的名字”mxs-key”匹配成功后,最后会调用驱动探测函数mxs_key_probe。
4、mxs_key_probe做的是一些针对lcd的初始化工作。其中也包括,注册输入子系统、中断,等等。