Linux设备模型之device_add

0.前言

linux设备模型是学习linux驱动的很重要的内容。了解这些知识,对于学习linux设备驱动有很大的帮助。 linux设备类型的内容还是比较多的,这里就重点说明device_add函数的流程。

1.总体框架

linux设备模型:设备device,驱动driver,总线bus。设备代表物理设备,驱动代表了设备操作方法,bus则是用来管理和匹配它们。device和driver里面都有一个成员变量bus,表示它们归哪个总线管理,bus里面则有两个链表,device链表和driver链表。当有新的设备加入的时候,就会将它加入它对应的bus的device链表,然后在它的驱动链表中寻找是否有驱动driver和该device匹配成功,如果匹配成功设备就可以正常使用了,否则,不好意思继续等待。当有新的驱动加入的时候,就会将它加入它对应的bus的driver链表,然后在它的设备链表中寻找是否有设备device和该driver匹配成功,如果成功设备就可以正常使用了。

device_add就是将设备加入到Linux设备模型的关键,它的内部将找到它的bus,然后让它的bus给它找到它的driver,其调用顺序为:

                                                                                 

2.1 device_add

int device_add(struct device *dev)
	dev = get_device(dev);//增加该设备的引用计数
	if (!dev->p) {
		error = device_private_init(dev);//初始化设备的私有成员p
		if (error)
			goto done;
	}

	if (dev->init_name) {
		dev_set_name(dev, "%s", dev->init_name);//初始化设备内部的kobject的名字
		dev->init_name = NULL;
	}

	if (!dev_name(dev) && dev->bus && dev->bus->dev_name)
		dev_set_name(dev, "%s%u", dev->bus->dev_name, dev->id);//使用bus以及设备id来初始化设备内部kobject名字

	if (!dev_name(dev)) {//获得设备的名字
		error = -EINVAL;
		goto name_error;
	}

	parent = get_device(dev->parent);增加设备父设备并增加父设备引用计数
	kobj = get_device_parent(dev, parent);
	if (kobj)
		dev->kobj.parent = kobj;//在kobject层实现设备父子关系

	if (parent)
		set_dev_node(dev, dev_to_node(parent));
	error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);//将设备加入到kobject模型中,创建sys相关目录
	
	/* notify platform of device entry */
	if (platform_notify)
		platform_notify(dev);

	error = device_create_file(dev, &uevent_attr);//创建sys目录下设备的uevent属性文件,通过它可以查看设备的uevent事件
	if (MAJOR(dev->devt)) {
		error = device_create_file(dev, &devt_attr);//创建sys目录下设备的设备号属性,即major和minor
		error = device_create_sys_dev_entry(dev);
		devtmpfs_create_node(dev);
	}
	error = device_add_class_symlinks(dev);
	error = device_add_attrs(dev);//创建sys目录下设备其他属性文件
	error = bus_add_device(dev);//将设备加入到管理它的bus总线的设备连表上
	error = dpm_sysfs_add(dev);//电源管理相关
	device_pm_add(dev);

	/* Notify clients of device addition.  This call must come
	 * after dpm_sysfs_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);//产生一个内核uevent事件,该事件可以被内核以及应用层捕获,属于linux设备模型中热插拔机制
	bus_probe_device(dev);//------------开始寻找设备所对应的驱动------------
	if (parent)
		klist_add_tail(&dev->p->knode_parent, &parent->p->klist_children);//建立设备与总线间的父子关系
	if (dev->class) {//如果设备的属于某个设备类,比如Mass storage,HID等等
		mutex_lock(&dev->class->p->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->interfaces, node)
			if (class_intf->add_dev)
				class_intf->add_dev(dev, class_intf);//通知有新设备加入
		mutex_unlock(&dev->class->p->mutex);
	}
2.2  bus_probe_device

//为设备找到一个驱动
void bus_probe_device(struct device *dev)
{
	struct bus_type *bus = dev->bus;//获得设备的隶属的总线,该值在设备初始化时设置
	struct subsys_interface *sif;
	int ret;
	if (!bus)
		return;
	if (bus->p->drivers_autoprobe) {
		ret = device_attach(dev);//-------尝试为该设备找一个driver-------
		WARN_ON(ret < 0);
	}
	mutex_lock(&bus->p->mutex);
	list_for_each_entry(sif, &bus->p->interfaces, node)
		if (sif->add_dev)
			sif->add_dev(dev, sif);
	mutex_unlock(&bus->p->mutex);
}

2.3 device_attach

/**
 * device_attach - 尝试为设备寻找到一个驱动
 *遍历设备隶属的总线上所有的driver,然后调用driver_probe_device匹配设备和驱动,成功就结束循环退出
 *成功返回值为1,失败为0,-ENODEV表示设备没有被注册
 */
