Linux--平台设备、平台驱动的注册源码分析

一、设备和驱动的注册

设备注册两种方式:

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;
#endifswiotlb_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 结构体注册到内核,使其成为内核设备模型的一部分。以下是对该函数的详细解释:

函数重点步骤
  1. 设备总线类型设置

    dev->bus = &platform_bus_type;
    

    将设备的总线类型设置为 platform_bus_type

  2. 设备名称设置: 根据 pdev->id 设置设备的名称。

  3. 资源管理: 为每个资源设置名称,并将其插入合适的父资源中。

    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;}}
    }
    
  4. 设备注册: 将设备添加到系统中

    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;
    
  5. 错误处理: 如果在注册设备时发生错误,释放已分配的资源和ID。

关键函数和结构体
  • device_add:将设备添加到内核设备模型中。
  • dev_set_name:设置设备的名称。
  • ida_allocida_free:用于分配和释放ID。
  • insert_resourcerelease_resource:用于管理设备资源。
  • pr_debugdev_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,
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/39952.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

一个opencv实现检测程序

引言 图像处理是计算机视觉中的一个重要领域&#xff0c;它在许多应用中扮演着关键角色&#xff0c;如自动驾驶、医疗图像分析和人脸识别等。边缘检测是图像处理中的基本任务之一&#xff0c;它用于识别图像中的显著边界。本文将通过一个基于 Python 和 OpenCV 的示例程序&…

UniApp 中 Web/H5 正确使用反向代理解决跨域问题

因为 Vue3 的构建工具是 Vite&#xff0c;所以配置 vue.config.js 是没用的&#xff08;Vue2 因为使用 webpack 所以才用这个文件&#xff09; 这里提供一份 vue.config.js 的示例&#xff1a; module.exports {devServer: {proxy: {/api: {target: http://example.com,chan…

Python学习速成必备知识,(20道练习题)!

基础题练习 1、打印出1-100之间的所有偶数&#xff1a; for num in range(1, 101):if num % 2 0:print(num) 2、打印出用户输入的字符串的长度&#xff1a; string input("请输入一个字符串&#xff1a;")print("字符串的长度为&#xff1a;", len(str…

使用Python进行文件合并和分割

哈喽,大家好,我是木头左! 引言 在数据处理过程中,经常需要将多个文件合并为一个文件,或者将一个大文件分割成多个小文件。Python作为一种功能强大的编程语言,提供了多种方法来实现这一目标。本文将介绍如何使用Python进行文件合并和分割。 文件合并 1. 逐行合并 最简…

Git不想跟踪某个文件

如果你不想跟踪某个文件&#xff0c;可以将该文件路径添加到 .gitignore 文件中。.gitignore 文件用于告诉 Git 哪些文件或目录应该被忽略&#xff0c;不进行版本控制。以下是具体步骤&#xff1a; 编辑 .gitignore 文件&#xff1a;在项目的根目录下找到或创建一个 .gitignore…

More Effective C++ 35个改善编程与设计的有效方法笔记与心得 5

五. 技术 条款25&#xff1a; 将 constructor 和 non-member functions虚化 请记住&#xff1a; 1. 利用重载技术&#xff08;overload&#xff09;避免隐式类型转换&#xff08;implicit type conversions&#xff09; ‌‌‌‌  重载技术是指在同一个作用域中声明多个同…

差分进化算法(Differential Evolution)及其python实现

### 背景&#xff1a; 差分进化算法&#xff08;Differential Evolution&#xff0c;DE&#xff09;是一种基于种群的优化算法&#xff0c;旨在解决连续优化问题。DE 最初由 Price 和 Storn 提出&#xff0c;是一种简单但有效的全局优化算法&#xff0c;适用于具有非线性、非光…

rillflow运行时,跳转子应用时,页面循环自动刷新

rill-flow-ui执行运行完毕&#xff0c;生成如下运行url&#xff0c;http://110.108.190.18:8080/选中执行&#xff0c;跳转子应用循环自动刷新&#xff0c;debugger发现主应用在和微应用的默认地址建立链接失败&#xff0c; 改为运行 http://localhost:8080/&#xff0c;即可和…

excel表格如何换行,这几个操作方法要收藏好

Excel表格作为一款强大的数据处理工具&#xff0c;在日常工作和生活中被广泛应用。当需要在单元格内显示较长的文本内容或使数据更加清晰易读时&#xff0c;我们需要掌握一些换行技巧。下面将介绍几种常用的Excel换行方法&#xff1a; 一、使用快捷键换行 1、首先&#xff0c;…

