原文地址:platform设备添加流程(转载)
作者:joee33
今天我以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/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
32#define IORESOURCE_BITS
0x000000ff
33
34#define IORESOURCE_IO
0x00000100
35#define IORESOURCE_MEM
0x00000200
36#define IORESOURCE_IRQ
0x00000400
37#define IORESOURCE_DMA
0x00000800
38
39#define IORESOURCE_PREFETCH
0x00001000
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
下面来看我们所使用的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)
我们所使用的处理器为PXA255,所以对应的PXA_IRQ_SKIP应该为7,所以IRQ_LCD = 10,也就是它对应的中断信号线为10。
设置完了platform_device的相关成员后,下一步就是调用platform_add_devices()来向系统中添加该设备了,首先来看它的定义:
// drivers/base/platform.c:
int platform_add_devices(struct platform_device **devs, int num)
{
int i, ret = 0;
for (i = 0; i < num; i++) {
ret = platform_device_register(devs[i]);
if (ret) {
while (--i >= 0)
platform_device_unregister(devs[i]);
break;
}
}
return ret;
}
我们目前只关注LCD设备,所以不管for循环,关键的一句就是platform_device_register(),该函数用来进行平台设备的注册,首先来看它的定义:
// drivers/base/platform.c:
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
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 < pdev->num_resources; i++) {
255
struct resource *p, *r = &pdev->resource[i];
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 %dn",
271
pdev->dev.bus_id, i);
272
ret = -EBUSY;
273
goto failed;
274
}
275
}
276
277
pr_debug("Registering platform device '%s'. Parent at %sn",
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[i].flags & (IORESOURCE_MEM|IORESOURCE_IO))
287
release_resource(&pdev->resource[i]);
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,在定义的时候,内核开发者是后面加了一个注释:
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/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
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
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 < new->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
389
if (next->start < new->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
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)
150
return root;
151
if (start < root->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 < start)
166
continue;
167
return tmp;
168
}
169}
149 - 150行判断是否是一段有效的资源,151 - 154行判断资源是否在root的范围之内,否则就返回root,表示与根结点冲突。
156 - 168行遍历根节点root的child链表,以便检查是否有资源冲突,并将new插入到child链表中的合适位置(child链表是以I/O资源物理地址从低到高的顺序排列的)。为此,它用tmp指针指向当前正被扫描的resource结构,用指针p指向前一个resource结构的sibling指针成员变量,p的初始值为指向root->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;
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
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_proben");
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 < 0)
1288
goto failed;
1289#endif
1290
1291#ifdef DEBUG_VAR
1292
1294
1295
if (inf->lccr0 & LCCR0_INVALID_CONFIG_MASK)
1296
dev_warn(&dev->dev, "machine LCCR0 setting contains illegal bits: xn",
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: xn",
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 moden");
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 moden");
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 moden");
1313#endif
1314
1315
dev_dbg(&dev->dev, "got a %dx%dx%d LCDn",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 depthn");
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 devicen");
1326
ret = -ENOMEM; // only reason for pxafb_init_fbinfo to fail is kmalloc
1327
goto failed;
1328
}
1329
1330
1331
ret = pxafb_map_video_memory(fbi);
1332
if (ret) {
1333
dev_err(&dev->dev, "Failed to allocate video RAM: %dn", 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: %dn", ret);
1341
ret = -EBUSY;
1342
goto failed;
1343
}
1344
1345
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 < 0) {
1356
dev_err(&dev->dev, "Failed to register framebuffer device: %dn", 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
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/zengzhaonong/blog/item/654c63d92307f0eb39012fff
.html
驱动程序模型-platform
http://www.ourkernel/bbs/archiver/?tid-67.html
Linux对I/O端口资源的管理
http://www.host01/article/server/00070002/0542417251875372.htm
Linux对I/O端口资源的管理(ZZ)
http://hi.baidu/zengzhaonong/blog/item/0d6f6909e2aa5dad2fddd444
.html
platform_device和platform_driver
http://linux.chinaunix/techdoc/net/2008/09/10/1031351.shtml
linux resource, platform_device和驱动的关系
http://blog.csdn/wawuta/archive/2007/03/14/1529621.aspx
转自http://blogold.chinaunix/u1/50916/showart_1722081.html
platform总线是kernel中最近加入的一种虚拟总线,它被用来连接处在仅有最少基本组件的总线上的那些设备.这样的总线包括许多片上系统上的那些用来整合外设的总线, 也包括一些"古董" PC上的连接器; 但不包括像PCI或USB这样的有庞大正规说明的总线.
平台设备
~~~~~~
那什么情况可以使用platform driver机制编写驱动呢?
首先要定义一个platform_device,我们先来看一下platform_device结构的定义,如下所示:
// include/linux/platform_device.h:
下面是对应的FB设备的变量定义
// arch/arm/mach-pxa/generic.c
// include/linux/ioport.h:
下面关于这方面的内容,参考了http://hi.baidu/zengzhaonong/blog/item/654c63d92307f0eb39012fff
struct resource 是linux对挂接在4G总线空间上的设备实体的管理方式。
// include/linux/ioport.h:
// arch/arm/mach-pxa/generic.c
static struct resource pxafb_resources[] = {
};
// include/asm-arm/arch-pxa/irqs.h:
设置完了platform_device的相关成员后,下一步就是调用platform_add_devices()来向系统中添加该设备了,首先来看它的定义:
// drivers/base/platform.c:
int platform_add_devices(struct platform_device **devs, int num)
{
}
// drivers/base/platform.c:
int platform_device_register(struct platform_device * pdev)
{
}
// drivers/base/platform.c:
// drivers/base/platform.c:
// drivers/base/platform.c:
// kernel/resource.c
// include/asm/io.h:
#define IO_SPACE_LIMIT 0xffffffff // 这并不是针对 ARM 平台的定义,针对 ARM 平台的定义我没有找到,所以暂且列一个在这里占位
// kernel/resource.c
----------------------------------------
struct resource ioport_resource = {
};
struct resource iomem_resource = {
};
/usr/src/linux/include/asm-i386/io.h
#define IO_SPACE_LIMIT 0xffff
0 ~ 0xffff
// kernel/resource.c
// kernel/resource.c:
// kernel/resource.c:
(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,表示发生资源冲突。
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// include/linux/platform_device.h:
// include/linux/device.h:
// drivers/video/pxafb.c:
1384static struct platform_driver pxafb_driver = {
1385
1386#ifdef CONFIG_PM
1387
1388
1389#endif
1390
1391
1392
1393};
// drivers/video/pxafb.c:
1411 int __devinit pxafb_init(void)
1412{
1413#ifndef MODULE
1414
1415
1416
1417
1418
1419#endif
1420
1421}
1422
1423 module_init(pxafb_init);
// drivers/base/platform.c
struct file_operations {
};
// drivers/video/pxafb.c:
1271int __init pxafb_probe(struct platform_device *dev)
1272{
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285#ifdef CONFIG_FB_PXA_PARAMETERS
1286
1287
1288
1289#endif
1290
1291#ifdef DEBUG_VAR
1292
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313#endif
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360#ifdef CONFIG_PM
1361
1362#endif
1363
1364#ifdef CONFIG_CPU_FREQ
1365
1366
1367
1368
1369#endif
1370
1371
1374
1375
1376
1377
1378failed:
1379
1380
1381
1382}
参考文献:
struct--resource
驱动程序模型-platform
Linux对I/O端口资源的管理
Linux对I/O端口资源的管理(ZZ)
platform_device和platform_driver
linux resource, platform_device和驱动的关系
转自http://blogold.chinaunix/u1/50916/showart_1722081.html
更多推荐
platform设备添加流程(转载)
发布评论