1. 程式人生 > >linux核心之class介紹(三)

linux核心之class介紹(三)

前兩節介紹了class,device結構體的成員,在這一節主要介紹class和device的關鍵函式.
1.classes_init()
系統呼叫路徑:
start_kernel()->>rest_init()->>kernel_init()->>do_basic_setup()->>driver_init()->>classes_init();
函式原始碼:

int __init classes_init(void)
{
	// 建立/sys/class目錄
	class_kset = kset_create_and_add("class", NULL, NULL);
	if (!class_kset)
		return -ENOMEM;
	return 0;
}

說明:
該函式動態建立一個kset(class_kset ),並把它註冊進系統.會在sysfs檔案系統建立sys/class目錄

2.class_create()
呼叫路徑:
am335x_gpio_init()->>class_create();
驅動模組初始化函式中呼叫.

函式原始碼:

/* 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);	\
})

struct class *__class_create(struct module *owner, const char *name,
			     struct lock_class_key *key)
{
	struct class *cls;
	int retval;

	// 動態建立class
	cls = kzalloc(sizeof(*cls), GFP_KERNEL);
	if (!cls) {
		retval = -ENOMEM;
		goto error;
	}

	cls->name = name;
	cls->owner = owner;
	cls->class_release = class_create_release;
	
	// 註冊該class
	retval = __class_register(cls, key);
	if (retval)
		goto error;

	return cls;

error:
	kfree(cls);
	return ERR_PTR(retval);
}

int __class_register(struct class *cls, struct lock_class_key *key)
{
	struct subsys_private *cp;
	int error;

	pr_debug("device class '%s': registering\n", cls->name);

	//動態建立subsys_private結構體
	cp = kzalloc(sizeof(*cp), GFP_KERNEL);
	if (!cp)
		return -ENOMEM;
	//初始化subsys_private中的裝置連結串列klist_devices
	klist_init(&cp->klist_devices, klist_class_dev_get, klist_class_dev_put);
	//初始化subsys_private中的介面連結串列class_interfaces
	INIT_LIST_HEAD(&cp->class_interfaces);
	kset_init(&cp->glue_dirs);
	__mutex_init(&cp->class_mutex, "struct class mutex", key);

	//設定class名稱為subsys_private.subsys.kobj的名字,用於建立/sys/class/name目錄
	error = kobject_set_name(&cp->subsys.kobj, "%s", cls->name);
	if (error) {
		kfree(cp);
		return error;
	}

	/* set the default /sys/dev directory for devices of this class */
	//設定class.dev_kobj成員指向sysfs_dev_char_kobj,該kobject表示/sys/dev/char目錄
	if (!cls->dev_kobj)
		cls->dev_kobj = sysfs_dev_char_kobj;// sys/dev/char

#if defined(CONFIG_BLOCK)
	/* let the block class directory show up in the root of sysfs */
	if (!sysfs_deprecated || cls != &block_class)
		cp->subsys.kobj.kset = class_kset; //class_kset在classes_init()中建立,表示/sys/class目錄
#else
	cp->subsys.kobj.kset = class_kset;
#endif
	cp->subsys.kobj.ktype = &class_ktype;
	cp->class = cls;
	cls->p = cp; // 設定p指標

	// 建立/sys/class/name目錄
	error = kset_register(&cp->subsys);
	if (error) {
		kfree(cp);
		return error;
	}
	// 建立class自己的屬性檔案class_attrs,這裡好像沒有,呵呵
	error = add_class_attrs(class_get(cls));
	class_put(cls);
	return error;
}

說明:
1).動態建立class
2).註冊該class
a.動態建立subsys_private結構體
b.初始化subsys_private中的裝置連結串列klist_devices
c.初始化subsys_private中的介面連結串列class_interfaces
d.設定class名稱為subsys_private.subsys.kobj的名字,用於建立/sys/class/name目錄
e.設定class.dev_kobj成員指向sysfs_dev_char_kobj,該kobject表示/sys/dev/char目錄
f.建立/sys/class/name目錄
g.建立class自己的屬性檔案class_attrs,如果有

3.device_create
呼叫路徑:
am335x_gpio_init()->>device_create();
class_dev = device_create(gpio_class,NULL,dev,0,“am335x_gpio”)
驅動模組初始化函式中呼叫.
函式原始碼:

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;
}

說明:
1).va_start(),va_end()函式的用法在後面會講解
2).該函式功能解析引數,呼叫device_create_vargs().

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;

	// 動態建立device
	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
	if (!dev) {
		retval = -ENOMEM;
		goto error;
	}
	// 初始化device
	dev->devt = devt;
	dev->class = class;
	dev->parent = parent;// NULL
	dev->release = device_create_release;
	dev_set_drvdata(dev, drvdata); // drvdata = 0

	// 設定dev->kobj.name = "am335x_gpio"
	retval = kobject_set_name_vargs(&dev->kobj, fmt, args); 
	if (retval)
		goto error;

	// 呼叫device_register()
	retval = device_register(dev);
	if (retval)
		goto error;

	return dev;

error:
	put_device(dev);
	return ERR_PTR(retval);
}

說明:
1). 動態建立device
2). 初始化device
3).設定dev->kobj.name = “am335x_gpio”
4).呼叫device_register()

