日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

platform 设备驱动实验

發布時間:2023/12/10 编程问答 46 豆豆
生活随笔 收集整理的這篇文章主要介紹了 platform 设备驱动实验 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

目錄

  • Linux 驅動的分離與分層
    • 驅動的分隔與分離
    • 驅動的分層
  • platform 平臺驅動模型簡介
    • platform 總線
    • platform 驅動
    • platform 設備
  • 硬件原理圖分析
  • 試驗程序編寫
    • platform 設備與驅動程序編寫
    • 測試APP 編寫
  • 54.5 運行測試
    • 編譯驅動程序和測試APP
    • 運行測試

我們在前面幾章編寫的設備驅動都非常的簡單,都是對IO 進行最簡單的讀寫操作。像I2C、SPI、LCD 等這些復雜外設的驅動就不能這么去寫了,Linux 系統要考慮到驅動的可重用性,因此提出了驅動的分離與分層這樣的軟件思路,在這個思路下誕生了我們將來最常打交道的platform 設備驅動,也叫做平臺設備驅動。本章我們就來學習一下Linux 下的驅動分離與分層,以及platform 框架下的設備驅動該如何編寫。

Linux 驅動的分離與分層

驅動的分隔與分離

對于Linux 這樣一個成熟、龐大、復雜的操作系統,代碼的重用性非常重要,否則的話就會在Linux 內核中存在大量無意義的重復代碼。尤其是驅動程序,因為驅動程序占用了Linux內核代碼量的大頭,如果不對驅動程序加以管理,任由重復的代碼肆意增加,那么用不了多久Linux 內核的文件數量就龐大到無法接受的地步。

假如現在有三個平臺A、B 和C,這三個平臺(這里的平臺說的是SOC)上都有MPU6050 這個I2C 接口的六軸傳感器,按照我們寫裸機I2C 驅動的時候的思路,每個平臺都有一個MPU6050的驅動,因此編寫出來的最簡單的驅動框架如圖54.1.1 所示:

從圖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 所示:

實際的I2C 驅動設備肯定有很多種,不止MPU6050 這一個,那么實際的驅動架構如圖54.1.1.3 所示:

這個就是驅動的分隔,也就是將主機驅動和設備驅動分隔開來,比如I2C、SPI 等等都會采用驅動分隔的方式來簡化驅動的開發。在實際的驅動開發中,一般I2C 主機控制器驅動已經由半導體廠家編寫好了,而設備驅動一般也由設備器件的廠家編寫好了,我們只需要提供設備信息即可,比如I2C 設備的話提供設備連接到了哪個I2C 接口上,I2C 的速度是多少等等。相當
于將設備信息從設備驅動中剝離開來,驅動使用標準方法去獲取到設備信息(比如從設備樹中獲取到設備信息),然后根據獲取到的設備信息來初始化設備。這樣就相當于驅動只負責驅動,設備只負責設備,想辦法將兩者進行匹配即可。這個就是Linux 中的總線(bus)、驅動(driver)和設備(device)模型,也就是常說的驅動分離。總線就是驅動和設備信息的月老,負責給兩者牽線
搭橋,如圖54.1.1.4 所示:

當我們向系統注冊一個驅動的時候,總線就會在右側的設備中查找,看看有沒有與之匹配的設備,如果有的話就將兩者聯系起來。同樣的,當向系統中注冊一個設備的時候,總線就會在左側的驅動中查找看有沒有與之匹配的設備,有的話也聯系起來。Linux 內核中大量的驅動程序都采用總線、驅動和設備模式,我們一會要重點講解的platform 驅動就是這一思想下的產
物。

驅動的分層

上一小節講了驅動的分隔與分離,本節我們來簡單看一下驅動的分層,大家應該聽說過網絡的7 層模型,不同的層負責不同的內容。同樣的,Linux 下的驅動往往也是分層的,分層的目的也是為了在不同的層處理不同的內容。以其他書籍或者資料常常使用到的input(輸入子系統,后面會有專門的章節詳細的講解)為例,簡單介紹一下驅動的分層。input 子系統負責管理所有跟輸入有關的驅動,包括鍵盤、鼠標、觸摸等,最底層的就是設備原始驅動,負責獲取輸入設備的原始值,獲取到的輸入事件上報給input 核心層。input 核心層會處理各種IO 模型,并且提供file_operations 操作集合。我們在編寫輸入設備驅動的時候只需要處理好輸入事件的上報即可,至于如何處理這些上報的輸入事件那是上層去考慮的,我們不用管。可以看出借助分層模型可以極大的簡化我們的驅動編寫,對于驅動編寫來說非常的友好。

platform 平臺驅動模型簡介

前面我們講了設備驅動的分離,并且引出了總線(bus)、驅動(driver)和設備(device)模型,比如I2C、SPI、USB 等總線。但是在SOC 中有些外設是沒有總線這個概念的,但是又要使用總線、驅動和設備模型該怎么辦呢?為了解決此問題,Linux 提出了platform 這個虛擬總線,相應的就有platform_driver 和platform_device。

