設備樹下的platform驅動

對於 Linux 這樣一個成熟、龐大、複雜的操作系統,代碼的重用性非常重要,否則的話就會在 Linux 內核中存在大量無意義的重複代碼。尤其是驅動程序,因爲驅動程序佔用了 Linux內核代碼量的大頭,如果不對驅動程序加以管理,任由重複的代碼肆意增加,那麼用不了多久Linux 內核的文件數量就龐大到無法接受的地步。
圖 54.1.1.1 傳統的 I2C 設備驅動 圖 54.1.1.1 傳統的 I2C 設備驅動
假如現在有三個平臺 A、B 和 C,這三個平臺(這裏的平臺說的是 SOC)上都有 MPU6050 這 個 I2C 接口的六軸傳感器,按照我們寫裸機 I2C 驅動的時候的思路,每個平臺都有一個MPU6050的驅動,因此編寫出來的最簡單的驅動框架如圖 54.1.1 所示: 圖 54.1.1.1 傳統的 I2C 設備驅動從圖 54.1.1.1 可以看出,每種平臺下都有一個主機驅動和設備驅動,主機驅動肯定是必須要的,畢竟不同的平臺其 I2C 控制器不同。但是右側的設備驅動就沒必要每個平臺都寫一個,因爲不管對於那個 SOC 來說,MPU6050 都是一樣,通過 I2C 接口讀寫數據就行了,只需要一個 MPU6050 的驅動程序即可。如果再來幾個 I2C 設備,比如 AT24C02、FT5206(電容觸摸屏)等,如果按照圖 54.1.1 中的寫法,那麼設備端的驅動將會重複的編寫好幾次。顯然在 Linux 驅動程序中這種寫法是不推薦的,最好的做法就是每個平臺的 I2C 控制器都提供一個統一的接口(也叫做主機驅動),每個設備的話也只提供一個驅動程序(設備驅動),每個設備通過統一的 I2C接口驅動來訪問,這樣就可以大大簡化驅動文件,比如 54.1.1 中三種平臺下的 MPU6050 驅動。
框架就可以簡化爲圖 54.1.1.2 所示
圖 54.1.1.2 改進後的設備驅動圖 54.1.1.2 改進後的設備驅動
實際的 I2C 驅動設備肯定有很多種,不止 MPU6050 這一個,那麼實際的驅動架構如圖
54.1.1.3 所示:圖 54.1.1.3 分隔後的驅動框架圖 54.1.1.3 分隔後的驅動框架
這個就是驅動的分隔,也就是將主機驅動和設備驅動分隔開來,比如 I2C、SPI 等等都會採用驅動分隔的方式來簡化驅動的開發。在實際的驅動開發中,一般 I2C 主機控制器驅動已經由半導體廠家編寫好了,而設備驅動一般也由設備器件的廠家編寫好了,我們只需要提供設備信息即可,比如 I2C 設備的話提供設備連接到了哪個 I2C 接口上,I2C 的速度是多少等等。相當於將設備信息從設備驅動中剝離開來,驅動使用標準方法去獲取到設備信息(比如從設備樹中獲取到設備信息),然後根據獲取到的設備信息來初始化設備。 這樣就相當於驅動只負責驅動,設備只負責設備,想辦法將兩者進行匹配即可。這個就是 Linux 中的總線(bus)、驅動(driver)和設備(device)模型,也就是常說的驅動分離。總線就是驅動和設備信息的月老,負責給兩者牽線搭橋,如圖 54.1.1.4 所示:
在這裏插入圖片描述當我們向系統註冊一個驅動的時候,總線就會在右側的設備中查找,看看有沒有與之匹配的設備,如果有的話就將兩者聯繫起來。同樣的,當向系統中註冊一個設備的時候,總線就會在左側的驅動中查找看有沒有與之匹配的設備,有的話也聯繫起來。Linux 內核中大量的驅動程序都採用總線、驅動和設備模式,我們一會要重點講解的 platform 驅動就是這一思想下的產物。

platform 平臺驅動模型簡介

驅動和設備的匹配有四種方法,我們依次來看一下:
platform 總線是 bus_type 的一個具體實例,定義在文件 drivers/base/platform.cplatform
線定義如下:
示例代碼 54.2.1.2 platform 總線實例

1 struct bus_type platform_bus_type = { 
2 		.name = "platform", 
3 		.dev_groups = platform_dev_groups,
4 		.match = platform_match, 
5 		.uevent = platform_uevent, 
6 		.pm = &platform_dev_pm_ops, 
 };

platform_bus_type 就是 platform 平臺總線,其中 platform_match 就是匹配函數。我們來看
一下驅動和設備是如何匹配的,platform_match 函數定義在文件 drivers/base/platform.c 中,函
數內容如下所示:

1 static int platform_match(struct device *dev,
struct device_driver *drv)
 