iSDF改进优化笔记

《iSDF: Real-Time Neural Signed Distance Fields for Robot Perception》论文提出了一种实时神经签名距离场&#xff08;SDF&#xff09;重建的方法&#xff0c;该方法在多个方面表现优异。然而&#xff0c;仍有一些潜在的改进空间&#xff1a; 1. 扩展实验范围 更多数据集和…

openresty lua用Redis的Stream解决消息订阅问题

使用 Redis Streams 解决消息订阅和消费的问题&#xff0c;可以避免在订阅模式下的连接管理问题。下面是如何使用 OpenResty 和 Redis Streams 实现类似的功能。 配置 nginx.conf 确保你的 nginx.conf 文件中配置了 Lua 模块和 Redis 集群的连接信息&#xff1a; http {lua_…

暑假学习DevEco Studio第一天

学习目标&#xff1a; 掌握构建第一个ArkTS应用 学习内容&#xff1a; 容器的应用 创建流程 点击file&#xff0c;new-> create project 点击empty ->next 进入配置界面 点击finsh&#xff0c;生成下面图片 这里需要注意记住index.ets &#xff0c;这是显示页面 –…

五款免费可视化利器分享,助力打造数字孪生新体验!

在当今数据驱动的时代&#xff0c;可视化工具已成为各行各业不可或缺的助手。它们不仅能帮助我们更好地理解和分析数据&#xff0c;还能以直观、生动的方式呈现复杂信息&#xff0c;提升沟通和决策效率。本文将为大家介绍五款免费的可视化工具&#xff0c;总有一款适合你。 一…

selenium 获取请求头cookie信息

在做接口测试如登陆接口过于复杂&#xff0c;可以先使用UI自动化把cookie保存在本地供接口测试使用 import time from selenium import webdriver from selenium.webdriver.chrome.options import Optionsdef get_seeion():# 创建Chrome浏览器的Options对象chrome_options Op…

如何做好企业品牌推广,看这篇文章就够了

在当今竞争激烈的市场环境中&#xff0c;品牌推广策略与方式成为企业成功的关键。因为相比起企业&#xff0c;消费者会更愿意为品牌买单。那么企业如何将品牌推广做好呢?今日投媒网与您分享。 1.明确品牌定位与目标受众 一切推广活动的起点在于清晰的品牌定位。首先&#xf…

苹果ios安卓apk应用APP文件怎么修改手机APP显示的名称

当我们安装了一款 APP后&#xff0c;该 APP的名称可能就是我们看到的名称&#xff0c;那么我们可以通过修改手机 APP显示的名称来修改该 APP文件的名称&#xff0c;那么具体怎么操作呢&#xff1f;下面就给大家来介绍一下。 首先我们进入手机上的应用商店&#xff0c;然后在搜…

SolrCloud Autoscaling 自动添加副本

SolrCloud Autoscaling 自动添加副本 前言 问题描述 起因是这样的&#xff0c;我在本地调试 Solr 源码&#xff08;版本 7.7.3&#xff09;&#xff0c;用 IDEA 以 solrcloud 方式启动了 2 个 Solr 服务&#xff0c;如下所示&#xff1a; 上图的启动参数 VM Options 如下&am…

RocketMQ实战:一键在docker中搭建rocketmq和doshboard环境

在本篇博客中&#xff0c;我们将详细介绍如何在 Docker 环境中一键部署 RocketMQ 和其 Dashboard。这个过程基于一个预配置的 Docker Compose 文件&#xff0c;使得部署变得简单高效。 项目介绍 该项目提供了一套 Docker Compose 配置&#xff0c;用于快速部署 RocketMQ 及其…

美国商超入驻细节全面曝光,电竞外设产品的国际化浪潮即将席卷全球

近年来&#xff0c;随着电子竞技(简称电竞)行业的蓬勃发展&#xff0c;电竞外设产品也逐渐成为消费者关注的热点。近期&#xff0c;一系列美国商超入驻细节的全面曝光&#xff0c;预示着电竞外设产品的出海风潮即将到来。 电竞行业迅速崛起&#xff0c;全球市场规模年均增长超1…

ResNet50V2

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 一、ResNetV1和ResNetV2的区别 ResNetV2 和 ResNetV1 都是深度残差网络&#xff08;ResNet&#xff09;的变体&#xff0c;它们的主要区别在于残差块的设计和…