在Linux設備樹語法詳解和Linux Platform驅動模型(一) _設備信息中咱們討論了設備信息的寫法,本文主要討論平臺總線中另一部分-驅動方法,將試圖回答下面幾個問題:html
寫驅動也有一段時間了,能夠發現,其實驅動本質上只作了兩件事:向上提供接口,向下控制硬件,固然,這裏的向上並非直接提供接口到應用層,而是提供接口給內核再由內核間接的將咱們的接口提供給應用層。而寫驅動也是有一些套路可尋的,拿到一個硬件,咱們大致能夠按照下面的流程寫一個驅動:node
內核用platform_driver結構來表示一個驅動方法對象linux
//include/linux/device.h 173 struct platform_driver { 174 int (*probe)(struct platform_device *); 175 int (*remove)(struct platform_device *); 176 void (*shutdown)(struct platform_device *); 177 int (*suspend)(struct platform_device *, pm_message_t state); 178 int (*resume)(struct platform_device *); 179 struct device_driver driver; 180 const struct platform_device_id *id_table; 181 bool prevent_deferred_probe; 182 };
在這個結構中,咱們主要關心如下幾個成員數組
struct platform_driver
--174-->探測函數,若是驅動匹配到了目標設備,總線會自動回調probe函數,必須實現,下面詳細討論。
--175-->釋放函數,若是匹配到的設備從總線移除了,總線會自動回調remove函數,必須實現
--179-->platform_driver的父類,咱們接下來討論
--180-->用於C語言寫的設備信息,下面詳細討論。架構
platform_driver裏面有些內容須要在父類driver中實現,函數
//include/linux/device.h 228 struct device_driver { 229 const char *name; 230 struct bus_type *bus; 231 232 struct module *owner; 233 const char *mod_name; /* used for built-in modules */ 234 235 bool suppress_bind_attrs; /* disables bind/unbind via sysfs */ 236 237 const struct of_device_id *of_match_table; 238 const struct acpi_device_id *acpi_match_table; 239 240 int (*probe) (struct device *dev); 241 int (*remove) (struct device *dev); 242 void (*shutdown) (struct device *dev); 243 int (*suspend) (struct device *dev, pm_message_t state); 244 int (*resume) (struct device *dev); 245 const struct attribute_group **groups; 246 247 const struct dev_pm_ops *pm; 248 249 struct driver_private *p; 250 };
下面是咱們關心的幾個成員ui
struct device_driver
--229-->驅動名,若是這個驅動只匹配一個C語言的設備,那麼能夠經過name相同來匹配
--230-->總線類型,這個成員由內核填充
--232-->owner,一般就寫THIS_MODULE
--237-->of_device_id顧名思義就是用來匹配用設備樹寫的設備信息,下面詳細討論
--249-->私有數據編碼
設備信息有三種表達方式,而一個驅動是能夠匹配多個設備的,平臺總線中的驅動要具備三種匹配信息的能力,基於這種需求,platform_driver中使用不一樣的成員來進行相應的匹配。設計
對於使用設備樹編碼的設備信息,咱們使用其父類device_driver中的of_match_table就是用來匹配code
//include/linux/mod_devicetable.h 220 /* 221 * Struct used for matching a device 222 */ 223 struct of_device_id 224 { 225 char name[32]; 226 char type[32]; 227 char compatible[128]; 228 const void *data; 229 };
struct of_device_id
--225-->name[32]設備名
--226-->type[32]設備類型
--227-->重點!compatible[128]用於與設備樹compatible屬性值匹配的字符串
--228-->data驅動私有數據
對於一個驅動匹配多個設備的狀況,咱們使用struct of_device_id tbl[]來表示。
struct of_device_id of_tbl[] = { {.compatible = "xj4412,demo0",}, {.compatible = "xj4412,demo1",}, {}, };
對於使用C語言編碼的設備信息,咱們用platform_driver對象中的id_table就是用來匹配。咱們使用struct platform_device_id ids[]來實現一個驅動匹配多個C語言編碼的設備信息。
//include/linux/mod_deviceid.h 485 struct platform_device_id { 486 char name[PLATFORM_NAME_SIZE]; 487 kernel_ulong_t driver_data; 488 };
struct platform_device_id
--486-->name就是設備名
下面這個例子就是用一個驅動來匹配兩個分別叫"demo0"和"demo1"的設備,注意,數組最後的{}是必定要的,這個是內核判斷數組已經結束的標誌。
static struct platform_device_id tbl[] = { {"demo0"}, {"demo1"}, {}, };
若是platform_driver和C語言編碼的platform_device是一一匹配的,咱們還可使用device_driver中的name來進行匹配
填充完platform_driver結構以後,咱們應該將其中用到的設備表註冊到內核,雖然不註冊也能夠工做,可是註冊能夠將咱們表加入到相關文件中,便於內核管理設備。
MODULE_DEVICE_TABLE(類型, ID表); 設備樹ID表 類型:of C寫的platform_device的ID表 類型:platform C寫的i2c設備的ID表 類型:i2c C寫的USB設備的ID表 類型:usb
細心的讀者可能會發現,這麼多方式都寫在一個對象中,那若是我同時註冊了三種匹配結構內核該用哪一種呢?此時就須要咱們搬出平臺總線的匹配方式:
//drivers/base/platform.c 748 static int platform_match(struct device *dev, struct device_driver *drv) 749 { 750 struct platform_device *pdev = to_platform_device(dev); 751 struct platform_driver *pdrv = to_platform_driver(drv); 752 753 /* Attempt an OF style match first */ 754 if (of_driver_match_device(dev, drv)) 755 return 1; 756 757 /* Then try ACPI style match */ 758 if (acpi_driver_match_device(dev, drv)) 759 return 1; 760 761 /* Then try to match against the id table */ 762 if (pdrv->id_table) 763 return platform_match_id(pdrv->id_table, pdev) != NULL; 764 765 /* fall-back to driver name match */ 766 return (strcmp(pdev->name, drv->name) == 0); 767 }
從中不難看出,這幾中形式的匹配是有優先級的:of_match_table>id_table>name,瞭解到這點,咱們甚至能夠構造出同時適應兩種設備信息的平臺驅動:
static struct platform_driver drv = { .probe = demo_probe, .remove = demo_remove, .driver = { .name = "demo", #ifdef CONFIG_OF .of_match_table = of_tbl, #endif }, .id_table = tbl, };
此外,若是你追一下of_driver_match_device(),就會發現平臺總線的最終的匹配是compatible,name,type三個成員,其中一個爲NULL或""時表示任意,因此咱們使用平臺總線時老是使用compatile匹配設備樹,而不是節點路徑或節點名。
probe即探測函數,若是驅動匹配到了目標設備,總線會自動回調probe函數,下面詳細討論。並把匹配到的設備信息封裝策劃嗯platform_device對象傳入,裏面主要完成下面三個工做
顯然,remove主要完成與probe相反的操做,這兩個接口都是咱們必須實現的。
在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; }
內核提供了兩個API來註冊/註銷platform_driver對象到內核
/** * platform_driver_register - 註冊 */ int platform_driver_register(struct platform_driver *drv);
/** * platform_driver_unregister - 註銷 */ int platform_driver_unregister(struct platform_driver *drv);
在動態編譯的狀況下,咱們每每在模塊初始化函數中註冊一個驅動方法對象,而在模塊卸載函數中註銷一個驅動方法對象,因此咱們可使用內核中以下的宏來提升代碼複用
module_platform_driver(driver_name);
這個實例同時使用了設備信息模塊和設備樹兩種設備信息來源,不過最終使用的是設備樹,須要注意的是,當咱們用設備樹的設備信息時,有一個成員platform_device.device.of_node來表示設備的節點,這樣就容許咱們使用豐富的設備樹操做API來操做。
//#include "private.h" /* /{ demo{ compatible = "4412,demo0"; reg = <0x5000000 0x2 0x5000008 0x2>; interrupt-parent = <&gic>; interrupts = <0 25 0>, <0 26 0>; intpriv = <0x12345678>; strpriv = "hello world"; }; }; */ struct privatedata { int val; char str[36]; }; static void getprivdata(struct device_node *np) { struct property *prop; prop = of_find_property(np, "intpriv", NULL); if(prop) printk("private val: %x\n", *((int *)(prop->value))); prop = of_find_property(np, "strpriv", NULL); if(prop) printk("private str: %s\n", (char *)(prop->value) ); } static int demo_probe(struct platform_device *pdev) { int irq; struct resource *addr; struct privatedata *priv; printk(KERN_INFO "%s : %s : %d - entry.\n", __FILE__, __func__, __LINE__); priv = dev_get_platdata(&pdev->dev); if(priv){ printk(KERN_INFO "%x : %s \n", priv->val, priv->str); }else{ getprivdata(pdev->dev.of_node); } addr = platform_get_resource(pdev, IORESOURCE_MEM, 0); if(addr){ printk(KERN_INFO "0: %x : %d \n", addr->start, resource_size(addr)); } addr = platform_get_resource(pdev, IORESOURCE_MEM, 1); if(addr){ printk(KERN_INFO "1: %x : %d \n", addr->start, resource_size(addr)); } addr = platform_get_resource(pdev, IORESOURCE_MEM, 2); if(!addr){ printk(KERN_INFO "No 2 resource\n"); } irq = platform_get_irq(pdev, 0); if(0 > irq){ return irq; }else{ printk(KERN_INFO "irq 0: %d \n", irq); } irq = platform_get_irq(pdev, 1); if(0 > irq){ return irq; }else{ printk(KERN_INFO "irq 0: %d \n", irq); } irq = platform_get_irq(pdev, 2); if(0 > irq){ printk(KERN_INFO "No 2 irq\n"); } return 0; } static int demo_remove(struct platform_device *pdev) { return 0; } static struct platform_device_id tbl[] = { {"demo0"}, {"demo1"}, {}, }; MODULE_DEVICE_TABLE(platform, tbl); #ifdef CONFIG_OF struct of_device_id of_tbl[] = { {.compatible = "4412,demo0",}, {.compatible = "4412,demo1",}, {}, }; #endif //1. alloc obj static struct platform_driver drv = { .probe = demo_probe, .remove = demo_remove, .driver = { .name = "demo", #ifdef CONFIG_OF .of_match_table = of_tbl, #endif }, .id_table = tbl, }; static int __init drv_init(void) { //get command and pid printk(KERN_INFO "(%s:pid=%d), %s : %s : %d - entry.\n",current->comm, current->pid, __FILE__, __func__, __LINE__); return platform_driver_register(&drv); } static void __exit drv_exit(void) { //get command and pid printk(KERN_INFO "(%s:pid=%d), %s : %s : %d - leave.\n",current->comm, current->pid, __FILE__, __func__, __LINE__); platform_driver_unregister(&drv); } module_init(drv_init); module_exit(drv_exit); MODULE_LICENSE("GPL");