Linux字符设备驱动 --device_add()与driver_register()

背景

linux 4.9
armv8

在学习怎么向/sys观测目录下注册节点时,发现很多这个函数会在/sys下注册很多节点,所以就想分析一下。

同时,这个函数也算是linux设备驱动面向对象思想的体现了,不止是各种驱动设备的结构体存在这种面向对象的思想(继承自kobject),我觉得这个也能勉强算是吧,各种子系统设备的add函数其实最终都是调用device_add(),只不过每个子系统设备都有自己的一些成员,所以子系统设备的add函数还要初始化这些成员之后再调用device_add(),比如说:

  • i2c_new_device()
  • platform_device_add()
  • spi_new_device()

这些函数都会调用device_add(),所以这个函数应该还有点东西的。

同时分析的过程中回顾一下device-driver的匹配方式,所以还会分析一下dirver_register()函数,该函数也是很多总线设备驱动注册API的底层调用函数。

一、device_add()

int device_add(struct device *dev)
{
	struct device *parent = NULL;
	struct kobject *kobj;
	struct class_interface *class_intf;
	int error = -EINVAL;
	struct kobject *glue_dir = NULL;

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

	/* subsystems can specify simple device enumeration */
	if (!dev_name(dev) && dev->bus && dev->bus->dev_name)
		dev_set_name(dev, "%s%u", dev->bus->dev_name, dev->id);	// g, 设置dev->kobj->name

	if (!dev_name(dev)) {
		error = -EINVAL;
		goto name_error;
	}

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

	parent = get_device(dev->parent);			// g, 如果有parent,检查dev->parent->kobject是否已经被初始化了,
	
	// g, 该函数的返回值直接决定了device将被挂在哪个目录下,也就是要找到dev->kobject.parent
	// g, 对于dev有parent无class,有class无parent,有class有parent,无class无parent的情况都做了区分
	// g, 无class无parent但是有bus,会挂在bus->dev_root->kobject的目录下,也就是所属总线的根目录
	// g, 无class有parent,会挂在parent->kobject的目录下
	// g, 有class无parent,会挂在/sys/devices/virtual/xxx/下,"xxx"是该class在virtual/下的名字,如果该class还没有在/sys/devices/virtual/下创建文件夹,则会创建一个文件夹以class->name命名
	// g, 有class有parent,会
	kobj = get_device_parent(dev, parent);		
	if (IS_ERR(kobj)) {
		error = PTR_ERR(kobj);
		goto parent_error;
	}
	if (kobj)
		dev->kobj.parent = kobj;		// g, 设置dev->kobj的parent,建立在sysfs中的继承关系

	/* use parent numa_node */
	if (parent && (dev_to_node(dev) == NUMA_NO_NODE))
		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);	// g, 添加该设备的kobject,kobjct->parent刚才已经找到
	if (error) {
		glue_dir = get_glue_dir(dev);
		goto Error;
	}

	/* notify platform of device entry */
	if (platform_notify)
		platform_notify(dev);

	error = device_create_file(dev, &dev_attr_uevent);  // g, 创建uevent节点,DEVICE_ATTR_RW(uevent)
	if (error)
		goto attrError;

	// g, 1. 创建"subsystem"软连接节点(指向dev->class所在的文件夹)
	// g, 2. 如果设备是个设备树节点(dev->of_node存在),会创建软连接“of_node”指向dev->of_node所在的文件夹
	// g, 3. 创建软连接,dev->parent->kobj
	// g, 如果dev有class无parent,会在class下创建一个指向真正位置的软连接
	// g, 对于我们有class无parent的设备,class->name = "axp",则会创建软连接 /sys/class/axp/xxx -> /sys/devices/virtual/axp/xxx
	error = device_add_class_symlinks(dev);		
	if (error)
		goto SymlinkError;

	// g, 这个函数,如果class->dev_groups/dev->type->groups/dev->groups存在
	// g, 则会调用sysfs_create_groups(&dev->kobj, class->dev_groups/type->groups/dev->groups),把很多属性都加到结点下
	// g, 所以说,我是不是可以通过给dev->groups赋值的方式来创建,就不用显示调用sysfs_create_groups了?
	error = device_add_attrs(dev);	
	if (error)
		goto AttrsError;

	// g, 如果是总线设备的话(dev->bus存在),把 bus->dev_groups也添加到attr中
	// g, 并且在bus所在文件夹下建立软连接指向dev
	error = bus_add_device(dev);
	if (error)
		goto BusError;

	// g, 添加"power"调试节点文件夹,是个设置了.name的attribute_group
	// g, 调用sysfs_create_groups传入的attribute_group,如果设置了.name属性,则会先在dev目录下创建一个文件夹,再创建调试节点
	// g, 如果没有设置.name属性,则会直接在dev目录下创建调试节点
	error = dpm_sysfs_add(dev);
	if (error)
		goto DPMError;
	device_pm_add(dev);

	if (MAJOR(dev->devt)) {
		error = device_create_file(dev, &dev_attr_dev); // g, 如果存在设备号,就创建dev节点,DEVICE_ATTR_RO(dev)
		if (error)
			goto DevAttrError;

		error = device_create_sys_dev_entry(dev);
		if (error)
			goto SysEntryError;

		devtmpfs_create_node(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);

	// g, tag 这里会产生一个KOBJ_ADD通知,一些像mdev这样的工具会扫描uevent通知,然后在/dev/下创建对应的节点
	// g, usb设备的检测就是通过uevent机制实现的
	kobject_uevent(&dev->kobj, KOBJ_ADD);		

	// g, 这里会进行device - driver的匹配过程
	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->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);
	}
