在BDD模型下,总线设备驱动分离。
总线负责匹配设备和驱动,而驱动以标准途径拿到板级硬件资源。
软件分层是linux驱动的基本思想。
驱动被分离为核心层,硬件层等。
驱动核心层用来处理某一类驱动的公共处理部分,从而让硬件层只需要专注与硬件操作。
对于PCI,USB,IIC,SPI等设备而言,他们本身挂载在一个具体的BUS上,自然不成问题。但是对于SOC,有些外设却是挂载在SOC的内部总线上。为此,linux设置了一种虚拟的总线,称为PlatformBus。相应的设备,被称为platform_device,而驱动,称为platform_driver。
PlatformBus为设备提供了一种架构清晰的注册手段。例如一个设备,它是一个CDEV,但是同时,它也被实现为一个platform_device。这样,用户就可以通过两种机制访问到这个设备。既可以通过/dev,以CDEV的方式来请求CDEV驱动进行IO,也可以通过/sys/bus/platform,以platform_device的方式来请求platform_driver驱动进行IO。
但是设备在注册时,是由platform_driver_probe来发起对CDEV的注册。
在通常的OFSTYLE的加载过程中,probe函数被调用时,需要在probe中进行CDEV的注册。
而不再是传统的CDEV在module_init函数中去注册了。
来看看具体实现。
struct platform_device{
const char* name;
int id;
struct device dev;
struct resource* resource;
u32 num_resource;
const struct platform_device_id* id_entry;
}
和BDD架构中的所有结构体一样,它有一个name成员,用于字符串比较。
可以看出,platform_device内嵌了一个DEV的实体,是对DEV的扩展。
它关联到一个RESOURCE结构体的VectorTable,并指定了长度。
它关联到一个platform_device_id结构体对象,这是一个描述符,用来对PDEV进行描述。
struct platform_device_id {
char name[PLATFORM_NAME_SIZE];
ulong driver_data;
};
这是一个PDEVID的结构体定义。
它有一个name成员,以及一个driver_data成员。
来看看RESOURCE是怎么实现的。
struct resource{
const char* name;
size_t start;
size_t end;
unsigned long flags;
struct resource * parent;
struct resource * child;
struct resource * sibling;
};
可以看出,由于插入了指针,所以RESOURCE构成树状结构,可以索引到父节点(父元),也可以索引到子节点(子首元)。也可以索引到弟节点(后元)。
RESOURCE具有一个name成员,这可以用来进行字符串比较。
它的关键成员是,start,end,flags。
这将对应到DTB中的属性。
flags可以为
1)IORESOURCE_IO,此时start,end代表IONUM的起始号和结束号。
如果只使用了一个IO,那么start==end。
2)IORESOURCE_MEM,此时start,end代表MEM的起始地址和结束地址。
3)IORESOURCE_IRQ,此时start,end代表IRQNUM的起始号和结束号。
如果只用了1个IRQNUM,那么start==end。
4)IORESOURCE_DMA,此时start,end代表DMANUM的起始号和结束号。
5)IORESOURCE_BUS。
5)IORESOURCE_REG。
struct resource* platform_get_resource(struct platform_device* dev, uint type, uint num);
是用来获取RESOURCE的内核API。它通过传入的PDEV,获取RESOURCE的句柄。
另一种RESOURCE,就是platform_data。(PLTDATA)
我们可以在注册时,同时注册私有数据。它是非标的,可以注册到device里。
static struct my_pltdata {
int reset_gpio;
int led_gpio;
};
static struct my_pltdata needed_pltdata = {
.reset_gpio = 47,
.led_gpio = 41,
};
static struct resource my_resources[] = {
[0] = {
.name = "mem1",
.start = BASEADDR1,
.end = (BASEADDR1+ SIZE - 1),
.flags = IORESOURCE_MEM,
},
[1] = {
.name = "mem2",
.start = BASEADDR2,
.end = (BASEADDR2+ SIZE - 1),
.flags = IORESOURCE_MEM,
},
[2] = {
.name = "mc",
.start = IRQ1,
.end = IRQ1,
.flags = IORESOURCE_IRQ,
},
};
static struct platform_device mydev = {
.name = "my pltdev"
.id = 0,
.dev = {
.platform_data = &needed_pltdata ,
},
.resource = my_resources,
.num_resource = ARRY_SIZE(my_resources),
};
platform_device_register(&mydev );
这个例子中,我们为PDEV配置了RESOURCE,同时也为PDEV配置了PLTDATA,放置在PDEV.dev.platform_data中。
这里用到了一个宏ARRY_SIZE,用来帮助我们得到一个TABLE的实际长度。
struct platform_driver{
struct device_driver driver;
const struct platform_device_id* id_table;
int (*probe)(struct platform_device*);
int (*remove)(struct platform_device*);
}
可以看出,platform_driver内嵌了一个device_driver实体,是对DRV的扩展。
它关联到一个platform_device_id结构体对象,这是一个描述符,用来对PDEV进行描述。
系统为PlatformBus定义了一个BUS的实例,
static struct bus_type platform_bus_type ={
.name = "platform"
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
这是一个全局的变量,内核启动时,已经注册了PlatformBus。
内核也实现了match函数。
static int platform_match(struct device *dev, struct device_driver *drv)
{
int ret;
struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv);
ret = of_driver_match_device(dev, drv);
if(ret)
return 1;
if(pdrv->id_table){
ret = platform_match_id(pdrv->id_table, pdev)
return ((ret != NULL));
}
ret = strcmp(pdev->name, drv->name);
return ((ret == 0));
}
首先是进行OFSTYLE的匹配,如果不成功就换下一种方式匹配,即PDEVID表匹配。
PDEVID表匹配时,查看PDEV是否出现在platform_driver的PDEVID表内。如果不成功,就换下一种方式匹配,即名称匹配。比较PDEV的名字和PDRV的名字是否一致。
来看一个简单的例子。定义了两个模块,PDRV和PDEV。
struct platform_driver pdrv = {
.driver = {
.name = "pdrv",
.owner = THIS_MODULE,
.pm = &pdrv_pm_ops,
},
.probe = pdrv_probe,
.remove = pdrv_remove,
};
static const struct dev_pm_ops pdrv_pm_ops = {
.suspend = pdrv_suspend,
.resume = pdrv_resume,
};
static int pdrv_suspend(struct device * dev)
{
return 0;
}
static int pdrv_resume(struct device * dev)
{
return 0;
}
static int pdrv_probe(struct platform_device * pdev)
{
return 0;
}
static int pdrv_remove(struct platform_device * pdev)
{
return 0;
}
module_platform_driver(pdrv);
这个模块中,定义了PDRV,并利用系统提供的宏module_platform_driver,将PDRV注册到了内核中。
struct platform_device pdev0 = {
.name= "pdrv",
.id = 0,
.num_resource = 0,
.resource = NULL,
.dev = {
.release = pdev_release,
}
}
struct platform_device pdev1 = {
.name= "pdrv",
.id = 1,
.num_resource = 0,
.resource = NULL,
.dev = {
.release = pdev_release,
}
}
static void pdev_release(struct device * dev)
{
return;
}
static int __init pdev_init(void)
{
platform_device_register(&pdev0);
platform_device_register(&pdev1);
return 0;
}
module_init(pdev_init);
这个模块中,定义了两个PDEV,他们具有不同的ID,所以PLATBUS可以区分这两个设备。但是name是一样的,都是和所需要匹配的DRV的名字相同。
使用DTB进行板级配置,是推荐的方式。
在DTB中配置DEVICE的RESOURCE,COMPATIBLE。
在驱动模块中定义全局变量OF_DEVICE_ID_MATCH_TABLE。
在DRIVER中配置OF_MATCH_TABLE。
当DTB中的OF_DEVICE_ID能够匹配DRVIER的OF_MATCH_TABLE中给出的OF_DEVICE_ID时,就将DEVICE和DRIVER绑定起来。
这样就实现了驱动和设备分离。
在linux中,BUS是最重要的部分。
LDM中,BUS维护着DEV列表,BUS也通过内核机制,间接维护着DRV集合(DRVSET)。
可以简单理解为,BUS维护着从属于BUS的DEV和DRV,以及DEV和DRV之间的绑定关系。
mdev负责管理着BDD。尤其是对于HotPlug的设备。
当内核通过uevent通知mdev进行新设备管理时,BUS会执行match,如果match成功,mdev会请求内核加载DRIVER。在内核加载DRIVER时,内核会调用DRIVER的probe函数。
这是一个通用的过程,对于任何BUS都一样。例如PlatformBus,IIC,SPI,USB,PCI等等。
整个BDD是一个树状结构,如果是SOC中的USBController,而这个USBC是由内部总线进行访问的,那么USBC所管理的BUS,也是PlatformBus的一个子节点。这是容易从逻辑上理解的。