关于 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.ht
ml
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.h
tml)
物理内存页面是重要的资源。从另一个角度看,地址空间本身,或者物理存储器在地址
空间中的位置,也是一种资源,也要加以管理 -- 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.h
驱动程序模型-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.ht
ml
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
由 fb 设备的注册过程来看内核的设备模型
在<<platform设备添加流程 >>一文中,关于 struct device 方面的内容没有详加
阐述,现在我们来一一分析。struct platform 结构里面有一个 struct device 类型的成
员,我们先来看看该结构的定义:
// include/linux/device.h:
305struct device {
306 struct klist klist_children;
307 struct klist_node knode_parent; /* node in sibling list */
308 struct klist_node knode_driver;
309 struct klist_node knode_bus;
310 struct device * parent;
311
312 struct kobject kobj;
313 char bus_id[BUS_ID_SIZE]; /* position on parent bus */
314 struct device_attribute uevent_attr;
315
316 struct semaphore sem; /* semaphore to synchronize calls to
317 * its driver.
318 */
319
320 struct bus_type * bus; /* type of bus device is on */
321 struct device_driver *driver; /* which driver has allocated this
322 device */
323 void *driver_data; /* data private to the driver */
324 void *platform_data; /* Platform specific data, device
325 core doesn't touch it */
326 void *firmware_data; /* Firmware specific data (e.g. ACPI,
327 BIOS data),reserved for device core*/
328 struct dev_pm_info power;
329
330 u64 *dma_mask; /* dma mask (if dma'able device) */
331 u64 coherent_dma_mask;/* Like dma_mask, but for
332 alloc_coherent mappings as
333 not all hardware supports
334 64 bit addresses for consistent
335 allocations such descriptors. */
336
337 struct list_head dma_pools; /* dma pools (if dma'ble) */
338
339 struct dma_coherent_mem *dma_mem; /* internal for coherent
mem 340 override */
341
342 void (*release)(struct device * dev);
343};
下面来看一下各个成员的函义:
g_list: 全局设备列表中的节点.
node: 父设备的孩子列表中的节点.
bus_list: 设备从属的总线所属的设备列表中的节点.
driver_list: 设备对应的驱动所属的设备列表中的节点.
intf_list: intf_data 列表. 对每个该设备所支持的接口, 程序会分配一个结
构体.
children: 子设备列表.
parent: *** 待修正 ***
name: 设备描述(用 ASCII 码).例: " 3Com Corporation 3c905
100BaseTX [Boomerang]"
bus_id: 设备总线的位置描述(用 ASCII 码). 此设备从属的总线下的所有
设备应对应唯一的描述符.
例: PCI 总线 bus_id 的格式为
::
系统中所有 PCI 设备的 bus_id 都对应上述同一个名字.
lock: 设备的自旋锁(spinlock).
(译注: 在自旋锁中, 线程简单地循环等待并检查, 直到锁可用.)(xlp
补注: 在 UP(uni processor)情况下,spinlock就是简单的 cli(清中断)和 seiL(设中断).
在 SMP 情况下,spinlock_lock 是循环检测锁,可用后获取锁,spinlock_unlock
是直接释放锁).
refcount: 设备引用的数量.
bus: 指向设备所属总线的 bus_type结构体的指针.
dir: 设备的 sysfs目录.(译注: sysfs 是 Linux 2.6提供的虚拟文件系统.
sysfs从内核设备模型中向用户空间导出设备及驱动的信息. 它也被用来进行配置.)
class_num: 设备的 Class-enumerated 值.
driver: 指向设备驱动程序结构体 device_driver 的指针.
driver_data: 驱动相关的数据.
platform_data: 设备所在平台的数据.例: 对嵌入式或片上系统(SOC)这类用户
自定义板上的设备, Linux 常使用 platform_data 指向一个针对板的结构体, 来描述设备
以及设备间
的连线.这样一个结构体中可能包含可用的端口, 芯片参数, GPIO 针扮演的额外
角色等等. 它可以缩小"板支持包"(Board Support Packages, BSP)的体积并减少
驱动中针对板的#ifdef 的数量.
current_state: 设备当前电源状态.
saved_state: 指向设备已保存的状态的指针. 驱动可以利用它来控制设备,
release: 当所有设备引用都撤销后用来释放设备的回调函数. 它应由为设备
分配空间的程序(也就是发现这个设备的总线驱动)来设置.
现在再来看一下 pxafb_device的定义
// 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};
也就是说只初始化了 struct device 结构的 platform_data、dma_mask 和
coherent_dma_mask 成员,相关的定义如下:
// arch/arm/mach-pxa/generic.c:
207 static struct pxafb_mach_info pxa_fb_info;
227 static u64 fb_dma_mask = ~(u64)0;
那其它成员是什么时候赋值呢?记得当初我们在 platform_device_register()曾经遇
到过 device_initialize(),当初直接跳过了,我们现在再来看它
// 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);
}
// drivers/base/core.c:
216/**
217 * device_initialize - init device structure.
218 * @dev: device.
219 *
220 * This prepares the device for use by other layers,
221 * including adding it to the device hierarchy.
222 * It is the first half of device_register(), if called by
223 * that, though it can also be called separately, so one
224 * may use @dev's fields (e.g. the refcount).
225 */
226
227void device_initialize(struct device *dev)
228{
229 kobj_set_kset_s(dev, devices_subsys); 230 kobject_init(&dev->kobj);
231 klist_init(&dev->klist_children, klist_children_get,
232 klist_children_put);
233 INIT_LIST_HEAD(&dev->dma_pools);
234 init_MUTEX(&dev->sem);
235 device_init_wakeup(dev, 0);
236}
这里的 devices_subsys定义于 drivers/base/core.c,如下所示:
// drivers/base/core.c:
164/*
165 * devices_subsys - structure to be registered with kobject core.
166 */
167
168decl_subsys(devices, &ktype_device, &device_uevent_ops);
其中 decl_subsys是一个宏,定义于 include/linux/kobject.h 中,如下所示:
// include/linux/kobject.h:
165struct subsystem {
166 struct kset kset;
167 struct rw_semaphore rwsem;
168};
169
170#define decl_subsys(_name,_type,_uevent_ops) \
171struct subsystem _name##_subsys = { \
172 .kset = { \
173 .kobj = { .name = __stringify(_name) }, \
174 .ktype = _type, \
175 .uevent_ops =_uevent_ops, \
176 } \
177}
其中的__stringify()也是一个宏,定义于 include/linux/stringify.h,如下所示:
#ifndef __LINUX_STRINGIFY_H
#define __LINUX_STRINGIFY_H
/* Indirect stringification. Doing two levels allows the parameter to be a
* macro itself. For example, compile with -DFOO=bar, __stringify(FOO)
* converts to "bar".
*/
#define __stringify_1(x) #x
#define __stringify(x) __stringify_1(x)
#endif /* !__LINUX_STRINGIFY_H */
"#"的作用是把宏参数转换成字符串,因此 __stringify(x) "x"
需要注意的是,这里用了两层,原因可以查看博客上面 C/C++里面>
把它代入即得到
struct subsystem devices_subsys = {
.kset = { .kobj = { .name = "devices" },
.ktype = &ktype_device,
.uevent_ops = &device_uevent_ops,
}
}
其中 ktype_device 和 device_uevent_ops 以及对应的 dev_sysfs_ops 均位于
drivers/base/core.c,里面主要是指定了一些回调函数,如下所示:
// drivers/base/core.c:
87static struct kobj_type ktype_device = {
88 .release = device_release,
89 .sysfs_ops = &dev_sysfs_ops,
90};
151static struct kset_uevent_ops device_uevent_ops = {
152 .filter = dev_uevent_filter,
153 .name = dev_uevent_name,
154 .uevent = dev_uevent,
155};
59static struct sysfs_ops dev_sysfs_ops = {
60 .show = dev_attr_show,
61 .store = dev_attr_store,
62};
实际上就是为一些函数指针指定了对应的调用函数。
定义了这个变量之后,但相当于定义了一个子系统,名字就是 devices,对应于/sys 下
面的 devices目录。需要注意的是,在 2.6内核的后期版本中,去掉了 struct subsystem
结构,直接用一个 struct kset 结构来代替,这是因为本质上二者其实是一个东西,去掉这
么一个名词会让人更容易理解。之后,我们便可以调用 subsystem_register()来对该子系
统进行注册,但目前我们关注的重点不在这里,所以对 subsystem的讨论暂且到这里。我
们只需要记住,我们已经在内核中注册进了一个子系统,子系统的名字就是"devices",对应
的变量为 devices_subsys。
现在继续回到我们的 device_initialize()函数, 229 行的 kobj_set_kset_s() 是一
个宏,如下所示:
// include/linux/kobject.h:
197/**
198 * kobj_set_kset_s(obj,subsys) - set kset for embedded kobject.
199 * @obj: ptr to some object type.
200 * @subsys: a subsystem object (not a ptr).
201 *
202 * Can be used for any object type with an embedded ->kobj.
203 */
204
205#define kobj_set_kset_s(obj,subsys) \
206 (obj)->kobj.kset = &(subsys).kset
116struct kset {
117 struct subsystem * subsys; 118 struct kobj_type * ktype;
119 struct list_head list;
120 spinlock_t list_lock;
121 struct kobject kobj;
122 struct kset_uevent_ops * uevent_ops;
123};
因此, kobj_set_kset_s(dev, devices_subsys)的作用实际上就是指定 dev 所指向
的设备所属的 kobject 的 kset 为 devices_subsys这个子系统(其实就是一个 kset)。
紧接着 230行初始化 struct device的 kobj成员,相关定义如下:
// include/linux/kobject.h:
50 struct kobject {
51 const char * k_name;
52 char name[KOBJ_NAME_LEN];
53 struct kref kref;
54 struct list_head entry;
55 struct kobject * parent;
56 struct kset * kset;
57 struct kobj_type * ktype;
58 struct dentry * dentry;
59};
85 struct kobj_type {
86 void (*release)(struct kobject *);
87 struct sysfs_ops * sysfs_ops;
88 struct attribute ** default_attrs;
89};
// lib/kobject.c
123/**
124 * kobject_init - initialize object.
125 * @kobj: object in question.
126 */
127 void kobject_init(struct kobject * kobj)
128{
129 kref_init(&kobj->kref);
130 INIT_LIST_HEAD(&kobj->entry);
131 kobj->kset = kset_get(kobj->kset);
132}
329/**
330 * kobject_get - increment refcount for object.
331 * @kobj: object.
332 */
333
334struct kobject * kobject_get(struct kobject * kobj)
335{
336 if (kobj) 337 kref_get(&kobj->kref);
338 return kobj;
339}
struct kref 是内核中用来计数的一个结构,它的操作具有原子性。kref_init()将对应
的 object 的引用计数设置为 1。INIT_LIST_HEAD(&kobj->entry)将 kobj 的 entry 的
prev 和 next 指针都指向 entry,最后来设置 kobj 的 kset 成员,首先来看用到的几个函
数,如下所示:
// include/linux/kobject.h:
131static inline struct kset * to_kset(struct kobject * kobj)
132{
133 return kobj ? container_of(kobj,struct kset,kobj) : NULL;
134}
135
136static inline struct kset * kset_get(struct kset * k)
137{
138 return k ? to_kset(kobject_get(&k->kobj)) : NULL;
139}
由上可知,LCD 对应的 struct device 中的 kobj 成员所指向的 kset 在之前已经设置
为指向 devices_subsys 子系统对应的 kset。所以将会执行 kset_get()中的
to_kset(kobject_get(&k->kobj))
这个函数首先使 kset 对应的 kobject 的引用计数增 1,也就是 devices_subsys子系统对
应的 kset 所对应的 kobject 的引用计数增 1,然后再将我们的 LCD 对应的 struct device
中的 kobj成员所指向的 kset 指向刚才的 kset,绕了这么多,实际上也很简单,就是将 kset
的引用计数增 1。
再来看 device_initialize() 里面的 231 - 232 行,即
klist_init(&dev->klist_children, klist_children_get,klist_children_put);
struct klist是对 struct list_head的一个包装,功能更强大一些:
// include/linux/klist.h
21struct klist {
22 spinlock_t k_lock;
23 struct list_head k_list;
24 void (*get)(struct klist_node *);
25 void (*put)(struct klist_node *);
26};
32struct klist_node {
33 struct klist * n_klist;
34 struct list_head n_node;
35 struct kref n_ref;
36 struct completion n_removed;
37};
// lib/klist.c
42/**
43 * klist_init - Initialize a klist structure.
44 * @k: The klist we're initializing. 45 * @get: The get function for the embedding object (NULL if none)
46 * @put: The put function for the embedding object (NULL if none)
47 *
48 * Initialises the klist structure. If the klist_node structures are
49 * going to be embedded in refcounted objects (necessary for safe
50 * deletion) then the get/put arguments are used to initialise
51 * functions that take and release references on the embedding
52 * objects.
53 */
54
55void klist_init(struct klist * k, void (*get)(struct klist_node *),
56 void (*put)(struct klist_node *))
57{
58 INIT_LIST_HEAD(&k->k_list);
59 spin_lock_init(&k->k_lock);
60 k->get = get;
61 k->put = put;
62}
到此,我们可以看出 231 -232行的功能其实也很简单,就是初始化 struct device里
面的 klist_children 的 k_list 链表,设置 klist_children 的 get 函数为
klist_children_get(),put 函数为 klist_children_put(),而这两个函数也很简单,如下
// drivers/base/core.c
201static void klist_children_get(struct klist_node *n)
202{
203 struct device *dev = container_of(n, struct device, knode_parent);
204
205 get_device(dev);
206}
207
208static void klist_children_put(struct klist_node *n)
209{
210 struct device *dev = container_of(n, struct device, knode_parent);
211
212 put_device(dev);
213}
322/**
323 * get_device - increment reference count for device.
324 * @dev: device.
325 *
326 * This simply forwards the call to kobject_get(), though
327 * we do take care to provide for the case that we get a NULL
328 * pointer passed in.
329 */
330 331struct device * get_device(struct device * dev)
332{
333 return dev ? to_dev(kobject_get(&dev->kobj)) : NULL;
334}
335
336
337/**
338 * put_device - decrement reference count.
339 * @dev: device in question.
340 */
341void put_device(struct device * dev)
342{
343 if (dev)
344 kobject_put(&dev->kobj);
345}
31#define to_dev(obj) container_of(obj, struct device, kobj)
从上可以看出, put 和get 函数也只是简单地减少或增加设备对就的 kobject 的引用计
数。
再回到 device_initialize()函数里面,233行初始化设备的 dma_pools链表,234行
初始化结构体里面包含的信号量,235行 device_init_wakeup(dev, 0)实际上是一个宏:
// include/linux/pm.h
190#ifdef CONFIG_PM
...
193#define device_set_wakeup_enable(dev,val) \
194 ((dev)->power.should_wakeup = !!(val))
........
201#else /* !CONFIG_PM */
........
208#define device_set_wakeup_enable(dev,val) do{}while(0)
........
221#endif
223/* changes to device_may_wakeup take effect on the next pm state change.
224 * by default, devices should wakeup if they can.
225 */
226#define device_can_wakeup(dev) \
227 ((dev)->power.can_wakeup)
228#define device_init_wakeup(dev,val) \
229 do { \
230 device_can_wakeup(dev) = !!(val); \
231 device_set_wakeup_enable(dev,val); \
232 } while(0)
233
因此,如果有电源管理单元的话,!!作用不清楚,暂且跳过。至此,device_initialize()
函数总算分析完毕,这还只是 device_register()的 top-half,OH GOD! 在接下来的 platform_device_add()函数里面,将 struct device 的 parent 成员指针
指向 platform_bus,这是一个 device类型的变量,它的定义如下:
// drivers/base/platform.c:
26struct device platform_bus = {
27 .bus_id = "platform",
28};
紧接着,设置设备的总线类型 , 将 struct device 的 bus 成员指针指向
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};
紧接着,设置设备的 bus_id,为"pxa2xx-fb",在申请了相应的资源后,调用
device_add() 来向系统中添加设备,这应该算是 device_register() 的
bottom-half,^-^!!下面来看它的定义:
// drivers/base/core.c
238/**
239 * device_add - add device to device hierarchy.
240 * @dev: device.
241 *
242 * This is part 2 of device_register(), though may be called
243 * separately _iff_ device_initialize() has been called separately.
244 *
245 * This adds it to the kobject hierarchy via kobject_add(), adds it
246 * to the global and sibling lists for the device, then
247 * adds it to the other relevant subsystems of the driver model.
248 */
249int device_add(struct device *dev)
250{
251 struct device *parent = NULL;
252 int error = -EINVAL;
253
254 dev = get_device(dev);
255 if (!dev || !strlen(dev->bus_id))
256 goto Error;
257
258 parent = get_device(dev->parent);
259
260 pr_debug("DEV: registering device: ID = '%s'\n", dev->bus_id);
261 262 /* first, register with generic layer. */
263 kobject_set_name(&dev->kobj, "%s", dev->bus_id);
264 if (parent)
265 dev->kobj.parent = &parent->kobj;
266
267 if ((error = kobject_add(&dev->kobj)))
268 goto Error;
269
270 dev->uevent_attr.attr.name = "uevent";
271 dev->uevent_attr.attr.mode = S_IWUSR;
272 if (dev->driver)
273 dev->uevent_attr.attr.owner = dev->driver->owner;
274 dev->uevent_attr.store = store_uevent;
275 device_create_file(dev, &dev->uevent_attr);
276
277 kobject_uevent(&dev->kobj, KOBJ_ADD);
278 if ((error = device_pm_add(dev)))
279 goto PMError;
280 if ((error = bus_add_device(dev)))
281 goto BusError;
282 if (parent)
283 klist_add_tail(&dev->knode_parent, &parent->klist_children);
284
285 /* notify platform of device entry */
286 if (platform_notify)
287 platform_notify(dev);
288 Done:
289 put_device(dev);
290 return error;
291 BusError:
292 device_pm_remove(dev);
293 PMError:
294 kobject_uevent(&dev->kobj, KOBJ_REMOVE);
295 kobject_del(&dev->kobj);
296 Error:
297 if (parent)
298 put_device(parent);
299 goto Done;
300}
254 行的 get_device()前面我们已经见过,就是增加 device 对应的 kobject 的引用
计数。
258行增加它的父 device 对应的 kobject 的引用计数。
263行设置 device 对应的 kobject 的名字为"pxa2xx-fb"
264 - 265行将device对应的kobject的parent指针指向父device对应的kobject 267 - 268行调用 kobject_add()来向系统中添加 device 对应的 kobject
270 - 274行设置 device 的 uevent_attr 成员,这是一个 struct device_attribute
结构,它的定义如下:
// include/linux/device.h:
291/* interface for exporting device attributes */
292 struct device_attribute {
293 struct attribute attr;
294 ssize_t (*show)(struct device *dev, struct device_attribute *attr,
295 char *buf);
296 ssize_t (*store)(struct device *dev, struct device_attribute *attr,
297 const char *buf, size_t count);
298};
// include/linux/sysfs.h:
18struct attribute {
19 const char * name;
20 struct module * owner;
21 mode_t mode;
22};
// drivers/base/core.c
157static ssize_t store_uevent(struct device *dev, struct device_attribute *attr,
158 const char *buf, size_t count)
159{
160 kobject_uevent(&dev->kobj, KOBJ_ADD);
161 return count;
162}
275行用来在 sys中创建相应的文件和目录,如下所示:
// drivers/base/core.c
171/**
172 * device_create_file - create sysfs attribute file for device.
173 * @dev: device.
174 * @attr: device attribute descriptor.
175 */
176
177int device_create_file(struct device * dev, struct device_attribute * attr)
178{
179 int error = 0;
180 if (get_device(dev)) {
181 error = sysfs_create_file(&dev->kobj, &attr->attr);
182 put_device(dev);
183 }
184 return error;
185}
277行用 kobject_uevent()来添加设备。
278 - 279行调用 device_pm_add(),它是关于设备的电源管理方面的,在此只需要知道它的用途就行了,详细情况我们以后再考虑。
280 - 281行调用 bus_add_device(),它的定义如下:
// drivers/base/bus.c:
355/**
356 * bus_add_device - add device to bus
357 * @dev: device being added
358 *
359 * - Add the device to its bus's list of devices.
360 * - Try to attach to driver.
361 * - Create link to device's physical location.
362 */
363int bus_add_device(struct device * dev)
364{
365 struct bus_type * bus = get_bus(dev->bus);
366 int error = 0;
367
368 if (bus) {
369 pr_debug("bus %s: add device %s\n", bus->name,
dev->bus_id);
370 device_attach(dev);
371 klist_add_tail(&dev->knode_bus, &bus->klist_devices);
372 error = device_add_attrs(bus, dev);
373 if (!error) {
374 sysfs_create_link(&bus->devices.kobj, &dev->kobj,
dev->bus_id);
375 sysfs_create_link(&dev->kobj,
&dev->bus->subsys.kset.kobj, "bus");
376 }
377 }
378 return error;
379}
365行中的 get_bus()定义如下:
// drivers/base/bus.c:
540struct bus_type * get_bus(struct bus_type * bus)
541{
542 return bus ? container_of(subsys_get(&bus->subsys), struct
bus_type, subsys) : NULL;
543}
544
由上可知,它的功能实际上是先将 bus 所指向的总线所属的 subsys 引用计数增 1,然
后再返回指向 bus 的指针。
由前面的分析可知,在 platform_device_add()函数中,我们已经将 device 的 bus
成员指向了 platform_bus_type,所以 bus 指针不为 NULL。 下面看 370行的 device_attach(),它的定义如下:
// drivers/base/dd.c
123/**
124 * device_attach - try to attach device to a driver.
125 * @dev: device.
126 *
127 * Walk the list of drivers that the bus has and call
128 * driver_probe_device() for each pair. If a compatible
129 * pair is found, break out and return.
130 *
131 * Returns 1 if the device was bound to a driver;
132 * 0 if no matching device was found; error code otherwise.
133 *
134 * When called for a USB interface, @dev->parent->sem must be held.
135 */
136int device_attach(struct device * dev)
137{
138 int ret = 0;
139
140 down(&dev->sem);
141 if (dev->driver) {
142 device_bind_driver(dev);
143 ret = 1;
144 } else
145 ret = bus_for_each_drv(dev->bus, NULL, dev,
__device_attach);
146 up(&dev->sem);
147 return ret;
148}
回顾整个过程,我们还没有给 device 的 driver 成员赋值,所以 driver 指针应该指向
NULL ,因此这里会调用 bus_for_each_drv(dev->bus, NULL, dev,
__device_attach),它的定义如下:
// drivers/base/bus.c:
279static struct device_driver * next_driver(struct klist_iter * i)
280{
281 struct klist_node * n = klist_next(i);
282 return n ? container_of(n, struct device_driver, knode_bus) : NULL;
283}
284
285/**
286 * bus_for_each_drv - driver iterator
287 * @bus: bus we're dealing with.
288 * @start: driver to start iterating on.
289 * @data: data to pass to the callback. 290 * @fn: function to call for each driver.
291 *
292 * This is nearly identical to the device iterator above.
293 * We iterate over each driver that belongs to @bus, and call
294 * @fn for each. If @fn returns anything but 0, we break out
295 * and return it. If @start is not NULL, we use it as the head
296 * of the list.
297 *
298 * NOTE: we don't return the driver that returns a non-zero
299 * value, nor do we leave the reference count incremented for that
300 * driver. If the caller needs to know that info, it must set it
301 * in the callback. It must also be sure to increment the refcount
302 * so it doesn't disappear before returning to the caller.
303 */
304
305int bus_for_each_drv(struct bus_type * bus, struct device_driver * start,
306 void * data, int (*fn)(struct device_driver *, void *))
307{
308 struct klist_iter i;
309 struct device_driver * drv;
310 int error = 0;
311
312 if (!bus)
313 return -EINVAL;
314
315 klist_iter_init_node(&bus->klist_drivers, &i,
316 start ? &start->knode_bus : NULL);
317 while ((drv = next_driver(&i)) && !error)
318 error = fn(drv, data);
319 klist_iter_exit(&i);
320 return error;
321}
315 - 316行初始化一个 klist_iter 结构
317 - 318行依次从上面的驱动链表中取出一个 driver然后调用相应的回调函数。
结合我们当前的情景,实际上就是从 platform_bus_type下面的驱动链中依次取出一
个 driver,然后调用__device_attach()函数,而该函数的定义如下:
// drivers/base/dd.c
54/**
55 * driver_probe_device - attempt to bind device & driver.
56 * @drv: driver.
57 * @dev: device.
58 *
59 * First, we call the bus's match function, if one present, which
60 * should compare the device IDs the driver supports with the 61 * device IDs of the device. Note we don't do this ourselves
62 * because we don't know the format of the ID structures, nor what
63 * is to be considered a match and what is not.
64 *
65 * This function returns 1 if a match is found, an error if one
66 * occurs (that is not -ENODEV or -ENXIO), and 0 otherwise.
67 *
68 * This function must be called with @dev->sem held. When called
69 * for a USB interface, @dev->parent->sem must be held as well.
70 */
71int driver_probe_device(struct device_driver * drv, struct device * dev)
72{
73 int ret = 0;
74
75 if (drv->bus->match && !drv->bus->match(dev, drv))
76 goto Done;
77
78 pr_debug("%s: Matched Device %s with Driver %s\n",
79 drv->bus->name, dev->bus_id, drv->name);
80 dev->driver = drv;
81 if (dev->bus->probe) {
82 ret = dev->bus->probe(dev);
83 if (ret) {
84 dev->driver = NULL;
85 goto ProbeFailed;
86 }
87 } else if (drv->probe) {
88 ret = drv->probe(dev);
89 if (ret) {
90 dev->driver = NULL;
91 goto ProbeFailed;
92 }
93 }
94 device_bind_driver(dev);
95 ret = 1;
96 pr_debug("%s: Bound Device %s to Driver %s\n",
97 drv->bus->name, dev->bus_id, drv->name);
98 goto Done;
99
100 ProbeFailed:
101 if (ret == -ENODEV || ret == -ENXIO) {
102 /* Driver matched, but didn't support device
103 * or device not found.
104 * Not an error; keep going. 105 */
106 ret = 0;
107 } else {
108 /* driver matched but the probe failed */
109 printk(KERN_WARNING
110 "%s: probe of %s failed with error %d\n",
111 drv->name, dev->bus_id, ret);
112 }
113 Done:
114 return ret;
115}
116
117static int __device_attach(struct device_driver * drv, void * data)
118{
119 struct device * dev = data;
120 return driver_probe_device(drv, dev);
121}
根据函数开头那部分的说明,我们首先检测 bus 的 match 函数是否存在,如果存在的
话则调用它来检测驱动支持的设备 ID 与设备的 ID 是否匹配。说明里面还提到我们不能自
己进行比较,因为我们自己不清楚 ID 结构的格式等,也不知道什么算是匹配,什么算是不
匹配。
如果找到匹配的设备与驱动的话,将 device 的 driver 指针指向该驱动。
81 - 86 行检测 bus 的 probe()函数是否存在,如果存在的话,则调用它来检测我
们的驱动是否合适,如果不合适的话则报错
87 - 93行 如果 bus的 probe()函数不存在,则检测 driver 中的 probe()函数是否存
在,如果存在的话,则调用它来检测设备,同样如果不符号的话将报错。
94行调用 device_bind_driver()函数进行设备与驱动的绑定。下面来看它的定义:
// drivers/base/dd.c
27/**
28 * device_bind_driver - bind a driver to one device.
29 * @dev: device.
30 *
31 * Allow manual attachment of a driver to a device.
32 * Caller must have already set @dev->driver.
33 *
34 * Note that this does not modify the bus reference count
35 * nor take the bus's rwsem. Please verify those are accounted
36 * for before calling this. (It is ok to call with no other effort
37 * from a driver's probe() method.)
38 *
39 * This function must be called with @dev->sem held.
40 */
41void device_bind_driver(struct device * dev)
42{ 43 if (klist_node_attached(&dev->knode_driver))
44 return;
45
46 pr_debug("bound device '%s' to driver '%s'\n",
47 dev->bus_id, dev->driver->name);
48 klist_add_tail(&dev->knode_driver, &dev->driver->klist_devices);
49 sysfs_create_link(&dev->driver->kobj, &dev->kobj,
50 kobject_name(&dev->kobj));
51 sysfs_create_link(&dev->kobj, &dev->driver->kobj, "driver");
52}
// lib/klist.c:
174/**
175 * klist_node_attached - Say whether a node is bound to a list or not.
176 * @n: Node that we're testing.
177 */
178
179int klist_node_attached(struct klist_node * n)
180{
181 return (n->n_klist != NULL);
182}
43 - 44行,如果我们的设备的驱动链表不为空的话,则返回
48行,将 dev->knode_driver 加到设备的驱动支持的设备链表中。
49 - 50行,调用 sysfs_create_link()函数在 dev->driver->kobj 对应的目录里面
(pxa2xx-fb目录?)创建链接,指向 dev->kobj 对应的目录(pxa2xx-fb目录?),链
接名为
51 行,调用 sysfs_create_link()函数在 dev->kobj 对应的目录里面(pxa2xx-fb
目录?)创建链接,指向 dev->driver->kobj 对应的目录(pxa2xx-fb目录?),链接名
为"driver"
至些,设备与驱动的绑定 device_attach()完毕。我们再回到 bus_add_device()函
数里面,我们再把该函数列一下:
// drivers/base/bus.c:
355/**
356 * bus_add_device - add device to bus
357 * @dev: device being added
358 *
359 * - Add the device to its bus's list of devices.
360 * - Try to attach to driver.
361 * - Create link to device's physical location.
362 */
363int bus_add_device(struct device * dev)
364{
365 struct bus_type * bus = get_bus(dev->bus);
366 int error = 0;
367 368 if (bus) {
369 pr_debug("bus %s: add device %s\n", bus->name,
dev->bus_id);
370 device_attach(dev);
371 klist_add_tail(&dev->knode_bus, &bus->klist_devices);
372 error = device_add_attrs(bus, dev);
373 if (!error) {
374 sysfs_create_link(&bus->devices.kobj, &dev->kobj,
dev->bus_id);
375 sysfs_create_link(&dev->kobj,
&dev->bus->subsys.kset.kobj, "bus");
376 }
377 }
378 return error;
379}
371行,将 dev->knode_bus,添加到总线支持的设备列表 bus->klist_deives 中。
372行,调用 device_add_attrs()函数添加设备属性,其定义如下所示:
// drivers/base/bus.c:
323static int device_add_attrs(struct bus_type * bus, struct device * dev)
324{
325 int error = 0;
326 int i;
327
328 if (bus->dev_attrs) {
329 for (i = 0; attr_name(bus->dev_attrs); i++) {
330 error = device_create_file(dev,&bus->dev_attrs);
331 if (error)
332 goto Err;
333 }
334 }
335 Done:
336 return error;
337 Err:
338 while (--i >= 0)
339 device_remove_file(dev,&bus->dev_attrs);
340 goto Done;
341}
实际上就是将设备的属性一一创建一个属性文件反映在 sys 中。
374 行,在 bus->devices.kobj 对应的目录里面(platform 目录?)创建链接,指
向 dev->kobj 对应的目录(pxa2xx-fb 目录?),链接名为 dev->bus_id,即
"pxa2xx-fb"。
372 行,在 dev->kobj 对应的目录(pxa2xx-fb 目录?)里面创建链接,指向
dev->bus->subsys.kset.kobj对应的目录(platform目录?),链接名为"bus"。
再回到 device_add()函数里面, // drivers/base/core.c
282 if (parent)
283 klist_add_tail(&dev->knode_parent, &parent->klist_children);
285 /* notify platform of device entry */
286 if (platform_notify)
287 platform_notify(dev);
282 - 283 行,如果你指针不为空的话,则将 dev->knode_parent 加到
parent->klist_children。
286 - 287行,如果 platform_notify 指针不为空的话,则调用它,它的定义如下:
// drivers/base/core.c
int (*platform_notify)(struct device * dev) = NULL;
至此,device_add()函数分析完毕,device_initialize()和 device_add()函数组合成
device_register(),它们分别被称为上半部与下半部。
驱动程序模型-device
http://www.ourkernel.com/bbs/archiver/?tid-55.html
platform_device_register()注册过程
------------------------------------
/* arch/arm/mach-s3c2410/mach-smdk2410.c */
struct platform_device s3c_device_i2c = {
.name = "s3c2410-i2c",
.id = -1,
.num_resources = ARRAY_SIZE(s3c_i2c_resource),
.resource = s3c_i2c_resource,
};
/*
* 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); //添加一个片上的设备到设备层
}
/**
* platform_device_add - add a platform device to device hierarchy
* @pdev: platform device we're adding
*
* This is part 2 of platform_device_register(), though may be called
* separately _iff_ pdev was allocated by platform_device_alloc().
*/
int platform_device_add(struct platform_device *pdev)
{
int i, ret = 0;
if (!pdev)
return -EINVAL;
if (!pdev->dev.parent)
pdev->dev.parent = &platform_bus;
pdev->dev.bus = &platform_bus_type;
if (pdev->id != -1)
snprintf(pdev->dev.bus_id, BUS_ID_SIZE, "%s.%d", pdev->name,
pdev->id); /* 若支持同类多个设备,则用 pdev->name 和 pdev->id 在总线上
标识该设备 */
else
strlcpy(pdev->dev.bus_id, pdev->name, BUS_ID_SIZE); /* 否则,用 pdev->name(如
"s3c2410-i2c")在总线上标识该设备 */
for (i = 0; i < pdev->num_resources; i++) { /* 遍历资源数,并为各自在总线地址空间请
求分配 */
struct resource *p, *r = &pdev->resource[i];
if (r->name == NULL)
r->name = pdev->dev.bus_id;
p = r->parent;
if (!p) {
if (r->flags & IORESOURCE_MEM)
p = &iomem_resource; /* 作为 IO内存资源分配 */
else if (r->flags & IORESOURCE_IO)
p = &ioport_resource; /* 作为 IO Port 资源分配 */
}
if (p && insert_resource(p, r)) { /* 将新的resource 插入内核 resource tree */
printk(KERN_ERR
"%s: failed to claim resource %d\n",
pdev->dev.bus_id, i);
ret = -EBUSY;
goto failed;
}
}
pr_debug("Registering platform device '%s'. Parent at %s\n",
pdev->dev.bus_id, pdev->dev.parent->bus_id);
ret = device_add(&pdev->dev);
if (ret == 0)
return ret;
failed:
while (--i >= 0)
if (pdev->resource[i].flags & (IORESOURCE_MEM|IORESOURCE_IO))
release_resource(&pdev->resource[i]);
return ret; }
这里发现,添加 device 到内核最终还是调用的 device_add 函数。Platform_device_add 和
device_add 最主要的区别是多了一步 insert_resource(p, r)即将 platform 资源(resource)添加进
内核,由内核统一管理。
platform_driver_register()注册过程
--------------------------------------
static struct platform_driver s3c2410_i2c_driver = {
.probe = s3c24xx_i2c_probe,
.remove = s3c24xx_i2c_remove,
.resume = s3c24xx_i2c_resume,
.driver = {
.owner = THIS_MODULE,
.name = "s3c2410-i2c",
},
};
platform_driver_register(&s3c2410fb_driver)----->
driver_register(&drv->driver)----->
bus_add_driver(drv)----->
driver_attach(drv)----->
bus_for_each_dev(drv->bus, NULL, drv, __driver_attach)----->
__driver_attach(struct device * dev, void * data)----->
driver_probe_device(drv, dev)----->
really_probe(dev, drv)----->
在 really_probe()中:为设备指派管理该设备的驱动:dev->driver = drv, 调用 probe()函数初始
化设备:drv->probe(dev)
注:Platform_device和Platform_driver的使用请参考这篇文章:
《Linux Platform Device and Driver》
http://blog.chinaunix.net/u2/60011/showart.php?id=1018502
从 Linux 2.6 起引入了一套新的驱动管理和注册机制:Platform_device 和
Platform_driver。
Linux 中大部分的设备驱动,都可以使用这套机制, 设备用 Platform_device 表
示,驱动用 Platform_driver 进行注册。
Linux platform driver 机制和传统的 device driver 机制(通过
driver_register 函数进行注册)相比,一个十分明显的优势在于 platform 机制
将设备本身的资源注册进内核,由内核统一管理,在驱动程序中使用这些资源时
通过 platform device 提供的标准接口进行申请并使用。这样提高了驱动和资源
管理的独立性,并且拥有较好的可移植性和安全性(这些标准接口是安全的)。
Platform 机制的本身使用并不复杂,由两部分组成:platform_device 和
platfrom_driver。
通过 Platform 机制开发发底层驱动的大致流程为: 定义 platform_device
注册 platform_device 定义 platform_driver 注册 platform_driver。
首先要确认的就是设备的资源信息,例如设备的地址,中断号等。
在 2.6 内核中 platform 设备用结构体 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;
};
下面举 s3c2410 平台的 i2c 驱动作为例子来说明:
/* arch/arm/mach-s3c2410/devs.c */
/* I2C */ static struct resource s3c_i2c_resource[] = {
[0] = {
.start = S3C24XX_PA_IIC,
.end = S3C24XX_PA_IIC + S3C24XX_SZ_IIC - 1,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_IIC, //S3C2410_IRQ(27)
.end = IRQ_IIC,
.flags = IORESOURCE_IRQ,
}
};
这里定义了两组 resource,它描述了一个 I2C 设备的资源,第 1 组描述了这个
I2C 设备所占用的总线地址范围,IORESOURCE_MEM 表示第 1 组描述的是内存类型
的资源信息,第 2 组描述了这个 I2C 设备的中断号,IORESOURCE_IRQ 表示第 2
组描述的是中断资源信息。设备驱动会根据 flags 来获取相应的资源信息。
有了 resource 信息,就可以定义 platform_device 了:
struct platform_device s3c_device_i2c = {
.name = "s3c2410-i2c",
.id = -1,
.num_resources = ARRAY_SIZE(s3c_i2c_resource),
.resource = s3c_i2c_resource,
};
定义好了 platform_device 结构体后就可以调用函数 platform_add_devices 向
系统中添加该设备了,之后可以调用 platform_driver_register()进行设备注
册。要注意的是,这里的 platform_device 设备的注册过程必须在相应设备驱动
加载之前被调用,即执行 platform_driver_register 之前,原因是因为驱动注册
时需要匹配内核中所以已注册的设备名。
s3c2410-i2c 的 platform_device 是在系统启动时,在 cpu.c 里的
s3c_arch_init()函数里进行注册的,这个函数申明为
arch_initcall(s3c_arch_init);会在系统初始化阶段被调用。
arch_initcall 的优先级高于 module_init。所以会在 Platform 驱动注册之前调
用。(详细参考 include/linux/init.h)
s3c_arch_init 函数如下:
/* arch/arm/mach-3sc2410/cpu.c */
static int __init s3c_arch_init(void)
{
int ret;
……
/* 这里 board 指针指向在 mach-smdk2410.c 里的定义的 smdk2410_board,
里面包含了预先定义的 I2C Platform_device 等. */
if (board != NULL) {
struct platform_device **ptr = board->devices;
int i;
for (i = 0; i < board->devices_count; i++, ptr++) {
ret = platform_device_register(*ptr); //在这里进行
注册
if (ret) {
printk(KERN_ERR "s3c24xx: failed to add board
device %s (%d) @%p\n", (*ptr)->name,
ret, *ptr);
}
}
/* mask any error, we may not need all these board
* devices */
ret = 0; }
return ret;
}
同时被注册还有很多其他平台的 platform_device,详细查看
arch/arm/mach-s3c2410/mach-smdk2410.c 里的 smdk2410_devices 结构体。
驱动程序需要实现结构体 struct platform_driver,参考 drivers/i2c/busses
/* device driver for platform bus bits */
static struct platform_driver s3c2410_i2c_driver = {
.probe = s3c24xx_i2c_probe,
.remove = s3c24xx_i2c_remove,
.resume = s3c24xx_i2c_resume,
.driver = {
.owner = THIS_MODULE,
.name = "s3c2410-i2c",
},
};
在驱动初始化函数中调用函数 platform_driver_register()注册
platform_driver,需要注意的是 s3c_device_i2c 结构中 name 元素和
s3c2410_i2c_driver 结构中 driver.name 必须是相同的,这样在
platform_driver_register()注册时会对所有已注册的所有 platform_device
中的 name 和当前注册的 platform_driver 的 driver.name 进行比较,只有找到
相同的名称的 platfomr_device 才能注册成功,当注册成功时会调用
platform_driver 结构元素 probe 函数指针,这里就是 s3c24xx_i2c_probe,当进
入 probe 函数后,需要获取设备的资源信息,常用获取资源的函数主要是:
struct resource * platform_get_resource(struct platform_device *dev,
unsigned int type, unsigned int num);
根据参数 type 所指定类型,例如 IORESOURCE_MEM,来获取指定的资源。
struct int platform_get_irq(struct platform_device *dev, unsigned int
num);
获取资源中的中断号。
下面举 s3c24xx_i2c_probe 函数分析,看看这些接口是怎么用的。
前面已经讲了,s3c2410_i2c_driver 注册成功后会调用 s3c24xx_i2c_probe 执
行,下面看代码:
/* drivers/i2c/busses/i2c-s3c2410.c */
static int s3c24xx_i2c_probe(struct platform_device *pdev)
{
struct s3c24xx_i2c *i2c = &s3c24xx_i2c;
struct resource *res;
int ret;
/* find the clock and enable it */
i2c->dev = &pdev->dev;
i2c->clk = clk_get(&pdev->dev, "i2c");
if (IS_ERR(i2c->clk)) {
dev_err(&pdev->dev, "cannot get clock\n");
ret = -ENOENT;
goto out;
}
dev_dbg(&pdev->dev, "clock source %p\n", i2c->clk);
clk_enable(i2c->clk);
/* map the registers */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); /* 获取
设备的 IO 资源地址 */ if (res == NULL) {
dev_err(&pdev->dev, "cannot find IO resource\n");
ret = -ENOENT;
goto out;
}
i2c->ioarea = request_mem_region(res->start,
(res->end-res->start)+1, pdev->name); /* 申请这块 IO Region */
if (i2c->ioarea == NULL) {
dev_err(&pdev->dev, "cannot request IO\n");
ret = -ENXIO;
goto out;
}
i2c->regs = ioremap(res->start, (res->end-res->start)+1); /*
映射至内核虚拟空间 */
if (i2c->regs == NULL) {
dev_err(&pdev->dev, "cannot map IO\n");
ret = -ENXIO;
goto out;
}
dev_dbg(&pdev->dev, "registers %p (%p, %p)\n", i2c->regs,
i2c->ioarea, res);
/* setup info block for the i2c core */
i2c->adap.algo_data = i2c; i2c->adap.dev.parent = &pdev->dev;
/* initialise the i2c controller */
ret = s3c24xx_i2c_init(i2c);
if (ret != 0)
goto out;
/* find the IRQ for this unit (note, this relies on the init call
to ensure no current IRQs pending */
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); /* 获取
设备 IRQ 中断号 */
if (res == NULL) {
dev_err(&pdev->dev, "cannot find IRQ\n");
ret = -ENOENT;
goto out;
}
ret = request_irq(res->start, s3c24xx_i2c_irq, IRQF_DISABLED,
/* 申请 IRQ */
pdev->name, i2c);
……
return ret;
}
小思考:
那什么情况可以使用 platform driver 机制编写驱动呢?
我的理解是只要和内核本身运行依赖性不大的外围设备(换句话说只要不在内核
运行所需的一个最小系统之内的设备),相对独立的,拥有各自独自的资源
(addresses and IRQs),都可以用 platform_driver 实现。如:lcd,usb,uart
等,都可以用 platfrom_driver 写,而 timer,irq 等最小系统之内的设备则最好
不用 platfrom_driver 机制,实际上内核实现也是这样的。
参考资料:
linux-2.6.24/Documentation/driver-model/platform.txt
首先介绍一下注册一个驱动的步骤:
1、定义一个platform_driver结构
2、初始化这个结构,指定其probe、remove等函数,并初始化其中的driver变量
3、实现其probe、remove等函数
看platform_driver结构,定义于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 device_driver driver;
};
可见,它包含了设备操作的几个功能函数,同样重要的是,它还包含了一个device_driver结构。刚
才提到了驱动程序中需要初始化这个变量。下面看一下这个变量的定义,位于include/linux/device.h中:
struct device_driver {
const char * name;
struct bus_type * bus;
struct kobject kobj;
struct klist klist_devices;
struct klist_node knode_bus;
struct module * owner;
const char * mod_name; /* used for built-in modules */
struct module_kobject * mkobj;
int (*probe) (struct device * dev);
int (*remove) (struct device * dev);
void (*shutdown) (struct device * dev);
int (*suspend) (struct device * dev, pm_message_t state);
int (*resume) (struct device * dev);
};
需要注意这两个变量:name和owner。那么的作用主要是为了和相关的platform_device关联起
来,owner的作用是说明模块的所有者,驱动程序中一般初始化为THIS_MODULE。
下面是一个platform_driver的初始化实例:
static struct platform_driver s3c2410iis_driver = {
.probe = s3c2410iis_probe, .remove = s3c2410iis_remove,
.driver = {
.name = "s3c2410-iis",
.owner = THIS_MODULE,
},
};
上面的初始化是一个音频驱动的实例。注意其中的driver这个结构体,只初始化了其name和owner
两个量。接着看一下和driver相关的另一个结构,定义如下:
struct platform_device {
const char * name;
int id;
struct device dev;
u32 num_resources;
struct resource * resource;
};
该结构中也有一个name变量。platform_driver从字面上来看就知道是设备驱动。设备驱动是为谁服
务的呢?当然是设备了。platform_device就描述了设备对象。下面是一个具体的实例:
struct platform_device s3c_device_iis = {
.name = "s3c2410-iis",
.id = -1,
.num_resources = ARRAY_SIZE(s3c_iis_resource),
.resource = s3c_iis_resource,
.dev = {
.dma_mask = &s3c_device_iis_dmamask,
.coherent_dma_mask = 0xffffffffUL
}
};
它的name变量和刚才上面的platform_driver的name变量是一致的,内核正是通过这个一致性来
为驱动程序找到资源,即platform_device中的resource。这个结构的定义如下,位于
include/linux/ioport.h中:
struct resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
struct resource *parent, *sibling, *child;
};
下面是一个具体的实例:
static struct resource s3c_iis_resource[] = {
[0] = {
.start = S3C24XX_PA_IIS,
.end = S3C24XX_PA_IIS + S3C24XX_SZ_IIS -1,
.flags = IORESOURCE_MEM,
} };
这个结构的作用就是告诉驱动程序设备的起始地址和终止地址和设备的端口类型。这里的地址指的是
物理地址。
另外还需要注意platform_device中的 device结构,它详细描述了设备的情况,定义如下:
struct device {
struct klist klist_children;
struct klist_node knode_parent; /* node in sibling list */
struct klist_node knode_driver;
struct klist_node knode_bus;
struct device *parent;
struct kobject kobj;
char bus_id[BUS_ID_SIZE]; /* position on parent bus */
struct device_type *type;
unsigned is_registered:1;
unsigned uevent_suppress:1;
struct semaphore sem; /* semaphore to synchronize calls to
* its driver.
*/
struct bus_type * bus; /* type of bus device is on */
struct device_driver *driver; /* which driver has allocated this
device */
void *driver_data; /* data private to the driver */
void *platform_data; /* Platform specific data, device
core doesn't touch it */
struct dev_pm_info power;
#ifdef CONFIG_NUMA
int numa_node; /* NUMA node this device is close to */
#endif
u64 *dma_mask; /* dma mask (if dma'able device) */
u64 coherent_dma_mask;/* Like dma_mask, but for
alloc_coherent mappings as
not all hardware supports
64 bit addresses for consistent
allocations such descriptors. */
struct list_head dma_pools; /* dma pools (if dma'ble) */
struct dma_coherent_mem *dma_mem; /* internal for coherent mem
override */
/* arch specific additions */
struct dev_archdata archdata;
spinlock_t devres_lock;
struct list_head devres_head;
/* class_device migration path */
struct list_head node;
struct class *class; dev_t devt; /* dev_t, creates the sysfs "dev" */
struct attribute_group **groups; /* optional groups */
void (*release)(struct device * dev);
};
上面把驱动程序中涉及到的主要结构都介绍了,下面主要说一下驱动程序中怎样对这个结构进行处理,
以使驱动程序能运行。
相信大家都知道module_init()这个宏。驱动模块加载的时候会调用这个宏。它接收一个函数为参数,
作为它的参数的函数将会对上面提到的platform_driver进行处理。看一个实例:假如这里module_init
要接收的参数为s3c2410_uda1341_init这个函数,下面是这个函数的定义:
static int __init s3c2410_uda1341_init(void) {
memzero(&input_stream, sizeof(audio_stream_t));
memzero(&output_stream, sizeof(audio_stream_t));
return platform_driver_register(&s3c2410iis_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
中,列出如下:
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 来
找到相关的函数。所以,用户驱动程序还需要实现这些函数,具体实现和相关的设备有密切的关系,这里
就不再介绍了。