linux裝置模型之Class
參考:http://www.wowotech.net/device_model/class.html
剛開始寫字元裝置驅動程式的時候,老師教我們自動建立裝置節點,“要先建立類,在類下面建立裝置,類名字不重要“。
firstdrv_class = class_create(THIS_MODULE, "firstdrv");
firstdrv_class_dev = device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "xyz"); /* /dev/xyz */
於是乎,這兩行程式碼被糊里糊塗的複製貼上了好多次,差點成為一種習慣~
前面分析裝置匯流排驅動模型的時候,我們知道,將一個裝置呼叫 device_add 函式註冊到核心中去的時候,如果指定了裝置號,那麼使用者空間的 mdev 會根據 sysfs 檔案系統中的裝置資訊去自動建立裝置節點。我們看到前面第二行程式碼裡有一個 device_create ,引數裡還有裝置號 ,八九不離十,裡邊也間接呼叫了device_add ,不信一會分析程式碼。
類是一個裝置的高層檢視,它抽象出了低層的實現細節,大概意思就是抽象出了一個通用的介面吧。常見的類裝置有 Input 、tty 、usb 、rtc 等等。
class 就好比 bus ,我們在裝置匯流排驅動模型中建立裝置時,要指定它所屬的 Bus ,那麼在建立類裝置的時候也需要指定它所從屬的類,class 也離不開 Kobject ,因此如果你瞭解匯流排裝置驅動模型,你就會發現,其實真的都是差不多的東西。
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; };
name,class的名稱,會在“/sys/class/”目錄下體現。
class_atrrs,該class的預設attribute,會在class註冊到核心時,自動在“/sys/class/xxx_class”下建立對應的attribute檔案。
dev_attrs,該class下每個裝置的attribute,會在設備註冊到核心時,自動在該裝置的sysfs目錄下建立對應的attribute檔案。
dev_bin_attrs,類似dev_attrs,只不過是二進位制型別attribute。
dev_kobj,表示該class下的裝置在/sys/dev/下的目錄,現在一般有char和block兩個,如果dev_kobj為NULL,則預設選擇char。
dev_uevent,當該class下有裝置發生變化時,會呼叫class的uevent回撥函式。
class_release,用於release自身的回撥函式。
dev_release,用於release class內裝置的回撥函式。在device_release介面中,會依次檢查Device、Device Type以及Device所在的class,是否註冊release介面,如果有則呼叫相應的release介面release裝置指標。
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;
};
struct class_interface是這樣的一個結構:它允許class driver在class下有裝置新增或移除的時候,呼叫預先設定好的回撥函式(add_dev和remove_dev)。那呼叫它們做什麼呢?想做什麼都行(例如修改裝置的名稱),由具體的class driver實現。該結構的定義如下:
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 *);
};
下面,我們來看 Class 的註冊過程,前面我們提到,class->name 會出現在/sys/class 目錄下,那麼這個目錄是哪裡來的,程式碼一看便知。
int __init classes_init(void)
{
class_kset = kset_create_and_add("class", NULL, NULL);
if (!class_kset)
return -ENOMEM;
return 0;
}
下面,我們來看一下一個Class 的註冊過程
#define class_create(owner, name) \
({ \
static struct lock_class_key __key; \
__class_create(owner, name, &__key); \
})
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;
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結構體,設定了名字以及所屬的模組,然後呼叫 class_register
int __class_register(struct class *cls, struct lock_class_key *key)
{
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);
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);
if (error) {
kfree(cp);
return error;
}
/* set the default /sys/dev directory for devices of this class */
if (!cls->dev_kobj)
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;
#endif
cp->class_subsys.kobj.ktype = &class_ktype;
cp->class = cls;
cls->p = cp;
error = kset_register(&cp->class_subsys);
if (error) {
kfree(cp);
return error;
}
error = add_class_attrs(class_get(cls));
class_put(cls);
return error;
}
程式碼第15行,將 cp->class_subsys.kobj 的 name 設定為cls->name
程式碼第28行,將cp->class_subsys.kobj.kest 設定為class_kest
程式碼第36行,將cp->class_subsys 註冊進核心,沒有設定 cp->class_subsys.kobj.parent ,核心會將cp->class_subsys.kobj.kset.kobj 設定成它的Parent ,這也就是為什麼說 class->name 會出現在 /sys/class 目錄下的原因。
下面,來看向 class 註冊 device 的過程
struct device *device_create(struct class *class, struct device *parent,
dev_t devt, void *drvdata, const char *fmt, ...)
{
va_list vargs;
struct device *dev;
va_start(vargs, fmt);
dev = device_create_vargs(class, parent, devt, drvdata, fmt, vargs);
va_end(vargs);
return dev;
}
struct device *device_create_vargs(struct class *class, struct device *parent,
dev_t devt, void *drvdata, const char *fmt,
va_list args)
{
struct device *dev = NULL;
int retval = -ENODEV;
if (class == NULL || IS_ERR(class))
goto error;
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (!dev) {
retval = -ENOMEM;
goto error;
}
dev->devt = devt;
dev->class = class;
dev->parent = parent;
dev->release = device_create_release;
dev_set_drvdata(dev, drvdata);
retval = kobject_set_name_vargs(&dev->kobj, fmt, args);
if (retval)
goto error;
retval = device_register(dev);
if (retval)
goto error;
return dev;
error:
put_device(dev);
return ERR_PTR(retval);
}
上邊程式碼也沒有什麼好分析的,與我們分析裝置匯流排驅動模型時分析 device 時有一點不一樣的就是這裡設定的是 dev->class 而不是 dev->bus ,同時這裡為 dev設定了裝置號 devt ,因此,在sysfs中會建立 dev 屬性檔案,mdev 就會自動為我們建立裝置節點了。
int device_register(struct device *dev)
{
device_initialize(dev);
return device_add(dev);
}
int device_add(struct device *dev)
{
struct device *parent = NULL;
struct class_interface *class_intf;
int error = -EINVAL;
dev = get_device(dev);
if (!dev)
goto done;
if (!dev->p) {
error = device_private_init(dev);
if (error)
goto done;
}
/*
* for statically allocated devices, which should all be converted
* some day, we need to initialize the name. We prevent reading back
* the name, and force the use of dev_name()
*/
if (dev->init_name) {
dev_set_name(dev, "%s", dev->init_name);
dev->init_name = NULL;
}
if (!dev_name(dev))
goto name_error;
pr_debug("device: '%s': %s\n", dev_name(dev), __func__);
parent = get_device(dev->parent);
setup_parent(dev, parent);
/* use parent numa_node */
if (parent)
set_dev_node(dev, dev_to_node(parent));
/* first, register with generic layer. */
/* we require the name to be set before, and pass NULL */
error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);
if (error)
goto Error;
/* notify platform of device entry */
if (platform_notify)
platform_notify(dev);
error = device_create_file(dev, &uevent_attr);
if (error)
goto attrError;
if (MAJOR(dev->devt)) {
error = device_create_file(dev, &devt_attr);
if (error)
goto ueventattrError;
error = device_create_sys_dev_entry(dev);
if (error)
goto devtattrError;
devtmpfs_create_node(dev);
}
error = device_add_class_symlinks(dev);
if (error)
goto SymlinkError;
error = device_add_attrs(dev);
if (error)
goto AttrsError;
error = bus_add_device(dev);
if (error)
goto BusError;
error = dpm_sysfs_add(dev);
if (error)
goto DPMError;
device_pm_add(dev);
/* Notify clients of device addition. This call must come
* after dpm_sysf_add() and before kobject_uevent().
*/
if (dev->bus)
blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
BUS_NOTIFY_ADD_DEVICE, dev);
kobject_uevent(&dev->kobj, KOBJ_ADD);
bus_probe_device(dev);
if (parent)
klist_add_tail(&dev->p->knode_parent,
&parent->p->klist_children);
if (dev->class) {
mutex_lock(&dev->class->p->class_mutex);
/* tie the class to the device */
klist_add_tail(&dev->knode_class,
&dev->class->p->class_devices);
/* notify any interfaces that the device is here */
list_for_each_entry(class_intf,
&dev->class->p->class_interfaces, node)
if (class_intf->add_dev)
class_intf->add_dev(dev, class_intf);
mutex_unlock(&dev->class->p->class_mutex);
}
done:
put_device(dev);
return error;
DPMError:
bus_remove_device(dev);
BusError:
device_remove_attrs(dev);
AttrsError:
device_remove_class_symlinks(dev);
SymlinkError:
if (MAJOR(dev->devt))
device_remove_sys_dev_entry(dev);
devtattrError:
if (MAJOR(dev->devt))
device_remove_file(dev, &devt_attr);
ueventattrError:
device_remove_file(dev, &uevent_attr);
attrError:
kobject_uevent(&dev->kobj, KOBJ_REMOVE);
kobject_del(&dev->kobj);
Error:
cleanup_device_parent(dev);
if (parent)
put_device(parent);
name_error:
kfree(dev->p);
dev->p = NULL;
goto done;
}
程式碼第41行,將 dev->kobj 註冊進核心,會在/sys/devices 目錄下建立目錄。
程式碼第53-63行,是建立屬性檔案dev 的過程,也就是這一步,讓mdev能夠自動為我們建立裝置節點。
程式碼第65行,建立 /sys/class 到 /sys/device/xx/dev->name的符號連結,這個跟裝置匯流排驅動模型中建立/sys/bus 到 /sys/device/xx/dev->name的符號連結是一樣一樣的。
程式碼第92-103行,將 dev 加入 class的裝置連結串列,並呼叫 class_interfaces 連結串列中的每一個 class_intf 結構,呼叫裡面的 add_dev 函式。
分析到這,class 好像並沒有幹什麼實質性的事情。後面到input、tty、rtc在具體分析吧。