在 "Linux設備樹語法詳解" 和 "Linux Platform驅動模型(一) _設備信息" 中我們討論了設備信息的寫法,本文主要討論平臺匯流排中另外一部分 驅動方法,將試圖回答下麵幾個問題: 1. 如何填充platform_driver對象? 2. 如何將驅動方法對象註冊到平臺匯流排中? 正文前的 ...
在Linux設備樹語法詳解和Linux Platform驅動模型(一) _設備信息中我們討論了設備信息的寫法,本文主要討論平臺匯流排中另外一部分-驅動方法,將試圖回答下麵幾個問題:
- 如何填充platform_driver對象?
- 如何將驅動方法對象註冊到平臺匯流排中?
正文前的一點羅嗦
寫驅動也有一段時間了,可以發現,其實驅動本質上只做了兩件事:向上提供介面,向下控制硬體,當然,這裡的向上並不是直接提供介面到應用層,而是提供介面給內核再由內核間接的將我們的介面提供給應用層。而寫驅動也是有一些套路可尋的,拿到一個硬體,我們大體可以按照下麵的流程寫一個驅動:
- 確定驅動架構:根據硬體連接方式結合分層/分離思想設計驅動的基本結構
- 確定驅動對象:內核中的一個驅動/設備就是一個對象,1.定義,2.初始化,3.註冊,4.註銷
- 向上提供介面:根據業務需要確定提供cdev/proc/sysfs哪種介面
- 向下控制硬體:1.查看原理圖確定引腳和控制邏輯,2.查看晶元手冊確定寄存器配置方式,3.進行記憶體映射,4.實現控制邏輯
認識驅動方法對象
內核用platform_driver結構來表示一個驅動方法對象
//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 };
下麵是我們關心的幾個成員
struct device_driver
--229-->驅動名,如果這個驅動只匹配一個C語言的設備,那麼可以通過name相同來匹配
--230-->匯流排類型,這個成員由內核填充
--232-->owner,通常就寫THIS_MODULE
--237-->of_device_id顧名思義就是用來匹配用設備樹寫的設備信息,下麵詳細討論
--249-->私有數據
driver與device的匹配
設備信息有三種表達方式,而一個驅動是可以匹配多個設備的,平臺匯流排中的驅動要具有三種匹配信息的能力,基於這種需求,platform_driver中使用不同的成員來進行相應的匹配。
of_match_table
對於使用設備樹編碼的設備信息,我們使用其父類device_driver中的of_match_table就是用來匹配
//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",},
{},
};
id_table
對於使用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"},
{},
};
name
如果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即探測函數,如果驅動匹配到了目標設備,匯流排會自動回調probe函數,下麵詳細討論。並把匹配到的設備信息封裝策劃嗯platform_device對象傳入,裡面主要完成下麵三個工作
- 申請資源
- 初始化
- 提供介面(cdev/sysfs/proc)
顯然,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)
/**
* 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;
}
註冊/註銷platform_driver對象
內核提供了兩個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);
在動態編譯的情況下,我們往往在模塊初始化函數中註冊一個驅動方法對象,而在模塊卸載函數中註銷一個驅動方法對象,所以我們可以寫一個如下的巨集來提高代碼復用
#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);
#endif
實例
這個實例同時使用了設備信息模塊和設備樹兩種設備信息來源,不過最終使用的是設備樹,需要註意的是,當我們用設備樹的設備信息時,有一個成員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");