Linux总线设备驱动模型主要包含总线、设备、驱动三个部分。
现实总线:一个现实的Linux设备和驱动通常都需要挂接在一种总线上,对于本身依附于PCI、USB、I2C、SPI等的设备而言,这自然不是问题。
虚拟总线(platform总线):在嵌入式系统里面,对于一些设备(内部的设备)可能没有现成的总线,如SoC 系统中集成的独立的外设控制器、挂接在SoC内存空间的外设等确不依附于此类总线。基于这一背景,Linux2.6起引入了一种虚拟的总线,称为platform总线,相应的设备称为platform_device,而驱动成为 platform_driver。
注意1:platform总线设备驱动模型与之前的三类驱动(字符、块设备、网络设备)没有必然的联系。设备只是搭载到了platform总线上,仅此而已。platform总线相比与常规的总线模型其优势主要是platform总线是由内核实现的,而不用自己定义总线类型,总线设备来加载总线。platform总线是内核已经实现好的。
注意2:所谓的platform_device并不是与字符设备、块设备和网络设备并列的概念,而是一种平行的概念,其从另一个角度对设备进行了概括。如果从内核开发者的角度来看,平台设备的引入是为了更容易开发字符设备、块设备和网络设备驱动。平台设备通常用于处理器上集成的额外功能的附加设备,例如某一SOC处理器中,把内部集成的I2C、RTC、SPI、LCD、看门狗等控制器都归纳为platform_device,而它们本身就是字符设备。如果在板级层面相应的总线上挂载了设备,例如SoC的I2C控制器连接了两个从设备,那么这两个从设备自然会注册为I2C设备和驱动,而不是platform_device。
Linux中大部分的Soc控制器驱动,都可以使用这套机制,设备用platform_device表示,驱动用platform_driver表示。平台设备模型与传统的device和driver模型相比,一个十分明显的优势在于平台设备模型将设备本身的资源注册进内核,由内核统一管理。这样提高了驱动和资源管理的独立性,并且拥有较好的可移植性和安全性。通过平台设备模型开发底层驱动的大致流程为下图:
1. platform_device的定义:
平台设备定义通常位于 linux-3.4.xarcharmmach-xxxxxx-devices.c,platform 设备用结构体 platform_device 来描述,该结构一个重要的元素是 resource ,该元素存入了最为重要的设备资源信息,以下代码中的platform_device定义了两组 resource ,它描述了一个 I2C 设备的资源,第 1 组描述了这个 I2C 设备所占用的总线地址范围, IORESOURCE_MEM 表示第 1 组描述的是内存类型的资源信息,第 2 组描述了这个 I2C 设备的中断号, IORESOURCE_IRQ 表示第 2 组描述的是中断资源信息。设备驱动会根据 flags 来获取相应的资源信息。
1 static struct resource i2c0_resources[] = {
2 [0] = {
3 .start = (u32)XX_I2C1_BASE,
4 .end = (u32)XX_I2C1_BASE + SZ_4K - 1,
5 .flags = IORESOURCE_MEM,
6 },
7 [1] = {
8 .start = I2C1_INT,
9 .end = I2C1_INT,
10 .flags = IORESOURCE_IRQ,
11 },
12 };
13
14
15 static struct xx29_i2c_platform_data xx29_i2c0_platform_data = {
16 .bus_clk_rate = 300000,
17 };
18
19 static struct platform_device xx29_i2c0_device = {
20 .name = "xx29_i2c",
21 .id = 1,
22 .resource = i2c0_resources,
23 .num_resources = ARRAY_SIZE(i2c0_resources),
24 .dev = {
25 .platform_data = &xx29_i2c0_platform_data,
26 },
27 };
2. platform_device添加:
xxx-devices.c会把所有已经定义的platform_device组成一个设备数组xx29_device_table,在board_init(板级初始化)函数中会调用platform_add_devices -> platform_device_register -> platform_device_add将该设备列表中的设备统一添加到内核。
1 struct platform_device *xx29_device_table[] __initdata={ 2 3 #ifdef CONFIG_SERIAL_XX29_UART 4 &xx29_uart0_device, 5 &xx29_uart1_device, 6 &xx29_uart2_device, 7 #endif 8 9 #ifdef CONFIG_MTD_NAND_DENALI 10 &xx29_device_nand, 11 #endif 12 13 #ifdef CONFIG_DWC_OTG_USB 14 &xx29_usb0_device, 15 #endif 16 17 #ifdef CONFIG_USB_DWC_OTG_HCD 18 &xx29_usb1_device, 19 #endif 20 21 #ifdef CONFIG_MTD_XX_SPIFC 22 &xx29_device_spi_nand, 23 #endif 24 25 #ifdef CONFIG_I2C0_XX29 26 &xx29_i2c0_device, 27 #endif 28 29 30 #ifdef CONFIG_SPI_XX29 31 &xx29_ssp_device, 32 #endif 33 34 ...... 35 }; 36 37 unsigned int xx29_device_table_num=ARRAY_SIZE(xx29_device_table);
3. platform_driver的定义和注册:
以下代码以I2C和SPI控制器platform_driver为例,说明了其定义和注册的形式:
static struct platform_driver xx29_i2c_driver = { .probe = xx29_i2c_probe, .remove = xx29_i2c_remove, #ifdef CONFIG_PM .suspend = xx29_i2c_suspend, .resume = xx29_i2c_resume, #endif .driver = { .owner = THIS_MODULE, .name = "xx29_i2c", .of_match_table = xx29_i2c_match, }, }; static int __init i2c_adap_xx29_init(void) { int ret; ret=platform_driver_register(&xx29_i2c_driver); if (ret<0) { printk(KERN_INFO "xx29 i2c driver register fail "); } return ret; } subsys_initcall(i2c_adap_xx29_init);
static struct platform_driver xx29_spi_driver = { .driver = { .name = "xx29_ssp", .owner = THIS_MODULE, }, .probe = xx29_spi_probe, .remove = __exit_p(xx29_spi_remove), }; static int __init xx29_spi_init(void) { return platform_driver_register(&xx29_spi_driver); } module_init(xx29_spi_init);
4. platform_add_devices和platform_driver_register调用时机
platform_add_devices和platform_driver_register两个接口都是通过xxx_initcall宏来实现初始化调用,其中platform_add_devices的调用路径:
arch_initcall(customize_machine);
machine_desc->init_machine
board_init
platform_add_devices
platform_driver_register的调用路径:
subsys_initcall(i2c_adap_xx29_init)
i2c_adap_xx29_init
platform_driver_register
module_init(xx29_spi_init)
xx29_spi_init
platform_driver_register
xxx_initcall宏的调用路径:
start_kernel
rest_init
kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
init thread:
kernel_init
do_basic_setup
do_initcalls
do_initcall_level
linux-3.4.xincludelinuxinit.h文件中定义了各个xxx_initcall宏的先后顺序,可以看到arch_initcall早于subsys_initcall早于device_initcall(module_init)。
5. 设备、驱动的匹配
大部分device和driver的匹配方式就是看名字是否相同,这部分属于总线分内的事情。match的工作是由总线(bus)来完成,匹配工作发生在xxx_device_register()或xxx_drvier_register()
设备(或驱动)注册的时候,都会引发总线调用自己的match函数来寻找目前platform总线是否挂载有与该设备(或驱动)名字匹配的驱动(或设备),如果存在则将双方绑定;
===>>如果先注册设备,驱动还没有注册,那么设备在被注册到总线上时,将不会匹配到与自己同名的驱动,然后在驱动注册到总线上时,因为设备已注册,那么总线会立即匹配与绑定这时的同名的设备与驱动,再调用驱动中的probe函数等;
===>>如果是驱动先注册,同设备驱动一样先会匹配失败,匹配失败将导致其probe暂不调用,而要等到设备注册成功并与自己匹配绑定后才会调用。
为什么两个name的名字必须匹配才能实现device和driver的绑定?
(1)在内核初始化时kernel_init()->do_basic_setup()->driver_init()->platform_bus_init()初始化platform_bus(虚拟总线);
(2)设备注册的时候platform_device_register()->platform_device_add()->(pdev->dev.bus = &platform_bus_type)把设备挂在虚拟的platform bus下;
(3)驱动注册的时候platform_driver_register()->driver_register()->bus_add_driver()->driver_attach()->bus_for_each_dev(),对每个挂在虚拟的platform bus的设备作__driver_attach()->driver_probe_device(),判断drv->bus->match()是否存在并且是否执行成功,此时通过指针执行platform_match,比较strncmp(pdev->name, drv->name, BUS_ID_SIZE),如果相符就调用really_probe(实际就是执行的相应设备的platform_driver->probe(platform_device),注意platform_drv_probe的_dev参数是由bus_for_each_dev的next_device获得)开始真正的探测加载,如果probe成功则绑定该设备到该驱动。
当进入probe函数后,需要获取设备的资源信息,根据参数type所指定类型,例如IORESOURCE_MEM,来分别获取指定的资源。 struct resource * platform_get_resource(struct platform_device *dev, unsigned int type, unsigned int num);当然,也可以固定资源类型,如获取资源中的中断号:struct int platform_get_irq(struct platform_device *dev, unsigned int num);
probe函数一般完成硬件设备使能,struct resource的获取以及虚拟地址的动态映射和具体类型设备的注册(因为平台设备只是一种虚拟的设备类型);remove函数完成硬件设备的关闭,struct resource以及虚拟地址的动态映射的释放和具体类型设备的注销。只要和内核本身运行依赖性不大的外围设备 ( 换句话说只要不在内核运行所需的一个最小系统之内的设备 ), 相对独立的拥有各自独自的资源 (addresses and IRQs) ,都可以用platform_driver 实现。如:lcd,usb,uart 等,都可以用platfrom_driver 写,而timer,irq等最小系统之内的设备则最好不用platfrom_driver 机制,实际上内核实现也是这样的。
当一个驱动注册[platform_driver_register()]的时候,他会遍历所有总线上的设备来寻找匹配,在启动的过程驱动的注册一般比较晚,或者在模块载入的时候 当一个驱动注册[platform_driver_probe()]的时候, 功能上和使用platform_driver_register()是一样的,唯一的区别是它不能被以后其他的device probe了,也就是说这个driver只能和一个device绑定。