done:
	put_device(dev);
	return error;
 SysEntryError:
	if (MAJOR(dev->devt))
		device_remove_file(dev, &dev_attr_dev);
 DevAttrError:
	device_pm_remove(dev);
	dpm_sysfs_remove(dev);
 DPMError:
	bus_remove_device(dev);
 BusError:
	device_remove_attrs(dev);
 AttrsError:
	device_remove_class_symlinks(dev);
 SymlinkError:
	device_remove_file(dev, &dev_attr_uevent);
 attrError:
	kobject_uevent(&dev->kobj, KOBJ_REMOVE);
	glue_dir = get_glue_dir(dev);
	kobject_del(&dev->kobj);
 Error:
	cleanup_glue_dir(dev, glue_dir);
parent_error:
	put_device(parent);
name_error:
	kfree(dev->p);
	dev->p = NULL;
	goto done;
}
EXPORT_SYMBOL_GPL(device_add);

/sys下相关观测节点的创建过程都在注释中给出,单独把匹配过程摘出来:

device_add()
	-->bus_probe_device()
		-->device_initial_probe()
			-->__device_attach()
				-->bus_for_each_drv(dev->bus, NULL, &data,__device_attach_driver),便利所有driver
					-->遍历挂在bus(bus->p->klist_drivers)上的所有driver,执行__device_attach_driver()
						-->driver_match_device(),driver和device匹配
							-->[drv->bus->match(dev, drv)],执行bus实现的匹配方式
							-->如果bus没实现,直接返回1,也就是ok
						-->driver_probe_device(),匹配通过,则执行probe
							-->really_probe(dev, drv)
								-->if (dev->bus->probe) dev->bus->probe(dev); 优先执行bus的probe函数
								-->else if(drv->probe) drv->probe(dev);
								--> 关于这一点,struct bus_type platform_bus_type未实现.probe,则对于platform设备来说,会直接执行driver提供的probe
									struct bus_type i22_bus_type实现了.probe = i2c_device_probe,则会执行
									i2c_device_probe()
										-->driver = to_i2c_driver(dev->driver);
										-->[driver->probe],最终也是调用driver的probe

二、bus的匹配方式

2.1 i2c bus的匹配方式:

i2c bus的实例如下:

struct bus_type i2c_bus_type = {
	.name		= "i2c",
	.match		= i2c_device_match,
	.probe		= i2c_device_probe,
	.remove		= i2c_device_remove,
	.shutdown	= i2c_device_shutdown,
	.pm		= &i2c_device_pm_ops,
};
EXPORT_SYMBOL_GPL(i2c_bus_type);

实现的匹配函数i2c_device_match():

static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
	struct i2c_client	*client = i2c_verify_client(dev);
	struct i2c_driver	*driver;

	if (!client)
		return 0;

	/* Attempt an OF style match */
	if (of_driver_match_device(dev, drv))
		return 1;

	/* Then ACPI style match */
	if (acpi_driver_match_device(dev, drv))
		return 1;

	driver = to_i2c_driver(drv);
	/* match on an id table if there is one */
	if (driver->id_table)
		return i2c_match_id(driver->id_table, client) != NULL;

	return 0;
}

首先执行of_driver_match_device(),是一种得分匹配机制:

static inline int of_driver_match_device(struct device *dev,
					 const struct device_driver *drv)
{
	return of_match_device(drv->of_match_table, dev) != NULL;
}

----->

of_match_device()
	->of_match_node()
		->__of_match_node(),遍历of_match_table的每一项
			->__of_device_is_compatible()
			->采用得分制:先比较device的compatible属性与of_match_table->compatible,匹配成功得非常多分
			->再比较dev->type与of_match_table->type,匹配成功得2->再比较dev->name与of_match_table->name,匹配成功得1->返回得分最大的match项。

然后执行acpi,这是x86电源管理相关的框架,不管。然后就是i2c_match_id(),比较driver->id_table->name和client->name:

