原文:http://blog.youkuaiyun.com/unbutun/article/details/4026474
看platform_driver结构,定义于include/linux/platform_device.h文件中:
|
|
需要注意这两个变量:name和owner。那么的作用主要是为了和相关的platform_device关联起来,owner的作用是说明模块的所有者,驱动程序中一般初始化为THIS_MODULE。
下面是一个platform_driver的初始化实例:
|
上面的初始化是一个音频驱动的实例。注意其中的driver这个结构体,只初始化了其name和owner两个量。接着看一下和driver相关的另一个结构,定义如下:
|
该结构中也有一个name变量。platform_driver从字面上来看就知道是设备驱动。设备驱动是为谁服务的呢?当然是设备了。platform_device就描述了设备对象。下面是一个具体的实例:
|
它的name变量和刚才上面的platform_driver的name变量是一致的,内核正是通过这个一致性来为驱动程序找到资源,即platform_device中的resource。这个结构的定义如下,位于include/linux/ioport.h中:
|
下面是一个具体的实例:
|
这个结构的作用就是告诉驱动程序设备的起始地址和终止地址和设备的端口类型。这里的地址指的是物理地址。
另外还需要注意platform_device中的device结构,它详细描述了设备的情况,定义如下:
|
上面把驱动程序中涉及到的主要结构都介绍了,下面主要说一下驱动程序中怎样对这个结构进行处理,以使驱动程序能运行。
由于篇幅限制,下面的内容见下一篇:
http://blog.chinaunix.net/u1/57747/showart.php?id=1074059
platform_device和platform_driver(二)
紧接上面介绍的数据结构,介绍驱动程序的具体实现过程。
|
注意函数体的最后一行,它调用的是platform_driver_register这个函数。这个函数定义于driver/base/platform.c中,原型如下:
int platform_driver_register(struct platform_driver *drv)
它的功能就是为上面提到的plarform_driver中的driver这个结构中的probe、remove这些变量指定功能函数。
到目前为止,内核就已经知道了有这么一个驱动模块。内核启动的时候,就会调用与该驱动相关的probe函数。我们来看一下probe函数实现了什么功能。
probe函数的原型为
int xxx_probe(struct platform_device *pdev)
即它的返回类型为int,接收一个platform_device类型的指针作为参数。返回类型就是我们熟悉的错误代码了,而接收的这个参数呢,我们上面已经说过,驱动程序为设备服务,就需要知道设备的信息。而这个参数,就包含了与设备相关的信息。
probe函数接收到plarform_device这个参数后,就需要从中提取出需要的信息。它一般会通过调用内核提供的platform_get_resource和platform_get_irq等函数来获得相关信息。如通过platform_get_resource获得设备的起始地址后,可以对其进行request_mem_region和ioremap等操作,以便应用程序对其进行操作。通过platform_get_irq得到设备的中断号以后,就可以调用request_irq函数来向系统申请中断。这些操作在设备驱动程序中一般都要完成。
在完成了上面这些工作和一些其他必须的初始化操作后,就可以向系统注册我们在/dev目录下能看在的设备文件了。举一个例子,在音频芯片的驱动中,就可以调用register_sound_dsp来注册一个dsp设备文件,lcd的驱动中就可以调用register_framebuffer来注册fb设备文件。这个工作完成以后,系统中就有我们需要的设备文件了。而和设备文件相关的操作都是通过一个file_operations来实现的。在调用register_sound_dsp等函数的时候,就需要传递一个file_operations类型的指针。这个指针就提供了可以供用户空间调用的write、read等函数。file_operations结构的定义位于include/linux/fs.h中,列出如下:
|
到目前为止,probe函数的功能就完成了。
当用户打开一个设备,并调用其read、write等函数的时候,就可以通过上面的file_operations来找到相关的函数。所以,用户驱动程序还需要实现这些函数,具体实现和相关的设备有密切的关系,这里就不再介绍了。
=========================================================================================================================
原文:http://blog.youkuaiyun.com/zhandoushi1982/article/details/5130207
做Linux方面也有三个多月了,对代码中的有些结构一直不是很明白,比如platform_device与platform_driver一直分不清关系。在网上搜了下,做个总结。两者的工作顺序是先定义platform_device -> 注册platform_device->,再定义 platform_driver-> 注册 platform_driver。
platform_device设备的注册过程必须在相应设备驱动加载之前被调用,因为驱动注册时需要匹配内核中所以已注册的设备名。platform_device 是在系统启动时在init.c 里的s3c_arch_init()函数里进行注册的。这个函数申明为arch_initcall(s3c_arch_init);会在系统初始化阶段被调用。arch_initcall 的优先级高于module_init,所以会在Platform驱动注册之前调用。现在内核中不是采用arch_initcall(s3c_arch_init) 注册platform_device结构体而是通过.init_machine成员将其保存在arch_initcall(customize_machine)等待调用(在mach-smdk6410.c中定义的MACHINE_START到MACHINE_END);其实质是一样的均放在.initcall3.init等待调用。之后再定义结构体struct platform_driver,在驱动初始化函数中调用函数platform_driver_register()注册 platform_driver。详细过程描述如下:
Linux从2.6版本开始引入了platform这个概念,在开发底层驱动程序时,首先要确认的就是设备的资源信息,在2.6内核中将每个设备的资源用结构platform_device来描述,该结构体定义在kernel/include/linux/platform_device.h中,
struct platform_device
{
const char * name;
u32 id;
struct device dev;
u32 num_resources;
struct resource * resource;
};
该结构一个重要的元素是resource,该元素存入了最为重要的设备资源信息,定义在kernel/include/linux/ioport.h中,
struct resource
{
const char *name;
unsigned long start, end;
unsigned long flags;
struct resource *parent, *sibling, *child;
};
比如:
static struct resource s3c_usb_resource[] = {
[0] = {
.start = S3C_PA_USBHOST,
.end = S3C_PA_USBHOST + S3C_SZ_USBHOST - 1,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_UHOST,
.end = IRQ_UHOST,
.flags = IORESOURCE_IRQ,
}
};
以上是6410的USB HOST分配的资源信息。第1组描述了这个usbhost设备所占用的总线地址范围,起始地址和大小由硬件决定,IORESOURCE_MEM表示第1组描述的是内存类型的资源信息;第2组描述了这个usbhost设备的中断号,也由硬件设定,IORESOURCE_IRQ表示第2组描述的是中断资源信息。设备驱动会根据flags来获取相应的资源信息。
有了resource信息,就可以定义platform_device了:
struct platform_device s3c_device_usb = {
.name = "s3c2410-ohci", //s3c6410-usb
.id = -1,
.num_resources = ARRAY_SIZE(s3c_usb_resource),
.resource = s3c_usb_resource,
.dev = {
.dma_mask = &s3c_device_usb_dmamask,
.coherent_dma_mask = 0xffffffffUL
}
};
有了platform_device就可以调用函数platform_add_devices向系统中添加该设备了。系统中的设备资源都可以采用这种方式列举在一起,然后成一个指针数组,如:
static struct platform_device *smdk6410_devices[] __initdata = {
......
&s3c_device_usbgadget,
&s3c_device_usb, //jeff add.
......
}
然后在6410的初始化函数smdk6410_machine_init()中执行:
platform_add_devices(smdk6410_devices,ARRAY_SIZE(smdk6410_devices));将所有的device添加进系统。platform_add_devices的好处在于它是一次性的执行多个platform_device_register。
至于驱动程序需要实现结构体struct platform_driver,也定义在kernel/include/linux/platform_device.h中:
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 pm_ext_ops *pm;
struct device_driver driver;
};
则该处的USB HOST实现是:
static struct platform_driver ohci_hcd_s3c2410_driver = {
.probe = ohci_hcd_s3c2410_drv_probe,
.remove = ohci_hcd_s3c2410_drv_remove,
.shutdown = usb_hcd_platform_shutdown,
/*.suspend = ohci_hcd_s3c2410_drv_suspend, */
/*.resume = ohci_hcd_s3c2410_drv_resume, */
.driver = {
.owner = THIS_MODULE,
.name = "s3c2410-ohci",
},
};
在驱动初始化(ohci-hcd.c的1124行)函数中调用函数platform_driver_register()注册该platform_driver,需要注意的是s3c_device_usb结构中name元素和ohci_hcd_s3c2410_driver 结构中driver.name必须是相同的,这样在platform_driver_register()注册时会对所有已注册的platform_device中元素的name和当前注册的platform_driver的driver.name进行比较,只有找到具备相同名称的platform_device存在后,platform_driver才能注册成功。当注册成功时会调用platform_driver结构元素probe函数指针,这里就是ohci_hcd_s3c2410_drv_probe开始探测加载。platform driver中的函数都是以platformdevice作为参数进入。
为什么两个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)把设备挂在虚拟的platformbus下;(3)驱动注册的时候platform_driver_register()->driver_register()->bus_add_driver()->driver_attach()->bus_for_each_dev(),对每个挂在虚拟的platformbus的设备作__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,来分别获取指定的资源。
structresource * platform_get_resource(struct platform_device *dev, unsignedint type, unsigned int num);当然,也可以固定资源类型,如获取资源中的中断号:struct intplatform_get_irq(struct platform_device *dev, unsigned int num);
probe函数一般完成硬件设备使能,structresource的获取以及虚拟地址的动态映射和具体类型设备的注册(因为平台设备只是一种虚拟的设备类型);remove函数完成硬件设备的关闭,struct resource以及虚拟地址的动态映射的释放和具体类型设备的注销。只要和内核本身运行依赖性不大的外围设备 (换句话说只要不在内核运行所需的一个最小系统之内的设备 ), 相对独立的拥有各自独自的资源 (addresses and IRQs),都可以用platform_driver 实现。如:lcd,usb,uart 等,都可以用platfrom_driver写,而timer,irq等最小系统之内的设备则最好不用platfrom_driver 机制,实际上内核实现也是这样的。