platform裝置驅動精講,例程詳細
本文出自https://wenku.baidu.com/view/05e1b550192e45361166f53c.html
感謝原作者!
匯流排裝置驅動模型主要包含匯流排、裝置、驅動三個部分,匯流排可以是一條真實存在的匯流排,例如USB、I2C等典型的裝置。但是對於一些裝置(內部的裝置)可能沒有現成的匯流排。Linux 2.6核心中引入了匯流排裝置驅動模型。匯流排裝置驅動模型與之前的三類驅動(字元、塊裝置、網路裝置)沒有必然的聯絡。裝置只是搭載到了匯流排中。在linux核心中假設存在一條虛擬匯流排,稱之為platform匯流排。platform匯流排相比與常規的匯流排模型其優勢主要是platform匯流排是由核心實現的,而不用自己定義匯流排型別,匯流排裝置來載入匯流排。
兩者的工作順序是先定義platform_device -> 註冊 platform_device->,再定義 platform_driver-> 註冊 platform_driver。
整體而言只需要完成兩個步驟,也就是裝置的實現和驅動的實現,每一個實現都包括相關結構體的定義和註冊。
platform_device註冊
需要注意的是platform_device 實質上是經過處理過的裝置,在platform_device結構體中存在一個裝置結構體,與之前的裝置存在差別的是引入了裝置資源。這些裝置資源就能實現對裝置暫存器,中斷等資源的訪問。平臺裝置的基本結構體如下:
struct platform_device {
/*裝置名*/
const char * name;
/*裝置ID號*/
int id;
/*結構體包含一個具體的device結構體*/
struct device dev;
/*資源的數量*/
u32 num_resources;
/*資源結構體,用來儲存硬體的資源*/
struct resource * resource;
/*平臺裝置的ID*/
struct platform_device_id *id_entry;
};
其中struct device 和
struct resource {
/*資源的起始值,如果是地址,那麼是實體地址,不是虛擬地址*/
resource_size_t start;
/*資源的結束值,如果是地址,那麼是實體地址,不是虛擬地址*/
resource_size_t end;
/*資源名*/
const char *name;
/*資源的標示,用來識別不同的資源*/
unsigned long flags;
/*資源指標,可以構成連結串列*/
struct resource *parent, *sibling, *child;
};
platform_device 的註冊很簡單,只需要在裝置的初始化函式中首先定義相應的裝置,通常採用函式platform_device *platform_device_alloc(const char *name, int id)動態申請,通常name就是需要申請的裝置名,而id為-1。然後採用
int platform_device_add(struct platform_device *pdev)
或者
int platform_device_register(struct platform_device *pdev)
註冊定義好的裝置即可。
同樣在退出函式中釋放註冊好的裝置即可,可以採用函式:
void platform_device_unregister(struct platform_device *pdev)。
然後一個平臺裝置就完成了,不需要像自己實現模型時定義相關的檔案屬性等。
裝置資源可以通過相關函式得到:
struct resource *platform_get_resource(struct platform_device *dev,
unsigned int type, unsigned int num)
中斷資源也可以通過:
int platform_get_irq(struct platform_device *dev, unsigned int num)
資源的使用主要是驅動實現過程中需要使用到的,但是後期的使用一般需要在驅動的probe函式中實現申請中斷或者IO記憶體才能使用,而不能直接使用。特別是資源中的地址通常是實體地址,需要通過申請IO記憶體和對映完成物理到虛擬地址的轉換,便於程序的訪問。
platform_driver註冊
平臺驅動結構體platform_driver實現如下:
struct platform_driver {
/*平臺驅動需要實現的相關函式操作,
其中的前4個函式與最後一個函式與device_driver中的函式是相同的
本質是實現對device_driver 中相關函式的賦值。
*/
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*suspend_late)(struct platform_device *, pm_message_t state);
int (*resume_early)(struct platform_device *);
int (*resume)(struct platform_device *);
/*內嵌了一個裝置驅動結構體*/
struct device_driver driver;
/*平臺裝置ID,這與platform_device中的struct platform_device_id *id_entry是相同的
主要是完成匯流排的匹配操作,platform匯流排的匹配操作第一匹配要素就是該元素。而不再是簡單的name選項。
*/
struct platform_device_id *id_table;
};
通常驅動的入口函式:
int (*probe)(struct platform_device *);
當匯流排完成了裝置的match 操作以後就會進入驅動中該函式的執行。
匯流排函式的匹配操作如下:
static int platform_match(struct device *dev, struct device_driver *drv)
{
/*得到平臺裝置的指標*/
struct platform_device *pdev = to_platform_device(dev);
/*得到平臺驅動指標*/
struct platform_driver *pdrv = to_platform_driver(drv);
/* match against the id table first */
/*從定義上分析,id_table是首先匹配的物件,然後才是name的匹配,當ID匹配完成時就說明匹配好了*/
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;
/* fall-back to driver name match */
return (strcmp(pdev->name, drv->name) == 0);
}
從上面的定義可以知道platform匯流排的匹配函式手下是比較id_table是匹配的首選項。
probe 函式稱之為探針函式,用於檢測總線上有該驅動能夠處理的裝置,而remove函式則是為了說明總線上該驅動能夠處理的裝置被移除。
因此這兩個函式是在平臺裝置中一定要被實現的函式。
其他的函式則不一樣要求實現。
平臺驅動的設計主要是完成平臺驅動結構體的填充和註冊。
通常的平臺驅動結構體實現如下:
static struct platform_driver my_driver =
{
/*平臺驅動的probe函式實現*/
.probe = my_probe,
/*平臺驅動的remove函式實現*/
.remove = my_remove,
/*實現裝置驅動的name和owner變數*/
.driver =
{
/*該引數主要實現匯流排中的匹配函式呼叫*/
.name = "my_dev",
/*該函式表示模組的擁有者*/
.owner = THIS_MODULE,
},
};
其中的my_probe和my_remove是自己定義的probe和remove函式。
最主要的是內嵌裝置驅動結構體的填充,主要的填充包括name和owner兩個,當然也可以包括其他的。由於沒有填充id_table,那麼name就是匯流排匹配操作的第一選擇。因此如果沒有填充好id_table,那麼name元素是一定要實現的,不然不能完成相應的裝置驅動匹配操作。
完成platform_driver結構體的填充過後就是完成驅動的在初始化階段的註冊以及退出階段的釋放操作,基本的實現函式為:
註冊函式,通常在驅動初始化函式中呼叫:
int platform_driver_register(struct platform_driver *drv)
釋放函式,通常在驅動退出函式呼叫:
void platform_driver_unregister(struct platform_driver *drv)
完成相關的註冊以後匯流排、裝置、驅動的大概框架就完成啦。
但是這只是常用的框架,還不能在應用程式中使用。
基於平臺驅動的裝置驅動都是基於匯流排架構的,基本的實現過程與之前的簡單字元裝置存在較大的差別,主要的區別在驅動的初始化不在是平臺裝置驅動的初始化函式中實現,而是在probe函式中實現。而驅動的解除安裝函式則是在remove函式中實現。probe函式是平臺匯流排實現匹配以後首先被呼叫的函式,因此在其中實現字元裝置、塊裝置、網路裝置驅動的初始化是有意義的,這樣的裝置驅動就是基於平臺匯流排的裝置驅動,便於維護。
平臺匯流排驅動的註冊過程分析:
int platform_driver_register(struct platform_driver *drv)
{
/*第一步,仍然是完成結構體的填充操作*/
/*驅動的匯流排型別*/
drv->driver.bus = &platform_bus_type;
/*將自己定義的probe函式賦值給平臺驅動中裝置驅動的probe函式,其他函式類似*/
if (drv->probe)
drv->driver.probe = platform_drv_probe;
if (drv->remove)
drv->driver.remove = platform_drv_remove;
if (drv->shutdown)
drv->driver.shutdown = platform_drv_shutdown;
if (drv->suspend)
drv->driver.suspend = platform_drv_suspend;
if (drv->resume)
drv->driver.resume = platform_drv_resume;
/*第二步,仍然是完成一般裝置驅動的註冊操作*/
/*然手就是一般驅動的註冊,這樣就完成了裝置的註冊*/
return driver_register(&drv->driver);
}
/*裝置驅動的probe函式的賦值過程*/
static int platform_drv_probe(struct device *_dev)
{
/*得到裝置對應的平臺驅動*/
struct platform_driver *drv = to_platform_driver(_dev->driver);
/*得到裝置的平臺裝置*/
struct platform_device *dev = to_platform_device(_dev);
/*下面的probe是自己實現的probe函式。具體的實現思路:
根據一般裝置找對應的平臺裝置,同時根據裝置的驅動找到平臺驅動。
然後返回平臺驅動的probe函式(自己實現通常是初始化操作)地址。
*/
return drv->probe(dev);
}
實現的匯流排平臺驅動模型的最簡單原始碼:
平臺裝置的實現:device.c
#include<linux/device.h>
#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/init.h>
#include<linux/string.h>
#include<linux/platform_device.h>
/*平臺模型驅動的平臺裝置物件*/
static struct platform_device *my_device;
/*初始化函式*/
static int __init my_device_init(void)
{
int ret = 0;
/*採用platform_device_alloc分配一個platform_device物件
引數分別為platform_device的name,和id。
*/
my_device = platform_device_alloc("my_dev",-1);
/*註冊裝置,注意不是platform_device_register,將平臺設備註冊到核心中*/
ret = platform_device_add(my_device);
/*如果出錯釋放相關的記憶體單元*/
if(ret)
{
platform_device_put(my_device);
}
return ret;
}
/*解除安裝處理函式*/
static void __exit my_device_exit(void)
{
platform_device_unregister(my_device);
}
module_init(my_device_init);
module_exit(my_device_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("GP-<[email protected]>");
平臺驅動的實現:driver.c
#include<linux/module.h>
#include<linux/init.h>
#include<linux/kernel.h>
#include<linux/platform_device.h>
#include<linux/string.h>
/*平臺驅動中的probe和remove函式是必須實現的函式*/
/*裝置驅動的探測函式,主要實現檢測總線上是否有該驅動對應的裝置*/
static my_probe(struct device *dev)
{
/*
如果新增實際的裝置到該平臺匯流排裝置驅動模型中,則可以在該函式
中實現具體的裝置驅動函式的初始化操作,包括裝置號的申請,裝置
的初始化,新增。自動裝置檔案建立函式的新增等操作。
或者是混雜字元裝置的相關初始化操作。當然結構體的相關處理仍
然採取全域性變數的形式。
*/
printk("Driver found devices which this driver can be handle\n");
return 0;
}
/*裝置驅動的移除函式,主要檢測該驅動支援裝置的移除活動檢測*/
static my_remove(struct device *dev)
{
/*
如果新增實際的裝置到該平臺匯流排裝置驅動模型中,則可以在該函式
中實現具體的裝置的釋放,包括裝置的刪除,裝置號的登出等操作。
*/
printk("Driver found device unpluded\n");
return 0;
}
static struct platform_driver my_driver =
{
/*平臺驅動的probe函式實現*/
.probe = my_probe,
/*平臺驅動的remove函式實現*/
.remove = my_remove,
/*實現裝置驅動的name和owner變數*/
.driver =
{
/*該引數主要實現匯流排中的匹配函式呼叫*/
.name = "my_dev",
/*該函式表示模組的擁有者*/
.owner = THIS_MODULE,
},
};
/*初始化函式*/
static int __init my_driver_init(void)
{
/*註冊平臺驅動*/
return platform_driver_register(&my_driver);
}
/*退出函式*/
static void __exit my_driver_exit(void)
{
/*登出平臺驅動*/
return platform_driver_unregister(&my_driver);
}
/*載入和解除安裝*/
module_init(my_driver_init);
module_exit(my_driver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("GP-<[email protected]>");
將一般裝置驅動加入到匯流排裝置模型中的相關操作是後期總結和學習的內容。裝置驅動實現的實現原理還是之前的那些操作,但是初始化和推出函式發生了改變。
總結:platform匯流排的驅動模型只是在一般匯流排模型的基礎上做了相關的延伸,實質上只要弄清除匯流排模型的一般原理,學習platform匯流排也就簡單不少。但是畢竟還是學習階段。
一. 概述 platform裝置和驅動與linux裝置模型密切相關。platform在linux裝置模型中,其實就是一種虛擬匯流排沒有對應的硬體結構。它的主要作用就是管理系統的外設資源,比如io記憶體,中斷訊號線。現在大多數處理器晶片都是soc,如s3c2440,它包括處理器核心(arm920t)和系統的外設(lcd介面,nandflash介面等)。linux在引入了platform機制之後,核心假設所有的這些外設都掛載在platform虛擬總線上,以便進行統一管理。二. platform 匯流排 1. 在系統中platform對應的檔案drivers/base/platform.c,它不是作為一個模組註冊到核心的,關鍵的註冊匯流排的函式由系統初始化部分,對應/init/main.c中的do_basic_setup函式間接呼叫。這裡可以看出platform非常重要,要在系統其他驅動載入之前註冊。下面分析platform匯流排註冊函式
1. int __init platform_bus_init(void)
2. {
3. int error;
4. early_platform_cleanup();
5. error = device_register(&platform_bus);
6. //匯流排也是裝置,所以也要進行裝置的註冊
7. if (error)
8. return error;
9. error = bus_register(&platform_bus_type);
10. //註冊platform_bus_type匯流排到核心
11. if (error)
12. device_unregister(&platform_bus);
13. return error;
14. }
這個函式向核心註冊了一種匯流排。他首先由/drivers/base/init.c中的driver_init函式呼叫,driver_init函式由/init/main.c中的do_basic_setup函式呼叫,do_basic_setup這個函式由kernel_init呼叫,所以platform匯流排是在核心初始化的時候就註冊進了核心。 2. platform_bus_type 匯流排結構與裝置結構 (1) platform匯流排 裝置結構
1. struct device platform_bus = {
2. .init_name = "platform",
3. };
platform匯流排也是一種裝置,這裡初始化一個device結構,裝置名稱platform,因為沒有指定父裝置,所以註冊後將會在/sys/device/下出現platform目錄。 (2) platform匯流排 匯流排結構
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_dev_pm_ops,
7. };
platform_dev_attrs 裝置屬性 platform_match match函式,這個函式在當屬於platform的裝置或者驅動註冊到核心時就會呼叫,完成裝置與驅動的匹配工作。 platform_uevent 熱插拔操作函式三. platform 裝置 1. platform_device 結構
1. struct platform_device {
2. constchar * name;
3. int id;
4. struct device dev;
5. u32 num_resources;
6. struct resource * resource;
7. struct platform_device_id *id_entry;
8. /* arch specific additions */
9. struct pdev_archdata archdata;
10. };
(1)platform_device結構體中有一個struct resource結構,是裝置佔用系統的資源,定義在ioport.h中,如下
1. struct resource {
2. resource_size_t start;
3. resource_size_t end;
4. constchar *name;
5. unsigned long flags;
6. struct resource *parent, *sibling, *child;
7. };
(2) num_resources 佔用系統資源的數目,一般裝置都佔用兩種資源,io記憶體和中斷訊號線。這個為兩種資源的總和。 2. 設備註冊函式 platform_device_register
1. int platform_device_register(struct platform_device *pdev)
2. {
3. device_initialize(&pdev->dev);
4. return platform_device_add(pdev);
5. }
這個函式首先初始化了platform_device的device結構,然後呼叫platform_device_add,這個是註冊函式的關鍵,下面分析platform_device_add:
1. int platform_device_add(struct platform_device *pdev)
2. {
3. int i, ret = 0;
4.
5. if (!pdev)
6. return -EINVAL;
7.
8. if (!pdev->dev.parent)
9. pdev->dev.parent = &platform_bus;
10. //可以看出,platform裝置的父裝置一般都是platform_bus,所以註冊後的platform裝置都出現在/sys/devices/platform_bus下
11. pdev->dev.bus = &platform_bus_type;
12. //掛到platform總線上
13. if (pdev->id != -1)
14. dev_set_name(&pdev->dev, "%s.%d", pdev->name, pdev->id);
15. else
16. dev_set_name(&pdev->dev, "%s", pdev->name);
17. //設定裝置名字,這個名字與/sys/devices/platform_bus下的名字對應
18. for (i = 0; i < pdev->num_resources; i++) { //下面操作裝置所佔用的系統資源
19. struct resource *p, *r = &pdev->resource[i];
20.
21. if (r->name == NULL)
22. r->name = dev_name(&pdev->dev);
23.
24. p = r->parent;
25. if (!p) {
26. if (resource_type(r) == IORESOURCE_MEM)
27. p = &iomem_resource;
28. elseif (resource_type(r) == IORESOURCE_IO)
29. p = &ioport_resource;
30. }
31.
32. if (p && insert_resource(p, r)) {
33. printk(KERN_ERR
34. "%s: failed to claim resource %d\n",
35. dev_name(&pdev->dev), i);
36. ret = -EBUSY;
37. goto failed;
38. }
39. }
40. //上面主要是遍歷裝置所佔用的資源,找到對應的父資源,如果沒有定義,那麼根據資源的型別,分別賦予iomem_resource和ioport_resource,然後呼叫insert_resource插入資源。
41. //這樣系統的資源就形成了一個樹形的資料結構,便於系統的管理
42. pr_debug("Registering platform device '%s'. Parent at %s\n",
43. dev_name(&pdev->dev), dev_name(pdev->dev.parent));
44.
45. ret = device_add(&pdev->dev);
46. //註冊到裝置模型中
47. if (ret == 0)
48. return ret;
49. failed:
50. while (--i >= 0) {
51. struct resource *r = &pdev->resource[i];
52. unsigned long type = resource_type(r);
53. if (type == IORESOURCE_MEM || type == IORESOURCE_IO)
54. release_resource(r);
55. }
56. return ret;
57. }
3. mini2440核心註冊platform裝置過程 因為一種soc確定之後,其外設模組就已經確定了,所以註冊platform裝置就由板級初始化程式碼來完成,在mini2440中是mach-mini2440.c的mini2440_machine_init函式中呼叫platform_add_devices(mini2440_devices, ARRAY_SIZE(mini2440_devices))來完成註冊。這個函式完成mini2440的所有platform裝置的註冊: (1) platform_add_devices函式是platform_device_register的簡單封裝,它向核心註冊一組platform裝置 (2) mini2440_devices是一個platform_device指標陣列,定義如下:
1. staticstruct platform_device *mini2440_devices[] __initdata = {
2. &s3c_device_usb,
3. &s3c_device_rtc,
4. &s3c_device_lcd,
5. &s3c_device_wdt,
6. &s3c_device_i2c0,
7. &s3c_device_iis,
8. &mini2440_device_eth,
9. &s3c24xx_uda134x,
10. &s3c_device_nand,
11. &s3c_device_sdi,
12. &s3c_device_usbgadget,
13. };
這個就是mini2440的所有外設資源了,每個外設的具體定義在/arch/arm/plat-s3c24xx/devs.c,下面以s3c_device_lcd為例說明,其他的類似。s3c_device_lcd在devs.c中它定義為:
1. struct platform_device s3c_device_lcd = {
2. .name = "s3c2410-lcd",
3.