platform 總線

Linux 系統內核使用bus_type 結構體表示總線,此結構體定義在文件include/linux/device.h,bus_type 結構體內容如下:

1 struct bus_type { 2 const char *name; /* 總線名字*/ 3 const char *dev_name; 4 struct device *dev_root; 5 struct device_attribute *dev_attrs; 6 const struct attribute_group **bus_groups; /* 總線屬性*/ 7 const struct attribute_group **dev_groups; /* 設備屬性*/ 8 const struct attribute_group **drv_groups; /* 驅動屬性*/ 9 10 int (*match)(struct device *dev, struct device_driver *drv); 11 int (*uevent)(struct device *dev, struct kobj_uevent_env *env); 12 int (*probe)(struct device *dev); 13 int (*remove)(struct device *dev); 14 void (*shutdown)(struct device *dev); 15 16 int (*online)(struct device *dev); 17 int (*offline)(struct device *dev); 18 int (*suspend)(struct device *dev, pm_message_t state); 19 int (*resume)(struct device *dev); 20 const struct dev_pm_ops *pm; 21 const struct iommu_ops *iommu_ops; 22 struct subsys_private *p; 23 struct lock_class_key lock_key; 24 };

第10 行,match 函數,此函數很重要,單詞match 的意思就是“匹配、相配”,因此此函數就是完成設備和驅動之間匹配的,總線就是使用match 函數來根據注冊的設備來查找對應的驅動,或者根據注冊的驅動來查找相應的設備,因此每一條總線都必須實現此函數。match 函數有兩個參數:dev 和drv,這兩個參數分別為device 和device_driver 類型,也就是設備和驅動。

platform 總線是bus_type 的一個具體實例,定義在文件drivers/base/platform.c,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, 7 };

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 int (*suspend)(struct device *dev, pm_message_t state); 19 int (*resume)(struct device *dev); 20 const struct dev_pm_ops *pm; 21 const struct iommu_ops *iommu_ops; 22 struct subsys_private *p; 23 struct lock_class_key lock_key; 24 };

第10 行,match 函數,此函數很重要,單詞match 的意思就是“匹配、相配”,因此此函數就是完成設備和驅動之間匹配的,總線就是使用match 函數來根據注冊的設備來查找對應的驅動,或者根據注冊的驅動來查找相應的設備,因此每一條總線都必須實現此函數。match 函數有兩個參數:dev 和drv,這兩個參數分別為device 和device_driver 類型,也就是設備和驅動。

platform 總線是bus_type 的一個具體實例,定義在文件drivers/base/platform.c,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, 7 };

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 驅動

platform_driver 結構體表示platform 驅動,此結構體定義在文件
include/linux/platform_device.h 中,內容如下:

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 (*resume)(struct platform_device *); 7 struct device_driver driver; 8 const struct platform_device_id *id_table; 9 bool prevent_deferred_probe; 10 };

第2 行,probe 函數,當驅動與設備匹配成功以后probe 函數就會執行,非常重要的函數!!一般驅動的提供者會編寫,如果自己要編寫一個全新的驅動,那么probe 就需要自行實現。
第7 行,driver 成員,為device_driver 結構體變量,Linux 內核里面大量使用到了面向對象的思維,device_driver 相當于基類,提供了最基礎的驅動框架。plaform_driver 繼承了這個基類,然后在此基礎上又添加了一些特有的成員變量。

第8 行,id_table 表,也就是我們上一小節講解platform 總線匹配驅動和設備的時候采用的第三種方法,id_table 是個表( 也就是數組) ,每個元素的類型為platform_device_id ,platform_device_id 結構體內容如下:

1 struct platform_device_id { 2 char name[PLATFORM_NAME_SIZE]; 3 kernel_ulong_t driver_data; 4 };

device_driver 結構體定義在include/linux/device.h,device_driver 結構體內容如下:

1 struct device_driver { 2 const char *name; 3 struct bus_type *bus; 4 5 struct module *owner; 6 const char *mod_name; /* used for built-in modules */ 7 8 bool suppress_bind_attrs; /* disables bind/unbind via sysfs */ 9 10 const struct of_device_id *of_match_table; 11 const struct acpi_device_id *acpi_match_table; 12 13 int (*probe) (struct device *dev); 14 int (*remove) (struct device *dev); 15 void (*shutdown) (struct device *dev); 16 int (*suspend) (struct device *dev, pm_message_t state); 17 int (*resume) (struct device *dev); 18 const struct attribute_group **groups; 19 20 const struct dev_pm_ops *pm; 21 22 struct driver_private *p; 23 };

第10 行,of_match_table 就是采用設備樹的時候驅動使用的匹配表,同樣是數組,每個匹配項都為of_device_id 結構體類型,此結構體定義在文件include/linux/mod_devicetable.h 中,內容如下:

