原文地址:linux設備驅動歸納總結(九):1.platform總線的設備和驅動
http://blog.chinaunix.net/uid-25014876-id-111745.html
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
這一節可以理解是第八章的延伸,從這節開始介紹platform設備驅動。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
一、什麼是paltform總線
一個現實的linux設備和驅動通常都需要掛接在一種總線上,比較常見的總線有USB、PCI總線等。但是,在嵌入式系統裏面,SoC系統中集成的獨立的外設控制器、掛接在SoC內存空間的外設卻不依附與此類總線。基於這樣的背景下,2.6內核加入了platform虛擬總線。platform機制將設備本身的資源註冊進內核,有內核統一管理,在驅動程序使用這些資源時使用統一的接口,這樣提高了程序的可移植性。
如果簡單的說,就像我在第八章第三節模擬的usb總線一樣(源代碼路徑:8th_devModule_3/2nd),platform總線對加入到該總線的設備和驅動分別封裝了兩個結構體——platform_device和platform_driver。並且提供了對應的註冊函數。當然,前提是要包含頭文件<linux/flatform_device.h>。
來個圖:
由上面兩個的關係我們可以看出來,需要在platform總線上註冊設備和驅動,只要定義指定的結構體後調用platform給出的註冊函數就可以了。
下面就介紹一下platform總線、設備和驅動。
1、platform總線:
和我之前虛擬的usb總線一樣,linux在系統啓動時就註冊了platform總線,看內核代碼:
/*drivers/base/platform.c*/
604 static int platform_match(struct device *dev, struct device_driver *drv)
605 {
606 struct platform_device *pdev;
607
608 pdev = container_of(dev, struct platform_device, dev);
609 return (strcmp(pdev->name, drv->name) == 0); //配對函數檢驗名字是否一致
610 }
。。。。。
949 struct bus_type platform_bus_type = {
950 .name = "platform", //定義了總線名字爲platform,總線註冊後新建目錄sys/bus/platform
951 .dev_attrs = platform_dev_attrs,
952 .match = platform_match, //指定配對函數
953 .uevent = platform_uevent,
954 .pm = PLATFORM_PM_OPS_PTR,
955 };
可以看到,和我的usb虛擬總線一樣,總線中定義了成員名字和match函數,當有總線或者設備註冊到platform總線時,內核自動調用match函數,判斷設備和驅動的name是否一致。
2、platform設備:
同樣的,先看一下platform設備對應的結構體paltform_device:
/*linux/platform_device.h*/
16 struct platform_device {
17 const char * name; //設備的名字,這將代替device->dev_id,用作sys/device下顯示的目錄名
18 int id; //設備id,用於給插入給該總線並且具有相同name的設備編號,如果只有一個設備的話填-1。
19 struct device dev; //結構體中內嵌的device結構體。
20 u32 num_resources; //資源數。
21 struct resource * resource; //用於存放資源的數組。
22 };
上面的結構體中先不介紹id、num_resources和resource。可以看到,platform_device的封裝就是指定了一個目錄的名字name,並且內嵌device。
platform_device的註冊和註銷使用以下函數:
/*drivers/base/platform.c*/
322 int platform_device_register(struct platform_device *pdev) //同樣的,需要判斷返回值
。。。
337 void platform_device_unregister(struct platform_device *pdev)
註冊後,同樣會在/sys/device/目錄下創建一個以name命名的目錄,並且創建軟連接到/sys/bus/platform/device下。
3、platform驅動:
先看一下platform驅動對應的結構體paltform_driver:
/*linux/platform_device.h*/
50 struct platform_driver {
51 int (*probe)(struct platform_device *);
52 int (*remove)(struct platform_device *);
53 void (*shutdown)(struct platform_device *);
54 int (*suspend)(struct platform_device *, pm_message_t state);
55 int (*suspend_late)(struct platform_device *, pm_message_t state);
56 int (*resume_early)(struct platform_device *);
57 int (*resume)(struct platform_device *);
58 struct device_driver driver;
59 };
可以看到,platform_driver結構體內嵌了device_driver,並且實現了prob、remove等操作。其實,當內核需要調用probe函數時,它會調用driver->probe,在dricer->probe中再調用platform_driver->probe。如果想了解清楚的話建議查看內核源代碼。
platform_driver的註冊和註銷使用以下函數:
/*drivers/base/platform.c*/
492 int platform_driver_register(struct platform_driver *drv)
。。。。。
513 void platform_driver_unregister(struct platform_driver *drv)
註冊成功後內核會在/sys/bus/platform/driver/目錄下創建一個名字爲driver->name的目錄。
介紹完後,那我就根據第八章第三節(linux設備驅動歸納總結(八):3.設備管理的分層與面向對象思想)的源程序(8th_devModule_3/2nd),將我想象出來的usb鼠標設備和驅動添加到platform總線上:
/*9th_platform_1/1st/device.c*/
4 #include <linux/platform_device.h>
5
6 void usb_dev_release(struct device *dev)
7 {
8 printk("<kernel> release\n");
9 }
10 struct platform_device mouse_dev = {
11 .name = "plat_usb_mouse", //將以這個名字創建目錄
12 .dev = {
13 .bus_id = "usb_mouse", //不會用這個名字創建目錄了,這裏不設置bus_id也行的。
14 .release = usb_dev_release,
15 },
16 };
17
18 static int __init usb_device_init(void)
19 {
20 int ret;
21
22 ret = platform_device_register(&mouse_dev);
23 if(ret){
24 printk("device register failed!\n");
25 return ret;
26 }
27
28 printk("usb device init\n");
29 return 0;
30 }
31
32 static void __exit usb_device_exit(void)
33 {
34 platform_device_unregister(&mouse_dev);
35 printk("usb device bye!\n");
36 }
一看就很簡單,將之前usb結構體和註冊函數更改爲platform類型就可以了。dirver.c也是一樣:
/*9th_platform_1/1st/driver.c */
25 struct platform_driver mouse_drv = {
26 .probe = usb_driver_probe,
27 .remove = usb_driver_remove,
28 .driver = {
29 .name = "plat_usb_mouse", //在/sys/中的驅動目錄名字
30 },
31 };
32
33 static int __init usb_driver_init(void)
34 {
35 int ret;
36 /*驅動註冊,註冊成功後在/sys/platform/usb/driver目錄下創建目錄
37 * plat_usb_mouse*/
38 ret = platform_driver_register(&mouse_drv);
39 if(ret){
40 printk("driver register failed!\n");
41 return ret;
42 }
43 printk("usb driver init\n");
44 return 0;
45 }
46
47 static void __exit usb_driver_exit(void)
48 {
49 platform_driver_unregister(&mouse_drv);
50 printk("usb driver bye!\n");
51 }
由上面的程序看到,設備和驅動都以”plat_usb_mouse”命名,這樣的話match函數也就能配對成功。
看效果:
[root: 1st]# insmod device.ko
usb device init
[root: 1st]# insmod driver.ko
init usb mouse
usb driver init
[root: 1st]# lsmod
driver 1604 0 - Live 0xbf006000
device 1584 0 - Live 0xbf000000
[root: 1st]# cd /
[root: /]# find -name "*usb_mouse*"
./sys/devices/platform/plat_usb_mouse.0
./sys/bus/platform/devices/plat_usb_mouse.0
./sys/bus/platform/drivers/plat_usb_mouse
./sys/bus/platform/drivers/plat_usb_mouse/plat_usb_mouse.0
當我查找usb_mouse時出現了四個目錄,但是爲什麼後面有個“0”?這個0代表設備的編號,是由paltform_device->id指定的。我的程序沒有設備,所以默認爲0。如果你不想的你的目錄名字沒有後綴,那你就設置platform_device->id = -1;
platform_device->id = 3的效果:
[root: /]# find -name "*usb_mouse*"
./sys/devices/platform/plat_usb_mouse.3
./sys/bus/platform/devices/plat_usb_mouse.3
./sys/bus/platform/drivers/plat_usb_mouse
./sys/bus/platform/drivers/plat_usb_mouse/plat_usb_mouse.3
platform_device->id = -1的效果:
[root: /]# find -name "*usb_mouse*"
./sys/devices/platform/plat_usb_mouse
./sys/bus/platform/devices/plat_usb_mouse
./sys/bus/platform/drivers/plat_usb_mouse
./sys/bus/platform/drivers/plat_usb_mouse/plat_usb_mouse
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
二、platform設備的資源和數據
上面講的usb鼠標都是假的,接下來簡單實現一個led驅動,實現的功能很簡單,加載後燈亮,卸載後燈滅,我的led燈對應管腳GPE12。順便介紹一些platform_device中的resource和platform_data。
再看一下platform_device:
16 struct platform_device {
17 const char * name;
18 int id;
19 struct device dev;
20 u32 num_resources;
21 struct resource * resource;
22 };
前三個已經介紹了,現在來介紹一下最後兩個。
resource是一個指向platform資源數組的指針,該數組中有num_resource個資源,看一下資源結構體:
/*linux/ioport.h"*/
18 struct resource {
19 resource_size_t start;
20 resource_size_t end;
21 const char *name;
22 unsigned long flags;
23 struct resource *parent, *sibling, *child;
24 };
常用的就是紅色標記的三個,分別是資源的開始值,結束值和類型。
常見的flags有IORESOURCE_MEM和IORESOURCE_IRQ。其他的可以自己查看include/linux/ioport.h
如果fiags爲IORESOURCE_MEM,start和end分別是該設備的連續的開始和結束地址,如果不連續你可以定義兩個或者更多的資源結構體。
如果flags爲IORESOURCE_IRQ,start和end分別是該設備連續的開始和結束的連續中斷號,如果不連續可以分開定義。
當然,如果地址或者中斷只有一個,你可以將start和end定義成一樣。
在device.c定義了led的資源:
/*9th_platform_1/2nd/device.c*/
10 struct resource s3c_led_res[1] = {
11 [0] = {
12 .start = 0x56000000,
13 .end = 0x560000ff,
14 .flags = IORESOURCE_MEM,
15 },
16 };
17
18 struct platform_device s3c_led_dev = {
19 .name = "plat_led",
20 .id = -1,
21 .dev = {
22 .release = led_dev_release,
23 },
24 .num_resources = ARRAY_SIZE(s3c_led_res), //platform資源的數量,爲1
25 .resource = s3c_led_res,
26 };
同時,還要修改一下driver.c中的的probe和remove,probe函數中點亮led,remove滅掉led。
/*9th_platform_1/2nd/driver.c*/
9 struct _plat_led_t {
10 unsigned long phys, virt;
11 unsigned long gpecon, gpedat, gpeup;
12 unsigned long reg;
13 };
14
15 struct _plat_led_t pled;
16
17 int led_driver_probe(struct platform_device *pdev)
18 {
19 pled.phys = pdev->resource[0].start;
20 pled.virt = ioremap(pled.phys, SZ_4K);
21 pled.gpecon = pled.virt + 0x40;
22 pled.gpedat = pled.virt + 0x44;
23 pled.gpeup = pled.virt + 0x48;
24
25 //config
26 pled.reg = ioread32(pled.gpecon);
27 pled.reg &= ~(3 << 24);
28 pled.reg |= (1 << 24);
29 iowrite32(pled.reg, pled.gpecon);
30
31 //up
32 pled.reg = ioread32(pled.gpeup);
33 pled.reg |= (1 << 12);
34 iowrite32(pled.reg, pled.gpeup);
35
36 //dat
37 pled.reg = ioread32(pled.gpedat);
38 pled.reg &= ~(1 << 12);
39 iowrite32(pled.reg, pled.gpedat);
40
41 printk("led on\n");
42 return 0;
43 }
上面的probe只要看紅色標記就可以了,在platform_device的資源中獲取資源的start,而其他的都是之前介紹過的led操作。
45 int led_driver_remove(struct platform_device *pdev)
46 {
47 pled.reg = ioread32(pled.gpedat);
48 pled.reg |= (1 << 12);
49 iowrite32(pled.reg, pled.gpedat);
50
51 printk("led off\n");
52 return 0;
53 }
接下來驗證一下:
[root: 2nd]# insmod device.ko
led device init
[root: 2nd]# insmod driver.ko
led on
led driver init
[root: 2nd]# rmmod driver
led off
led driver bye!
[root: 2nd]# rmmod device
<kernel> release
led device bye!
最後在介紹一下paltform設備的數據:
在device結構體下有一個paltform_data:
390 void *platform_data; /* Platform specific data, device
391 core doesn't touch it */
它也說明了,這是用與platform,device的代碼不會使用該結構體。
這是一個void指針類型,用於存放platform的數據地址,類似字符設備時介紹的private_data。這裏就不寫代碼了。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
三、platform設備的靜態註冊
在上面的程序中,設備和驅動都是通過手動加載的,但有些情況下,內核已經爲設備註冊到platform總線上,只需要我們註冊驅動就可以了。接下來介紹一下設備的靜態註冊。
先看一下內核是從哪裏獲取靜態註冊的platform_device。
內核是從 arch/arm/mach-s3c2440/mach-mini2440.c文件中獲取到platform_device的信息:
/*arch/arm/mach-s3c2440/mach-mini2440.c*/
250 static struct platform_device *mini2440_devices[] __initdata = {
251 &s3c_device_usb,
252 &s3c_device_rtc,
253 &s3c_device_lcd,
254 &s3c_device_wdt,
255 &s3c_device_led, //這是我新加的
257 &s3c_device_i2c0,
258 &s3c_device_iis,
259 &s3c_device_dm9k,
260 &net_device_cs8900,
261 &s3c24xx_uda134x,
262 };
可以看到,這個數組存放着所有靜態註冊的platform_device信息,我按照它們的格式,也添加了一個s3c_device_led結構體指針在這個數組中。接下來就要看看是在哪裏定義了。
我全局搜索內核代碼中含有s3c_device_wdt的文件,然後在這結構體後面依樣畫葫蘆。
讓我搜到兩個相關的文件:
1)arch/arm/plat-s3c24xx/devs.c
可以看到,內核在在這個文件中聲明並定義s3c_device_wdt的platform_device結構體,所以,我也在這裏定義的個strucr platform_device s3c_device_led:
/*arch/arm/plat-s3c24xx/devs.c*/
359 /*test by xiaobai*/
360 /* led_test */
361
362 static struct resource s3c_led_resource[] = {
363 [0] = {
364 .start = 0x56000000,
365 .end = 0x560000ff,
366 .flags = IORESOURCE_MEM,
367 }
368 };
369
370 struct platform_device s3c_device_led = {
371 .name = "plat_led",
372 .id = -1,
373 .num_resources = ARRAY_SIZE(s3c_led_resource),
374 .resource = s3c_led_resource,
375 };
376
377 EXPORT_SYMBOL(s3c_device_led);
會發現,上面的內容跟我前面卸載device.c的代碼一模一樣。
2)第二個文件是arch/arm/plat-s3c/include/plat/devs.h:
platform_match函數是通過包含該文件後讀取裏面的platform_device信息來跟platform_driver匹配。所以,必須在這裏加上一行代碼:
/*arch/arm/plat-s3c/include/plat/devs.h*/
32 extern struct platform_device s3c_device_wdt;
33 extern struct platform_device s3c_device_led; //這是我新添加的
修改完上面的3個文件後,重新編譯內核後就實現了靜態註冊platform設備,在內核啓動時會自動註冊s3c_device_led。所以,我們只需要註冊platform驅動就可以了,代碼在driver.c中,和上一個程序一模一樣(2nd/driver.c),我就不貼出來了,可以自己看3th/driver.c。接下來看效果:
Please press Enter to activate this console.
[root: /]# find -name "*plat_led*" //開機後查找plat_led
./sys/devices/platform/plat_led //發現led設備已經被靜態註冊上
./sys/bus/platform/devices/plat_led
[root: /]# cd review_driver/9th_platform/9th_platform_1/3rd/
[root: 3rd]# insmod driver.ko //加載led驅動
led on //燈亮
led driver init
[root: 3rd]# rmmod driver
led off
led driver bye!
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
四、總結
這節由第八章的usb虛擬總線的延伸開始介紹platform的設備和驅動使用和註冊。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
源代碼: 9th_platform_1.rar