/************************************************************************************html
*本文爲我的學習記錄,若有錯誤,歡迎指正。node
* http://www.cnblogs.com/xiaojiang1025/p/6367061.html數組
* http://www.cnblogs.com/xiaojiang1025/p/6367910.html數據結構
* http://www.cnblogs.com/xiaojiang1025/p/6369065.htmlide
* http://www.javashuo.com/article/p-dnlpbddz-bh.html函數
* http://www.javashuo.com/article/p-rzrvxiho-by.htmlpost
************************************************************************************/學習
在Linux2.6之後的設備驅動模型中,需關心總線,設備和驅動這三種實體,總線將設備和驅動綁定。Linux內核中的總線主要負責管理掛接在該總線下的設備與驅動,將設備信息與驅動程序分類管理,提升驅動程序的可移植性。在系統每註冊一個設備的時候,會尋找與之匹配的驅動;相同地,在系統每註冊一個驅動的時候,會尋找與之匹配的設備,而匹配由總線完成。
對於依附在USB、PCI、I2C、SPI等物理總線來 這些都不是問題。可是在嵌入式系統裏面,在SoC中集成的獨立外設控制器,掛接在SoC內存空間的外設等卻不依附在此類總線。基於這一背景,Linux發明了一種總線,稱爲platform。相對於USB、PCI、I2C、SPI等物理總線來講,platform總線是一種虛擬、抽象出來的總線,實際中並不存在這樣的總線。 ui
(1)platform總線相關代碼:/kernel/driver/base/platform.c 文件;
(2)相關數據結構體定義:/kernel/include/linux/platform_device.h 文件中。
platform總線在sysfs下的目錄爲/sys/bus/platform,該目錄下有兩個子目錄和相關的platform屬性文件;/platform/devices目錄下存放的是platform總線下的全部設備,/platform/drivers目錄下存放的是platform總線下的全部驅動程序。
platform總線的驅動與設備的管理與匹配機制以下圖所示。
在設備樹出現以前,設備信息只能使用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),裏面填充相應的設備數量,若是隻是一個,填-1 struct 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_NUMA int numa_node; /* NUMA node this device is close to */ #endif u64 *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_OF struct device_node *of_node; #endif dev_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; };
下面是一個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 0x00001000
Linux內核中定義了相關的宏來幫助咱們快速的建立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);
(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_MODULE const 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的方法之一 #endif int (*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);
(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; #else kernel_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方式 }