1 struct of_device_id { 2 char name[32]; 3 char type[32]; 4 char compatible[128]; 5 const void *data; 6 };

第4 行的compatible 非常重要,因為對于設備樹而言,就是通過設備節點的compatible 屬性值和of_match_table 中每個項目的compatible 成員變量進行比較,如果有相等的就表示設備和此驅動匹配成功。

在編寫platform 驅動的時候,首先定義一個platform_driver 結構體變量,然后實現結構體中的各個成員變量,重點是實現匹配方法以及probe 函數。當驅動和設備匹配成功以后probe函數就會執行,具體的驅動程序在probe 函數里面編寫,比如字符設備驅動等等。

當我們定義并初始化好platform_driver 結構體變量以后,需要在驅動入口函數里面調用platform_driver_register 函數向Linux 內核注冊一個platform 驅動,platform_driver_register 函數原型如下所示:

int platform_driver_register (struct platform_driver *driver)

函數參數和返回值含義如下:
driver:要注冊的platform 驅動。
返回值:負數,失敗;0,成功。
還需要在驅動卸載函數中通過platform_driver_unregister 函數卸載platform 驅動,platform_driver_unregister 函數原型如下:

void platform_driver_unregister(struct platform_driver *drv)

函數參數和返回值含義如下:
drv:要卸載的platform 驅動。
返回值:無。
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");

第1~27 行,傳統的字符設備驅動,所謂的platform 驅動并不是獨立于字符設備驅動、塊設備驅動和網絡設備驅動之外的其他種類的驅動。platform 只是為了驅動的分離與分層而提出來的一種框架,其驅動的具體實現還是需要字符設備驅動、塊設備驅動或網絡設備驅動。

第33~39 行,xxx_probe 函數,當驅動和設備匹配成功以后此函數就會執行,以前在驅動入口init 函數里面編寫的字符設備驅動程序就全部放到此probe 函數里面。比如注冊字符設備驅動、添加cdev、創建類等等。

第41~47 行,xxx_remove 函數,platform_driver 結構體中的remove 成員變量,當關閉platfor備驅動的時候此函數就會執行,以前在驅動卸載exit 函數里面要做的事情就放到此函數中來。比如,使用iounmap 釋放內存、刪除cdev,注銷設備號等等。

第50~53 行,xxx_of_match 匹配表,如果使用設備樹的話將通過此匹配表進行驅動和設備的匹配。第51 行設置了一個匹配項,此匹配項的compatible 值為“xxx-gpio”,因此當設備樹中設備節點的compatible 屬性值為“xxx-gpio”的時候此設備就會與此驅動匹配。

第52 行是一個標記,of_device_id 表最后一個匹配項必須是空的。

第58~ 65 行,定義一個platform_driver 結構體變量xxx_driver,表示platform 驅動,

第59~62行設置paltform_driver 中的device_driver 成員變量的name 和of_match_table 這兩個屬性。其中name 屬性用于傳統的驅動與設備匹配,也就是檢查驅動和設備的name 字段是不是相同。of_match_table 屬性就是用于設備樹下的驅動與設備檢查。對于一個完整的驅動程序,必須提供
有設備樹和無設備樹兩種匹配方法。最后63 和64 這兩行設置probe 和remove 這兩成員變量。

第68~71 行,驅動入口函數,調用platform_driver_register 函數向Linux 內核注冊一個platform驅動,也就是上面定義的xxx_driver 結構體變量。

第74~77 行,驅動出口函數,調用platform_driver_unregister 函數卸載前面注冊的platform驅動。

總體來說,platform 驅動還是傳統的字符設備驅動、塊設備驅動或網絡設備驅動,只是套上了一張“platform”的皮,目的是為了使用總線、驅動和設備這個驅動模型來實現驅動的分離與分層。

platform 設備

platform 驅動已經準備好了,我們還需要platform 設備,否則的話單單一個驅動也做不了什么。platform_device 這個結構體表示platform 設備,這里我們要注意,如果內核支持設備樹的話就不要再使用platform_device 來描述設備了,因為改用設備樹去描述了。當然了,你如果一定要用platform_device 來描述設備信息的話也是可以的。platform_device 結構體定義在文件
include/linux/platform_device.h 中,結構體內容如下:

22 struct platform_device { 23 const char *name; 24 int id; 25 bool id_auto; 26 struct device dev; 27 u32 num_resources; 28 struct resource *resource; 29 30 const struct platform_device_id *id_entry; 31 char *driver_override; /* Driver name to force a match */ 32 33 /* MFD cell pointer */ 34 struct mfd_cell *mfd_cell; 35 36 /* arch specific additions */ 37 struct pdev_archdata archdata; 38 };

第23 行,name 表示設備名字,要和所使用的platform 驅動的name 字段相同,否則的話設備就無法匹配到對應的驅動。比如對應的platform 驅動的name 字段為“xxx-gpio”,那么此name字段也要設置為“xxx-gpio”。