int device_attach(struct device *dev)
{
	int ret = 0;

	device_lock(dev);
	if (dev->driver) {//如果设备已经有驱动
		if (klist_node_attached(&dev->p->knode_driver)) {
			ret = 1;
			goto out_unlock;
		}
		ret = device_bind_driver(dev);
		if (ret == 0)
			ret = 1;
		else {
			dev->driver = NULL;
			ret = 0;
		}
	} else {//设备没有驱动
		pm_runtime_get_noresume(dev);
		ret = bus_for_each_drv(dev->bus, NULL, dev, __device_attach);-------遍历总线上的driver链表-------
		pm_runtime_put_sync(dev);
	}
out_unlock:
	device_unlock(dev);
	return ret;
}

2.4 bus_for_each_drv

/**
 * bus_for_each_drv - driver迭代器
 * @bus: 设备隶属的总线
 * @start: 迭代器轮训的起始元素
 * @data: 传递给回调函数的参数
 * @fn: 回调函数
 */
int bus_for_each_drv(struct bus_type *bus, struct device_driver *start,
		     void *data, int (*fn)(struct device_driver *, void *))
{
	struct klist_iter i;
	struct device_driver *drv;
	int error = 0;

	if (!bus)
		return -EINVAL;

	klist_iter_init_node(&bus->p->klist_drivers, &i,start ? &start->p->knode_bus : NULL);
	while ((drv = next_driver(&i)) && !error)
		error = fn(drv, data);//-------对于总线中的每个driver调用fn函数进行匹配,fn为__device_attach-------
	klist_iter_exit(&i);
	return error;
}

2.5  __device_attach

static int __device_attach(struct device_driver *drv, void *data)
{
	struct device *dev = data;

	if (!driver_match_device(drv, dev))//设备和驱动是否匹配函数,成功就继续下面,否则退出,将调用总线的match函数进行匹配
		return 0;

	return driver_probe_device(drv, dev);//-------设备和驱动匹配成功,调用probe函数-------
}-

2.6 driver_probe_device

int driver_probe_device(struct device_driver *drv, struct device *dev)
{
	int ret = 0;

	if (!device_is_registered(dev))//如果设备已经被注册过了,直接退出
		return -ENODEV;

	pr_debug("bus: '%s': %s: matched device %s with driver %s\n",
		 drv->bus->name, __func__, dev_name(dev), drv->name);

	pm_runtime_get_noresume(dev);
	pm_runtime_barrier(dev);
	ret = really_probe(dev, drv);//-------继续调用really_probe函数-------
	pm_runtime_put_sync(dev);
	return ret;
}

2.7 really_probe

static int really_probe(struct device *dev, struct device_driver *drv)
{
	int ret = 0;

	atomic_inc(&probe_count);
	pr_debug("bus: '%s': %s: probing driver %s with device %s\n",
		 drv->bus->name, __func__, drv->name, dev_name(dev));
	WARN_ON(!list_empty(&dev->devres_head));

	dev->driver = drv;//匹配好后,将驱动信息记录到设备内部
	if (driver_sysfs_add(dev)) {
		printk(KERN_ERR "%s: driver_sysfs_add(%s) failed\n",
			__func__, dev_name(dev));
		goto probe_failed;
	}

	if (dev->bus->probe) {//如果总线存在probe函数,则调用总线的probe函数
		ret = dev->bus->probe(dev);
		if (ret)
			goto probe_failed;
	} else if (drv->probe) {
		ret = drv->probe(dev);//如果总线中没有probe函数,则调用驱动的probe函数
		if (ret)
			goto probe_failed;
	}

	driver_bound(dev);//将设备加入到驱动支持的设备链表中,一个设备需要一个驱动,一个驱动支持多个设备
	ret = 1;
	atomic_dec(&probe_count);
	wake_up(&probe_waitqueue);
	return ret;
}

在驱动或者总线的probe函数中,一般会在/dev/目录先创建相应的设备节点,这样应用程序就可以通过该设备节点来使用设备了。

你可能感兴趣的:(Linux内核,Linux驱动)