int device_register(struct device *dev)
{
	device_initialize(dev);
	return device_add(dev);
}
void device_initialize(struct device *dev)
{
	dev->kobj.kset = devices_kset; // devices_kset指向sys/devices目錄的kset
	kobject_init(&dev->kobj, &device_ktype);//A top-level, abstract class from which other classes are derived.
	INIT_LIST_HEAD(&dev->dma_pools);/* dma pools (if dma'ble) */
	mutex_init(&dev->mutex);/* mutex to synchronize calls to its driver.*/
	lockdep_set_novalidate_class(&dev->mutex);
	spin_lock_init(&dev->devres_lock);//Spinlock to protect the resource of the device.
	INIT_LIST_HEAD(&dev->devres_head);//The resources list of the device.
	device_pm_init(dev);//For device power management.
	set_dev_node(dev, -1);// CONFIG_NUMA未定義,所以該函式是空函式
}
int device_add(struct device *dev)
{
	struct device *parent = NULL;
	struct class_interface *class_intf;
	int error = -EINVAL;

	//增加device的引用計數器
	dev = get_device(dev);
	if (!dev)
		goto done;

	//檢測device->p私有資料成員,如果空則需要初始化
	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()
	 */
	// 設定dev.kobject.name = dev->init_name
	if (dev->init_name) {
		dev_set_name(dev, "%s", dev->init_name);
		dev->init_name = NULL;
	}
	// 回讀驗證name是否已經設定
	if (!dev_name(dev)) {
		error = -EINVAL;
		goto name_error;
	}

	pr_debug("device: '%s': %s\n", dev_name(dev), __func__);

	//增加device->parent的引用計數器,因為要增加裝置節點
	parent = get_device(dev->parent);

	//設定dev->kobj.parent = parent,如果未設定parent,則建立到"sys/devices/virtual/class名"目錄裡
	setup_parent(dev, parent);

	/* use parent numa_node */
	if (parent)
		set_dev_node(dev, dev_to_node(parent));//CONFIG_NUMA未定義,空函式

	/* first, register with generic layer. */
	/* we require the name to be set before, and pass NULL */
	//建立裝置目錄,如:/sys/devices/virtual/am335x_gpio/am335x_gpio
	error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);
	if (error)
		goto Error;

	/* notify platform of device entry */
	//呼叫平臺相關的通知函式
	if (platform_notify)
		platform_notify(dev);

	// 建立uevent屬性檔案
	error = device_create_file(dev, &uevent_attr);
	if (error)
		goto attrError;

	
	if (MAJOR(dev->devt)) {
		// 建立dev屬性檔案
		error = device_create_file(dev, &devt_attr);
		if (error)
			goto ueventattrError;
		//在sys/dev/char目錄建立檔案連線到dev->kobj目錄,如: 251:0 -> ../../devices/virtual/am335x_gpio/am335x_gpio
		error = device_create_sys_dev_entry(dev);
		if (error)
			goto devtattrError;

		//建立裝置節點,原理還需要再研究???
		devtmpfs_create_node(dev);
	}

	// 建立裝置和class目錄的符號連線,
	//1,建立檔名subsystem到class的連線,如:subsystem -> ../../../../class/am335x_gpio
	//2.建立class目錄到當前目錄的連線,如:am335x_gpio -> ../../devices/virtual/am335x_gpio/am335x_gpio
	error = device_add_class_symlinks(dev);
	if (error)
		goto SymlinkError;
	// 建立裝置相關的屬性檔案
	error = device_add_attrs(dev);
	if (error)
		goto AttrsError;
	// 新增匯流排相關的屬性檔案,並加入匯流排的裝置連結串列(這裡沒有新增到bus)
	error = bus_add_device(dev);
	if (error)
		goto BusError;
	// 新增電源管理屬性檔案
	error = dpm_sysfs_add(dev);
	if (error)
		goto DPMError;
	//Add a device to the PM core's list of active devices.
	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的KOBJ_ADD事件
	kobject_uevent(&dev->kobj, KOBJ_ADD);

	// probe drivers for a new device
	bus_probe_device(dev);

	// 新增到裝置樹
	if (parent)
		klist_add_tail(&dev->p->knode_parent,
			       &parent->p->klist_children);
	// 新增裝置到class,並呼叫相應的interfaces
	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->klist_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))
		devtmpfs_delete_node(dev);
	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;
}

說明:
1).增加device的引用計數器
2).檢測device->p私有資料成員,如果空則需要初始化
3).回讀驗證name是否已經設定
4).設定dev->kobj.parent = parent,如果未設定parent,則建立到"sys/devices/virtual/class名"目錄裡
5).建立裝置目錄,如:/sys/devices/virtual/am335x_gpio/am335x_gpio
6).呼叫平臺相關的通知函式
7).建立uevent屬性檔案
8).建立dev屬性檔案
9).在sys/dev/char目錄建立檔案連線到dev->kobj目錄,如: 251:0 -> …/…/devices/virtual/am335x_gpio/am335x_gpio
10).建立裝置節點,原理還需要再研究???
11).建立裝置和class目錄的符號連線
a.建立檔名subsystem到class的連線,如:subsystem -> …/…/…/…/class/am335x_gpio
b.建立class目錄到當前目錄的連線,如:am335x_gpio -> …/…/devices/virtual/am335x_gpio/am335x_gpio
12).建立裝置相關的屬性檔案
13).新增匯流排相關的屬性檔案,並加入匯流排的裝置連結串列(這裡沒有新增到bus)
14).新增電源管理屬性檔案
15).新增device到pm(電源管理)連結串列
16).通知使用者空間kobject的KOBJ_ADD事件
17). 新增到裝置樹
18).新增裝置到class,並呼叫相應的interfaces