第27 行,num_resources 表示資源數量,一般為第28 行resource 資源的大小。

第28 行,resource 表示資源,也就是設備信息,比如外設寄存器等。Linux 內核使用resource結構體表示資源,resource 結構體內容如下:

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

start 和end 分別表示資源的起始和終止信息,對于內存類的資源,就表示內存起始和終止地址,name 表示資源名字,flags 表示資源類型,可選的資源類型都定義在了文件include/linux/ioport.h 里面,如下所示:

29 #define IORESOURCE_BITS 0x000000ff /* Bus-specific bits */ 30 31 #define IORESOURCE_TYPE_BITS 0x00001f00 /* Resource type */ 32 #define IORESOURCE_IO 0x00000100 /* PCI/ISA I/O ports */ 33 #define IORESOURCE_MEM 0x00000200 34 #define IORESOURCE_REG 0x00000300 /* Register offsets */ 35 #define IORESOURCE_IRQ 0x00000400 36 #define IORESOURCE_DMA 0x00000800 37 #define IORESOURCE_BUS 0x00001000 ...... 104 /* PCI control bits. Shares IORESOURCE_BITS with above PCI ROM. */ 105 #define IORESOURCE_PCI_FIXED (1<<4) /* Do not move resource */

在以前不支持設備樹的Linux 版本中,用戶需要編寫platform_device 變量來描述設備信息,然后使用platform_device_register 函數將設備信息注冊到Linux 內核中,此函數原型如下所示:

int platform_device_register(struct platform_device *pdev)

函數參數和返回值含義如下:
pdev:要注冊的platform 設備。
返回值:負數,失敗;0,成功。
如果不再使用platform 的話可以通過platform_device_unregister 函數注銷掉相應的platform設備,platform_device_unregister 函數原型如下:

void platform_device_unregister(struct platform_device *pdev)

函數參數和返回值含義如下:
pdev:要注銷的platform 設備。
返回值:無。
platform 設備信息框架如下所示:

1 /* 寄存器地址定義*/ 2 #define PERIPH1_REGISTER_BASE (0X20000000) /* 外設1寄存器首地址*/ 3 #define PERIPH2_REGISTER_BASE (0X020E0068) /* 外設2寄存器首地址*/ 4 #define REGISTER_LENGTH 4 5 6 /* 資源*/ 7 static struct resource xxx_resources[] = { 8 [0] = { 9 .start = PERIPH1_REGISTER_BASE, 10 .end = (PERIPH1_REGISTER_BASE + REGISTER_LENGTH - 1), 11 .flags = IORESOURCE_MEM, 12 }, 13 [1] = { 14 .start = PERIPH2_REGISTER_BASE, 15 .end = (PERIPH2_REGISTER_BASE + REGISTER_LENGTH - 1), 16 .flags = IORESOURCE_MEM, 17 }, 18 }; 19 20 /* platform設備結構體*/ 21 static struct platform_device xxxdevice = { 22 .name = "xxx-gpio", 23 .id = -1, 24 .num_resources = ARRAY_SIZE(xxx_resources), 25 .resource = xxx_resources, 26 }; 27 28 /* 設備模塊加載*/ 29 static int __init xxxdevice_init(void) 30 { 31 return platform_device_register(&xxxdevice); 32 } 33 34 /* 設備模塊注銷*/ 35 static void __exit xxx_resourcesdevice_exit(void) 36 { 37 platform_device_unregister(&xxxdevice); 38 } 39 40 module_init(xxxdevice_init); 41 module_exit(xxxdevice_exit); 42 MODULE_LICENSE("GPL"); 43 MODULE_AUTHOR("zuozhongkai");

第7~18 行,數組xxx_resources 表示設備資源,一共有兩個資源,分別為設備外設1 和外設2 的寄存器信息。因此flags 都為IORESOURCE_MEM,表示資源為內存類型的。
第21~26 行,platform 設備結構體變量,注意name 字段要和所使用的驅動中的name 字段一致,否則驅動和設備無法匹配成功。num_resources 表示資源大小,其實就是數組xxx_resources的元素數量,這里用ARRAY_SIZE 來測量一個數組的元素個數。
第29~32 行,設備模塊加載函數,在此函數中調用platform_device_register 向Linux 內核注冊platform 設備。
第35~38 行,設備模塊卸載函數,在此函數中調用platform_device_unregister 從Linux 內核中卸載platform 設備。

示例代碼54.2.3.4 主要是在不支持設備樹的Linux 版本中使用的,當Linux 內核支持了設備樹以后就不需要用戶手動去注冊platform 設備了。因為設備信息都放到了設備樹中去描述,Linux 內核啟動的時候會從設備樹中讀取設備信息,然后將其組織成platform_device 形式,至于設備樹到platform_device 的具體過程就不去詳細的追究了,感興趣的可以去看一下,網上也有很多博客詳細的講解了整個過程。

