今天我以fb设备的注册过程来分析platform设备的添加流程 platform总线是kernel中最近加入的一种虚拟总线,它被用来连接处在仅有最少基本组件的总线上的那些设备.这样的总线包括许多片上系统上的那些用来整合外设的总线, 也包括一些"古董" PC上的连接器; 但不包括像PCI或USB这样的有庞大正规说明的总线. 平台设备 ~~~~~~ 平台设备通常指的是系统中的自治体, 包括老式的基于端口的设备和连接外设总线的北桥(host bridges),以及集成在片上系统中的绝大多数控制器. 它们通常拥有的一个共同特征是直接编址于CPU总线上. 即使在某些罕见的情况下, 平台设备会通过某段其他类型的总线连入系统, 它们的寄存器也会被直接编址.平台设备会分到一个名称(用在驱动绑定中)以及一系列诸如地址和中断请求号(IRQ)之类的资源. 那什么情况可以使用platform driver机制编写驱动呢? 我的理解是只要和内核本身运行依赖性不大的外围设备(换句话说只要不在内核运行所需的一个最小系统之内的设备),相对独立的,拥有各自独自的资源(addresses and IRQs),都可以用platform_driver实现。如:lcd,usb,uart等,都可以用platfrom_driver写,而timer,irq等最小系统之内的设备则最好不用platfrom_driver机制,实际上内核实现也是这样的。下面继续我们的分析过程。 首先要定义一个platform_device,我们先来看一下platform_device结构的定义,如下所示: // include/linux/platform_device.h: 16struct platform_device { 17 const char * name; 18 u32 id; 19 struct device dev; 20 u32 num_resources; 21 struct resource * resource; 22}; 下面是对应的FB设备的变量定义 // arch/arm/mach-pxa/generic.c 229static struct platform_device pxafb_device = { 230 .name = "pxa2xx-fb", 231 .id = -1, 232 .dev = { 233 .platform_data = &pxa_fb_info, 234 .dma_mask = &fb_dma_mask, 235 .coherent_dma_mask = 0xffffffff, 236 }, 237 .num_resources = ARRAY_SIZE(pxafb_resources), 238 .resource = pxafb_resources, 239}; 由上可以看出,name成员表示设备名,系统正是通过这个名字来与驱动绑定的,所以驱动里面相应的设备名必须与该项相符合;id表示设备编号,id的值为-1表示只有一个这样的设备。 该结构中比较重要的一个成员就是resource, Linux设计了这个通用的数据结构来描述各种I/O资源(如:I/O端口、外设内存、DMA和IRQ等)。它的定义如下: // include/linux/ioport.h: 16struct resource { 17 const char *name; 18 unsigned long start, end; 19 unsigned long flags; 20 struct resource *parent, *sibling, *child; 21}; 下面关于这方面的内容,参考了http://hi.baidu.com/zengzhaonong/blog/item/654c63d92307f0eb39012fff.html struct resource 是linux对挂接在4G总线空间上的设备实体的管理方式。 一个独立的挂接在cpu总线上的设备单元,一般都需要一段线性的地址空间来描述设备自身,linux是怎么管理所有的这些外部"物理地址范围段",进而给用户和linux自身一个比较好的观察4G总线上挂接的一个个设备实体的简洁、统一级联视图的呢? linux采用struct resource结构体来描述一个挂接在cpu总线上的设备实体(32位cpu的总线地址范围是0~4G): resource->start 描述设备实体在cpu总线上的线性起始物理地址; resource->end 描述设备实体在cpu总线上的线性结尾物理地址; resource->name 描述这个设备实体的名称,这个名字开发人员可以随意起,但最好贴切; resource->flag 描述这个设备实体的一些共性和特性的标志位; 只需要了解一个设备实体的以上4项,linux就能够知晓这个挂接在cpu总线的上的设备实体的基本使用情况,也就是 [resource->start, resource->end]这段物理地址现在是空闲着呢,还是被什么设备占用着呢? linux会坚决避免将一个已经被一个设备实体使用的总线物理地址区间段[resource->start, resource->end],再分配给另一个后来的也需要这个区间段或者区间段内部分地址的设备实体,进而避免设备之间出现对同一总线物理地址段的重复引用,而造成对唯一物理地址的设备实体二义性. 以上的4个属性仅仅用来描述一个设备实体自身,或者是设备实体可以用来自治的单元,但是这不是linux所想的,linux需要管理4G物理总线的所有空间,所以挂接到总线上的形形色色的各种设备实体,这就需要链在一起,因此resource结构体提供了另外3个成员:指针parent、sibling和 child:分别为指向父亲、兄弟和子资源的指针,它们的设置是为了以一种树的形式来管理各种I/O资源,以root source为例,root->child(*pchild)指向root所有孩子中地址空间最小的一个;pchild->sibling是兄弟链表的开头,指向比自己地址空间大的兄弟。 属性flags是一个unsigned long类型的32位标志值,用以描述资源的属性。比如:资源的类型、是否只读、是否可缓存,以及是否已被占用等。下面是一部分常用属性标志位的定义 // include/linux/ioport.h: 29/* 30 * IO resources have these defined flags. 31 */ 32#define IORESOURCE_BITS 0x000000ff /* Bus-specific bits */ 33 34#define IORESOURCE_IO 0x00000100 /* Resource type */ 35#define IORESOURCE_MEM 0x00000200 36#define IORESOURCE_IRQ 0x00000400 37#define IORESOURCE_DMA 0x00000800 38 39#define IORESOURCE_PREFETCH 0x00001000 /* No side effects */ 40#define IORESOURCE_READONLY 0x00002000 41#define IORESOURCE_CACHEABLE 0x00004000 42#define IORESOURCE_RANGELENGTH 0x00008000 43#define IORESOURCE_SHADOWABLE 0x00010000 44#define IORESOURCE_BUS_HAS_VGA 0x00080000 45 46#define IORESOURCE_DISABLED 0x10000000 47#define IORESOURCE_UNSET 0x20000000 48#define IORESOURCE_AUTO 0x40000000 49#define IORESOURCE_BUSY 0x80000000 /* Driver has marked this resource busy */ 下面来看我们所使用的LCD所占用的资源,如下所示: // arch/arm/mach-pxa/generic.c static struct resource pxafb_resources[] = { [0] = { .start = 0x44000000, .end = 0x4400ffff, .flags = IORESOURCE_MEM, }, [1] = { .start = IRQ_LCD, .end = IRQ_LCD, .flags = IORESOURCE_IRQ, }, }; 由上可知LCD占用的资源包括两类,一类是MEM类型,一类是IRQ类型。MEME类型资源对应的物理地址范围是 0x44000000 - 0x4400ffff;IRQ类型资源对应的物理地址范围是IRQ_LCD,查看相应的定义: // include/asm-arm/arch-pxa/irqs.h: 15#ifdef CONFIG_PXA27x 16#define PXA_IRQ_SKIP 0 17#else 18#define PXA_IRQ_SKIP 7 19#endif 20 21#define PXA_IRQ(x) ((x) - PXA_IRQ_SKIP) 43#define IRQ_LCD PXA_IRQ(17) /* LCD Controller Service Request */ 我们所使用的处理器为PXA255,所以对应的PXA_IRQ_SKIP应该为7,所以IRQ_LCD = 10,也就是它对应的中断信号线为10。 设置完了platform_device的相关成员后,下一步就是调用platform_add_devices()来向系统中添加该设备了,首先来看它的定义: // drivers/base/platform.c: /** * platform_add_devices - add a numbers of platform devices * @devs: array of platform devices to add * @num: number of platform devices in array */ int platform_add_devices(struct platform_device **devs, int num) { int i, ret = 0; for (i = 0; i = 0) platform_device_unregister(devs); break; } } return ret; } 我们目前只关注LCD设备,所以不管for循环,关键的一句就是platform_device_register(),该函数用来进行平台设备的注册,首先来看它的定义: // drivers/base/platform.c: /** * platform_device_register - add a platform-level device * @pdev: platform device we're adding * */ int platform_device_register(struct platform_device * pdev) { device_initialize(&pdev->dev); return platform_device_add(pdev); } 它首先调用device_initialize()来初始化该设备,然后调用platform_device_add()来添加该设备。关于device_initialize()我们暂且不分析,在这里只关注platform_device_add() // drivers/base/platform.c: 229/** 230 * platform_device_add - add a platform device to device hierarchy 231 * @pdev: platform device we're adding 232 * 233 * This is part 2 of platform_device_register(), though may be called 234 * separately _iff_ pdev was allocated by platform_device_alloc(). 235 */ 236int platform_device_add(struct platform_device *pdev) 237{ 238 int i, ret = 0; 239 240 if (!pdev) 241 return -EINVAL; 242 243 if (!pdev->dev.parent) 244 pdev->dev.parent = &platform_bus; 245 246 pdev->dev.bus = &platform_bus_type; 247 248 if (pdev->id != -1) 249 snprintf(pdev->dev.bus_id, BUS_ID_SIZE, "%s.%d", pdev->name, 250 pdev->id); 251 else 252 strlcpy(pdev->dev.bus_id, pdev->name, BUS_ID_SIZE); 253 254 for (i = 0; i num_resources; i++) { 255 struct resource *p, *r = &pdev->resource; 256 257 if (r->name == NULL) 258 r->name = pdev->dev.bus_id; 259 260 p = r->parent; 261 if (!p) { 262 if (r->flags & IORESOURCE_MEM) 263 p = &iomem_resource; 264 else if (r->flags & IORESOURCE_IO) 265 p = &ioport_resource; 266 } 267 268 if (p && insert_resource(p, r)) { 269 printk(KERN_ERR 270 "%s: failed to claim resource %d\n", 271 pdev->dev.bus_id, i); 272 ret = -EBUSY; 273 goto failed; 274 } 275 } 276 277 pr_debug("Registering platform device '%s'. Parent at %s\n", 278 pdev->dev.bus_id, pdev->dev.parent->bus_id); 279 280 ret = device_add(&pdev->dev); 281 if (ret == 0) 282 return ret; 283 284 failed: 285 while (--i >= 0) 286 if (pdev->resource.flags & (IORESOURCE_MEM|IORESOURCE_IO)) 287 release_resource(&pdev->resource); 288 return ret; 289} 先看243 - 244两行,如果该设备的父指针为空,则将它的父指针指向platform_bus,这是一个device类型的变量,它的定义如下: // drivers/base/platform.c: 26struct device platform_bus = { 27 .bus_id = "platform", 28}; 紧接着,246行设置设备的总线类型为platform_bus_type // drivers/base/platform.c: 892struct bus_type platform_bus_type = { 893 .name = "platform", 894 .dev_attrs = platform_dev_attrs, 895 .match = platform_match, 896 .uevent = platform_uevent, 897 .pm = PLATFORM_PM_OPS_PTR, 898}; 248 - 252行设置设备指向的dev结构的bus_id成员,由前面可知,我们只有一个LCD设备,所以 pdev->id = -1,因而对应的 bus_id = "pxa2xx-fb",关于这个bus_id,在定义的时候,内核开发者是后面加了一个注释: /* position on parent bus */ 254 - 275行进行资源处理,首先设置资源的名称,如果name成员为空的话,就将该成员设置为我们前面已经赋值的bus_id,也就是"pxa2xx-fb" 260 - 266行先将 p 指向我们当前处理的资源的 parent 指针成员,如果 p 指向NULL,也就是我们当前处理的资源的 parent 指针成员指向NULL的话,再检测当前处理的资源的类型,如果是MEM类型的,则设置 p 指向 iomem_resource ,如果是IO类型的,则使 p 指向 ioport_resource,这两个均是 struct resource 类型的变量,它们的定义如下: // kernel/resource.c 23 struct resource ioport_resource = { 24 .name = "PCI IO", 25 .start = 0, 26 .end = IO_SPACE_LIMIT, 27 .flags = IORESOURCE_IO, 28}; 29 EXPORT_SYMBOL(ioport_resource); 30 31 struct resource iomem_resource = { 32 .name = "PCI mem", 33 .start = 0, 34 .end = -1, 35 .flags = IORESOURCE_MEM, 36}; 37 EXPORT_SYMBOL(iomem_resource); // include/asm/io.h: #define IO_SPACE_LIMIT 0xffffffff // 这并不是针对 ARM 平台的定义,针对 ARM 平台的定义我没有找到,所以暂且列一个在这里占位 关于这两个struct resource类型的变量,在网络上搜到了如下的信息:(http://hi.baidu.com/zengzhaonong/blog/item/654c63d92307f0eb39012fff.html) 物理内存页面是重要的资源。从另一个角度看,地址空间本身,或者物理存储器在地址空间中的位置,也是一种资源,也要加以管理 -- resource管理地址空间资源。 内核中有两棵resource树,一棵是iomem_resource,另一棵是ioport_resource,分别代表着两类不同性质的地址资源。两棵树的根也都是resource数据结构,不过这两个数据结构描述的并不是用于具体操作对象的地址资源,而是概念上的整个地址空间。 将主板上的ROM空间纳入iomem_resource树中;系统固有的I/O类资源则纳入ioport_resource树 // kernel/resource.c ---------------------------------------- struct resource ioport_resource = { .name = "PCI IO", .start = 0, .end = IO_SPACE_LIMIT, .flags = IORESOURCE_IO, }; struct resource iomem_resource = { .name = "PCI mem", .start = 0, .end = -1, .flags = IORESOURCE_MEM, }; /usr/src/linux/include/asm-i386/io.h #define IO_SPACE_LIMIT 0xffff 0 ~ 0xffff 64K 继续我们的函数, 268 - 276行将我们当前处理的资源插入到 p 指针指向的resource树里面。这里面只有一个关键的函数insert_resource() // kernel/resource.c 416/** 417 * insert_resource - Inserts a resource in the resource tree 418 * @parent: parent of the new resource 419 * @new: new resource to insert 420 * 421 * Returns 0 on success, -EBUSY if the resource can't be inserted. 422 * 423 * This function is equivalent to request_resource when no conflict 424 * happens. If a conflict happens, and the conflicting resources 425 * entirely fit within the range of the new resource, then the new 426 * resource is inserted and the conflicting resources become children of 427 * the new resource. 428 */ 429int insert_resource(struct resource *parent, struct resource *new) 430{ 431 struct resource *conflict; 432 433 write_lock(&resource_lock); 434 conflict = __insert_resource(parent, new); 435 write_unlock(&resource_lock); 436 return conflict ? -EBUSY : 0; 437} 资源锁resource_lock对所有资源树进行读写保护,任何代码段在访问某一颗资源树之前都必须先持有该锁,该锁的定义也在 resource.c中。锁机制我们暂且不管,该函数里面关键的就是__insert_resource()函数: // kernel/resource.c: 365/* 366 * Insert a resource into the resource tree. If successful, return NULL, 367 * otherwise return the conflicting resource (compare to __request_resource()) 368 */ 369static struct resource * __insert_resource(struct resource *parent, struct resource *new) 370{ 371 struct resource *first, *next; 372 373 for (;; parent = first) { 374 first = __request_resource(parent, new); 375 if (!first) 376 return first; 377 378 if (first == parent) 379 return first; 380 381 if ((first->start > new->start) || (first->end end)) 382 break; 383 if ((first->start == new->start) && (first->end == new->end)) 384 break; 385 } 386 387 for (next = first; ; next = next->sibling) { 388 /* Partial overlap? Bad, and unfixable */ 389 if (next->start start || next->end > new->end) 390 return next; 391 if (!next->sibling) 392 break; 393 if (next->sibling->start > new->end) 394 break; 395 } 396 397 new->parent = parent; 398 new->sibling = next->sibling; 399 new->child = first; 400 401 next->sibling = NULL; 402 for (next = first; next; next = next->sibling) 403 next->parent = new; 404 405 if (parent->child == first) { 406 parent->child = new; 407 } else { 408 next = parent->child; 409 while (next->sibling != first) 410 next = next->sibling; 411 next->sibling = new; 412 } 413 return NULL; 414} 374行有个__request_resource(),它完成实际的资源分配工作。如果参数new所描述的资源中的一部分或全部已经被其它节点所占用,则函数返回与new相冲突的resource结构的指针。否则就返回NULL。该函数的源代码如下: // kernel/resource.c: 142/* Return the conflict entry if you can't request it */ 143static struct resource * __request_resource(struct resource *root, struct resource *new) 144{ 145 resource_size_t start = new->start; 146 resource_size_t end = new->end; 147 struct resource *tmp, **p; 148 149 if (end start) 152 return root; 153 if (end > root->end) 154 return root; 155 p = &root->child; 156 for (;;) { 157 tmp = *p; 158 if (!tmp || tmp->start > end) { 159 new->sibling = tmp; 160 *p = new; 161 new->parent = root; 162 return NULL; 163 } 164 p = &tmp->sibling; 165 if (tmp->end sibling。For循环体的执行步骤如下: (1)让tmp指向当前正被扫描的resource结构(tmp=*p)。 (2)判断tmp指针是否为空(tmp指针为空说明已经遍历完整个child链表),或者当前被扫描节点的起始位置start是否比new的结束位置end还要大。只要这两个条件之一成立的话,就说明没有资源冲突,于是就可以把new链入child链表中:①设置new的sibling指针指向当前正被扫描的节点tmp(new->sibling=tmp);②当前节点tmp的前一个兄弟节点的sibling指针被修改为指向new这个节点(*p=new);③将new的parent指针设置为指向root。然后函数就可以返回了(返回值NULL表示没有资源冲突)。 (3)如果上述两个条件都不成立,这说明当前被扫描节点的资源域有可能与new相冲突(实际上就是两个闭区间有交集),因此需要进一步判断。为此它首先修改指针p,让它指向tmp->sibling,以便于继续扫描child链表。然后,判断tmp->end是否小于new->start,如果小于,则说明当前节点tmp和new没有资源冲突,因此执行continue语句,继续向下扫描child链表。否则,如果tmp->end大于或等于new->start,则说明tmp->[start,end]和new->[start,end]之间有交集。所以返回当前节点的指针tmp,表示发生资源冲突。 继续回到platform_device_add()函数里面,如果insert_resource()成功,下一步就会调用280行device_add()函数来将设备添加到设备树里面。这个函数暂且不做分析。 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 下面来看platform_driver驱动的注册过程,一般分为三个步骤: 1、定义一个platform_driver结构 2、初始化这个结构,指定其probe、remove等函数,并初始化其中的driver变量 3、实现其probe、remove等函数 platform_device对应的驱动是struct platform_driver,它的定义如下 // include/linux/platform_device.h: 48struct platform_driver { 49 int (*probe)(struct platform_device *); 50 int (*remove)(struct platform_device *); 51 void (*shutdown)(struct platform_device *); 52 int (*suspend)(struct platform_device *, pm_message_t state); 53 int (*suspend_late)(struct platform_device *, pm_message_t state); 54 int (*resume_early)(struct platform_device *); 55 int (*resume)(struct platform_device *); 56 struct device_driver driver; 57}; 可见,它包含了设备操作的几个功能函数,同样重要的是,它还包含了一个device_driver结构。刚才提到了驱动程序中需要初始化这个变量。下面看一下这个变量的定义,位于include/linux/device.h中: // include/linux/device.h: 120struct device_driver { 121 const char *name; 122 struct bus_type *bus; 123 124 struct module *owner; 125 const char *mod_name; /* used for built-in modules */ 126 127 int (*probe) (struct device *dev); 128 int (*remove) (struct device *dev); 129 void (*shutdown) (struct device *dev); 130 int (*suspend) (struct device *dev, pm_message_t state); 131 int (*resume) (struct device *dev); 132 struct attribute_group **groups; 133 134 struct driver_private *p; 135}; 需要注意这两个变量:name和owner。那么的作用主要是为了和相关的platform_device关联起来,owner的作用是说明模块的所有者,驱动程序中一般初始化为THIS_MODULE。 对于我们的LCD设备,它的platform_driver变量就是: // drivers/video/pxafb.c: 1384static struct platform_driver pxafb_driver = { 1385 .probe = pxafb_probe, 1386#ifdef CONFIG_PM 1387 .suspend = pxafb_suspend, 1388 .resume = pxafb_resume, 1389#endif 1390 .driver = { 1391 .name = "pxa2xx-fb", 1392 }, 1393}; 由上可知pxafb_driver.driver.name的值与前面我们讲过的platform_device里面的name成员的值是一致的,内核正是通过这个一致性来为驱动程序找到资源,即platform_device中的resource。 上面把驱动程序中涉及到的主要结构都介绍了,下面主要说一下驱动程序中怎样对这些结构进行处理,以使驱动程序能运行。相信大家都知道module_init()这个宏。驱动模块加载的时候会调用这个宏。它接收一个函数为参数,作为它的参数的函数将会对上面提到的platform_driver进行处理。看我们的实例:这里的module_init()要接收的参数为pxafb_init这个函数,下面是这个函数的定义: // drivers/video/pxafb.c: 1411 int __devinit pxafb_init(void) 1412{ 1413#ifndef MODULE 1414 char *option = NULL; 1415 1416 if (fb_get_options("pxafb", &option)) 1417 return -ENODEV; 1418 pxafb_setup(option); 1419#endif 1420 return platform_driver_register(&pxafb_driver); 1421} 1422 1423 module_init(pxafb_init); 注意函数体的最后一行,它调用的是platform_driver_register这个函数。这个函数定义于driver/base/platform.c中,定义如下: // drivers/base/platform.c 439/** 440 * platform_driver_register 441 * @drv: platform driver structure 442 */ 443int platform_driver_register(struct platform_driver *drv) 444{ 445 drv->driver.bus = &platform_bus_type; 446 if (drv->probe) 447 drv->driver.probe = platform_drv_probe; 448 if (drv->remove) 449 drv->driver.remove = platform_drv_remove; 450 if (drv->shutdown) 451 drv->driver.shutdown = platform_drv_shutdown; 452 if (drv->suspend) 453 drv->driver.suspend = platform_drv_suspend; 454 if (drv->resume) 455 drv->driver.resume = platform_drv_resume; 456 if (drv->pm) 457 drv->driver.pm = &drv->pm->base; 458 return driver_register(&drv->driver); 459} 460EXPORT_SYMBOL_GPL(platform_driver_register); 由上可知,它的功能先是为上面提到的plarform_driver中的driver这个结构中的probe、remove这些变量指定功能函数,最后调用driver_register()进行设备驱动的注册。在注册驱动的时候,这个函数会以上面提到的name成员的值为搜索内容,搜索系统中注册的device中有没有与这个name值相一致的device,如果有的话,那么接着就会执行platform_driver 里probe函数。 到目前为止,内核就已经知道了有这么一个驱动模块。内核启动的时候,就会调用与该驱动相关的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中,列出如下: struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t); ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t); int (*readdir) (struct file *, void *, filldir_t); unsigned int (*poll) (struct file *, struct poll_table_struct *); int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); long (*compat_ioctl) (struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *, fl_owner_t id); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, struct dentry *, int datasync); int (*aio_fsync) (struct kiocb *, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); int (*check_flags)(int); int (*dir_notify)(struct file *filp, unsigned long arg); int (*flock) (struct file *, int, struct file_lock *); ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int); ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int); int (*setlease)(struct file *, long, struct file_lock **); }; 到目前为止,probe函数的功能就完成了。 当用户打开一个设备,并调用其read、write等函数的时候,就可以通过上面的file_operations来找到相关的函数。所以,用户驱动程序还需要实现这些函数,具体实现和相关的设备有密切的关系,这里就不再介绍了。 关于我们所使用的LCD设备的xxx_probe()函数的分析可查看文章 <<pxafb驱动程序分析>> 下面看我们所使用的LCD设备的 probe() 函数: // drivers/video/pxafb.c: 1271int __init pxafb_probe(struct platform_device *dev) 1272{ 1273 struct pxafb_info *fbi; 1274 struct pxafb_mach_info *inf; 1275 int ret; 1276 1277 dev_dbg(dev, "pxafb_probe\n"); 1278 1279 inf = dev->dev.platform_data; 1280 ret = -ENOMEM; 1281 fbi = NULL; 1282 if (!inf) 1283 goto failed; 1284 1285#ifdef CONFIG_FB_PXA_PARAMETERS 1286 ret = pxafb_parse_options(&dev->dev, g_options); 1287 if (ret lccr0 & LCCR0_INVALID_CONFIG_MASK) 1296 dev_warn(&dev->dev, "machine LCCR0 setting contains illegal bits: %08x\n", 1297 inf->lccr0 & LCCR0_INVALID_CONFIG_MASK); 1298 if (inf->lccr3 & LCCR3_INVALID_CONFIG_MASK) 1299 dev_warn(&dev->dev, "machine LCCR3 setting contains illegal bits: %08x\n", 1300 inf->lccr3 & LCCR3_INVALID_CONFIG_MASK); 1301 if (inf->lccr0 & LCCR0_DPD && 1302 ((inf->lccr0 & LCCR0_PAS) != LCCR0_Pas || 1303 (inf->lccr0 & LCCR0_SDS) != LCCR0_Sngl || 1304 (inf->lccr0 & LCCR0_CMS) != LCCR0_Mono)) 1305 dev_warn(&dev->dev, "Double Pixel Data (DPD) mode is only valid in passive mono" 1306 " single panel mode\n"); 1307 if ((inf->lccr0 & LCCR0_PAS) == LCCR0_Act && 1308 (inf->lccr0 & LCCR0_SDS) == LCCR0_Dual) 1309 dev_warn(&dev->dev, "Dual panel only valid in passive mode\n"); 1310 if ((inf->lccr0 & LCCR0_PAS) == LCCR0_Pas && 1311 (inf->upper_margin || inf->lower_margin)) 1312 dev_warn(&dev->dev, "Upper and lower margins must be 0 in passive mode\n"); 1313#endif 1314 1315 dev_dbg(&dev->dev, "got a %dx%dx%d LCD\n",inf->xres, inf->yres, inf->bpp); 1316 if (inf->xres == 0 || inf->yres == 0 || inf->bpp == 0) { 1317 dev_err(&dev->dev, "Invalid resolution or bit depth\n"); 1318 ret = -EINVAL; 1319 goto failed; 1320 } 1321 pxafb_backlight_power = inf->pxafb_backlight_power; 1322 pxafb_lcd_power = inf->pxafb_lcd_power; 1323 fbi = pxafb_init_fbinfo(&dev->dev); 1324 if (!fbi) { 1325 dev_err(&dev->dev, "Failed to initialize framebuffer device\n"); 1326 ret = -ENOMEM; // only reason for pxafb_init_fbinfo to fail is kmalloc 1327 goto failed; 1328 } 1329 1330 /* Initialize video memory */ 1331 ret = pxafb_map_video_memory(fbi); 1332 if (ret) { 1333 dev_err(&dev->dev, "Failed to allocate video RAM: %d\n", ret); 1334 ret = -ENOMEM; 1335 goto failed; 1336 } 1337 1338 ret = request_irq(IRQ_LCD, pxafb_handle_irq, SA_INTERRUPT, "LCD", fbi); 1339 if (ret) { 1340 dev_err(&dev->dev, "request_irq failed: %d\n", ret); 1341 ret = -EBUSY; 1342 goto failed; 1343 } 1344 1345 /* 1346 * This makes sure that our colour bitfield 1347 * descriptors are correctly initialised. 1348 */ 1349 pxafb_check_var(&fbi->fb.var, &fbi->fb); 1350 pxafb_set_par(&fbi->fb); 1351 1352 platform_set_drvdata(dev, fbi); 1353 1354 ret = register_framebuffer(&fbi->fb); 1355 if (ret dev, "Failed to register framebuffer device: %d\n", ret); 1357 goto failed; 1358 } 1359 1360#ifdef CONFIG_PM 1361 // TODO 1362#endif 1363 1364#ifdef CONFIG_CPU_FREQ 1365 fbi->freq_transition.notifier_call = pxafb_freq_transition; 1366 fbi->freq_policy.notifier_call = pxafb_freq_policy; 1367 cpufreq_register_notifier(&fbi->freq_transition, CPUFREQ_TRANSITION_NOTIFIER); 1368 cpufreq_register_notifier(&fbi->freq_policy, CPUFREQ_POLICY_NOTIFIER); 1369#endif 1370 1371 /* 1372 * Ok, now enable the LCD controller 1373 */ 1374 set_ctrlr_state(fbi, C_ENABLE); 1375 1376 return 0; 1377 1378failed: 1379 platform_set_drvdata(dev, NULL); 1380 kfree(fbi); 1381 return ret; 1382} 参考文献: struct--resource http://hi.baidu.com/zengzhaonong/blog/item/654c63d92307f0eb39012fff.html 驱动程序模型-platform http://www.ourkernel.com/bbs/archiver/?tid-67.html Linux对I/O端口资源的管理 http://www.host01.com/article/server/00070002/0542417251875372.htm Linux对I/O端口资源的管理(ZZ) http://hi.baidu.com/zengzhaonong/blog/item/0d6f6909e2aa5dad2fddd444.html platform_device和platform_driver http://linux.chinaunix.net/techdoc/net/2008/09/10/1031351.shtml linux resource, platform_device和驱动的关系 http://blog.csdn.net/wawuta/archive/2007/03/14/1529621.aspx