一、设备和驱动的注册
设备注册两种方式:
1、从设备树解析动态注册。 设备树dts文件中定义了设备节点,描述了硬件信息,比如寄存器信息,引脚信息等,内核将从设备树中解析得到的platform_device注册到平台总线中。具体设备树在内核中的编译流程可以看设备树的概念、设备树如何变成device、与driver的匹配_驱动和设备树的匹配过程-CSDN博客
2、在入口函数中,驱动程序中静态注册平台设备到平台总线上。
static int __init vivid_init(void)
{
int ret;
ret = platform_device_register(&vivid_pdev);/*设备注册*/
if (ret)
return ret;
ret = platform_driver_register(&vivid_pdrv);/*驱动注册*/
if (ret)
platform_device_unregister(&vivid_pdev);
return ret;
}
驱动注册的一种方式:
在驱动程序的入口函数中注册
二、注册函数源码分析:
(基于Linux6.8.8.8内核版本的vivid-core.c驱动程序)
1、设备注册platform_device_register
int platform_device_register(struct platform_device *pdev)
{
device_initialize(&pdev->dev);
setup_pdev_dma_masks(pdev);
return platform_device_add(pdev);
}
device_initialize(&pdev->dev);
void device_initialize(struct device *dev)
{
dev->kobj.kset = devices_kset;
kobject_init(&dev->kobj, &device_ktype);
INIT_LIST_HEAD(&dev->dma_pools);
mutex_init(&dev->mutex);
lockdep_set_novalidate_class(&dev->mutex);
spin_lock_init(&dev->devres_lock);
INIT_LIST_HEAD(&dev->devres_head);
device_pm_init(dev);
set_dev_node(dev, NUMA_NO_NODE);
INIT_LIST_HEAD(&dev->links.consumers);
INIT_LIST_HEAD(&dev->links.suppliers);
INIT_LIST_HEAD(&dev->links.defer_sync);
dev->links.status = DL_DEV_NO_DRIVER;
#if defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_DEVICE) || \
defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU) || \
defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU_ALL)
dev->dma_coherent = dma_default_coherent;
#endif
swiotlb_dev_init(dev);
}
device_initialize
函数的作用是初始化 struct device
的各个成员变量,确保设备在添加到系统时处于一个已知的良好状态。通过初始化 kobject、互斥锁、设备资源列表、电源管理、NUMA 节点和 DMA 设置等,各个部分协同工作,确保设备能被内核正确管理和使用
platform_device_add(pdev)是关键函数
int platform_device_add(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
u32 i;
int ret;
if (!dev->parent)
dev->parent = &platform_bus;
dev->bus = &platform_bus_type;
switch (pdev->id) {
default:
dev_set_name(dev, "%s.%d", pdev->name, pdev->id);
break;
case PLATFORM_DEVID_NONE:
dev_set_name(dev, "%s", pdev->name);
break;
case PLATFORM_DEVID_AUTO:
/*
* Automatically allocated device ID. We mark it as such so
* that we remember it must be freed, and we append a suffix
* to avoid namespace collision with explicit IDs.
*/
ret = ida_alloc(&platform_devid_ida, GFP_KERNEL);
if (ret < 0)
return ret;
pdev->id = ret;
pdev->id_auto = true;
dev_set_name(dev, "%s.%d.auto", pdev->name, pdev->id);
break;
}
for (i = 0; i < pdev->num_resources; i++) {
struct resource *p, *r = &pdev->resource[i];
if (r->name == NULL)
r->name = dev_name(dev);
p = r->parent;
if (!p) {
if (resource_type(r) == IORESOURCE_MEM)
p = &iomem_resource;
else if (resource_type(r) == IORESOURCE_IO)
p = &ioport_resource;
}
if (p) {
ret = insert_resource(p, r);
if (ret) {
dev_err(dev, "failed to claim resource %d: %pR\n", i, r);
goto failed;
}
}
}
pr_debug("Registering platform device '%s'. Parent at %s\n", dev_name(dev),
dev_name(dev->parent));
ret = device_add(dev);
if (ret)
goto failed;
return 0;
failed:
if (pdev->id_auto) {
ida_free(&platform_devid_ida, pdev->id);
pdev->id = PLATFORM_DEVID_AUTO;
}
while (i--) {
struct resource *r = &pdev->resource[i];
if (r->parent)
release_resource(r);
}
return ret;
}
platform_device_add
函数是将一个平台设备添加到系统中的关键函数。它的主要作用是将一个 platform_device
结构体注册到内核,使其成为内核设备模型的一部分。以下是对该函数的详细解释:
函数重点步骤
-
设备总线类型设置:
dev->bus = &platform_bus_type;
将设备的总线类型设置为
platform_bus_type
。 -
设备名称设置 : 根据
pdev->id
设置设备的名称。 -
资源管理: 为每个资源设置名称,并将其插入合适的父资源中。
for (i = 0; i < pdev->num_resources; i++) { struct resource *p, *r = &pdev->resource[i]; if (r->name == NULL) r->name = dev_name(dev); p = r->parent; if (!p) { if (resource_type(r) == IORESOURCE_MEM) p = &iomem_resource; else if (resource_type(r) == IORESOURCE_IO) p = &ioport_resource; } if (p) { ret = insert_resource(p, r); if (ret) { dev_err(dev, "failed to claim resource %d: %pR\n", i, r); goto failed; } } }
-
设备注册: 将设备添加到系统中
pr_debug("Registering platform device '%s'. Parent at %s\n", dev_name(dev), dev_name(dev->parent)); ret = device_add(dev); if (ret) goto failed; return 0;
-
错误处理: 如果在注册设备时发生错误,释放已分配的资源和ID。
关键函数和结构体
device_add
:将设备添加到内核设备模型中。dev_set_name
:设置设备的名称。ida_alloc
和ida_free
:用于分配和释放ID。insert_resource
和release_resource
:用于管理设备资源。pr_debug
和dev_err
:用于打印调试和错误信息。
2、驱动注册platform_driver_register
int __platform_driver_register(struct platform_driver *drv,
struct module *owner)
{
drv->driver.owner = owner;
drv->driver.bus = &platform_bus_type;
return driver_register(&drv->driver);
}
设置驱动总线为平台总线
driver_register(&drv->driver);
是向平台总线注册驱动的底层实现函数。
int driver_register(struct device_driver *drv)
{
int ret;
struct device_driver *other;
// 检查驱动程序要注册的总线是否已经注册
if (!bus_is_registered(drv->bus)) {
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; // 如果总线未初始化,返回参数无效的错误码
}
// 检查是否存在相同名称的驱动程序已经注册
other = driver_find(drv->name, drv->bus);
if (other) {
pr_err("Error: Driver '%s' is already registered, aborting...\n", drv->name);
return -EBUSY; // 如果已经存在同名驱动程序注册,返回设备忙的错误码
}
// 如果驱动程序的方法与总线的方法有冲突,发出警告
if ((drv->bus->probe && drv->probe) ||
(drv->bus->remove && drv->remove) ||
(drv->bus->shutdown && drv->shutdown))
pr_warn("Driver '%s' needs updating - please use bus_type methods\n", drv->name);
// 将驱动程序添加到总线中
ret = bus_add_driver(drv);
if (ret)
return ret; // 如果添加驱动程序到总线失败,直接返回错误码
// 添加驱动程序的组属性(groups)
ret = driver_add_groups(drv, drv->groups);
if (ret) {
bus_remove_driver(drv); // 如果添加组属性失败,则移除驱动程序
return ret; // 返回错误码
}
// 发送内核事件通知,表明驱动程序已添加
kobject_uevent(&drv->p->kobj, KOBJ_ADD);
// 延迟探测扩展超时时间
deferred_probe_extend_timeout();
return ret; // 返回操作的结果码(通常为0表示成功)
}
ret = bus_add_driver(drv);
int bus_add_driver(struct device_driver *drv)
{
struct subsys_private *sp = bus_to_subsys(drv->bus); // 获取总线对应的子系统私有数据结构
struct driver_private *priv;
int error = 0;
if (!sp)
return -EINVAL; // 如果获取的子系统私有数据结构为空,则返回参数无效的错误码
pr_debug("bus: '%s': add driver %s\n", sp->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 = sp->drivers_kset; // 设置驱动程序的 kobject 所属的 kset
// 初始化并添加驱动程序的 kobject 到内核对象模型中
error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL, "%s", drv->name);
if (error)
goto out_unregister; // 如果初始化并添加失败,则跳转到解注册操作
// 将驱动程序私有数据结构添加到总线的驱动程序列表中
klist_add_tail(&priv->knode_bus, &sp->klist_drivers);
// 如果总线支持自动探测,则尝试附加驱动程序
if (sp->drivers_autoprobe) {
error = driver_attach(drv);
if (error)
goto out_del_list; // 如果附加失败,则跳转到删除列表操作
}
// 将驱动程序的 owner 添加到模块的驱动程序列表中
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, sp->bus->drv_groups);
if (error) {
printk(KERN_ERR "%s: driver_add_groups(%s) failed\n", __func__, drv->name);
}
// 如果驱动程序不抑制绑定文件属性,则添加绑定文件属性
if (!drv->suppress_bind_attrs) {
error = add_bind_files(drv);
if (error) {
printk(KERN_ERR "%s: add_bind_files(%s) failed\n", __func__, drv->name);
}
}
return 0; // 返回成功
out_del_list:
klist_del(&priv->knode_bus); // 从总线驱动程序列表中删除驱动程序
out_unregister:
kobject_put(&priv->kobj); // 取消驱动程序的 kobject
out_put_bus:
kfree(priv); // 释放驱动程序私有数据结构内存
return error; // 返回操作错误码
}
三、平台总线:
总线通信都是有协议的。类似一条高速公路分为物理 总线和虚拟 总线
物理总线(现实中看的见的 ): i2c总线,spi总线,usb总线等,连接两个设备
虚拟总线(内核中 ):平台总线,连接两个对象
给两个对象提供一个匹配的平台,一个驱动可以匹配多个设备。
平台总线(Platform Bus)是 Linux 内核中用于管理和连接平台设备(Platform Devices)和平台驱动(Platform Drivers)的一种总线类型。平台总线是内核的一部分,用于处理那些与特定总线(如 PCI、USB 等)无关的设备。这些设备通常是片上系统(SoC)的一部分,通过内存映射 I/O 或直接连接到 CPU。
总线结构体如下:
struct bus_type {
const char *name; // 总线名称
const char *dev_name; // 设备名称格式
struct device *dev_root; // 根设备(可选)
struct bus_attribute *bus_attrs; // 总线属性
struct device_attribute *dev_attrs; // 设备属性
struct driver_attribute *drv_attrs; // 驱动属性
int (*match)(struct device *dev, struct device_driver *drv); // 设备和驱动匹配函数
int (*uevent)(struct device *dev, struct kobj_uevent_env *env); // 热插拔事件
int (*probe)(struct device *dev); // 驱动探测函数
int (*remove)(struct device *dev); // 驱动移除函数
void (*shutdown)(struct device *dev); // 设备关闭函数
int (*online)(struct device *dev); // 设备上线函数
int (*offline)(struct device *dev); // 设备下线函数
struct bus_type_private *p; // 私有数据
struct kset kset; // kobject 集合
struct klist klist_devices; // 设备列表
struct klist klist_drivers; // 驱动列表
};
注册一个平台总线实例如下:
struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.probe = platform_probe,
.remove = platform_remove,
.shutdown = platform_shutdown,
.dma_configure = platform_dma_configure,
.dma_cleanup = platform_dma_cleanup,
.pm = &platform_dev_pm_ops,
};