關于platform 下的總線、驅動和設備就講解到這里,我們接下來就使用platform 驅動框架來編寫一個LED 燈驅動,本章我們不使用設備樹來描述設備信息,我們采用自定義platform_device 這種“古老”方式來編寫LED 的設備信息。下一章我們來編寫設備樹下的platform驅動,這樣我們就掌握了無設備樹和有設備樹這兩種platform 驅動的開發方式。

硬件原理圖分析

本章實驗我們只使用到IMX6U-ALPHA 開發板上的LED 燈,因此實驗硬件原理圖參考8.3小節即可。

試驗程序編寫

本實驗對應的例程路徑為:開發板光盤-> 2、Linux 驅動例程-> 17_platform。
本章實驗我們需要編寫一個驅動模塊和一個設備模塊,其中驅動模塊是platform 驅動程序,設備模塊是platform 的設備信息。當這兩個模塊都加載成功以后就會匹配成功,然后platform驅動模塊中的probe 函數就會執行,probe 函數中就是傳統的字符設備驅動那一套。

platform 設備與驅動程序編寫

新建名為“17_platform”的文件夾,然后在17_platform 文件夾里面創建vscode 工程,工作區命名為“platform”。新建名為leddevice.c 和leddriver.c 這兩個文件,這兩個文件分別為LED燈的platform 設備文件和LED 燈的platform 的驅動文件。在leddevice.c 中輸入如下所示內容:

#include <linux/types.h> #include <linux/kernel.h> #include <linux/delay.h> #include <linux/ide.h> #include <linux/init.h> #include <linux/module.h> #include <linux/errno.h> #include <linux/gpio.h> #include <linux/cdev.h> #include <linux/device.h> #include <linux/of_gpio.h> #include <linux/semaphore.h> #include <linux/timer.h> #include <linux/irq.h> #include <linux/wait.h> #include <linux/poll.h> #include <linux/fs.h> #include <linux/fcntl.h> #include <linux/platform_device.h> #include <asm/mach/map.h> #include <asm/uaccess.h> #include <asm/io.h> /*************************************************************** Copyright ? ALIENTEK Co., Ltd. 1998-2029. All rights reserved. 文件名 : leddevice.c 作者 : 左忠凱 版本 : V1.0 描述 : platform設備 其他 : 無 論壇 : www.openedv.com 日志 : 初版V1.0 2019/8/13 左忠凱創建 ***************************************************************//* * 寄存器地址定義*/ #define CCM_CCGR1_BASE (0X020C406C) #define SW_MUX_GPIO1_IO03_BASE (0X020E0068) #define SW_PAD_GPIO1_IO03_BASE (0X020E02F4) #define GPIO1_DR_BASE (0X0209C000) #define GPIO1_GDIR_BASE (0X0209C004) #define REGISTER_LENGTH 4/* @description : 釋放flatform設備模塊的時候此函數會執行 * @param - dev : 要釋放的設備 * @return : 無*/ static void led_release(struct device *dev) {printk("led device released!\r\n"); }/* * 設備資源信息,也就是LED0所使用的所有寄存器*/ static struct resource led_resources[] = {[0] = {.start = CCM_CCGR1_BASE,.end = (CCM_CCGR1_BASE + REGISTER_LENGTH - 1),.flags = IORESOURCE_MEM,}, [1] = {.start = SW_MUX_GPIO1_IO03_BASE,.end = (SW_MUX_GPIO1_IO03_BASE + REGISTER_LENGTH - 1),.flags = IORESOURCE_MEM,},[2] = {.start = SW_PAD_GPIO1_IO03_BASE,.end = (SW_PAD_GPIO1_IO03_BASE + REGISTER_LENGTH - 1),.flags = IORESOURCE_MEM,},[3] = {.start = GPIO1_DR_BASE,.end = (GPIO1_DR_BASE + REGISTER_LENGTH - 1),.flags = IORESOURCE_MEM,},[4] = {.start = GPIO1_GDIR_BASE,.end = (GPIO1_GDIR_BASE + REGISTER_LENGTH - 1),.flags = IORESOURCE_MEM,}, };/** platform設備結構體 */ static struct platform_device leddevice = {.name = "imx6ul-led",.id = -1,.dev = {.release = &led_release,},.num_resources = ARRAY_SIZE(led_resources),.resource = led_resources, };/** @description : 設備模塊加載 * @param : 無* @return : 無*/ static int __init leddevice_init(void) {return platform_device_register(&leddevice); }/** @description : 設備模塊注銷* @param : 無* @return : 無*/ static void __exit leddevice_exit(void) {platform_device_unregister(&leddevice); }module_init(leddevice_init); module_exit(leddevice_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("zuozhongkai");

