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 中,內容如下:
第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 函數原型如下:
函數參數和返回值含義如下:
drv:要卸載的platform 驅動。
返回值:無。
platform 驅動框架如下所示:
第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 中,結構體內容如下:
第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 函數原型如下:
函數參數和返回值含義如下:
pdev:要注銷的platform 設備。
返回值:無。
platform 設備信息框架如下所示:
第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 里面輸入如下內容:
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 內容如下所示:
第4 行,設置obj-m 變量的值為“leddevice.o leddriver.o”。
輸入如下命令編譯出驅動模塊文件:
編譯成功以后就會生成一個名為“leddevice.ko leddriver.ko”的驅動模塊文件。
2、編譯測試APP
輸入如下命令編譯測試ledApp.c 這個測試程序:
編譯成功以后就會生成ledApp 這個應用程序。
運行測試
將上一小節編譯出來leddevice.ko 、leddriver.ko 和ledApp 這兩個文件拷貝到
rootfs/lib/modules/4.1.15 目錄中,重啟開發板,進入到目錄lib/modules/4.1.15 中,輸入如下命令加載leddevice.ko 設備模塊和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 燈:
在輸入如下命令關閉LED 燈:
./ledApp /dev/platled 0 //關閉LED 燈觀察一下LED 燈能否打開和關閉,如果可以的話就說明驅動工作正常,如果要卸載驅動的話輸入如下命令即可:
rmmod leddevice.ko rmmod leddriver.ko總結
以上是生活随笔為你收集整理的platform 设备驱动实验的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: matlab 取整数命令,matlab取
- 下一篇: ios开发之.pch文件的使用