何谓platform bus
在Linux3.4的设备驱动模型中,关心总线、设备和驱动这3个实体,总线将设备和驱动绑定。在系统每注册一个设备的时候,会寻找与之匹配的驱动;相反的,在系统每注册一个驱动的时候,会寻找与之匹配的设备,而匹配由总线完成。一个现实的Linux设备和驱动通常都需要挂接在一种总线上,对于本身依附于PCI、USB、I2 C、SPI等的设备而言,这自然不是问题,但是在嵌入式系统里面,SoC系统中集成的独立的外设控制器、挂接在SoC内存空间的外设等确不依附于此类总线。基于这一背景,Linux发明了一种虚拟的总线,称为platform总线,相应的设备称为platform_device,而驱动成为platform_driver。 Platform bus 是一种虚拟的总线,而不是物理上存在的,为了方便内核对设备的管理而出现的。
platform device与platform driver
Platform_device和Platform_driver是从Linux 2.6起引入了一套新的驱动管理和注册机制,在现在的Linux中大部分的设备驱动,都可以使用这套机制, 设备用Platform_device表示,驱动用Platform_driver进行注册。 platform driver机制和传统的device driver 机制(通过driver_register函数进行注册)相比,一个十分明显的优势在于platform机制将设备本身的资源注册进内核,由内核统一管理,在驱动程序中使用这些资源时通过platform device提供的标准接口进行申请并使用。这样提高了驱动和资源管理的独立性,并且拥有较好的可移植性和安全性。这里的platform driver并不是与字符设备、块设备和网络设备并列的概念,而是linux系统提供的一种附加手段,其本身可能即是字符设备。Platform device定义如下:
struct platform_device { const char * name; //定义平台设备的名称,此处设备的命名应和相应驱动程序命名一致 int id; struct device dev; u32 num_resources; struct resource * resource; //定义平台设备的资源 };
Platform driver结构体当中,包含了probe()、remove()、shutdown()、suspend()、resume()等函数,需要由驱动实现,定义如下:
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 (*suspend_late)(struct platform_device *, pm_message_t state); int (*resume_early)(struct platform_device *); int (*resume)(struct platform_device *); struct device_driver driver; };
Platform driver和platform device是通过platform bus联系起来的,在Platform drive注册的时候,会遍历platform bus下所有的device,将当前driver和总线上的device进行match,如果匹配成功,则将device和driver绑定起来调用。Platform bus定义如下:
struct bus_type platform_bus_type = { .name = "platform", .dev_attrs = platform_dev_attrs, .match = platform_match, .uevent = platform_uevent, .pm = PLATFORM_PM_OPS_PTR, };
我们需要关注其Match成员函数,正是此函数确定了Platform driver和platform device之间如果匹配的,代码如下:
static int platform_match(struct device *dev, struct device_driver *drv) { struct platform_device *pdev; pdev = container_of(dev, struct platform_device, dev); return (strncmp(pdev->name, drv->name, BUS_ID_SIZE) == 0); }
简单的进行字符串匹配,这样我们在进行platform驱动开发的时候,需要注意 platform_device和platform_driver中的name属性要保持一致。在platform device结构体中,描述了platform_device的资源,由resource结构体来描述,定义如下:
struct resource { resource_size_t start; //定义资源的起始地址 resource_size_t end; //定义资源的结束地址 const char *name; //定义资源的名称 unsigned long flags; 定义资源的类型,比如MEM,IO,IRQ,DMA类型 struct resource *parent, *sibling, *child; };
这个结构表示设备所拥有的资源,即I/O端口、I/O映射内存、中断及DMA等。这里的地址指的是物理地址。在设备驱动当中,我们可以通过platform_get_resource()这样的API来获取设备资源,原型为:
Struct resource *platform_get_resource(struct platform_device*, unsigned int,unsigned int); int platofrm_get_irq(struct platform_device*, unsigned int num);
哪些适用于plarform驱动
platform机制将设备本身的资源注册进内核,由内核统一管理,在驱动程序中使用这些资源时通过 platform device提供的标准接口进行申请并使用。这样提高了驱动和资源管理的独立性,这样拥有更好的可移植性。platform机制的本身使用并不复杂,由两部分组成:platform_device和platfrom_driver。Platform driver通过platform bus获取platform_device。通常情况下只要和内核本身运行依赖性不大的外围设备,相对独立的,拥有各自独立的资源(地址总线和IRQs),都可以用 platform_driver来管理,而timer,irq等小系统之内的设备则最好不用platfrom_driver机制。 platform_device最大的特定是CPU直接寻址设备的寄存器空间,即使对于其他总线设备,设备本身的寄存器无法通过CPU总线访问,但总线的controller仍然需要通过platform bus来管理。总之,platfrom_driver的根本目的是为了统一管理系统的外设资源,为驱动程序提供统一的接口来访问系统资源,将驱动和资源分离,提高程序的可移植性。
基于platform总线的驱动开发流程
定义platform_device
定义platform_device,并集中定义设备资源。示例如下
struct platform_device disp_device = { .name = "disp", .id = -1, .num_resources = ARRAY_SIZE(disp_resource), .resource = disp_resource, .dev = {} }; static struct resource disp_resource[DISP_IO_NUM] = { [DISP_IO_SCALER0] = { .start = 0x01e00000, .end = 0x01e0077f, .flags = IORESOURCE_MEM, }, [DISP_IO_SCALER1] = { .start = 0x01e20000, .end = 0x01e2077f, .flags = IORESOURCE_MEM, }, //… };
注册platform_device
定义好了platform_device,调用platform_device_register()向系统注册,连同资源统一注册进内核,原型如下:
int platform_device_register(struct platform_device *pdev);
在此函数中,会调用void device_initialize(struct device *dev),初始化platform_device的device结构,然后调用int platform_device_add(struct platform_device *pdev),将设备所有资源注册进系统,并生成设备结点。
定义实现platform_driver
定义platform_driver以驱动该设备,示例如下:
static struct platform_driver disp_driver = { .probe = disp_probe, .remove = disp_remove, .suspend = disp_suspend, .resume = disp_resume, .shutdown = disp_shutdown, .driver = { .name = "disp", .owner = THIS_MODULE, }, };
实现platform_driver中的probe、remove、suspend、resume等函数。注意.name与设备的.name一致,不然无法匹配成功。
注册platform_driver
调用platform_driver_register()注册platform_driver,函数代码如下:
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); }
platform_driver_register注册过程中会遍历platform_bus上的所有设备,调用platform_match函数,发现匹配设备,即调用该驱动的probe函数。
probe函数
Probe即探测函数,是platform设备驱动match之后执行的第一个函数,到这里,说明platform设备驱动已经注册加载了。一般在Probe函数中去获取platform设备注册的资源及进行设备驱动的初始化。
操作设备
进行了platform_device_register 和platform_driver_register后,驱动的相应信息就出现在sys目录的相应文件夹下,然后,我们该如何调用设备呢?怎么对设备进行打开读写等操作呢? Platform总线只是为了方便系统管理设备而已(比如统一的sysfs结点、电源管理等),想要与用户空间交互,如读写还需要利用file_operations,即需要创建一个基本类型的设备,如字符设备。通过字符设备与用户空间进行交互,同时系统可以通过platform bus对挂载在其上的该设备进行管理。