2 { 3 struct platform_device *pdev = to_platform_device(dev);
4 struct platform_driver *pdrv = to_platform_driver(drv);
5 
6 /*When driver_override is set,only bind to the matching driver*/
7 if (pdev->driver_override) 8 return !strcmp(pdev->driver_override, drv->name);
9 
10 /* Attempt an OF style match first */
11 if (of_driver_match_device(dev, drv))
12 return 1;
13
14 /* Then try ACPI style match */
15 if (acpi_driver_match_device(dev, drv))
16 return 1;
17
18 /* Then try to match against the id table */
19 if (pdrv->id_table)
20 return platform_match_id(pdrv->id_table, pdev) != NULL;
21
22 /* fall-back to driver name match */
23 return (strcmp(pdev->name, drv->name) == 0);
24 }

驅動和設備的匹配有四種方法,我們依次來看一下:
第 11~12 行,第一種匹配方式OF 類型的匹配也就是設備樹採用的匹配方式
of_driver_match_device 函數定義在文件 include/linux/of_device.h 中。device_driver 結構體(表示
設備驅動)中有個名爲of_match_table的成員變量,此成員變量保存着驅動的compatible匹配表,
設備樹中的每個設備節點的 compatible 屬性會和 of_match_table 表中的所有成員比較,查看是
否有相同的條目,如果有的話就表示設備和此驅動匹配,設備和驅動匹配成功以後 probe 函數
就會執行
第 15~16 行,第二種匹配方式,ACPI 匹配方式
第 19~20 行,第三種匹配方式id_table 匹配,每個 platform_driver 結構體有一個 id_table
成員變量,顧名思義,保存了很多 id 信息。這些 id 信息存放着這個 platformd 驅動所支持的驅
動類型。
第 23 行,第四種匹配方式,如果第三種匹配方式的 id_table 不存在的話就直接較驅動和
設備的 name 字段,看看是不是相等,如果相等的話就匹配成功。
對於支持設備樹的 Linux 版本號,一般設備驅動爲了兼容性都支持設備樹和無設備樹兩種
匹配方式。也就是第一種匹配方式一般都會存在,第三種和第四種只要存在一種就可以,一般
用的最多的還是第四種,也就是直接比較驅動和設備的 name 字段,畢竟這種方式最簡單了。

platform 驅動框架如下所示:
示例代碼 54.2.2.5 platform 驅動框架
/* 設備結構體 */
1 struct xxx_dev{ 2 struct cdev cdev; 3 /* 設備結構體其他具體內容 */
4 };
5 
6 struct xxx_dev xxxdev; /* 定義個設備結構體變量 */
7 
8 static int xxx_open(struct inode *inode, struct file *filp) 9 { 
10 /* 函數具體內容 */
11 return 0;
12 }
13
14 static ssize_t xxx_write(struct file *filp, const char __user *buf,
size_t cnt, loff_t *offt)
15 {
16 /* 函數具體內容 */
17 return 0;
18 }
19
20 /*
21 * 字符設備驅動操作集
22 */
23 static struct file_operations xxx_fops = {
24 .owner = THIS_MODULE,
25 .open = xxx_open,
26 .write = xxx_write,
27 };
28
29 /*
30 * platform 驅動的 probe 函數
31 * 驅動與設備匹配成功以後此函數就會執行
32 */
33 static int xxx_probe(struct platform_device *dev)
34 { 
35 ......
36 cdev_init(&xxxdev.cdev, &xxx_fops); /* 註冊字符設備驅動 */
37 /* 函數具體內容 */
38 return 0;
39 }
40
41 static int xxx_remove(struct platform_device *dev)
42 {
43 ......
44 cdev_del(&xxxdev.cdev);/* 刪除 cdev */
45 /* 函數具體內容 */
46 return 0;
47 }
48
49 /* 匹配列表 */
50 static const struct of_device_id xxx_of_match[] = {
51 { .compatible = "xxx-gpio" },
52 { /* Sentinel */ }
53 };
54
55 /* 
56 * platform 平臺驅動結構體
57 */
58 static struct platform_driver xxx_driver = {
59 .driver = {
60 .name = "xxx",
61 .of_match_table = xxx_of_match,
62 },
63 .probe = xxx_probe,
64 .remove = xxx_remove,
65 };
66 
67 /* 驅動模塊加載 */
68 static int __init xxxdriver_init(void)
69 {
70 return platform_driver_register(&xxx_driver);
71 }
72
73 /* 驅動模塊卸載 */
74 static void __exit xxxdriver_exit(void)
75 {
76 platform_driver_unregister(&xxx_driver);
77 }
78
79 module_init(xxxdriver_init);
80 module_exit(xxxdriver_exit);
81 MODULE_LICENSE("GPL");
82 MODULE_AUTHOR("zuozhongkai")

可以看到與字符設備驅動不同的是,platform驅動框架①擁有一個用於匹配設備的of_device_id用於匹配設備,②probe函數裏註冊字符設備或者其他類型的設備註冊操作、設備樹裏IO的獲取和設置

更多文章請關注嵌入式機器人公衆號
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章