leddevice.c 文件內容就是按照示例代碼54.2.3.4 的platform 設備模板編寫的。
第56~82 行,led_resources 數組,也就是設備資源,描述了LED 所要使用到的寄存器信息,也就是IORESOURCE_MEM 資源。
第88~96,platform 設備結構體變量leddevice,這里要注意name 字段為“imx6ul-led”,所以稍后編寫platform 驅動中的name 字段也要為“imx6ul-led”,否則設備和驅動匹配失敗。
第103~106 行,設備模塊加載函數,在此函數里面通過platform_device_register 向Linux 內核注冊leddevice 這個platform 設備。
第113~116 行,設備模塊卸載函數,在此函數里面通過platform_device_unregister 從Linux內核中刪除掉leddevice 這個platform 設備。
leddevice.c 文件編寫完成以后就編寫leddriver.c 這個platform 驅動文件,在leddriver.c 里面輸入如下內容:

#include <linux/types.h> #include <linux/kernel.h> #include <linux/delay.h> #include <linux/ide.h> #include <linux/init.h> #include <linux/module.h> #include <linux/errno.h> #include <linux/gpio.h> #include <linux/cdev.h> #include <linux/device.h> #include <linux/of_gpio.h> #include <linux/semaphore.h> #include <linux/timer.h> #include <linux/irq.h> #include <linux/wait.h> #include <linux/poll.h> #include <linux/fs.h> #include <linux/fcntl.h> #include <linux/platform_device.h> #include <asm/mach/map.h> #include <asm/uaccess.h> #include <asm/io.h> /*************************************************************** Copyright ? ALIENTEK Co., Ltd. 1998-2029. All rights reserved. 文件名 : leddriver.c 作者 : 左忠凱 版本 : V1.0 描述 : platform驅動 其他 : 無 論壇 : www.openedv.com 日志 : 初版V1.0 2019/8/13 左忠凱創建 ***************************************************************/#define LEDDEV_CNT 1 /* 設備號長度 */ #define LEDDEV_NAME "platled" /* 設備名字 */ #define LEDOFF 0 #define LEDON 1/* 寄存器名 */ static void __iomem *IMX6U_CCM_CCGR1; static void __iomem *SW_MUX_GPIO1_IO03; static void __iomem *SW_PAD_GPIO1_IO03; static void __iomem *GPIO1_DR; static void __iomem *GPIO1_GDIR;/* leddev設備結構體 */ struct leddev_dev{dev_t devid; /* 設備號 */struct cdev cdev; /* cdev */struct class *class; /* 類 */struct device *device; /* 設備 */int major; /* 主設備號 */ };struct leddev_dev leddev; /* led設備 *//** @description : LED打開/關閉* @param - sta : LEDON(0) 打開LED,LEDOFF(1) 關閉LED* @return : 無*/ void led0_switch(u8 sta) {u32 val = 0;if(sta == LEDON){val = readl(GPIO1_DR);val &= ~(1 << 3); writel(val, GPIO1_DR);}else if(sta == LEDOFF){val = readl(GPIO1_DR);val|= (1 << 3); writel(val, GPIO1_DR);} }/** @description : 打開設備* @param - inode : 傳遞給驅動的inode* @param - filp : 設備文件,file結構體有個叫做private_data的成員變量* 一般在open的時候將private_data指向設備結構體。* @return : 0 成功;其他 失敗*/ static int led_open(struct inode *inode, struct file *filp) {filp->private_data = &leddev; /* 設置私有數據 */return 0; }/** @description : 向設備寫數據 * @param - filp : 設備文件,表示打開的文件描述符* @param - buf : 要寫給設備寫入的數據* @param - cnt : 要寫入的數據長度* @param - offt : 相對于文件首地址的偏移* @return : 寫入的字節數,如果為負值,表示寫入失敗*/ static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt) {int retvalue;unsigned char databuf[1];unsigned char ledstat;retvalue = copy_from_user(databuf, buf, cnt);if(retvalue < 0) {return -EFAULT;}ledstat = databuf[0]; /* 獲取狀態值 */if(ledstat == LEDON) {led0_switch(LEDON); /* 打開LED燈 */}else if(ledstat == LEDOFF) {led0_switch(LEDOFF); /* 關閉LED燈 */}return 0; }/* 設備操作函數 */ static struct file_operations led_fops = {.owner = THIS_MODULE,.open = led_open,.write = led_write, };/** @description : flatform驅動的probe函數,當驅動與設備匹配以后此函數就會執行* @param - dev : platform設備* @return : 0,成功;其他負值,失敗*/ static int led_probe(struct platform_device *dev) { int i = 0;int ressize[5];u32 val = 0;struct resource *ledsource[5];printk("led driver and device has matched!\r\n");/* 1、獲取資源 */for (i = 0; i < 5; i++) {ledsource[i] = platform_get_resource(dev, IORESOURCE_MEM, i); /* 依次MEM類型資源 */if (!ledsource[i]) {dev_err(&dev->dev, "No MEM resource for always on\n");return -ENXIO;}ressize[i] = resource_size(ledsource[i]); } /* 2、初始化LED *//* 寄存器地址映射 */IMX6U_CCM_CCGR1 = ioremap(ledsource[0]->start, ressize[0]);SW_MUX_GPIO1_IO03 = ioremap(ledsource[1]->start, ressize[1]);SW_PAD_GPIO1_IO03 = ioremap(ledsource[2]->start, ressize[2]);GPIO1_DR = ioremap(ledsource[3]->start, ressize[3]);GPIO1_GDIR = ioremap(ledsource[4]->start, ressize[4]);val = readl(IMX6U_CCM_CCGR1);val &= ~(3 << 26); /* 清除以前的設置 */val |= (3 << 26); /* 設置新值 */writel(val, IMX6U_CCM_CCGR1);/* 設置GPIO1_IO03復用功能,將其復用為GPIO1_IO03 */writel(5, SW_MUX_GPIO1_IO03);writel(0x10B0, SW_PAD_GPIO1_IO03);/* 設置GPIO1_IO03為輸出功能 */val = readl(GPIO1_GDIR);val &= ~(1 << 3); /* 清除以前的設置 */val |= (1 << 3); /* 設置為輸出 */writel(val, GPIO1_GDIR);/* 默認關閉LED1 */val = readl(GPIO1_DR);val |= (1 << 3) ; writel(val, GPIO1_DR);/* 注冊字符設備驅動 *//*1、創建設備號 */if (leddev.major) { /* 定義了設備號 */leddev.devid = MKDEV(leddev.major, 0);register_chrdev_region(leddev.devid, LEDDEV_CNT, LEDDEV_NAME);} else { /* 沒有定義設備號 */alloc_chrdev_region(&leddev.devid, 0, LEDDEV_CNT, LEDDEV_NAME); /* 申請設備號 */leddev.major = MAJOR(leddev.devid); /* 獲取分配號的主設備號 */}/* 2、初始化cdev */leddev.cdev.owner = THIS_MODULE;cdev_init(&leddev.cdev, &led_fops);/* 3、添加一個cdev */cdev_add(&leddev.cdev, leddev.devid, LEDDEV_CNT);/* 4、創建類 */leddev.class = class_create(THIS_MODULE, LEDDEV_NAME);if (IS_ERR(leddev.class)) {return PTR_ERR(leddev.class);}/* 5、創建設備 */leddev.device = device_create(leddev.class, NULL, leddev.devid, NULL, LEDDEV_NAME);if (IS_ERR(leddev.device)) {return PTR_ERR(leddev.device);}return 0; }/** @description : platform驅動的remove函數,移除platform驅動的時候此函數會執行* @param - dev : platform設備* @return : 0,成功;其他負值,失敗*/ static int led_remove(struct platform_device *dev) {iounmap(IMX6U_CCM_CCGR1);iounmap(SW_MUX_GPIO1_IO03);iounmap(SW_PAD_GPIO1_IO03);iounmap(GPIO1_DR);iounmap(GPIO1_GDIR);cdev_del(&leddev.cdev);/* 刪除cdev */unregister_chrdev_region(leddev.devid, LEDDEV_CNT); /* 注銷設備號 */device_destroy(leddev.class, leddev.devid);class_destroy(leddev.class);return 0; }/* platform驅動結構體 */ static struct platform_driver led_driver = {.driver = {.name = "imx6ul-led", /* 驅動名字,用于和設備匹配 */},.probe = led_probe,.remove = led_remove, };/** @description : 驅動模塊加載函數* @param : 無* @return : 無*/ static int __init leddriver_init(void) {return platform_driver_register(&led_driver); }/** @description : 驅動模塊卸載函數* @param : 無* @return : 無*/ static void __exit leddriver_exit(void) {platform_driver_unregister(&led_driver); }module_init(leddriver_init); module_exit(leddriver_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("zuozhongkai");

