1. 程式人生 > >linux裝置模型之Class

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在具體分析吧。