背景
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,
};