参考: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 ,不信一会分析代码。
在设备总线驱动模型中,我们将一个驱动程序强制分为 device 和 driver 两部分,device 与 driver 一一对应,一个 deivce 只能有一个 driver 方法,那么如果现在有一类device它们向用户空间采用统一的接口,那么此时我们仍然使用设备总线驱动模型的话,就会导致有一堆一样的 driver ,冗余代码。于是乎,就引出了类的概念,类是一个设备的高层视图,它抽象出了低层的实现细节,大概意思就是抽象出了一个通用的接口吧。常见的类设备有 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在具体分析吧。