博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
platform设备添加流程(转载)
阅读量:4052 次
发布时间:2019-05-25

本文共 21189 字,大约阅读时间需要 70 分钟。

原文地址:
作者:
今天我以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
 
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.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
 
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.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
转自http://blogold.chinaunix.net/u1/50916/showart_1722081.html
你可能感兴趣的文章
Jenkins + Docker + SpringCloud 微服务持续集成 - 高可用集群部署(三)
查看>>
Golang struct 指针引用用法(声明入门篇)
查看>>
Linux 粘滞位 suid sgid
查看>>
C#控件集DotNetBar安装及破解
查看>>
Winform皮肤控件IrisSkin4.dll使用
查看>>
Winform多线程
查看>>
C# 托管与非托管
查看>>
Node.js中的事件驱动编程详解
查看>>
mongodb 命令
查看>>
MongoDB基本使用
查看>>
mongodb管理与安全认证
查看>>
nodejs内存控制
查看>>
nodejs Stream使用中的陷阱
查看>>
MongoDB 数据文件备份与恢复
查看>>
数据库索引介绍及使用
查看>>
MongoDB数据库插入、更新和删除操作详解
查看>>
MongoDB文档(Document)全局唯一ID的设计思路
查看>>
mongoDB简介
查看>>
Redis持久化存储(AOF与RDB两种模式)
查看>>
memcached工作原理与优化建议
查看>>