leddriver.c 文件內容就是按照示例代碼54.2.2.5 的platform 驅動模板編寫的。

第34~122 行,傳統的字符設備驅動。
第130~206 行,probe 函數,當設備和驅動匹配以后此函數就會執行,當匹配成功以后會在終端上輸出“led driver and device has matched!”這樣語句。在probe 函數里面初始化LED、注冊字符設備驅動。也就是將原來在驅動加載函數里面做的工作全部放到probe 函數里面完成。
第213~226 行,remobe 函數,當卸載platform 驅動的時候此函數就會執行。在此函數里面釋放內存、注銷字符設備等。也就是將原來驅動卸載函數里面的工作全部都放到remove 函數中完成。
第229~235 行,platform_driver 驅動結構體,注意name 字段為"imx6ul-led",和我們在leddevice.c 文件里面設置的設備name 字段一致。

第242~245 行,驅動模塊加載函數,在此函數里面通過platform_driver_register 向Linux 內核注冊led_driver 驅動。

第252~255 行,驅動模塊卸載函數,在此函數里面通過platform_driver_unregister 從Linux內核卸載led_driver 驅動。

測試APP 編寫

測試APP 的內容很簡單,就是打開和關閉LED 燈,新建ledApp.c 這個文件,然后在里面輸入如下內容:

