瑞芯微RK3568芯片是一款定位中高端的通用型SOC,采用22nm制程工艺,搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码,支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU,可用于轻量级人工智能应用。RK3568 支持安卓 11 和 linux 系统,主要面向物联网网关、NVR 存储、工控平板、工业检测、工控盒、卡拉 OK、云终端、车载中控等行业。
【公众号】迅为电子
【粉丝群】824412014(加群获取驱动文档+例程)
【视频观看】嵌入式学习之Linux驱动(第九期_设备模型_全新升级)_基于RK3568
【购买链接】迅为RK3568开发板瑞芯微Linux安卓鸿蒙ARM核心板人工智能AI主板
第108章 驱动注册流程分析实验
在上一章节中,我们学习了设备注册到自定义总线下,并进行了实验。在本章节中,我们将继续探讨驱动是如何注册到总线上的。
一旦驱动程序的结构体和匹配信息准备就绪,我们就可以调用 driver_register() 函数将驱动程序注册到总线上。
108.1 driver_register函数解析
driver_register函数实现如下所示:
int driver_register(struct device_driver *drv)
{
int ret;
struct device_driver *other;
// 检查总线是否已初始化
if (!drv->bus->p) {
pr_err("Driver '%s' was unable to register with bus_type '%s' because the bus was not initialized.\n",
drv->name, drv->bus->name);
return -EINVAL;
}
// 检查驱动程序的方法是否需要更新
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);
if (other) {
printk(KERN_ERR "Error: Driver '%s' is already registered, "
"aborting...\n", drv->name);
return -EBUSY;
}
ret = bus_add_driver(drv); // 将驱动程序添加到总线
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函数用于注册设备驱动程序并将其添加到总线中。以下是该函数的功能解释:
- 第5~11行代码 检查总线是否已初始化:
- 首先,通过 drv->bus访问设备驱动程序结构体中的总线信息。如果总线的 p成员为 NULL,表示总线未初始化。如果总线未初始化,则打印错误消息,并返回 -EINVAL错误码表示无效的参数。
- 第13行~19行代码 检查驱动程序的方法是否需要更新:
- 通过检查驱动程序结构体中的 bus->probe和 drv->probe、bus->remove 和 drv->remove、bus->shutdown 和 drv->shutdown 成员是否同时存在来判断。如果存在需要更新的方法组合,说明驱动程序需要更新。在这种情况下,打印警告消息,建议使用 bus_type 方法进行更新。
- 第21行~26行 检查驱动程序是否已被注册:
- 调用 driver_find函数来查找是否已经注册了同名的驱动程序。如果找到同名驱动程序,表示驱动程序已经注册过。在这种情况下,打印错误消息,并返回 -EBUSY错误码表示设备忙。
- 第28行代码 添加驱动程序到总线:
- 调用 bus_add_driver 函数将驱动程序添加到总线。如果添加失败,则返回相应的错误码。
- 第31行代码 添加驱动程序的组属性:
调用 driver_add_groups函数将驱动程序的组属性添加到驱动程序中。如果添加失败,则调用 bus_remove_driver函数移除已添加的驱动程序,并返回相应的错误码。
- 第36行代码 发送内核对象事件:
调用 kobject_uevent函数向驱动程序的内核对象发送事件,通知驱动程序已成功添加到系统中。
综上所述,driver_register函数的功能是注册设备驱动程序并将其添加到总线中,同时进行各种检查和错误处理操作。
在上面代码中,调用 bus_add_driver 函数将驱动程序添加到总线。我们来详细分析下bus_add_driver 函数。
int bus_add_driver(struct device_driver *drv)
{
struct bus_type *bus;
struct driver_private *priv;
int error = 0;
// 获取总线对象
bus = bus_get(drv->bus);
if (!bus)
return -EINVAL; // 返回无效参数错误码
pr_debug("bus: '%s': add driver %s\n", bus->name, drv->name);
// 分配并初始化驱动程序私有数据
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
if (!priv) {
error = -ENOMEM;
goto out_put_bus;
}
klist_init(&priv->klist_devices, NULL, NULL);
priv->driver = drv;
drv->p = priv;
priv->kobj.kset = bus->p->drivers_kset;
// 初始化并添加驱动程序的内核对象
error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,
"%s", drv->name);
if (error)
goto out_unregister;
// 将驱动程序添加到总线的驱动程序列表
klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);
// 如果总线启用了自动探测,则尝试自动探测设备
if (drv->bus->p->drivers_autoprobe) {
error = driver_attach(drv);
if (error)
goto out_unregister;
}
// 将驱动程序添加到模块
module_add_driver(drv->owner, drv);
// 创建驱动程序的uevent属性文件
error = driver_create_file(drv, &driver_attr_uevent);
if (error) {
printk(KERN_ERR "%s: uevent attr (%s) failed\n",
__func__, drv->name);
}
// 添加驱动程序的组属性
error = driver_add_groups(drv, bus->drv_groups);
if (error) {
/* How the hell do we get out of this pickle? Give up */
printk(KERN_ERR "%s: driver_create_groups(%s) failed\n",
__func__, drv->name);
}
// 如果驱动程序不禁止绑定属性文件,则添加绑定属性文件
if (!drv->suppress_bind_attrs) {
error = add_bind_files(drv);
if (error) {
/* Ditto */
printk(KERN_ERR "%s: add_bind_files(%s) failed\n",
__func__, drv->name);
}
}
return 0; // 返回成功
out_unregister:
kobject_put(&priv->kobj);
/* drv->p is freed in driver_release() */
drv->p = NULL;
out_put_bus:
bus_put(bus);
return error; // 返回错误码
}
bus_add_driver该函数用于将设备驱动程序添加到总线中。以下是功能的详细解释:
- 第8行代码获取总线对象:
- 通过 drv->bus 访问设备驱动程序结构体中的总线信息。通过调用 bus_get 函数获取总线对象。如果总线对象不存在,则返回 -EINVAL 错误码表示无效的参数。
- 第15行~23行代码分配并初始化驱动程序私有数据:
- 调用 kzalloc 函数为驱动程序的私有数据结构体 priv 分配内存,并使用 GFP_KERNEL 标志进行内存分配。如果内存分配失败,则返回 -ENOMEM 错误码表示内存不足。使用 klist_init 函数初始化 priv 结构体中的设备列表。设置 priv 结构体中的驱动程序指针,并将其赋值为当前的驱动程序。将 drv->p 指向 priv 结构体,以便后续的释放操作。
- 第25行代码初始化并添加驱动程序的内核对象:
- 设置 priv->kobj.kset 成员为总线对象的 drivers_kset。调用 kobject_init_and_add 函数初始化并添加驱动程序的内核对象。如果初始化或添加失败,则跳转到 out_unregister 进行错误处理。
- 第31行代码将驱动程序添加到总线的驱动程序列表:
- 使用 klist_add_tail 函数将驱动程序的节点添加到总线的驱动程序列表中。
到此,如何在总线下注册驱动流程已经分析完毕。