由于TINY4412被学长借去做毕设了,因此从本章开始,所有示例代码均基于iTOP4412_SCP精英版
如读者使用TINY4412开发板,可自行修改代码
本章所说的总线是虚拟的总线,只是为了让设备属性和驱动行为更好的分离所提出的概念
实际的Linux设备和驱动通常都会挂接在一种总线上,对于USB、I2C、SPI等总线设备而言,自然不是问题。但是挂接在SoC之外的外设却不依附于此类总线,因此Linux发明了虚拟的总线,称为platform总线,所有直接通过内存寻址的设备都映射到这条总线上。总线相应的结构体为struct bus_type,相应的设备为platform_device,相应的驱动为platform_drvier
在使用总线分层时,如果设备代码需要更改,而驱动代码不需要更改。那么我们只需要更改设备代码即可,而不需要大片地更改驱动代码
在以下章节中,我会依次介绍platform_device、platform_driver和总线结构体platform_bus_type
一、platform_device
之前说过platform_device定义的是属性,其结构体定义如下:
struct platform_device { const char * name; // 名字,用于与driver匹配 int id; struct device dev; u32 num_resources; // resource的个数 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; };
我们需要重点关注的是struct resource * resource;,此变量存储设备资源信息,其定义和示例如下:
struct resource { resource_size_t start; // 起始地址 resource_size_t end; // 结束地址 const char *name; unsigned long flags; // 资源类型 struct resource *parent, *sibling, *child; }; static struct resource led_resource[] = { /* 在iTOP4412中LED3对应GPK1_1 */ [0] = { .start = 0x11000060, // GPK1CON地址 .end = 0x11000060 + 8 - 1, .flags = IORESOURCE_MEM, // 内存 }, /* 要控制哪个I/O口(它属于参数,这里为了方便,用resource传参) */ [1] = { .start = 1, .end = 1, .flags = IORESOURCE_IRQ, // 中断属性 }, };
资源有以下几种常用类型:
#define IORESOURCE_IO 0x00000100 #define IORESOURCE_MEM 0x00000200 #define IORESOURCE_IRQ 0x00000400 #define IORESOURCE_DMA 0x00000800 #define IORESOURCE_BUS 0x00001000
因为ARM中寄存器和内存是统一编址的,所以GPIO所使用的资源标志用IORESOURCE_MEM和IORESOURCE_REG都是可以的
需要注意的是,这里不能用I/O,这里的IORESOURCE_IO特指PCI/ISA总线的I/O
在定义完成resource之后,我们可以定义如下platform_device:
1 static struct platform_device led_platform_dev = { 2 .name = "led", 3 .id = 0, 4 .resource = led_resource, 5 .num_resources = ARRAY_SIZE(led_resource), 6 };
在paltform_device定义完成后,我们需要使用如下函数向总线bus_type注册/注销:
/* 注册platform_device */ platform_device_register(&led_platform_dev); /* 注销platform_driver */ platform_device_unregister(&led_platform_dev);
platform_device_register()函数调用过程如下:
platform_device_register() -> device_initialize(&pdev->dev); // 初始化struct device -> platform_device_add(pdev); // 添加设备到链表中 -> pdev->dev.bus = &platform_bus_type; // 指定总线 -> device_add(&pdev->dev); -> bus_probe_device(struct device *dev) -> device_attach(struct device *dev) -> bus_for_each_drv(dev->bus, NULL, dev, __device_attach); -> __device_attach() -> driver_match_device(drv, dev); // 调用总线成员函数match() -> return drv->bus->match ? drv->bus->match(dev, drv) : 1; -> driver_probe_device(drv, dev); -> really_probe(dev, drv); -> drv->probe(dev); // 调用probe()函数
二、platform_driver
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_driver示例如下:
static struct platform_driver led_platform_drv = { .driver = { .name = "led", .owner = THIS_MODULE, }, .probe = led_probe, .remove = __devexit_p(led_remove), /* __devexit_p(x) x */ };
和platform_device一样,在paltform_driver定义完成后,我们需要使用如下函数向总线bus_type注册/注销:
/* 注册platform_driver */ platform_driver_register(&led_platform_drv); /* 注销platform_driver */ platform_driver_unregister(&led_platform_drv);
platform_driver_register()函数调用过程如下:
platform_driver_register() -> drv->driver.bus = &platform_bus_type; // 指定总线 -> driver_register(&drv->driver); -> bus_add_driver(drv); // 添加驱动到链表中 -> driver_attach(drv); -> bus_for_each_dev(drv->bus, NULL, drv, __driver_attach); -> __driver_attach() -> driver_match_device(drv, dev); // 调用总线成员函数match() -> return drv->bus->match ? drv->bus->match(dev, drv) : 1; -> driver_probe_device(drv, dev); -> really_probe(dev, drv); -> drv->probe(dev); // 调用probe()函数
和platform_device_register()一样,platform_driver_register()也会调用总线的成员函数match();匹配成功则会调用paltform_driver的probe()函数
因此我们要在paltform_driver结构体中提供probe()函数
下面,我们来分析总线结构体platform_bus_type
三、platform_bus_type
platform_bus_type定义如下:
struct bus_type platform_bus_type = { .name = "platform", .dev_attrs = platform_dev_attrs, .match = platform_match, .uevent = platform_uevent, .pm = &platform_dev_pm_ops, };
我们需要分析其match()函数:
/* 1. 使用设备数进行匹配,我们没有用到 */ of_driver_match_device(dev, drv) /* 2. 使用platform_driver的id_table进行匹配,我们也没有用到 */ platform_match_id(pdrv->id_table, pdev) /* 3. 匹配名字,我们使用这个 */ strcmp(pdev->name, drv->name)
四、总结
总线与输入子系统不同,总线并没有提供struct file_operations结构体,因此仍需要我们自己定义
1. 注册platform_driver:platform_driver_register()
1.1 设置platform_driver的总线为platform_bus_type
1.2 添加platform_driver到总线的drv链表中
1.3 调用drv->bus->match(dev, drv)进行匹配
2. 注册platform_device:platform_device_register()
2.1 设置platform_device的总线为platform_bus_type
2.2 添加platform_device到总线的dev链表中
2.3 调用drv->bus->match(dev, drv)进行匹配
3. 匹配:drv->bus->match(dev, drv)
3.1 匹配设备树信息
3.2 匹配dev和drv->id_table
3.3 匹配dev->name和drv->name
3.4 成功,调用drv->probe(dev)
4. 驱动初始化:probe()
4.1 在probe()中做之前init()函数所做的事
5. 驱动注销:remove()
5.1 在remove()中做之前exit()函数所做的事
五、更改led.c为总线设备驱动
此代码我们需要完成platform_device和platform_driver两个结构体,因此分为两个文件
我们在probe()函数中需要获取platform_device的数据,此时需要使用platform_get_resource()函数,示例代码如下:
struct resource *led_resource; led_resource = platform_get_resource(platdev, IORESOURCE_MEM, 0); GPFCON = (volatile unsigned long *)ioremap(led_resource->start, led_resource->end - led_resource->start + 1); GPFDAT = GPFCON + 1; // 映射 led_resource = platform_get_resource(platdev, IORESOURCE_IRQ, 0); pin = led_resource->start;
在rmmod device时,产生如下图错误:
也就是说我们需要提供release()函数,更改platform_device如下:
1 static void led_release(struct device *dev) 2 { 3 /* NULL */ 4 } 5 6 static struct platform_device led_platform_dev = { 7 .name = "led", 8 .id = 0, 9 .resource = led_resource, 10 .num_resources = ARRAY_SIZE(led_resource), 11 .dev = { 12 .release = led_release, 13 }, 14 };