static const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id,
						const struct i2c_client *client)
{
	while (id->name[0]) {
		if (strcmp(client->name, id->name) == 0)
			return id;
		id++;
	}
	return NULL;
}

2.2 platform bus的匹配方式:

platform bus的实例如下:

struct bus_type platform_bus_type = {
	.name		= "platform",
	.dev_groups	= platform_dev_groups,
	.match		= platform_match,
	.uevent		= platform_uevent,
	.pm		= &platform_dev_pm_ops,
};
EXPORT_SYMBOL_GPL(platform_bus_type);

实现的match方式为platform_match:

static int platform_match(struct device *dev, struct device_driver *drv)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct platform_driver *pdrv = to_platform_driver(drv);

	/* When driver_override is set, only bind to the matching driver */
	if (pdev->driver_override)
		return !strcmp(pdev->driver_override, drv->name);

	/* Attempt an OF style match first */
	if (of_driver_match_device(dev, drv))
		return 1; 
	/* Then try ACPI style match */
	if (acpi_driver_match_device(dev, drv))
		return 1;

	/* Then try to match against the id table */
	if (pdrv->id_table)
		return platform_match_id(pdrv->id_table, pdev) != NULL;

	/* fall-back to driver name match */
	return (strcmp(pdev->name, drv->name) == 0);
}

可以看到,platform bus有五种匹配方式,比i2c bus多了两种,并且有一种匹配方式还不太一样,所以共三处不同。第一处不同:

	/* When driver_override is set, only bind to the matching driver */
	if (pdev->driver_override)
		return !strcmp(pdev->driver_override, drv->name);

如果pdev设置了driver_override域,会直接进行字符串匹配,匹配该pdev该域域驱动的name是否相等。而且优先级是最高的,优先执行这种匹配方式。

第二处不同在:

	if (pdrv->id_table)
		return platform_match_id(pdrv->id_table, pdev) != NULL;

----->

static const struct platform_device_id *platform_match_id(
			const struct platform_device_id *id,
			struct platform_device *pdev)
{
	while (id->name[0]) {
		if (strcmp(pdev->name, id->name) == 0) {
			pdev->id_entry = id;
			return id;
		}
		id++;
	}
	return NULL;
}

其实也是id_table的匹配方式。最后还多了一项,就是直接比较pdev和driver的名字:

	/* fall-back to driver name match */
	return (strcmp(pdev->name, drv->name) == 0);

二、 driver_register()

int driver_register(struct device_driver *drv)
{
	int ret;
	struct device_driver *other;

	BUG_ON(!drv->bus->p);

	if ((drv->bus->probe && drv->probe) ||
	    (drv->bus->remove && drv->remove) ||
	    (drv->bus->shutdown && drv->shutdown))
		printk(KERN_WARNING "Driver '%s' needs updating - please use "
			"bus_type methods\n", drv->name);

	other = driver_find(drv->name, drv->bus);		// g, 如果driver已经注册,会出错,不能二次注册driver
	if (other) {
		printk(KERN_ERR "Error: Driver '%s' is already registered, "
			"aborting...\n", drv->name);
		return -EBUSY;
	}

	ret = bus_add_driver(drv);		// g, 向bus中添加driver
	if (ret)
		return ret;
	ret = driver_add_groups(drv, drv->groups);
	if (ret) {
		bus_remove_driver(drv);
		return ret;
	}
	kobject_uevent(&drv->p->kobj, KOBJ_ADD);

	return ret;
}

就不做深入分析了,主要看一下匹配过程:

driver_register()
	-->bus_add_driver()
		-->driver_attach()
			-->bus_for_each_dev(drv->bus, NULL, drv, __driver_attach),device_add是遍历所有driver,这里是遍历所有device
				-->__driver_attach()
					-->driver_match_device(),这里后面的就与device_add的匹配一样了。

三、编写驱动:

  • 要做的事情:①需要对应总线对应的driver结构体;②在里面绑定.probe,绑定.remove,绑定.driver,绑定.id_table(也是一种匹配方式);③.driver里面要绑定name(作为driver的name,可以与device的name进行匹配)和compatible表(最优先的匹配方式);④ 调用对应的框架提供的register接口就行了。
/* 传统匹配方式,使用name进行匹配 */
static const struct i2c_device_id ap3216c_id[] = {
    {"alientek,ap3216c", 0},
    {}};

/* 设备树匹配方式,使用.compatible进行匹配 */
static const struct of_device_id ap3216c_of_match[] = {
    {.compatible = "alientek,ap3216c"},
    {}};

/* i2c 驱动结构体 */
static struct i2c_driver ap3216c_driver = {
    .probe = ap3216c_probe,
    .remove = ap3216c_remove,
    .driver = {
        .owner = THIS_MODULE,
        .name = "ap3216c",
        .of_match_table = ap3216c_of_match,
    },
    .id_table = ap3216c_id,
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值