1. 前言
device和device driver是Linux驅動開發的基本概念。Linux kernel的思路很簡單:驅動開發,就是要開發指定的軟件(driver)以驅動指定的設備,所以kernel就為設備和驅動它的driver定義了兩個數據結構,分別是device和device_driver。因此本文將會圍繞這兩個數據結構,介紹Linux設備模型的核心邏輯,包括:
設備及設備驅動在kernel中的抽象、使用和維護;
設備及設備驅動的注冊、加載、初始化原理;
設備模型在實際驅動開發過程中的使用方法。
注:在介紹device和device_driver的過程中,會遇到很多額外的知識點,如Class、Bus、DMA、電源管理等等,這些知識點都很復雜,任何一個都可以作為一個單獨的專題區闡述,因此本文不會深入解析它們,而會在后續的文章中專門描述。
2. struct device和struct device_driver
在閱讀Linux內核源代碼時,通過核心數據結構,即可理解某個模塊60%以上的邏輯,設備模型部分尤為明顯。
在include/linux/device.h中,Linux內核定義了設備模型中最重要的兩個數據結構,struct device和struct device_driver。
struct device
1: /* include/linux/device.h, line 660 */
2: struct device {
3: ????struct device *parent;
4:
5: ????struct device_private *p;
6:
7: ????struct kobject kobj;
8: ????const char *init_name; /* initial name of the device */
9: ????const struct device_type *type;
10:
11:????struct mutex mutex; /* mutex to synchronize calls to
12: ????????????????????????????* its driver.
13: ????????????????????????????*/
14:
15:????struct bus_type *bus; /* type of bus device is on */
16:????struct device_driver *driver; /* which driver has allocated this
17: ????????????????????????????????device */
18:????void *platform_data; /* Platform specific data, device
19: ????????????????????????core doesn't touch it */
20:????struct dev_pm_info power;
21:????struct dev_pm_domain *pm_domain;
22:
23: #ifdef CONFIG_PINCTRL
24:????struct dev_pin_info *pins;
25: #endif
26:
27: #ifdef CONFIG_NUMA
28:????int numa_node; /* NUMA node this device is close to */
29: #endif
30:????u64 *dma_mask; /* dma mask (if dma'able device) */
31:????u64 coherent_dma_mask;/* Like dma_mask, but for
32: ????????????????????????????alloc_coherent mappings as
33: ????????????????????????????not all hardware supports
34: ????????????????????????????64 bit addresses for consistent
35: ????????????????????????????allocations such descriptors. */
36:
37:????struct device_dma_parameters *dma_parms;
38:
39:????struct list_head dma_pools; /* dma pools (if dma'ble) */
40:
41:????struct dma_coherent_mem *dma_mem; /* internal for coherent mem
42:????????????????????????????override */
43: #ifdef CONFIG_CMA
44:????struct cma *cma_area; /* contiguous memory area for dma
45:????????????????????????????allocations */
46: #endif
47:????/* arch specific additions */
48:????struct dev_archdata archdata;
49:
50:????struct device_node *of_node; /* associated device tree node */
51:????struct acpi_dev_node acpi_node; /* associated ACPI device node */
52:
53:????dev_t devt; /* dev_t, creates the sysfs "dev" */
54:????u32 id; /* device instance */
55:
56:????spinlock_t devres_lock;
57:????struct list_head devres_head;
58:
59:????struct klist_node knode_class;
60:????struct class *class;
61:????const struct attribute_group **groups; /* optional groups */
62:
63:????void (*release)(struct device *dev);
64:????struct iommu_group *iommu_group;
65: };
device結構很復雜(不過linux內核的開發人員素質是很高的,該接口的注釋寫的非常詳細,感興趣的同學可以參考內核源代碼),這里將會選一些對理解設備模型非常關鍵的字段進行說明。
parent,該設備的父設備,一般是該設備所從屬的bus、controller等設備。
p,一個用于struct device的私有數據結構指針,該指針中會保存子設備鏈表、用于添加到bus/driver/prent等設備中的鏈表頭等等,具體可查看源代碼。
kobj,該數據結構對應的struct kobject。
init_name,該設備的名稱。
注1:在設備模型中,名稱是一個非常重要的變量,任何注冊到內核中的設備,都必須有一個合法的名稱,可以在初始化時給出,也可以由內核根據“bus name + device ID”的方式創造。
type,struct device_type結構是新版本內核新引入的一個結構,它和struct device關系,非常類似stuct kobj_type和struct kobject之間的關系,后續會再詳細說明。
bus,該device屬于哪個總線(后續會詳細描述)。
driver,該device對應的device driver。
platform_data,一個指針,用于保存具體的平臺相關的數據。具體的driver模塊,可以將一些私有的數據,暫存在這里,需要使用的時候,再拿出來,因此設備模型并不關心該指針得實際含義。
power、pm_domain,電源管理相關的邏輯,后續會由電源管理專題講解。
pins,"PINCTRL”功能,暫不描述。
numa_node,"NUMA”功能,暫不描述。
dma_mask~archdata,DMA相關的功能,暫不描述。
devt,dev_t是一個32位的整數,它由兩個部分(Major和Minor)組成,在需要以設備節點的形式(字符設備和塊設備)向用戶空間提供接口的設備中,當作設備號使用。在這里,該變量主要用于在sys文件系統中,為每個具有設備號的device,創建/sys/dev/* 下的對應目錄,如下:
1|root@android:/storage/sdcard0 #ls /sys/dev/char/1\:??????????????????????????????????????????????????????????????????????
1:1/? 1:11/ 1:13/ 1:14/ 1:2/? 1:3/? 1:5/? 1:7/? 1:8/? 1:9/??
1|root@android:/storage/sdcard0 #ls /sys/dev/char/1:1?????????????????????????????????????????????????????????????????????
1:1/? 1:11/ 1:13/ 1:14/?
1|root@android:/storage/sdcard0 # ls /sys/dev/char/1\:1??
/sys/dev/char/1:1??
class,該設備屬于哪個class。
groups,該設備的默認attribute集合。將會在設備注冊時自動在sysfs中創建對應的文件。
struct device_driver
1: /* include/linux/device.h, line 213 */
2: struct device_driver {
3: ????const char *name;
4: ????struct bus_type *bus;
5:
6: ????struct module *owner;
7: ????const char *mod_name; /* used for built-in modules */
8:
9: ????bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
10:
11:????const struct of_device_id *of_match_table;
12:????const struct acpi_device_id *acpi_match_table;
13:
14:????int (*probe) (struct device *dev);
15:????int (*remove) (struct device *dev);
16:????void (*shutdown) (struct device *dev);
17:????int (*suspend) (struct device *dev, pm_message_t state);
18:????int (*resume) (struct device *dev);
19:????const struct attribute_group **groups;
20:
21:????const struct dev_pm_ops *pm;
22:
23:????struct driver_private *p;
24: };
device_driver就簡單多了(在早期的內核版本中driver的數據結構為"struct driver”,不知道從哪個版本開始,就改成device_driver了):
name,該driver的名稱。和device結構一樣,該名稱非常重要,后面會再詳細說明。
bus,該driver所驅動設備的總線設備。為什么driver需要記錄總線設備的指針呢?因為內核要保證在driver運行前,設備所依賴的總線能夠正確初始化。
owner、mod_name,內核module相關的變量,暫不描述。
suppress_bind_attrs,是不在sysfs中啟用bind和unbind attribute,如下:root@android:/storage/sdcard0?# ls /sys/bus/platform/drivers/switch-gpio/???????????????????????????????????????????????????
bind?? uevent unbind?
在kernel中,bind/unbind是從用戶空間手動的為driver綁定/解綁定指定的設備的機制。這種機制是在bus.c中完成的,后面會詳細解釋。
probe、remove,這兩個接口函數用于實現driver邏輯的開始和結束。Driver是一段軟件code,因此會有開始和結束兩個代碼邏輯,就像PC程序,會有一個main函數,main函數的開始就是開始,return的地方就是結束。而內核driver卻有其特殊性:在設備模型的結構下,只有driver和device同時存在時,才需要開始執行driver的代碼邏輯。這也是probe和remove兩個接口名稱的由來:檢測到了設備和移除了設備(就是為熱拔插起的!)。
shutdown、suspend、resume、pm,電源管理相關的內容,會在電源管理專題中詳細說明。
groups,和struct device結構中的同名變量類似,driver也可以定義一些默認attribute,這樣在將driver注冊到內核中時,內核設備模型部分的代碼(driver/base/driver.c)會自動將這些attribute添加到sysfs中。
p,私有數據的指針,具體的driver代碼可以把任何需要的內容放在這里,反正設備模型代碼不關心。
3. 設備模型框架下驅動開發的基本步驟
在設備模型框架下,設備驅動的開發是一件很簡單的事情,主要包括2個步驟:
步驟1:分配一個struct device類型的變量,填充必要的信息后,把它注冊到內核中。
步驟2:分配一個struct device_driver類型的變量,填充必要的信息后,把它注冊到內核中。
這兩步完成后,內核會在合適的時機(后面會講),調用struct device_driver變量中的probe、remove、suspend、resume等回調函數,從而觸發或者終結設備驅動的執行。而所有的驅動程序邏輯,都會由這些回調函數實現,此時,驅動開發者眼中便不再有“設備模型”,轉而只關心驅動本身的實現。
以上兩個步驟的補充說明:
1. 一般情況下,Linux驅動開發很少直接使用device和device_driver,因為內核在它們之上又封裝了一層,如soc device、platform device等等,而這些層次提供的接口更為簡單、易用(也正是因為這個原因,本文并不會過多涉及device、device_driver等模塊的實現細節)。
2. 內核提供很多struct device結構的操作接口(具體可以參考include/linux/device.h和drivers/base/core.c的代碼),主要包括初始化(device_initialize)、注冊到內核(device_register)、分配存儲空間+初始化+注冊到內核(device_create)等等,可以根據需要使用。
3. device和device_driver必須具備相同的名稱,內核才能完成匹配操作,進而調用device_driver中的相應接口。這里的同名,作用范圍是同一個bus下的所有device和device_driver。
4. device和device_driver必須掛載在一個bus之下,該bus可以是實際存在的,也可以是虛擬的。
5. driver開發者可以在struct device變量中,保存描述設備特征的信息,如尋址空間、依賴的GPIOs等,因為device指針會在執行probe等接口時傳入,這時driver就可以根據這些信息,執行相應的邏輯操作了。
4. 設備驅動probe的時機
所謂的"probe”,是指在Linux內核中,如果存在相同名稱的device和device_driver(注:還存在其它方式,我們先不關注了),內核就會執行device_driver中的probe回調函數,而該函數就是所有driver的入口,可以執行諸如硬件設備初始化、字符設備注冊、設備文件操作ops注冊等動作("remove”是它的反操作,發生在device或者device_driver任何一方從內核注銷時,其原理類似,就不再單獨說明了)。
設備驅動prove的時機有如下幾種(分為自動觸發和手動觸發):
將struct device類型的變量注冊到內核中時自動觸發(device_register,device_add,device_create_vargs,device_create)
將struct device_driver類型的變量注冊到內核中時自動觸發(driver_register)
手動查找同一bus下的所有device_driver,如果有和指定device同名的driver,執行probe操作(device_attach)
手動查找同一bus下的所有device,如果有和指定driver同名的device,執行probe操作(driver_attach)
自行調用driver的probe接口,并在該接口中將該driver綁定到某個device結構中----即設置dev->driver(device_bind_driver)
注2:probe動作實際是由bus模塊(會在下一篇文章講解)實現的,這不難理解:device和device_driver都是掛載在bus這根線上,因此只有bus最清楚應該為哪些device、哪些driver配對。
注3:每個bus都有一個drivers_autoprobe變量,用于控制是否在device或者driver注冊時,自動probe。該變量默認為1(即自動probe),bus模塊將它開放到sysfs中了,因而可在用戶空間修改,進而控制probe行為。
5. 其它雜項
5.1 device_attribute和driver_attribute
在"Linux設備模型(4)_sysfs”中,我們有講到,大多數時候,attribute文件的讀寫數據流為:vfs---->sysfs---->kobject---->attibute---->kobj_type---->sysfs_ops---->xxx_attribute,其中kobj_type、sysfs_ops和xxx_attribute都是由包含kobject的上層數據結構實現。
Linux內核中關于該內容的例證到處都是,device也不無例外的提供了這種例子,如下:
1: /* driver/base/core.c, line 118 */
2: static ssize_t dev_attr_show(struct kobject *kobj, struct attribute *attr,
3: char *buf)
4: {
5: ????struct device_attribute *dev_attr = to_dev_attr(attr);
6: ????struct device *dev = kobj_to_dev(kobj);
7: ????ssize_t ret = -EIO;
8:
9: ????if (dev_attr->show)
10:????????ret = dev_attr->show(dev, dev_attr, buf);
11:????????if (ret >= (ssize_t)PAGE_SIZE) {
12:????????????print_symbol("dev_attr_show: %s returned bad count\n",
13:????????????????????????(unsigned long)dev_attr->show);
14:????}
15:????return ret;
16: }
17:
18: static ssize_t dev_attr_store(struct kobject *kobj, struct attribute *attr,
19: const char *buf, size_t count)
20: {
21:????struct device_attribute *dev_attr = to_dev_attr(attr);
22:????struct device *dev = kobj_to_dev(kobj);
23:????ssize_t ret = -EIO;
24:
25:????if (dev_attr->store)
26:????????ret = dev_attr->store(dev, dev_attr, buf, count);
27:????return ret;
28: }
29:
30: static const struct sysfs_ops dev_sysfs_ops = {
31:????.show = dev_attr_show,
32:????.store = dev_attr_store,
33: };
34:
35: /* driver/base/core.c, line 243 */
36: static struct kobj_type device_ktype = {
37:????.release = device_release,
38:????.sysfs_ops = &dev_sysfs_ops,
39:????.namespace = device_namespace,
40: };
41:
42: /* include/linux/device.h, line 478 */
43: /* interface for exporting device attributes */
44: struct device_attribute {
45:????struct attribute attr;
46:????ssize_t (*show)(struct device *dev, struct device_attribute *attr,
47:????????????????????char *buf);
48:????ssize_t (*store)(struct device *dev, struct device_attribute *attr,
49:????????????????????const char *buf, size_t count);
50: };
至于driver的attribute,則要簡單的多,其數據流為:vfs---->sysfs---->kobject---->attribute---->driver_attribute,如下:
1: /* include/linux/device.h, line 247 */
2: /* sysfs interface for exporting driver attributes */
3:
4: struct driver_attribute {
5: ????struct attribute attr;
6: ????ssize_t (*show)(struct device_driver *driver, char *buf);
7: ????ssize_t (*store)(struct device_driver *driver, const char *buf,
8: ????????????????????size_t count);
9: };
10:
11: #define DRIVER_ATTR(_name, _mode, _show, _store) \
12: struct driver_attribute driver_attr_##_name = \
13:????__ATTR(_name, _mode, _show, _store)
5.2 device_type
device_type是內嵌在struct device結構中的一個數據結構,用于指明設備的類型,并提供一些額外的輔助功能。它的的形式如下:
1: /* include/linux/device.h, line 467 */
2: struct device_type {
3: ????const char *name;
4: ????const struct attribute_group **groups;
5: ????int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
6: ????char *(*devnode)(struct device *dev, umode_t *mode,
7: ????????????????????kuid_t *uid, kgid_t *gid);
8: ????void (*release)(struct device *dev);
9:
10:????const struct dev_pm_ops *pm;
11: };
device_type的功能包括:
name表示該類型的名稱,當該類型的設備添加到內核時,內核會發出"DEVTYPE=‘name’”類型的uevent,告知用戶空間某個類型的設備available了
groups,該類型設備的公共attribute集合。設備注冊時,會同時注冊這些attribute。這就是面向對象中“繼承”的概念
uevent,同理,所有相同類型的設備,會有一些共有的uevent需要發送,由該接口實現
devnode,devtmpfs有關的內容,暫不說明
release,如果device結構沒有提供release接口,就要查詢它所屬的type是否提供。用于釋放device變量所占的空間
5.3 root device
在sysfs中有這樣一個目錄:/sys/devices,系統中所有的設備,都歸集在該目錄下。有些設備,是通過device_register注冊到Kernel并體現在/sys/devices/xxx/下。但有時候我們僅僅需要在/sys/devices/下注冊一個目錄,該目錄不代表任何的實體設備,這時可以使用下面的接口:
1: /* include/linux/device.h, line 859 */
2: /*
3: * Root device objects for grouping under /sys/devices
4: */
5: extern struct device *__root_device_register(const char *name,
6: struct module *owner);
7:
8: /*
9: * This is a macro to avoid include problems with THIS_MODULE,
10: * just as per what is done for device_schedule_callback() above.
11: */
12: #define root_device_register(name) \
13: __root_device_register(name, THIS_MODULE)
14:
15: extern void root_device_unregister(struct device *root);
該接口會調用device_register函數,向內核中注冊一個設備,但是(你也想到了),沒必要注冊與之對應的driver(順便提一下,內核中有很多不需要driver的設備,這是之一)。
?
評論
查看更多