今天我以fb裝置的註冊過程來分析platform裝置的新增流程
platform匯流排是kernel中最近加入的一種虛擬匯流排,它被用來連線處在僅有最少基本元件的匯流排上的那些裝置.這樣的匯流排包括許多片上系統上的那些用來整合外設的匯流排, 也包括一些"古董" PC上的聯結器; 但不包括像PCI或USB這樣的有龐大正規說明的匯流排.
平臺裝置
~~~~~~
平臺裝置通常指的是系統中的自治體, 包括老式的基於埠的裝置和連線外設匯流排的北橋(host bridges),以及整合在片上系統中的絕大多數控制器. 它們通常擁有的一個共同特徵是直接編址於CPU匯流排上. 即使在某些罕見的情況下, 平臺裝置會通過某段其他型別的匯流排連入系統, 它們的暫存器也會被直接編址.平臺裝置會分到一個名稱(用在驅動繫結中)以及一系列諸如地址和中斷請求號(IRQ)之類的資源.
那什麼情況可以使用platform driver機制編寫驅動呢?
我的理解是隻要和核心本身執行依賴性不大的外圍裝置(換句話說只要不在核心執行所需的一個最小系統之內的裝置),相對獨立的,擁有各自獨自的資源(addresses and IRQs),都可以用platform_driver實現。如:lcd,usb,uart等,都可以用platfrom_driver寫,而timer,irq等最小系統之內的裝置則最好不用platfrom_driver機制,實際上核心實現也是這樣的。下面繼續我們的分析過程。
首先要定義一個platform_device,我們先來看一下platform_device結構的定義,如下所示:
// include/linux/platform_device.h:
16struct platform_device {
17 const char * name;
18 u32 id;
19 struct device dev;
20 u32 num_resources;
21 struct resource * resource;
22};
下面是對應的FB裝置的變數定義
// arch/arm/mach-pxa/generic.c
229static struct platform_device pxafb_device = {
230 .name = "pxa2xx-fb",
231 .id = -1,
232 .dev = {
233 .platform_data = &pxa_fb_info,
234 .dma_mask = &fb_dma_mask,
235 .coherent_dma_mask = 0xffffffff,
236 },
237 .num_resources = ARRAY_SIZE(pxafb_resources),
238 .resource = pxafb_resources,
239};
由上可以看出,name成員表示裝置名,系統正是通過這個名字來與驅動繫結的,所以驅動裡面相應的裝置名必須與該項相符合;id表示裝置編號,id的值為-1表示只有一個這樣的裝置。
該結構中比較重要的一個成員就是resource, Linux設計了這個通用的資料結構來描述各種I/O資源(如:I/O埠、外設記憶體、DMA和IRQ等)。它的定義如下:
// include/linux/ioport.h:
16struct resource {
17 const char *name;
18 unsigned long start, end;
19 unsigned long flags;
20 struct resource *parent, *sibling, *child;
21};
下面關於這方面的內容,參考了http://hi.baidu.com/zengzhaonong/blog/item/654c63d92307f0eb39012fff.html
struct resource 是linux對掛接在4G匯流排空間上的裝置實體的管理方式。
一個獨立的掛接在cpu匯流排上的裝置單元,一般都需要一段線性的地址空間來描述裝置自身,linux是怎麼管理所有的這些外部"實體地址範圍段",進而給使用者和linux自身一個比較好的觀察4G匯流排上掛接的一個個裝置實體的簡潔、統一級聯檢視的呢?
linux採用struct resource結構體來描述一個掛接在cpu匯流排上的裝置實體(32位cpu的匯流排地址範圍是0~4G):
resource->start 描述裝置實體在cpu匯流排上的線性起始實體地址;
resource->end 描述裝置實體在cpu匯流排上的線性結尾實體地址;
resource->name 描述這個裝置實體的名稱,這個名字開發人員可以隨意起,但最好貼切;
resource->flag 描述這個裝置實體的一些共性和特性的標誌位;
只需要瞭解一個裝置實體的以上4項,linux就能夠知曉這個掛接在cpu匯流排的上的裝置實體的基本使用情況,也就是 [resource->start, resource->end]這段實體地址現在是空閒著呢,還是被什麼裝置佔用著呢?
linux會堅決避免將一個已經被一個裝置實體使用的匯流排實體地址區間段[resource->start, resource->end],再分配給另一個後來的也需要這個區間段或者區間段內部分地址的裝置實體,進而避免裝置之間出現對同一匯流排實體地址段的重複引用,而造成對唯一實體地址的裝置實體二義性.
以上的4個屬性僅僅用來描述一個裝置實體自身,或者是裝置實體可以用來自治的單元,但是這不是linux所想的,linux需要管理4G物理匯流排的所有空間,所以掛接到匯流排上的形形色色的各種裝置實體,這就需要鏈在一起,因此resource結構體提供了另外3個成員:指標parent、sibling和 child:分別為指向父親、兄弟和子資源的指標,它們的設定是為了以一種樹的形式來管理各種I/O資源,以root source為例,root->child(*pchild)指向root所有孩子中地址空間最小的一個;pchild->sibling是兄弟連結串列的開頭,指向比自己地址空間大的兄弟。
屬性flags是一個unsigned long型別的32位標誌值,用以描述資源的屬性。比如:資源的型別、是否只讀、是否可快取,以及是否已被佔用等。下面是一部分常用屬性標誌位的定義
// include/linux/ioport.h:
29/*
30 * IO resources have these defined flags.
31 */
32#define IORESOURCE_BITS 0x000000ff /* Bus-specific bits */
33
34#define IORESOURCE_IO 0x00000100 /* Resource type */
35#define IORESOURCE_MEM 0x00000200
36#define IORESOURCE_IRQ 0x00000400
37#define IORESOURCE_DMA 0x00000800
38
39#define IORESOURCE_PREFETCH 0x00001000 /* No side effects */
40#define IORESOURCE_READONLY 0x00002000
41#define IORESOURCE_CACHEABLE 0x00004000
42#define IORESOURCE_RANGELENGTH 0x00008000
43#define IORESOURCE_SHADOWABLE 0x00010000
44#define IORESOURCE_BUS_HAS_VGA 0x00080000
45
46#define IORESOURCE_DISABLED 0x10000000
47#define IORESOURCE_UNSET 0x20000000
48#define IORESOURCE_AUTO 0x40000000
49#define IORESOURCE_BUSY 0x80000000 /* Driver has marked this resource busy */
下面來看我們所使用的LCD所佔用的資源,如下所示:
// arch/arm/mach-pxa/generic.c
static struct resource pxafb_resources[] = {
[0] = {
.start = 0x44000000,
.end = 0x4400ffff,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_LCD,
.end = IRQ_LCD,
.flags = IORESOURCE_IRQ,
},
};
由上可知LCD佔用的資源包括兩類,一類是MEM型別,一類是IRQ型別。MEME型別資源對應的實體地址範圍是 0x44000000 - 0x4400ffff;IRQ型別資源對應的實體地址範圍是IRQ_LCD,檢視相應的定義:
// include/asm-arm/arch-pxa/irqs.h:
15#ifdef CONFIG_PXA27x
16#define PXA_IRQ_SKIP 0
17#else
18#define PXA_IRQ_SKIP 7
19#endif
20
21#define PXA_IRQ(x) ((x) - PXA_IRQ_SKIP)
43#define IRQ_LCD PXA_IRQ(17) /* LCD Controller Service Request */
我們所使用的處理器為PXA255,所以對應的PXA_IRQ_SKIP應該為7,所以IRQ_LCD = 10,也就是它對應的中斷訊號線為10 .
設定完了platform_device的相關成員後,下一步就是呼叫platform_add_devices()來向系統中新增該裝置了,首先來看它的定義:
// drivers/base/platform.c:
/**
* platform_add_devices - add a numbers of platform devices
* @devs: array of platform devices to add
* @num: number of platform devices in array
*/
int platform_add_devices(struct platform_device **devs, int num)
{
int i, ret = 0;
for (i = 0; i < 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:
/**
* 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 < 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 %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[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,在定義的時候,核心開發者是後面加了一個註釋: /* position on parent bus */
254 - 275行進行資源處理,首先設定資源的名稱,如果name成員為空的話,就將該成員設定為我們前面已經賦值的bus_id,也就是"pxa2xx-fb"
260 - 266行先將 p 指向我們當前處理的資源的 parent 指標成員,如果 p 指向NULL,也就是我們當前處理的資源的 parent 指標成員指向NULL的話,再檢測當前處理的資源的型別,如果是MEM型別的,則設定 p 指向 iomem_resource ,如果是IO型別的,則使 p 指向 ioport_resource,這兩個均是 struct resource 型別的變數,它們的定義如下:
// kernel/resource.c
23 struct resource ioport_resource = {
24 .name = "PCI IO",
25 .start = 0,
26 .end = IO_SPACE_LIMIT,
27 .flags = IORESOURCE_IO,
28};
29 EXPORT_SYMBOL(ioport_resource);
30
31 struct resource iomem_resource = {
32 .name = "PCI mem",
33 .start = 0,
34 .end = -1,
35 .flags = IORESOURCE_MEM,
36};
37 EXPORT_SYMBOL(iomem_resource);
// include/asm/io.h:
#define IO_SPACE_LIMIT 0xffffffff // 這並不是針對 ARM 平臺的定義,針對 ARM 平臺的定義我沒有找到,所以暫且列一個在這裡佔位
關於這兩個struct resource型別的變數,在網路上搜到了如下的資訊:(http://hi.baidu.com/zengzhaonong/blog/item/654c63d92307f0eb39012fff.html)
實體記憶體頁面是重要的資源。從另一個角度看,地址空間本身,或者物理儲存器在地址空間中的位置,也是一種資源,也要加以管理 -- resource管理地址空間資源。
核心中有兩棵resource樹,一棵是iomem_resource,另一棵是ioport_resource,分別代表著兩類不同性質的地址資源。兩棵樹的根也都是resource資料結構,不過這兩個資料結構描述的並不是用於具體操作物件的地址資源,而是概念上的整個地址空間。
將主機板上的ROM空間納入iomem_resource樹中;系統固有的I/O類資源則納入ioport_resource樹
// kernel/resource.c
----------------------------------------
struct resource ioport_resource = {
.name = "PCI IO",
.start = 0,
.end = IO_SPACE_LIMIT,
.flags = IORESOURCE_IO,
};
struct resource iomem_resource = {
.name = "PCI mem",
.start = 0,
.end = -1,
.flags = IORESOURCE_MEM,
};
/usr/src/linux/include/asm-i386/io.h
#define IO_SPACE_LIMIT 0xffff
0 ~ 0xffff <===> 64K
繼續我們的函式, 268 - 276行將我們當前處理的資源插入到 p 指標指向的resource樹裡面。這裡面只有一個關鍵的函式insert_resource()
// kernel/resource.c
416/**
417 * insert_resource - Inserts a resource in the resource tree
418 * @parent: parent of the new resource
419 * @new: new resource to insert
420 *
421 * Returns 0 on success, -EBUSY if the resource can't be inserted.
422 *
423 * This function is equivalent to request_resource when no conflict
424 * happens. If a conflict happens, and the conflicting resources
425 * entirely fit within the range of the new resource, then the new
426 * resource is inserted and the conflicting resources become children of
427 * the new resource.
428 */
429int insert_resource(struct resource *parent, struct resource *new)
430{
431 struct resource *conflict;
432
433 write_lock(&resource_lock);
434 conflict = __insert_resource(parent, new);
435 write_unlock(&resource_lock);
436 return conflict ? -EBUSY : 0;
437}
資源鎖resource_lock對所有資源樹進行讀防寫,任何程式碼段在訪問某一顆資源樹之前都必須先持有該鎖,該鎖的定義也在 resource.c中。鎖機制我們暫且不管,該函式裡面關鍵的就是__insert_resource()函式:
// kernel/resource.c:
365/*
366 * Insert a resource into the resource tree. If successful, return NULL,
367 * otherwise return the conflicting resource (compare to __request_resource())
368 */
369static struct resource * __insert_resource(struct resource *parent, struct resource *new)
370{
371 struct resource *first, *next;
372
373 for (;; parent = first) {
374 first = __request_resource(parent, new);
375 if (!first)
376 return first;
377
378 if (first == parent)
379 return first;
380
381 if ((first->start > new->start) || (first->end < 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 /* Partial overlap? Bad, and unfixable */
389 if (next->start < new->start || next->end > new->end)
390 return next;
391 &n
bsp; 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)
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; /* 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 < 0)
1288 goto failed;
1289#endif
1290
1291#ifdef DEBUG_VAR
1292 /* Check for various illegal bit-combinations. Currently only
1293 * a warning is given. */
1294
1295 if (inf->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 correc
tly 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 < 0) {
1356 dev_err(&dev->dev, "Failed to register framebuffer device: %d/n", ret);
1357 goto failed;
1358 }
1359
1360#ifdef CONFIG_PM
1361 // TODO
1362#endif
1363
1364#ifdef CONFIG_CPU_FREQ
1365 fbi->freq_transition.notifier_call = pxafb_freq_transition;
1366 fbi->freq_policy.notifier_call = pxafb_freq_policy;
1367 cpufreq_register_notifier(&fbi->freq_transition, CPUFREQ_TRANSITION_NOTIFIER);
1368 cpufreq_register_notifier(&fbi->freq_policy, CPUFREQ_POLICY_NOTIFIER);
1369#endif
1370
1371 /*
1372 * Ok, now enable the LCD controller
1373 */
1374 set_ctrlr_state(fbi, C_ENABLE);
1375
1376 return 0;
1377
1378failed:
1379 platform_set_drvdata(dev, NULL);
1380 kfree(fbi);
1381 return ret;
1382}
參考文獻:
struct--resource http://hi.baidu.com/zengzhaonong/blog/item/654c63d92307f0eb39012fff.html
驅動程式模型-platform http://www.ourkernel.com/bbs/archiver/?tid-67.html
Linux對I/O埠資源的管理 http://www.host01.com/article/server/00070002/0542417251875372.htm
Linux對I/O埠資源的管理(ZZ) http://hi.baidu.com/zengzhaonong/blog/item/0d6f6909e2aa5dad2fddd444.html
platform_device和platform_driver http://linux.chinaunix.net/techdoc/net/2008/09/10/1031351.shtml
linux resource, platform_device和驅動的關係 http://blog.csdn.net/wawuta/archive/2007/03/14/1529621.aspx