Linux platform驱动模型
/************************************************************************************
*本文為個人學習記錄,如有錯誤,歡迎指正。
*本文參考資料:?
* http://www.cnblogs.com/xiaojiang1025/p/6367061.html
* http://www.cnblogs.com/xiaojiang1025/p/6367910.html
* http://www.cnblogs.com/xiaojiang1025/p/6369065.html
* https://www.cnblogs.com/lifexy/p/7569371.html
* https://www.cnblogs.com/biaohc/p/6667529.html
************************************************************************************/
1. platform總線
1.1 platform總線簡介
在Linux2.6以后的設備驅動模型中,需關心總線,設備和驅動這三種實體,總線將設備和驅動綁定。Linux內核中的總線主要負責管理掛接在該總線下的設備與驅動,將設備信息與驅動程序分類管理,提高驅動程序的可移植性。在系統每注冊一個設備的時候,會尋找與之匹配的驅動;相同地,在系統每注冊一個驅動的時候,會尋找與之匹配的設備,而匹配由總線完成。?
對于依附在USB、PCI、I2C、SPI等物理總線來 這些都不是問題。但是在嵌入式系統里面,在SoC中集成的獨立外設控制器,掛接在SoC內存空間的外設等卻不依附在此類總線。基于這一背景,Linux發明了一種總線,稱為platform。相對于USB、PCI、I2C、SPI等物理總線來說,platform總線是一種虛擬、抽象出來的總線,實際中并不存在這樣的總線。?
1.2 platform總線核心代碼
(1)platform總線相關代碼:/kernel/driver/base/platform.c 文件;
(2)相關數據結構體定義:/kernel/include/linux/platform_device.h 文件中。
2. platform總線機制
platform總線在sysfs下的目錄為/sys/bus/platform,該目錄下有兩個子目錄和相關的platform屬性文件;/platform/devices目錄下存放的是platform總線下的所有設備,/platform/drivers目錄下存放的是platform總線下的所有驅動程序。
platform總線的驅動與設備的管理與匹配機制如下圖所示。
?
2.1 platform_device對象
在設備樹出現之前,設備信息只能使用C語言的方式進行編寫,在Linux3.0之后,設備信息就開始同時支持兩種編寫方式:設備樹、C語言。對于ARM平臺,使用設備樹封裝設備信息是將來的趨勢,但是由于歷史原因,當下的內核中這兩種種方式并存。
1)設備樹
使用設備樹,手動將設備信息寫到設備樹中之后,內核就可以自動從設備樹中提取相應的設備信息并將其封裝成相應的platform_device對象,并注冊到相應的總線中。從而,我們就不需要對設備信息再進行編碼。
2)C語言
使用C語言,我們需要將使用內核提供的結構將設備信息進行手動封裝,這種封裝又分為兩種形式,一種是使用平臺文件(靜態),將整個板子的所有設備都寫在一個文件中并編譯進內核。另一種是使用模塊(動態),將我們需要的設備信息編譯成模塊在insmod進內核。
本文主要討論C語言的方式實現設備信息的填充。
(1)struct platform_device
Linux內核中,使用struct platform_device來描述一個注冊在platform總線上的設備。對設備信息進行編碼,其實就是創建一個struct?platform_device對象,platform_device和其他設備一樣,都是device的子類。
struct platform_device{const char * name;//設備的名稱,是設備和驅動match的方法之一int id; //表示這個platform_device對象表征了幾個設備,當多個設備有共用資源的時候(MFD),里面填充相應的設備數量,如果只是一個,填-1struct device dev; //父類對象u32 num_resources;//資源的數量,即resource數組中元素的個數,我們用ARRAY_SIZE()宏來確定數組的大小struct resource * resource;//資源指針,如果是多個資源就是struct resource[]數組名const struct platform_device_id *id_entry;//設備和驅動match的方法之一/* arch specific additions */struct pdev_archdata archdata; };
struct platform_device的父類struct device。我們通常關心里面的platform_data和release,前者是用來存儲私有設備信息的,后者是供當這個設備的最后引用被刪除時被內核回調,注意和rmmod沒關系。
struct device {struct device *parent;struct device_private *p;struct kobject kobj;const char *init_name; /* initial name of the device */struct device_type *type;struct mutex mutex; /* mutex to synchronize calls to its driver.*/struct bus_type *bus; /* type of bus device is on */struct device_driver *driver; /* which driver has allocated this device */void *platform_data; /* Platform specific data, device core doesn't touch it */struct dev_pm_info power;#ifdef CONFIG_NUMAint numa_node; /* NUMA node this device is close to */ #endifu64 *dma_mask; /* dma mask (if dma'able device) */u64 coherent_dma_mask;struct device_dma_parameters *dma_parms;struct list_head dma_pools; /* dma pools (if dma'ble) */struct dma_coherent_mem *dma_mem; /* internal for coherent mem override */struct dev_archdata archdata; #ifdef CONFIG_OFstruct device_node *of_node; #endifdev_t devt; /* dev_t, creates the sysfs "dev" */spinlock_t devres_lock;struct list_head devres_head;struct klist_node knode_class;struct class *class;const struct attribute_group **groups; /* optional groups */void (*release)(struct device *dev); };struct device_private {struct klist klist_children;struct klist_node knode_parent;struct klist_node knode_driver;struct klist_node knode_bus;void *driver_data;struct device *device; }; struct device下面是一個platform_device的實例。
static struct platform_device demo_device ={.name = "demo",.id = -1,.dev =
{.platform_data = &priv,.release = dev_release,},.num_resources = ARRAY_SIZE(res),.resource = res, };
(2)struct resource
Linux內核中,使用struct resource來描述一個設備所用到的資源(地址資源或中斷資源)。
struct resource {resource_size_t start;//表示資源開始的位置,如果是IO地址資源,就是起始物理地址;如果是中斷資源,就是中斷號resource_size_t end; //表示資源結束的位置,如果是IO地址地址,就是映射的最后一個物理地址;如果是中斷資源,就不用填const char *name; //資源的名字unsigned long flags; //資源類型struct resource *parent, *sibling, *child;//用于組成管理資源的鏈表 };Linux內核中定義了相關的宏來表示resource的類型。
#define IORESOURCE_TYPE_BITS 0x00001f00 #define IORESOURCE_IO 0x00000100 #define IORESOURCE_MEM 0x00000200 #define IORESOURCE_IRQ 0x00000400 #define IORESOURCE_DMA 0x00000800 #define IORESOURCE_BUS 0x00001000Linux內核中定義了相關的宏來幫助我們快速的創建resource對象。(一些版本較低的內核可能不支持resource的相關宏)
#define DEFINE_RES_IO(_start, _size) #define DEFINE_RES_MEM(_start, _size) #define DEFINE_RES_IRQ(_irq) #define DEFINE_RES_DMA(_dma)一個設備一般會用到多種不同類型的資源,因此一般用一個struct? resource數組來描述一個設備所需的資源。下面是一個resource的實例。該實例用到了resource的兩種寫法,一般推薦使用內核中的宏來實現。
struct resource res[] ={[0] =
{.start = 0x10000000,.end = 0x20000000-1,.flags = IORESOURCE_MEM},[1] = DEFINE_RES_MEM(0x20000000, 1024),[2] =
{.start = 10, //中斷號.flags = IORESOURCE_IRQ|IRQF_TRIGGER_RISING//include/linux/interrupt.h },[3] = DEFINE_RES_IRQ(11), };
(3)設備對象的注冊與注銷
/**注冊:把指定設備添加到內核中平臺總線的設備列表,等待匹配,匹配成功則回調驅動中probe;*/ int platform_device_register(struct platform_device *pdev); /**注銷:把指定設備從設備列表中刪除,如果驅動已匹配則回調驅動方法和設備信息中的release;*/ void platform_device_unregister(struct platform_device *pdev);通常,我們會將platform_device_register寫在模塊加載的函數中,將platform_device_unregister寫在模塊卸載函數中。我們可以模仿內核的宏寫一個注冊、注銷的快捷方式。
#define module_platform_device (xxx) \ static int __init xxx##_init(void) \ { \return platform_device_register(&xxx); \ } \ static void __exit xxx##_exit(void) \ { \platform_device_unregister(&xxx); \ } \ module_init(xxx##_init); \ module_exit(xxx##_exit);2.2 platform_driver對象
(1)struct platform_driver
Linux內核中,使用struct platform_driver來描述一個注冊在platform總線上的驅動程序。對驅動信息進行編碼,其實就是創建一個struct?platform_driver對象,platform_driver是device_driver的子類。
struct platform_driver {int (*probe)(struct platform_device *); //探測函數,如果驅動匹配到了目標設備,總線會自動回調probe函數,由驅動工程師實現(必須實現)int (*remove)(struct platform_device *); //釋放函數,如果匹配到的設備從總線移除了,總線會自動回調remove函數,由驅動工程師實現(必須實現)void (*shutdown)(struct platform_device *);int (*suspend)(struct platform_device *, pm_message_t state);int (*resume)(struct platform_device *);struct device_driver driver; //platform_driver的父類const struct platform_device_id *id_table;//設備信息,設備與驅動match的方法之一 };platform_driver里面有些內容需要在其父類driver中實現。
struct device_driver {const char *name; //驅動名稱,設備和驅動的match方法之一struct bus_type *bus; //總線類型,這個成員由內核填充struct module *owner; //owner,通常就寫THIS_MODULEconst char *mod_name; /* used for built-in modules */bool suppress_bind_attrs; /* disables bind/unbind via sysfs */ #if defined(CONFIG_OF)const struct of_device_id *of_match_table; //設備樹表示的驅動信息,設備和驅動match的方法之一 #endifint (*probe) (struct device *dev);int (*remove) (struct device *dev);void (*shutdown) (struct device *dev);int (*suspend) (struct device *dev, pm_message_t state);int (*resume) (struct device *dev);const struct attribute_group **groups;const struct dev_pm_ops *pm;struct driver_private *p; //私有數據 };下面是一個platform_driver的實例。
static struct platform_driver drv = {.probe = demo_probe,.remove = demo_remove,.driver = {.name = "demo",}, };(2)probe與remove函數
probe即探測函數,如果驅動匹配到了目標設備,總線會自動回調probe函數,并把匹配到的設備信息platform_device對象傳入。probe函數的主要工作如下:
1)申請資源;
2)初始化;
3)提供接口(cdev/sysfs/proc)。
顯然,remove主要完成與probe相反的操作,這兩個接口都是我們必須實現的。remove函數的主要工作如下:
1)釋放資源;
2)釋放接口(cdev/sysfs/proc)。
在probe的工作中,最常見的就是提取設備信息,雖然總線會將設備信息封裝成一個platform_device對象并傳入probe函數,我們可以很容易的得到關于這個設備的所有信息,但是更好的方法就是直接使用內核API中相關的函數。
/*** platform_get_resource - 獲取資源* @dev: 平臺總線設備* @type:資源類型,include/linux/ioport.h中有定義* @num: 資源索引,即第幾個此類型的資源,從0開始*/ struct resource *platform_get_resource(struct platform_device *dev,unsigned int type, unsigned int num)注意,通過內核API(eg,上下這兩個API)獲取的resource如果是中斷,那么只能是軟中斷號,而不是芯片手冊/C語言設備信息/設備樹設備信息中的硬中斷號,但是此時獲取的resource的flag是可以正確的反映該中斷的觸發方式的,只需要 "?flag & IRQF_TRIGGER_MASK?" 即可獲取該中斷的觸發方式。
/*** platform_get_irq - 獲取一個設備的中斷號* @dev: 平臺總線設備* @num: 中斷號索引,即想要獲取的第幾個中斷號,從0開始*/ int platform_get_irq(struct platform_device *dev, unsigned int num) /*** dev_get_platdata - 獲取私有數據*/ static inline void *dev_get_platdata(const struct device *dev) { return dev->platform_data; }(3)驅動對象的注冊與注銷
/** platform_driver_register - 注冊*/ int platform_driver_register(struct platform_driver *drv);/** platform_driver_unregister - 注銷*/ int platform_driver_unregister(struct platform_driver *drv);通常,我們會將platform_driver_register寫在模塊加載的函數中,將platform_driver_unregister寫在模塊卸載函數中。我們可以模仿內核的宏寫一個注冊、注銷的快捷方式。
#define module_platform_driver (xxx) \ static int __init xxx##_init(void) \ { \return platform_driver_register(&xxx); \ } \ static void __exit xxx##_exit(void) \ { \platform_driver_unregister(&xxx); \ } \ module_init(xxx##_init); \ module_exit(xxx##_exit);2.3 platform總線
(1)struct bus_type
platform總線由內核工程師注冊好的,驅動工程師無需修改platform總線的相關代碼,只需調用其接口即可。了解platform總線的數據結構,有助于驅動工程師對platform總線的總體理解與應用。
struct bus_type platform_bus_type = {.name = "platform", //總線名稱.dev_attrs = platform_dev_attrs, //總線屬性.match = platform_match, //總線下的設備與驅動的匹配函數.uevent = platform_uevent,.pm = &platform_dev_pm_ops, //電源管理函數 };(2)device與driver的匹配(match)
從platform_match函數中可知,它支持三種匹配方式,這三種匹配方式的優先級為:of_match_table > id_table > name。
1)of_match_table?
platform_driver->device_driver->of_device_id(of_match_table?)包含了該驅動所支持的設備(使用設備樹編碼的設備信息)。of_match_table 與設備樹進行匹配,查看設備樹中是否有相應的設備。
struct of_device_id {char name[32]; //設備名char type[32]; //設備類型char compatible[128];//用于與設備樹compatible屬性值匹配的字符串 #ifdef __KERNEL__void *data; #elsekernel_ulong_t data; //私有數據 #endif };對于一個驅動匹配多個設備的情況,使用struct of_device_id tbl[]來表示。
struct of_device_id of_tbl[] = {{.compatible = "x210,demo0",},{.compatible = "x210,demo1",},{}, //表示結束,必須要有 };2)id_table?
將設備中的platform_device -> platform_device_id與驅動中的platform_driver -> platform_device_id進行比對,看該設備與驅動是否匹配。
struct platform_device_id {char name[PLATFORM_NAME_SIZE]; //設備名 kernel_ulong_t driver_data__attribute__((aligned(sizeof(kernel_ulong_t)))); };對于一個驅動匹配多個設備的情況,使用struct platform_device_id tbl[]來表示。
static struct platform_device_id tbl[] = {{"demo0"},{"demo1"},{},//表示結束,必須要有 };3)name
如果platform_driver和C語言編碼的platform_device是一 一匹配,則將設備中的platform_device -> name與驅動中的platform_driver -> name進行比對,看該設備與驅動是否匹配。?
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);/* Attempt an OF style match first */if (of_driver_match_device(dev, drv)) //of_match_table方式return 1;/* Then try ACPI style match */if (acpi_driver_match_device(dev, drv))return 1;/* Then try to match against the id table */if (pdrv->id_table)return platform_match_id(pdrv->id_table, pdev) != NULL; //platform_device_id方式/* fall-back to driver name match */ return (strcmp(pdev->name, drv->name) == 0); //platform_device->name方式 }3. platform總線應用實例
驅動程序實例(一):LED設備驅動程序( platform + cdev)
驅動程序實例(二):LED設備驅動程序( platform + /sys接口)
轉載于:https://www.cnblogs.com/linfeng-learning/p/9334853.html
總結
以上是生活随笔為你收集整理的Linux platform驱动模型的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: IOC和DI 控制反转和依赖注入
- 下一篇: QMsgPack的用法DEMO