LINUX裝置驅動模型之class
轉自 https://blog.csdn.net/qq_20678703/article/details/52754661
1、LINUX裝置驅動模型中的bus、device、driver,。其中bus:實際的匯流排,device:實際的裝置和介面,而driver:對應存在的驅動。
2、但本節要介紹的class,是裝置類,完全是抽象出來的概念,沒有對應的實體。所謂裝置類,是指提供的使用者介面相似的一類裝置的集合,常見的裝置類的有block、tty、input、usb等等。
3、class對應的程式碼在drivers/base/class.c中,對應的標頭檔案在include/linux/device.h和drivers/base/base.h
還是先來看class涉及的結構。
[cpp] view plain copy- struct class {
- const char *name;
- struct module *owner;
- struct class_attribute *class_attrs;
- struct device_attribute *dev_attrs;
- struct kobject *dev_kobj;
- int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env);
- char *(*devnode)(struct device *dev, mode_t *mode);
- void (*class_release)(struct class *class);
- void (*dev_release)(struct device *dev);
- int (*suspend)(struct device *dev, pm_message_t state);
- int (*resume)(struct device *dev);
- const struct dev_pm_ops *pm;
- struct class_private *p;
- };
struct class就是裝置驅動模型中通用的裝置類結構。
name代表類名稱,會在“/sys/class/”目錄下體現,例如:sys/class/p但和bus/device/driver中的名稱一樣,是初始名稱,實際使用的是內部kobj包含的動態建立的名稱。
owner是class所屬的模組,雖然class是涉及一類裝置,但也是由相應的模組註冊的。比如usb類就是由usb模組註冊的。
class_atrrs,該class的預設attribute,會在class註冊到核心時,自動在“/sys/class/xxx_class”下建立對應的attribute檔案
dev_attrs,該class下每個裝置的attribute,會在設備註冊到核心時,自動在該裝置的sysfs目錄下建立對應的attribute檔案。
dev_bin_attrs,類似dev_attrs,只不過是二進位制型別attribute。
class_attrs是class給自己新增的屬性,dev_attrs是class給所包含的裝置新增的屬性。這裡就像bus中一樣,只是bus是bus、driver、device全部包含的。
dev_kobj是一個kobject指標。如果你的記性很好(至少要比我好得多),你應該記得在device註冊時,會在/sys/dev下建立名為自己裝置號的軟連結。但裝置不知道自己屬於塊裝置還是字元裝置,所以會請示自己所屬的class,class就是用dev_kobj記錄本類裝置應屬於的哪種裝置。表示該class下的裝置在/sys/dev/下的目錄,現在一般有char和block兩個,如果dev_kobj為NULL,則預設選擇char。
dev_uevent()是在裝置發出uevent訊息時新增環境變數用的。還記得在core.c中的dev_uevent()函式,其中就包含對裝置所屬bus或class中dev_uevent()方法的呼叫,只是bus結構中定義方法用的函式名是uevent。
devnode()返回裝置節點的相對路徑名。在core.c的device_get_devnode()中有呼叫到。
class_release()是在class釋放時呼叫到的。類似於device在結構中為自己定義的release函式。
dev_release()自然是在裝置釋放時呼叫到的。具體在core.c的device_release()函式中呼叫。
suspend()是在裝置休眠時呼叫。
resume()是恢復裝置時呼叫。
pm是電源管理用的函式集合,在bus、driver、class中都有看到,只是在device中換成了dev_pm_info結構,但device_type中還是隱藏著dev_pm_ops的指標。可見電源管理還是很重要的,只是這些東西都要結合具體的裝置來分析,這裡的裝置驅動模型能給的,最多是一個函式指標與通用資料的框架。
p是指向class_private結構的指標。
[cpp] view plain copy- /**
- * struct class_private - structure to hold the private to the driver core portions of the class structure.
- *
- * @class_subsys - the struct kset that defines this class. This is the main kobject
- * @class_devices - list of devices associated with this class
- * @class_interfaces - list of class_interfaces associated with this class
- * @class_dirs - "glue" directory for virtual devices associated with this class
- * @class_mutex - mutex to protect the children, devices, and interfaces lists.
- * @class - pointer back to the struct class that this structure is associated
- * with.
- *
- * This structure is the one that is the actual kobject allowing struct
- * class to be statically allocated safely. Nothing outside of the driver
- * core should ever touch these fields.
- */
- struct class_private {
- struct kset class_subsys;
- struct klist class_devices;
- struct list_head class_interfaces;
- struct kset class_dirs;
- struct mutex class_mutex;
- struct class *class;
- };
- #define to_class(obj) \
- container_of(obj, struct class_private, class_subsys.kobj)
struct class_private,是class連線到系統中的重要結構 私有資料。
class_subsys是kset型別,代表class在sysfs中的位置。
class_devices是klist型別,是class下的裝置連結串列。
class_interfaces是list_head型別的類介面連結串列,裝置類介面稍後會介紹。
class_dirs也是kset型別,它並未實際在sysfs中體現,反而是其下連結了一系列膠水kobject。記得在core.c中的get_device_parent()函式,好像小蝌蚪找媽媽一樣,我們在為新註冊的裝置尋找sysfs中可以存放的位置。如果發現dev->class存在,而dev->parent->class不存在,就要建立一個膠水目錄,在sysfs中隔離這兩個實際上有父子關係的裝置。linux這麼做也是為了在sysfs顯示時更清晰一些。但如果父裝置下有多個屬於同一類的裝置,它們需要放在同一膠水目錄下。怎麼尋找這個膠水目錄有沒有建立過,就要從這裡的class_dirs下的kobject中找了。
class_mutex是互斥訊號量,用於保護class內部的資料結構。
class是指回struct class的指標。
[cpp] view plain copy- struct class_interface {
- struct list_head node;
- struct class *class;
- int (*add_dev) (struct device *, struct class_interface *);
- void (*remove_dev) (struct device *, struct class_interface *);
- };
struct class_interface就是之前被串在class->p->class_interface上的類介面的結構。用於描述裝置類對外的一種介面。
node就是class->p->class_interface連結串列上的節點。
class是指向所屬class的指標。
add_dev()是在有裝置新增到所屬class時呼叫的函式。當然,如果class_interface比裝置更晚新增到class,也會補上的。
remove_dev()是在裝置刪除時呼叫的。
從結構來看class_interface真是太簡單了。我們都懷疑其到底有沒有用。但往往看起來簡單的內容實際可能更復雜,比如driver,還有這裡的class_interface。
[cpp] view plain copy- struct class_attribute {
- struct attribute attr;
- ssize_t (*show)(struct class *class, char *buf);
- ssize_t (*store)(struct class *class, const char *buf, size_t count);
- };
- #define CLASS_ATTR(_name, _mode, _show, _store) \
- struct class_attribute class_attr_##_name = __ATTR(_name, _mode, _show, _store)
從bus_attribute,到driver_attribute,到device_attribute,當然也少不了這裡的class_attribute。struct attribute封裝這種東西,既簡單又耐用,何樂而不為?
結構講完了,下面看看class.c中的實現,還是我喜歡的自上而下式。
[cpp] view plain copy- #define to_class_attr(_attr) container_of(_attr, struct class_attribute, attr)
- static ssize_t class_attr_show(struct kobject *kobj, struct attribute *attr,
- char *buf)
- {
- struct class_attribute *class_attr = to_class_attr(attr);
- struct class_private *cp = to_class(kobj);
- ssize_t ret = -EIO;
- if (class_attr->show)
- ret = class_attr->show(cp->class, buf);
- return ret;
- }
- static ssize_t class_attr_store(struct kobject *kobj, struct attribute *attr,
- const char *buf, size_t count)
- {
- struct class_attribute *class_attr = to_class_attr(attr);
- struct class_private *cp = to_class(kobj);
- ssize_t ret = -EIO;
- if (class_attr->store)
- ret = class_attr->store(cp->class, buf, count);
- return ret;
- }
- static struct sysfs_ops class_sysfs_ops = {
- .show = class_attr_show,
- .store = class_attr_store,
- };
class_sysfs_ops就是class定義的sysfs讀寫函式集合。
[cpp] view plain copy- static void class_release(struct kobject *kobj)
- {
- struct class_private *cp = to_class(kobj);
- struct class *class = cp->class;
- pr_debug("class '%s': release.\n", class->name);
- if (class->class_release)
- class->class_release(class);
- else
- pr_debug("class '%s' does not have a release() function, "
- "be careful\n", class->name);
- }
- static struct kobj_type class_ktype = {
- .sysfs_ops = &class_sysfs_ops,
- .release = class_release,
- };
class_release()是在class引用計數降為零時呼叫的釋放函式。因為class在結構中提供了class_release的函式指標,所以可以由具體的class呼叫相應的處理方法。
class_ktype是為class對應的kobject(也可以說kset)定義的kobj_type。
[cpp] view plain copy- /* Hotplug events for classes go to the class class_subsys */
- static struct kset *class_kset;
- int __init classes_init(void)
- {
- class_kset = kset_create_and_add("class", NULL, NULL); //class_kset代表了/sys/class
- if (!class_kset)
- return -ENOMEM;
- return 0;
- }
class_kset代表了/sys/class對應的kset,在classes_init()中建立。
classes_init()的作用,和之前見到的buses_init()、devices_init()作用相似,都是構建/sys下的主要目錄結構。
[cpp] view plain copy- int class_create_file(struct class *cls, const struct class_attribute *attr)
- {
- int error;
- if (cls)
- error = sysfs_create_file(&cls->p->class_subsys.kobj,
- &attr->attr); //cls->p->class_subsys.kobj 代表sys/class/下的目錄 例如:sys/class/video4linux
- else
- error = -EINVAL;
- return error;
- }
- void class_remove_file(struct class *cls, const struct class_attribute *attr)
- {
- if (cls)
- sysfs_remove_file(&cls->p->class_subsys.kobj, &attr->attr);
- }
class_create_file()建立class的屬性檔案。
class_remove_files()刪除class的屬性檔案。這兩個都是對外提供的API。
[cpp] view plain copy- static struct class *class_get(struct class *cls)
- {
- if (cls)
- kset_get(&cls->p->class_subsys); //class_subsys是一個容器,kset,其容器本身也是一個 kobj
- return cls;
- }
- static void class_put(struct class *cls)
- {
- if (cls)
- kset_put(&cls->p->class_subsys);
- }
class_get()增加對cls的引用計數,class_put()減少對cls的引用計數,並在計數降為零時呼叫相應的釋放函式,也就是之前見過的class_release函式。
class的引用計數是由class_private結構中的kset來管的,kset又是由其內部kobject來管的,kobject又是呼叫其結構中的kref來管的。這是一種巢狀的封裝技術。
[cpp] view plain copy- static int add_class_attrs(struct class *cls)
- {
- int i;
- int error = 0;
- if (cls->class_attrs) {
- for (i = 0; attr_name(cls->class_attrs[i]); i++) {
- error = class_create_file(cls, &cls->class_attrs[i]);
- if (error)
- goto error;
- }
- }
- done:
- return error;
- error:
- while (--i >= 0)
- class_remove_file(cls, &cls->class_attrs[i]);
- goto done;
- }
- static void remove_class_attrs(struct class *cls)
- {
- int i;
- if (cls->class_attrs) {
- for (i = 0; attr_name(cls->class_attrs[i]); i++)
- class_remove_file(cls, &cls->class_attrs[i]);
- }
- }
add_class_attrs()把cls->class_attrs中的屬性加入sysfs。
remove_class_attrs()把cls->class_attrs中的屬性刪除。
到了class這個級別,就和bus一樣,除了自己,沒有其它結構能為自己新增屬性。
[cpp] view plain copy- static void klist_class_dev_get(struct klist_node *n)
- {
- struct device *dev = container_of(n, struct device, knode_class);
- get_device(dev);
- }
- static void klist_class_dev_put(struct klist_node *n)
- {
- struct device *dev = container_of(n, struct device, knode_class);
- put_device(dev);
- }
klist_class_dev_get()增加節點對應裝置的引用計數,klist_class_dev_put()減少節點對應裝置的引用計數。
這是class的裝置連結串列,在節點新增和刪除時呼叫的。相似的klist連結串列,還有驅動的裝置連結串列,不過由於linux對驅動不太信任,所以沒有讓驅動佔用裝置的引用計數。還有匯流排的裝置連結串列,在新增釋放節點時分別呼叫klist_devices_get()和list_devices_put(),是在bus.c中定義的。還有裝置的子裝置連結串列,在新增釋放節點時分別呼叫klist_children_get()和klist_children_put(),是在device.c中定義的。看來klist中的get()/put()函式,是在初始化klist時設定的,也由建立方負責實現。
[cpp] view plain copy- /* This is a #define to keep the compiler from merging different
- * instances of the __key variable */
- #define class_register(class) \
- ({ \
- static struct lock_class_key __key; \
- __class_register(class, &__key); \
- })
- int __class_register(struct class *cls, struct lock_class_key *key) //就是填充class_private 私有資料結構體。。然後註冊到核心中
- {
- struct class_private *cp;
- int error;
- pr_debug("device class '%s': registering\n", cls->name);
- cp = kzalloc(sizeof(*cp), GFP_KERNEL);
- if (!cp)
- return -ENOMEM;
- klist_init(&cp->class_devices, klist_class_dev_get, klist_class_dev_put); //裝置節點列表初始化,初始化klist的結構。如果klist_node結構將要被嵌入引用計數的對 象(所必需的安全的刪除),則獲得/放參數用於初始化該採取的功能並釋放嵌入物件的引用。
- INIT_LIST_HEAD(&cp->class_interfaces); //初始化關聯的子系統介面列表
- kset_init(&cp->class_dirs);
- __mutex_init(&cp->class_mutex, "struct class mutex", key);
- error = kobject_set_name(&cp->class_subsys.kobj, "%s", cls->name); //設定類的名字,例如video4linux ,sys/class/video4linux
- if (error) {
- kfree(cp);
- return error;
- }
- /* set the default /sys/dev directory for devices of this class */ //裝置預設目錄sys/dev 為類裝置
- if (!cls>dev_kobj) //表示該class下的裝置在/sys/dev/下的目錄,現在一般有char和block兩個,如果dev_kobj為NULL,則預設選擇char。
- cls->dev_kobj = sysfs_dev_char_kobj;
- #if defined(CONFIG_SYSFS_DEPRECATED) && defined(CONFIG_BLOCK)
- /* let the block class directory show up in the root of sysfs */
- if (cls != &block_class)
- cp->class_subsys.kobj.kset = class_kset; //
- #else
- cp->class_subsys.kobj.kset = class_kset; //設定 例如:video4linux的頂級容器,sys/class
- #endif
- cp->class_subsys.kobj.ktype = &class_ktype; //設zhi 例如:video4linux的型別
- cp->class = cls; //將類class 賦給 私有資料結構體class_private
- cls->p = cp; //將私有資料結構體class_private 賦給類class的私有資料結構體class_private
- error = kset_register(&cp->class_subsys); //註冊進入核心,建立目錄 sys/class/video4linux
- if (error) {
- kfree(cp);
- return error;
- }
- error = add_class_attrs(class_get(cls)); //新增類屬性,並增加模組引用計數
- class_put(cls); //減少模組引用計數
- return error;
- }
class_register()將class註冊到系統中。之所以把class_register()寫成巨集定義的形式,似乎是為了__key的不同例項合併,在__class_register()中確實使用了__key,但是是為了除錯class中使用的mutex用的。__key的型別lock_class_key是隻有使用LOCKDEP定義時才會有內容,寫成這樣也許是為了在lock_class_key定義為空時減少一些不必要的空間消耗。總之這類trick的做法,是不會影響我們理解程式碼邏輯的。
__class_register()中進行實際的class註冊工作:
先是分配和初始化class_private結構。
可以看到對cp->class_dirs,只是呼叫kset_init()定義,並未實際註冊到sysfs中。
呼叫kobject_set_name()建立kobj中實際的類名。
cls->dev_kobj如果未設定,這裡會被設為sysfs_dev_char_kobj。
呼叫kset_register()將class註冊到sysfs中,所屬kset為class_kset,使用型別為class_ktype。因為沒有設定parent,會以/sys/class為父目錄。
最後呼叫add_class_attrs()新增相關的屬性檔案。
在bus、device、driver、class中,最簡單的註冊過程就是class的註冊,因為它不僅和bus一樣屬於一種頂層結構,而且連通用的屬性檔案都不需要,所有的操作就圍繞在class_private的建立初始化與新增到sysfs上面。
[cpp] view plain copy- void class_unregister(struct class *cls)
- {
- pr_debug("device class '%s': unregistering\n", cls->name);
- remove_class_attrs(cls);
- kset_unregister(&cls->p->class_subsys);
- }
class_unregister()取消class的註冊。它的操作也簡單到了極點。
只是這裡的class登出也太懶了些。無論是class_unregister(),還是在計數完全釋放時呼叫的class_release(),都找不到釋放class_private結構的地方。這是bug嗎?
我懷著敬畏的心情,查看了linux-3.0.4中的drivers/base/class.c,發現其中的class_release()函式最後添加了釋放class_private結構的程式碼。看來linux-2.6.32還是有較為明顯的缺陷。奈何木已成舟,只能先把這個bug在現有程式碼裡改正,至少以後自己編譯核心時不會再這個問題上出錯。
不過說起來,像bus_unregister()、class_unregister()這種函式,估計只有在關機時才可能呼叫得到,實在是無關緊要。
[cpp] view plain copy- /* This is a #define to keep the compiler from merging different
- * instances of the __key variable */
- #define class_create(owner, name) \
- ({ \
- static struct lock_class_key __key; \
- __class_create(owner, name, &__key); \
- })
- /**
- * class_create - create a struct class structure
- * @owner: pointer to the module that is to "own" this struct class
- * @name: pointer to a string for the name of this class.
- * @key: the lock_class_key for this class; used by mutex lock debugging
- *
- * This is used to create a struct class pointer that can then be used
- * in calls to device_create().
- *
- * Note, the pointer created here is to be destroyed when finished by
- * making a call to class_destroy().
- */
- struct class *__class_create(struct module *owner, const char *name,
- struct lock_class_key *key)
- {
- struct class *cls;
- int retval;
- cls = kzalloc(sizeof(*cls), GFP_KERNEL); //分配類結構體記憶體
- if (!cls) {
- retval = -ENOMEM;
- goto error;
- }
- cls->name = name; //填充類的名字,例如:video4linux gpio i2c等等。
- cls->owner = owner; //填充類所屬模組
- cls->class_release = class_create_release; //類的釋放函式
- retval = __class_register(cls, key); //在核心中註冊一個類
- if (retval)
- goto error;
- return cls;
- error:
- kfree(cls);
- return ERR_PTR(retval);
- }
class_create()是提供給外界快速建立class的API。應該說,class中可以提供的一系列函式,這裡都沒有提供,或許可以在建立後再加上。
相似的函式是在core.c中的device_create(),那是提供一種快速建立device的API。
[cpp] view plain copy- static void class_create_release(struct class *cls)
- {
- pr_debug("%s called for %s\n", __func__, cls->name);
- kfree(cls);
- }
- /**
- * class_destroy - destroys a struct class structure
- * @cls: pointer to the struct class that is to be destroyed
- *
- * Note, the pointer to be destroyed must have been created with a call
- * to class_create().
- */
- void class_destroy(struct class *cls)
- {
- if ((cls == NULL) || (IS_ERR(cls)))
- return;
- class_unregister(cls);
- }
class_destroy()是與class_create()相對的刪除class的函式。
雖然在class_destroy()中沒有看到釋放class記憶體的程式碼,但這是在class_create_release()中做的。class_create_release()之前已經在class_create()中被作為class結構中定義的class_release()函式,會在class引用計數降為零時被呼叫。
在class中,class結構和class_private結構都是在class引用計數降為零時才釋放的。這保證了即使class已經被登出,仍然不會影響其下裝置的正常使用。但在bus中,bus_private結構是在bus_unregister()中就被釋放的。沒有了bus_private,bus下面的device和driver想必都無法正常工作了吧。這或許和bus對應與實際匯流排有關。匯流排都沒了,下面的裝置自然沒人用了。
class為了遍歷裝置連結串列,特意定義了專門的結構和遍歷函式,實現如下。
[cpp] view plain copy- struct class_dev_iter {
- struct klist_iter ki;
- const struct device_type *type;
- };
- /**
- * class_dev_iter_init - initialize class device iterator 初始化類裝置遍歷表
- * @iter: class iterator to initialize
- * @class: the class we wanna iterate over
- * @start: the device to start iterating from, if any
- * @type: device_type of the devices to iterate over, NULL for all
- *
- * Initialize class iterator @iter such that it iterates over devices
- * of @class. If @start is set, the list iteration will start there,
- * otherwise if it is NULL, the iteration starts at the beginning of start被設定,列表遍歷從start開始;否則,從列表的表頭開始。。
- * the list.
- */
- void class_dev_iter_init(struct class_dev_iter *iter, struct class *class,
- struct device *start, const struct device_type *type)
- {
- struct klist_node *start_knode = NULL;
- if (start)
- start_knode = &start->knode_class;
- klist_iter_init_node(&class->p->class_devices, &iter->ki, start_knode);
- iter->type = type;
- }
- struct device *class_dev_iter_next(struct class_dev_iter *iter)
- {
- struct klist_node *knode;
- struct device *dev;
- while (1) {
- knode = klist_next(&iter->ki);
- if (!knode)
- return NULL;
- dev = container_of(knode, struct device, knode_class);
- if (!iter->type || iter->type == dev->type)
- return dev;
- }
- }
- void class_dev_iter_exit(struct class_dev_iter *iter)
- {
- klist_iter_exit(&iter->ki);
- }
之所以要如此費一番周折,在klist_iter外面加上這一層封裝,完全是為了對連結串列進行選擇性遍歷。選擇的條件就是device_type。device_type是在device結構中使用的型別,其中定義了相似裝置使用的一些處理操作,可以說比class的劃分還要小一層。class對裝置連結串列如此遍歷,也是用心良苦啊。
[cpp] view plain copy- int class_for_each_device(struct class *class, struct device *start,
- void *data, int (*fn)(struct device *, void *))
- {
- struct class_dev_iter iter;
- struct device *dev;
- int error = 0;
- if (!class)
- return -EINVAL;
- if (!class->p) {
- WARN(1, "%s called for class '%s' before it was initialized",
- __func__, class->name);
- return -EINVAL;
- }
- class_dev_iter_init(&iter, class, start, NULL);
- while ((dev = class_dev_iter_next(&iter))) {
- error = fn(dev, data);
- if (error)
- &