天天看點

platform裝置驅動全透析

在Linux 2.6的裝置驅動模型中,關心總線、裝置和驅動這3個實體,總線将裝置和驅動綁定。在系統每注冊一個裝置的時候,會尋找與之比對的驅動;相反的,在系統每注冊一個驅動的時候,會尋找與之比對的裝置,而比對由總線完成。

一個現實的Linux裝置和驅動通常都需要挂接在一種總線上,對于本身依附于PCI、USB、I2 C、SPI等的裝置而言,這自然不是問題,但是在嵌入式系統裡面,SoC系統中內建的獨立的外設控制器、挂接在SoC記憶體空間的外設等确不依附于此類總 線。基于這一背景,Linux發明了一種虛拟的總線,稱為platform總線,相應的裝置稱為platform_device,而驅動成為 platform_driver。

注意,所謂的platform_device并不是與字元裝置、塊裝置和網絡裝置并列的概念,而是Linux系統提供的一種附加手段,例如,在S3C6410處理器中,把内部內建的I2 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,

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()函數可以将平台裝置添加到系統中,這個函數的 原型為:

int platform_add_devices(struct platform_device **devs, int num);

該函數的第一個參數為平台裝置數組的指針,第二個參數為平台裝置的數量,它内部調用了platform_device_register()函數用于注冊單個的平台裝置。

現在我們将前面章節的globalfifo驅動挂接到platform總線上,要完成2個工作:

1. 将globalfifo移植為platform驅動。

2. 在闆檔案中添加globalfifo這個platform裝置。

為完成将globalfifo移植到platform驅動的工作,需要在原始的globalfifo字元裝置驅動中套一層 platform_driver的外殼,如代碼清單5。注意進行這一工作後,并沒有改變globalfifo是字元裝置的本質,隻是将其挂接到了 platform總線。

代碼清單5 為globalfifo添加platform_driver

1 static int __devinit globalfifo_probe(struct platform_device *pdev)

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()成員函數中。

代碼清單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數組,如:

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中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;

我們通常關心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資 源。

對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,

[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:

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去擷取資源和資料,做到了闆相關代碼和驅動代碼的分離,使得驅動具有更好的可擴充性和跨平台性。

 本文轉自 21cnbao 51CTO部落格,原文連結:http://blog.51cto.com/21cnbao/337609,如需轉載請自行聯系原作者

繼續閱讀