#include "stdio.h" #include "unistd.h" #include "sys/types.h" #include "sys/stat.h" #include "fcntl.h" #include "stdlib.h" #include "string.h" /*************************************************************** Copyright ? ALIENTEK Co., Ltd. 1998-2029. All rights reserved. 文件名 : ledApp.c 作者 : 左忠凱 版本 : V1.0 描述 : platform驅動驅測試APP。 其他 : 無 使用方法 :./ledApp /dev/platled 0 關閉LED./ledApp /dev/platled 1 打開LED 論壇 : www.openedv.com 日志 : 初版V1.0 2019/8/16 左忠凱創建 ***************************************************************/ #define LEDOFF 0 #define LEDON 1/** @description : main主程序* @param - argc : argv數組元素個數* @param - argv : 具體參數* @return : 0 成功;其他 失敗*/ int main(int argc, char *argv[]) {int fd, retvalue;char *filename;unsigned char databuf[2];if(argc != 3){printf("Error Usage!\r\n");return -1;}filename = argv[1];/* 打開led驅動 */fd = open(filename, O_RDWR);if(fd < 0){printf("file %s open failed!\r\n", argv[1]);return -1;}databuf[0] = atoi(argv[2]); /* 要執行的操作:打開或關閉 */retvalue = write(fd, databuf, sizeof(databuf));if(retvalue < 0){printf("LED Control Failed!\r\n");close(fd);return -1;}retvalue = close(fd); /* 關閉文件 */if(retvalue < 0){printf("file %s close failed!\r\n", argv[1]);return -1;}return 0; }

ledApp.c 文件內容很簡單,就是控制LED 燈的亮滅,和第四十一章的測試APP 基本一致,這里就不重復講解了。

54.5 運行測試

編譯驅動程序和測試APP

1、編譯驅動程序
編寫Makefile 文件,本章實驗的Makefile 文件和第四十章實驗基本一樣,只是將obj-m 變量的值改為“leddevice.o leddriver.o”,Makefile 內容如下所示:

KERNELDIR := /home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek CURRENT_PATH := $(shell pwd)obj-m := leddevice.o obj-m += leddriver.obuild: kernel_moduleskernel_modules:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modulesclean:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

第4 行,設置obj-m 變量的值為“leddevice.o leddriver.o”。
輸入如下命令編譯出驅動模塊文件:

make -j32

編譯成功以后就會生成一個名為“leddevice.ko leddriver.ko”的驅動模塊文件。

2、編譯測試APP
輸入如下命令編譯測試ledApp.c 這個測試程序:

arm-linux-gnueabihf-gcc ledApp.c -o ledApp

編譯成功以后就會生成ledApp 這個應用程序。

運行測試

將上一小節編譯出來leddevice.ko 、leddriver.ko 和ledApp 這兩個文件拷貝到
rootfs/lib/modules/4.1.15 目錄中,重啟開發板,進入到目錄lib/modules/4.1.15 中,輸入如下命令加載leddevice.ko 設備模塊和leddriver.ko 這個驅動模塊。

depmod //第一次加載驅動的時候需要運行此命令 modprobe leddevice.ko //加載設備模塊 modprobe leddriver.ko //加載驅動模塊

根文件系統中/sys/bus/platform/目錄下保存著當前板子platform 總線下的設備和驅動,其中devices 子目錄為platform 設備,drivers 子目錄為plartofm 驅動。查看/sys/bus/platform/devices/目錄,看看我們的設備是否存在,我們在leddevice.c 中設置leddevice(platform_device 類型)的
name 字段為“imx6ul-led”,也就是設備名字為imx6ul-led,因此肯定在/sys/bus/platform/devices/目錄下存在一個名字“imx6ul-led”的文件,否則說明我們的設備模塊加載失敗,結果如圖54.4.2.1所示:

同理,查看/sys/bus/platform/drivers/目錄,看一下驅動是否存在,我們在leddriver.c 中設置led_driver (platform_driver 類型)的name 字段為“imx6ul-led”,因此會在/sys/bus/platform/drivers/目錄下存在名為“imx6ul-led”這個文件,結果如圖54.4.2.2 所示:

驅動模塊和設備模塊加載成功以后platform 總線就會進行匹配,當驅動和設備匹配成功以后就會輸出如圖54.4.2.3 所示一行語句:

驅動和設備匹配成功以后就可以測試LED 燈驅動了,輸入如下命令打開LED 燈:

./ledApp /dev/platled 1 //打開LED 燈

在輸入如下命令關閉LED 燈:

./ledApp /dev/platled 0 //關閉LED 燈

觀察一下LED 燈能否打開和關閉,如果可以的話就說明驅動工作正常,如果要卸載驅動的話輸入如下命令即可:

rmmod leddevice.ko rmmod leddriver.ko

總結

以上是生活随笔為你收集整理的platform 设备驱动实验的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。