1.1 platform匯流排、裝置與驅動
在Linux 2.6的裝置驅動模型中,關心匯流排、裝置和驅動這3個實體,匯流排將裝置和驅動繫結。在系統每註冊一個裝置的時候,會尋找與之匹配的驅動;相反的,在系統每註冊一個驅動的時候,會尋找與之匹配的裝置,而匹配由匯流排完成。
一個現實的Linux裝置和驅動通常都需要掛接在一種匯流排上,對於本身依附於PCI、USB、I2
C、SPI等的裝置而言,這自然不是問題,但是在嵌入式系統裡面,SoC系統中整合的獨立的外設控制器、掛接在SoC記憶體空間的外設等確不依附於此類總
線。基於這一背景,Linux發明了一種虛擬的匯流排,稱為platform匯流排,相應的裝置稱為platform_device,而驅動成為
platform_driver。
C、SPI等的裝置而言,這自然不是問題,但是在嵌入式系統裡面,SoC系統中整合的獨立的外設控制器、掛接在SoC記憶體空間的外設等確不依附於此類總
線。基於這一背景,Linux發明了一種虛擬的匯流排,稱為platform匯流排,相應的裝置稱為platform_device,而驅動成為
platform_driver。
注意,所謂的platform_device並不是與字元裝置、塊裝置和網路裝置並列的概念,而是Linux系統提供的一種附加手段,例如,在S3C6410處理器中,把內部整合的I2
C、RTC、SPI、LCD、看門狗等控制器都歸納為platform_device,而它們本身就是字元裝置。platform_device結構體的定義如程式碼清單1所示。
C、RTC、SPI、LCD、看門狗等控制器都歸納為platform_device,而它們本身就是字元裝置。platform_device結構體的定義如程式碼清單1所示。
程式碼清單1 platform_device結構體
1 struct platform_device {
2 const char * name;/* 裝置名 */
3 u32 id;
4 struct device dev;
5 u32 num_resources;/* 裝置所使用各類資源數量 */
6 struct resource * resource;/* 資源 */
7 };
platform_driver這個結構體中包含probe()、remove()、shutdown()、suspend()、resume()函式,通常也需要由驅動實現,如程式碼清單2。
程式碼清單2 platform_driver結構體
1 struct platform_driver {
2 int (*probe)(struct platform_device *);
3 int (*remove)(struct platform_device *);
4 void (*shutdown)(struct platform_device *);
5 int (*suspend)(struct platform_device *, pm_message_t state);
6 int (*suspend_late)(struct platform_device *, pm_message_t state);
7 int (*resume_early)(struct platform_device *);
8 int (*resume)(struct platform_device *);
9 struct pm_ext_ops *pm;
10 struct device_driver driver;
11};
系統中為platform匯流排定義了一個bus_type的例項platform_bus_type,其定義如程式碼清單15.3。
程式碼清單15.3 platform匯流排的bus_type 例項platform_bus_type
1 struct bus_type platform_bus_type = {
2 .name = “platform”,
3 .dev_attrs = platform_dev_attrs,
4 .match = platform_match,
5 .uevent = platform_uevent,
6 .pm = PLATFORM_PM_OPS_PTR,
7 };
8 EXPORT_SYMBOL_GPL(platform_bus_type);
這裡要重點關注其match()成員函式,正是此成員表明了platform_device和platform_driver之間如何匹配,如程式碼清單4所示。
程式碼清單4 platform_bus_type的match()成員函式
1 static int platform_match(struct device *dev, struct device_driver *drv)
2 {
3 struct platform_device *pdev;
4
5 pdev = container_of(dev, struct platform_device, dev);
6 return (strncmp(pdev->name, drv->name, BUS_ID_SIZE) == 0);
7 }
從程式碼清單4的第6行可以看出,匹配platform_device和platform_driver主要看二者的name欄位是否相同。
對platform_device的定義通常在BSP的板檔案中實現,在板檔案中,將platform_device歸納為一個陣列,最終通過
platform_add_devices()函式統一註冊。platform_add_devices()函式可以將平臺裝置新增到系統中,這個函式的
原型為:
platform_add_devices()函式統一註冊。platform_add_devices()函式可以將平臺裝置新增到系統中,這個函式的
原型為:
int platform_add_devices(struct platform_device **devs, int num);
該函式的第一個引數為平臺裝置陣列的指標,第二個引數為平臺裝置的數量,它內部呼叫了platform_device_register()函式用於註冊單個的平臺裝置。
1.2 將globalfifo作為platform裝置
現在我們將前面章節的globalfifo驅動掛接到platform匯流排上,要完成2個工作:
1. 將globalfifo移植為platform驅動。
2. 在板檔案中新增globalfifo這個platform裝置。
為完成將globalfifo移植到platform驅動的工作,需要在原始的globalfifo字元裝置驅動中套一層
platform_driver的外殼,如程式碼清單5。注意進行這一工作後,並沒有改變globalfifo是字元裝置的本質,只是將其掛接到了
platform匯流排。
platform_driver的外殼,如程式碼清單5。注意進行這一工作後,並沒有改變globalfifo是字元裝置的本質,只是將其掛接到了
platform匯流排。
程式碼清單5 為globalfifo新增platform_driver
1 static int __devinit globalfifo_probe(struct platform_device *pdev)
2 {
3 int ret;
4 dev_t devno = MKDEV(globalfifo_major, 0);
5
6 /* 申請裝置號*/
7 if (globalfifo_major)
8 ret = register_chrdev_region(devno, 1, “globalfifo”);
9 else { /* 動態申請裝置號 */
10 ret = alloc_chrdev_region(&devno, 0, 1, “globalfifo”);
11 globalfifo_major = MAJOR(devno);
12 }
13 if (ret < 0)
14 return ret;
15 /* 動態申請裝置結構體的記憶體*/
16 globalfifo_devp = kmalloc(sizeof(struct globalfifo_dev), GFP_KERNEL);
17 if (!globalfifo_devp) { /*申請失敗*/
18 ret = – ENOMEM;
19 goto fail_malloc;
20 }
21
22 memset(globalfifo_devp, 0, sizeof(struct globalfifo_dev));
23
24 globalfifo_setup_cdev(globalfifo_devp, 0);
25
26 init_MUTEX(&globalfifo_devp->sem); /*初始化訊號量*/
27 init_waitqueue_head(&globalfifo_devp->r_wait); /*初始化讀等待佇列頭*/
28 init_waitqueue_head(&globalfifo_devp->w_wait); /*初始化寫等待佇列頭*/
29
30 return 0;
31
32 fail_malloc: unregister_chrdev_region(devno, 1);
33 return ret;
34 }
35
36 static int __devexit globalfifo_remove(struct platform_device *pdev)
37 {
38 cdev_del(&globalfifo_devp->cdev); /*登出cdev*/
39 kfree(globalfifo_devp); /*釋放裝置結構體記憶體*/
40 unregister_chrdev_region(MKDEV(globalfifo_major, 0), 1); /*釋放裝置號*/
41 return 0;
42 }
43
44 static struct platform_driver globalfifo_device_driver = {
45 .probe = globalfifo_probe,
46 .remove = __devexit_p(globalfifo_remove),
47 .driver = {
48 .name = “globalfifo”,
49 .owner = THIS_MODULE,
50 }
51 };
52
53 static int __init globalfifo_init(void)
54 {
55 return platform_driver_register(&globalfifo_device_driver);
56 }
57
58 static void __exit globalfifo_exit(void)
59 {
60 platform_driver_unregister(&globalfifo_device_driver);
61 }
62
63 module_init(globalfifo_init);
64 module_exit(globalfifo_exit);
在程式碼清單5中,模組載入和解除安裝函式僅僅通過platform_driver_register()、
platform_driver_unregister()函式進行platform_driver的註冊與登出,而原先註冊和登出字元裝置的工作已經被
移交到platform_driver的probe()和remove()成員函式中。
platform_driver_unregister()函式進行platform_driver的註冊與登出,而原先註冊和登出字元裝置的工作已經被
移交到platform_driver的probe()和remove()成員函式中。
程式碼清單5未列出的部分與原始的globalfifo驅動相同,都是實現作為字元裝置驅動核心的file_operations的成員函式。
為了完成在板檔案中新增globalfifo這個platform裝置的工作,需要在板檔案(對於LDD6410而言,為arch/arm/mach-s3c6410/ mach-ldd6410.c)中新增相應的程式碼,如程式碼清單6。
程式碼清單6 globalfifo對應的platform_device
1 static struct platform_device globalfifo_device = {
2 .name = “globalfifo”,
3 .id = -1,
4 };
對於LDD6410開發板而言,為了完成上述globalfifo_device這一platform_device的註冊,只需要將其地址放入
arch/arm/mach-s3c6410/ mach-ldd6410.c中定義的ldd6410_devices陣列,如:
arch/arm/mach-s3c6410/ mach-ldd6410.c中定義的ldd6410_devices陣列,如:
static struct platform_device *ldd6410_devices[] __initdata = {
+ & globalfifo_device,
#ifdef CONFIG_FB_S3C_V2
&s3c_device_fb,
#endif
&s3c_device_hsmmc0,
…
}
在載入LDD6410驅動後,在sysfs中會發現如下結點:
/sys/bus/platform/devices/globalfifo/
/sys/devices/platform/globalfifo/
留意一下程式碼清單5的第48行和程式碼清單6的第2行,platform_device和platform_driver的name一致,這是二者得以匹配的前提。
1.3 platform裝置資源和資料
留意一下程式碼清單1中platform_device結構體定義的第5~6行,描述了platform_device的資源,資源本身由resource結構體描述,其定義如程式碼清單7。
程式碼清單7 resouce結構體定義
1 struct resource {
2 resource_size_t start;
3 resource_size_t end;
4 const char *name;
5 unsigned long flags;
6 struct resource *parent, *sibling, *child;
7 };
我們通常關心start、end和flags這3個欄位,分別標明資源的開始值、結束值和型別,flags可以為IORESOURCE_IO、
IORESOURCE_MEM、IORESOURCE_IRQ、IORESOURCE_DMA等。start、end的含義會隨著flags而變更,如當
flags為IORESOURCE_MEM時,start、end分別表示該platform_device佔據的記憶體的開始地址和結束地址;當
flags為IORESOURCE_IRQ時,start、end分別表示該platform_device使用的中斷號的開始值和結束值,如果只使用了
1箇中斷號,開始和結束值相同。對於同種型別的資源而言,可以有多份,譬如說某裝置佔據了2個記憶體區域,則可以定義2個IORESOURCE_MEM資
源。
IORESOURCE_MEM、IORESOURCE_IRQ、IORESOURCE_DMA等。start、end的含義會隨著flags而變更,如當
flags為IORESOURCE_MEM時,start、end分別表示該platform_device佔據的記憶體的開始地址和結束地址;當
flags為IORESOURCE_IRQ時,start、end分別表示該platform_device使用的中斷號的開始值和結束值,如果只使用了
1箇中斷號,開始和結束值相同。對於同種型別的資源而言,可以有多份,譬如說某裝置佔據了2個記憶體區域,則可以定義2個IORESOURCE_MEM資
源。
對resource的定義也通常在BSP的板檔案中進行,而在具體的裝置驅動中透過platform_get_resource()這樣的API來獲取,此API的原型為:
struct resource *platform_get_resource(struct platform_device *, unsigned int, unsigned int);
譬如在LDD6410開發板的板檔案中為DM9000網路卡定義瞭如下resouce:
static struct resource ldd6410_dm9000_resource[] = {
[0] = {
.start = 0x18000000,
.end = 0x18000000 + 3,
.flags = IORESOURCE_MEM
},
[1] = {
.start = 0x18000000 + 0x4,
.end = 0x18000000 + 0x7,
.flags = IORESOURCE_MEM
},
[2] = {
.start = IRQ_EINT(7),
.end = IRQ_EINT(7),
.flags = IORESOURCE_IRQ | IORESOURCE_IRQ_HIGHLEVEL,
}
};
在DM9000網路卡的驅動中則是通過如下辦法拿到這3份資源:
db->addr_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
db->data_res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
db->irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
對於IRQ而言,platform_get_resource()還有一個進行了封裝的變體platform_get_irq(),其原型為:
int platform_get_irq(struct platform_device *dev, unsigned int num);
它實際上呼叫了“platform_get_resource(dev, IORESOURCE_IRQ, num);”。
裝置除了可以在BSP中定義資源以外,還可以附加一些資料資訊,因為對裝置的硬體描述除了中斷、記憶體、DMA通道以外,可能還會有一些配置資訊,而
這些配置資訊也依賴於板,不適宜直接放置在裝置驅動本身,因此,platform也提供了platform_data的支援。platform_data
的形式是自定義的,如對於DM9000網路卡而言,platform_data為一個dm9000_plat_data結構體,我們就可以將MAC地址、總
線寬度、有無EEPROM資訊放入platform_data:
這些配置資訊也依賴於板,不適宜直接放置在裝置驅動本身,因此,platform也提供了platform_data的支援。platform_data
的形式是自定義的,如對於DM9000網路卡而言,platform_data為一個dm9000_plat_data結構體,我們就可以將MAC地址、總
線寬度、有無EEPROM資訊放入platform_data:
static struct dm9000_plat_data ldd6410_dm9000_platdata = {
.flags = DM9000_PLATF_16BITONLY | DM9000_PLATF_NO_EEPROM,
.dev_addr = { 0x0, 0x16, 0xd4, 0x9f, 0xed, 0xa4 },
};
static struct platform_device ldd6410_dm9000 = {
.name = “dm9000”,
.id = 0,
.num_resources = ARRAY_SIZE(ldd6410_dm9000_resource),
.resource = ldd6410_dm9000_resource,
.dev = {
.platform_data = &ldd6410_dm9000_platdata,
}
};
而在DM9000網路卡的驅動中,通過如下方式就拿到了platform_data:
struct dm9000_plat_data *pdata = pdev->dev.platform_data;
其中,pdev為platform_device的指標。
由以上分析可知,裝置驅動中引入platform的概念至少有如下2大好處:
1. 使得裝置被掛接在一個匯流排上,因此,符合Linux 2.6的裝置模型。其結果是,配套的sysfs結點、裝置電源管理都成為可能。
2. 隔離BSP和驅動。在BSP中定義platform裝置和裝置使用的資源、裝置的具體配置資訊,而在驅動中,只需要通過通用API去獲取資源和資料,做到了板相關程式碼和驅動程式碼的分離,使得驅動具有更好的可擴充套件性和跨平臺性。