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

函数重点步骤
  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.mfbz.cn/a/768299.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

安全隔离上网的有效途径:沙箱

在数字化浪潮日益汹涌的今天&#xff0c;网络安全成为了不可忽视的重要议题。沙箱技术作为一种高效的隔离机制&#xff0c;为企业和个人提供了一种在享受网络便利的同时&#xff0c;保障系统安全的解决方案。本文旨在深入探讨沙箱技术如何做到隔离上网&#xff0c;从而为用户提…

(五十二)第 8 章 动态存储管理(边界标识法)

1. 背景说明 2. 示例代码 1) errorRecord.h // 记录错误宏定义头文件#ifndef ERROR_RECORD_H #define ERROR_RECORD_H#include <stdio.h> #include <string.h> #include <stdint.h>// 从文件路径中提取文件名 #define FILE_NAME(X) strrchr(X, \\) ? strr…

QT创建地理信息shp文件编辑器shp_editor

空闲之余创建一个简单的矢量shp文件编辑器&#xff0c;加深对shp文件的理解。 一、启动程序 二、打开shp文件 三、显示shp文件的几何图形 四、双击右边表格中的feature&#xff0c;主窗体显示选中feature的各个节点。 五、鼠标在主窗体中选中feature的节点&#xff0c;按鼠标左…

js学习--制作选项卡

选项卡制作 <!DOCTYPE html> <html lang"zh"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><style>.text_one {width: 11.4%;height: 200px…

web前端开发(概述篇)

一、概念 Web是Internet上的一种多媒体信息服务系统&#xff0c;整个系统由Web服务器、浏览器和通信协议组成。 通信协议HTTP能够传输任意类型的数据对象&#xff0c;满足Web服务器与客户之间的多媒体通信的需求。 一般来说&#xff0c;Web开发分为前端&#xff08;Front-en…

番外篇 | 手把手教你如何去更换YOLOv5的检测头为ASFF_Detect

前言:Hello大家好,我是小哥谈。自适应空间特征融合(ASFF)的主要原理旨在解决单次检测器中不同尺度特征的不一致性问题。具体来说,ASFF通过动态调整来自不同尺度特征金字塔层的特征贡献,确保每个检测对象的特征表示是一致且最优的。本文所做出的改进是将YOLOv5的检测头更换…

身边的故事(十三):阿文的故事:出现

如果他知道一件事情如果违背正常的市场规律就是骗局或者存在巨大的风险&#xff0c;比如市场正常投资回报率在5-6%已经算高回报&#xff0c;像股神巴菲特的投资回报率应该不会超过10%吧。那些说20-30%甚至更高回报率肯定是骗局。如果...哪有那么多如果&#xff0c;人生每一秒都…

从4D CT灌注成像中使用时空卷积神经网络预测急性缺血性中风的特定治疗病变结果| 文献速递-深度学习自动化疾病检查

Title 题目 Predicting treatment-specific lesion outcomes in acute ischemic stroke from 4D CT perfusion imaging using spatio-temporal convolutional neural networks 从4D CT灌注成像中使用时空卷积神经网络预测急性缺血性中风的特定治疗病变结果 01 文献速递介绍…

【电商指标详解】

前言&#xff1a; &#x1f49e;&#x1f49e;大家好&#xff0c;我是书生♡&#xff0c;本篇文章主要和大家分享一下电商行业中常见指标的详解&#xff01;存在的原因和作用&#xff01;&#xff01;&#xff01;希望对大家有所帮助。 &#x1f49e;&#x1f49e;代码是你的画…

打卡第一天

今天是参加算法训练营的第一天&#xff0c;希望我能把这个训练营坚持下来&#xff0c;希望我的算法编程题的能力有所提升&#xff0c;不再面试挂了&#xff0c;面试总是挂编程题&#xff0c;记录我leetcode刷题数量&#xff1a; 希望我通过这个训练营能够实现两份工作的无缝衔接…

Vue项目打包上线

Nginx 是一个高性能的开源HTTP和反向代理服务器&#xff0c;也是一个IMAP/POP3/SMTP代理服务器。它在设计上旨在处理高并发的请求&#xff0c;是一个轻量级、高效能的Web服务器和反向代理服务器&#xff0c;广泛用于提供静态资源、负载均衡、反向代理等功能。 1、下载nginx 2、…

2024企业数据资产化及数据资产入表方案梳理

01 数据资产入表&#xff1a;是一个将组织的各类数据资产进行登记、分类、评估和管理的流程。 数据资产包括&#xff1a;客户信息、交易记录、产品数据、财务数据等。 做个比喻吧&#xff1a;数据资产入表就像是给公司的数据资产做“人口普查”—— ①找出公司有哪些数据找…

python中的文件

1.什么是文件&#xff1f; 硬盘上存储的数据都是以文件的形式来组织的~ 文件是数据在硬盘上的存储形式&#xff0c;不同的数据在硬盘上的存储形式是不同的&#xff0c; 2.文件路径 文件夹/目录。 文件夹&#xff0c;再包含文件夹的情况&#xff0c;这就是一个嵌套的关系&…

顺序表--数据结构第一关

顺序表 数据结构概念 定义&#xff1a;数据结构是计算机存储、组织数据的⽅式 根据学过C语言的基础上&#xff0c;数组是最简单的数据结构 顺序表的底层就是数组 为什么呢&#xff1f; 例子如下&#xff1a; int arr[100]{1,2,3,4,5}; //修改某一个数据&#xff1a;arr[…

电子部件烧录流程(仅供参考)

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a;日常聊聊 ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 前言 正文 部件烧录流程的详细步骤 1. 准备工作 2. 连接硬件 3. 配置烧录软件 4. 校验和设置 5. 开始烧录 6. 验证和测试 7. 断开…

吉利银河L6 AQS空气质量监控系统

结论 顶配才有AQS 开启空调且auto模式 则默认开启AQS 无法关闭AQS AQS的作用 银河L6 AQS触发 和 图标 AQS官方配置参数 官方文档 吉利用户手册

机器学习基础概念

1.机器学习定义 2.机器学习工作流程 &#xff08;1&#xff09;数据集 ①一行数据&#xff1a;一个样本 ②一列数据&#xff1a;一个特征 ③目标值&#xff08;标签值&#xff09;&#xff1a;有些数据集有目标值&#xff0c;有些数据集没有。因此数据类型由特征值目标值构成或…

数据结构与算法笔记:实战篇 - 剖析微服务接口鉴权限流背后的数据结构和算法

概述 微服务是最近几年才兴起的概念。简单点将&#xff0c;就是把复杂的大应用&#xff0c;解耦成几个小的应用 。这样做的好处有很多。比如&#xff0c;这样有利于团队组织架构的拆分&#xff0c;比较团队越大协作的难度越大&#xff1b;再比如&#xff0c;每个应用都可以独立…

程序算法设计分析

动态规划和分治、贪心相比有什么区别&#xff1f;各自的优缺点&#xff1f; 分治算法特征&#xff1a; 1&#xff09;规模如果很小&#xff0c;则很容易解决。//一般问题都能满足 2&#xff09;大问题可以分为若干规模小的相同问题。//前提 3&#xff09;利用子问题的解&#x…

最靓丽的C++开源通知弹框SnoreToasts自动监听软件及网页通知

SnoreToasts&#xff0c;作为一款轻量级的C开源项目&#xff0c;为开发者提供了一个便捷的方式来在Windows操作系统上展示通知弹框&#xff08;Toast Notifications&#xff09;。 特点与优势 轻量级&#xff1a;SnoreToasts采用了简洁的代码设计&#xff0c;避免了不必要的依…