Uboot中的DM驱动模型

news2025/1/12 13:35:50

这一篇我们学习uboot中的驱动模型的初始化,在uboot中,驱动模型被称为Driver Model,简称DM。这种驱动模型为uboot中的各类驱动提供了统一的接口。

1. 数据结构及概念

DM模型主要依赖于下面四种数据结构:

  1. udevice,具有硬件设备的抽象, 和driver实例相关
  2. driver,特定udevice的硬件驱动,包含了驱动的绑定、初始化、probe和卸载等函数。使用U_BOOT_DRIVER来注册。需要声明所属的uclass。
  3. uclass,维护一类驱动,例如显示部分有lcdif驱动,display controller驱动,他们都在驱动中声明自己属于UCLASS_VIDEO类。又例如所有的MIPI驱动都属于UCLASS_DSI_HOST类。
  4. uclass_driver,在我们写的驱动中,我们会使用UCLASS_DRIVER来注册一个uclass_driver对象。这个uclass驱动维护了这一类硬件驱动的接口,为上层的调用提供了统一的接口。

我这里以GPIO为例,绘制了这四种数据结构的依赖关系。首先是DM框架中所定义的关于GPIO的UCLASS DRIVER,这个driver只有三个统一的接口,gpio_post_probegpio_post_bindgpio_pre_remove。在以spi中的gpio操作为例,gpio_request->gpio_to_device拿着传入的gpio number遍历UCLASS_GPIO下所有GPIO udevice,获取GPIO的描述符(包含udevice信息和GPIO的偏移量),此时已经找到了这个GPIO所对应的udevice,也就找到了驱动函数gpio_mxc,然后设置输入输出方向函数会调用这个驱动的相应函数来实现指定的输入输出方向。

uboot.drawio

2. DM模型的初始化:dm_init

在全局数据global_data定义了DM根节点,dm初始化的接口在dm_init_and_scan(bool pre_reloc_only)中,初始化流程主要有两次,入口函数分别是static int initf_dm(void)static int initr_dm(void)。第一次是在重定位之前,调用的是initf_dm函数。第二次是在重定位之后,调用的是initr_dm函数。

typedef struct global_data {
   // dts中的根节点,第一个创建的udevice
   struct udevice  *dm_root;

   // relocation之前的根设备
   struct udevice  *dm_root_f;

  // uclass的链表, 挂的是有udevice的uclass
   struct list_head uclass_root;  
} gd_

DM root初始化函数dm_init。

int dm_init(bool of_live)
{
	gd->uclass_root = &DM_UCLASS_ROOT_S_NON_CONST;
    //初始化uclass_root链表头
	INIT_LIST_HEAD(DM_UCLASS_ROOT_NON_CONST);
    //创建一个device dm_root并将其绑定到driver name “root_driver”。
    device_bind_by_name(NULL, false, &root_info,
					  &DM_ROOT_NON_CONST);
    //探测设备udevice dm_root并激活它
    ret = device_probe(DM_ROOT_NON_CONST);
}

2.1 device_bind_by_name分析

lists_driver_lookup_name通过driver name遍历整个driver list,找到U_BOOT_DRIVER(root_driver)定义的driver地址。

device_bind_common创建udevice dm_root和uclass root,并将driver root_driver、udevice dm_root和uclass root三者进行绑定。

int device_bind_by_name(struct udevice *parent, bool pre_reloc_only,
			const struct driver_info *info, struct udevice **devp)
{
	struct driver *drv;
	uint plat_size = 0;
	int ret;
	//通过driver name遍历整个driver list,找到U_BOOT_DRIVER(root_driver)定义的driver地址
	drv = lists_driver_lookup_name(info->name);
	
    //创建udevice dm_root和uclass root,并将driver root_driver、udevice dm_root和uclass root三者进行绑定。
	ret = device_bind_common(parent, drv, info->name, (void *)info->plat, 0,
				 ofnode_null(), plat_size, devp);

	return ret;
}

2.2 device_probe分析

DM_ROOT_NON_CONST代表gd中得dm_root:(((gd_t *)gd)->dm_root)device_probe是一个通用的函数, 其大概步骤如下:

  1. 检测该device是否已经激活,已激活就直接返回。
  2. 获取该设备对应的driver。
  3. 读取设备的平台数据。
  4. 如果该设备存在parent,那么先probe parent设备,确保所有的parent dev都被probed。
  5. 标记该设备处于激活状态。
  6. 处理除root device的pinctrl之外的所有设备,对于pinctrl device不进行pinctrl的设置,因为设备可能还没有被probed。
  7. 如果配置了IOMMU,则需要先打开IOMMU。
  8. 预使能设备,包括uclass的pre-probe()和父uclass的child_pre_probe()方法。
  9. 处理{clocks/clock-parents/clock-rates}属性配置时钟。
  10. 执行该设备的driver的probe函数,激活该设备。
  11. uclass_post_probe_device() 处理一个刚刚被probed过的设备。
  12. 最后处理IOMUX驱动,如果是IOMUX驱动则需要设置pinctl的默认状态。
int device_probe(struct udevice *dev)
{
	const struct driver *drv;
	int ret;

	// 检测该device是否已经激活,已激活就直接返回。
	if (dev->flags & DM_FLAG_ACTIVATED)
		return 0;

	drv = dev->driver; // 获取该设备对应的driver

	//device_ofdata_to_platdata() - 设备读取平台数据
	ret = device_ofdata_to_platdata(dev);

	// 如果该设备存在parent,那么先probe parent设备,确保所有的parent dev都被probed。
	if (dev->parent) {
		ret = device_probe(dev->parent);
	}

	// 标记该设备处于激活状态。
	dev_or_flags(dev, DM_FLAG_ACTIVATED);

	 //处理除root device的pinctrl之外的所有设备,对于pinctrl device不进行pinctrl的设置,因为设备可能还没有被probed。
	if (dev->parent && device_get_uclass_id(dev) != UCLASS_PINCTRL)
		pinctrl_select_state(dev, "default");
    //如果配置了IOMMU,则需要先打开IOMMU
    if (CONFIG_IS_ENABLED(IOMMU) && dev->parent &&
	    (device_get_uclass_id(dev) != UCLASS_IOMMU)) {
		ret = dev_iommu_enable(dev);
	}

	 //预使能设备,包括uclass的pre-probe()和父uclass的child_pre_probe()方法。
	ret = uclass_pre_probe_device(dev);
	if (dev->parent && dev->parent->driver->child_pre_probe) {
		ret = dev->parent->driver->child_pre_probe(dev);
	}

	//只处理具有有效ofnode的设备
	if (dev_of_valid(dev) && !(dev->driver->flags & DM_FLAG_IGNORE_DEFAULT_CLKS)) {
		// 处理{clocks/clock-parents/clock-rates}属性配置时钟
		ret = clk_set_defaults(dev, CLK_DEFAULTS_PRE);
	}

	// 执行该设备的driver的probe函数,激活该设备。
	if (drv->probe) {
		ret = drv->probe(dev);
	}

	//uclass_post_probe_device() - 处理一个刚刚被probed过的设备
	ret = uclass_post_probe_device(dev);
	//设置pinctl的默认状态
	if (dev->parent && device_get_uclass_id(dev) == UCLASS_PINCTRL)
		pinctrl_select_state(dev, "default");

	return 0;
}

3. DM模型Device Tree节点的设备初始化:dm_scan(pre_reloc_only)

dm_scan函数是在其他地方(设备树)搜索设备并进行驱动匹配,然后bind。主要分为三步:

  1. dm_scan_plat,搜索并绑定所有的驱动到根节点(((gd_t *)gd)->dm_root)上;
  2. dm_extended_scan,扫描dtb文件;
  3. dm_scan_other,留给厂商自定义覆盖的弱函数。

本节主要展开分析dm_extended_scan函数,下面是这个函数的流程:

dm_extended_scan(bool pre_reloc_only)
-->dm_scan_fdt(pre_reloc_only)
    -->dm_scan_fdt_node(gd->dm_root, ofnode_root(), pre_reloc_only)
    	-->lists_bind_fdt(parent, node, NULL, NULL, pre_reloc_only)  
-->for : dm_scan_fdt_ofnode_path(nodes[i], pre_reloc_only)
    -->dm_scan_fdt_node(gd->dm_root, node, pre_reloc_only)
        

首先这个函数定义了需要扫描的三个父节点,以下的扫描和绑定都是基于这三个节点展开的。

const char * const nodes[] = {
		"/chosen",
		"/clocks",
		"/firmware"
};

Device Tree设备初始化的核心函数是dm_scan_fdt_node->lists_bind_fdt,扫描设备树遍历父节点,并且将驱动绑定和设备树节点绑定。

步骤:

  1. ofnode_get_property() ,根据node节点获取compatible属性字符串list;
  2. 遍历兼容字符串列表,driver_check_compatible尝试匹配每个字符串兼容字符串,以便我们按优先级顺序匹配从第一个字符串到最后一个;
  3. 找到匹配的驱动,device_bind_with_driver_data() 创建一个设备并且绑定到driver。

4. device_bind_common

device_bind_common用于绑定设备树节点和驱动,主要有三处调用,分别是device_bind_by_namedevice_bind_with_driver_datadevice_binddevice_bind_by_name 在dm模型初始化的时候来初始化根设备global_data->dm_rootdevice_bind_with_driver_data在扫描设备树并绑定驱动的时候调用;device_bind在可以选择其余驱动的bind函数中调用,手动绑定想要的设备树节点和驱动。例如下图中apple笔记本的pinctl驱动:

image-20221225212201955

绑定的主要步骤:

  1. uclass_get() 根据driver->id获取一个uclass(.id = UCLASS_PINCTRL),如果它不存在就创建它。每个类都由一个ID标识,一个从0到n-1的数字,其中n是类的数量。这个函数允许根据类的ID查找类。

    uclass_get(drv->id, &uc)
    
  2. 创建一个新的device,申请一个struct udevice空间

    dev = calloc(1, sizeof(struct udevice))
    
  3. 初始化新device的相关列表头

    INIT_LIST_HEAD(&dev->sibling_node);
    INIT_LIST_HEAD(&dev->child_head); 
    INIT_LIST_HEAD(&dev->uclass_node);
    INIT_LIST_HEAD(&dev->devres_head);
    
  4. 初始化新udevice相关数据,将新udevice和传入的driver、uclass进行绑定。

    dev->platdata = platdata;-------->传入
    dev->driver_data = driver_data;-->传入
    dev->name = name;------>传入
    dev->node = node;
    dev->parent = parent;
    dev->driver = drv;----->传入
    dev->uclass = uc;
    
  5. dev->seq为该设备分配的序列号(-1 = none)。这是在设备probe时设置的,并且在设备的uclass中是唯一的。dev->req_seq为此设备请求的序列号(-1 = any)

    dev->seq = -1;
    dev->req_seq = -1;
    if (CONFIG_IS_ENABLED(OF_CONTROL) && !CONFIG_IS_ENABLED(OF_PLATDATA)) {
    	if (uc->uc_drv->name && ofnode_valid(node))
    		dev_read_alias_seq(dev, &dev->req_seq);
    } else {
    	dev->req_seq = uclass_find_next_free_req_seq(drv->id);
    }
    
  6. sibling_node对应该设备,并将它添加到parent的child_head设备列表中

    if (parent)
    	list_add_tail(&dev->sibling_node, &parent->child_head);
    
  7. uclass_bind_device() 将udevice与uclass进行关联

    uclass_bind_device(dev);
    
  8. device绑定成功后,就会调用drv->bind

    if (drv->bind) {
    		ret = drv->bind(dev);
    }
    
  9. 在一个新的child被绑定后,就会调用parent的parent->driver->child_post_bind(dev)

    if (parent && parent->driver->child_post_bind) {
    		ret = parent->driver->child_post_bind(dev);
    }
    
  10. 在一个新设备绑定到这个uclass后被调用

    if (uc->uc_drv->post_bind) {
    		ret = uc->uc_drv->post_bind(dev);
    }
    

5. 总结

对于DM模型初始化来说,uboot会在启动序列中使用dm_init创建一个dm_root(udevice)并将其绑定到“root_driver”(driver),然后device_probe来激活这个设备。第二步使用dm_scan来绑定设备树中的设备和驱动到dm_root下面。

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

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

相关文章

MySQL数据库闭包 Closure Table 表实现

1、 数据库闭包表简介 像MySQL这样的关系型数据库,比较适合存储一些类似表格的扁平化数据,但是遇到像树形结构这样有深度的数据,就很难驾驭了。 针对这种场景,闭包表(Closure Table )是最通用的设计&…

面试官:系统需求多变时如何设计?

面试官:我想问个问题哈,项目里比较常见的问题 面试官:我现在有个系统会根据请求的入参,做出不同动作。但是,这块不同的动作很有可能是会发生需求变动的,这块系统你会怎么样设计? 面试官&#…

FFmpeg简单使用:视频编码 ---- YUV转H264

基本流程 从本地读取YUV数据编码为h264格式的数据,然后再存⼊到本地,编码后的数据有带startcode。 与FFmpeg 示例⾳频编码的流程基本⼀致。 函数说明:avcodec_find_encoder_by_name:根据指定的编码器名称查找注册的编码器。 av…

第二十九章 数论——中国剩余定理与线性同余方程组

第二十九章 数论——中国剩余定理与线性同余方程组一、中国剩余定理1、作用:2、内容:3、证明:(1)逆元的存在性(2)验证定理的正确性4、代码实现:(1)步骤&#…

国产操作系统openEuler22.03配置yum源

作者:IT圈黎俊杰 本文选用的操作系统版本是openEuler22.03-LTS。openEuler是指操作系统的品牌英文名,中文名叫“欧拉”;22.03是指版本号(openEuler以年月为版本号,22.03表示2022年03月发布的版本)&#xff…

sonarqube——前端vue本地代码审查code review查看代码行数和注释率

目录一、环境二、操作1.启动2.中文3.使用三、过程踩坑1.sonarqube启动闪退2.解析报错 node 14.17一、环境 windows 64位 环境压缩包下载(sonar9.8,jdk11,sonar-scanner) 下载完成解压后,将 sonar-scanner-4.7.0.2747-…

curl 指令

勿以恶小而为之,勿以善小而不为---- 刘备 curl 是常用的命令行工具,用来请求 Web 服务器。 它的名字就是客户端(client)的 URL 工具的意思。 它的功能非常强大,命令行参数多达几十种 我们后端开发者, 可以…

MyISAM索引解析、InnoDB索引解析

我们经常说到的存储引擎是说数据库级别还是说表级别? 答:表级别。(数据库级别也可以设置,但是最终它的级别生效是在表级别) 1、MylSAM存储引擎索引实现 MylSAM索引文件和数据文件是分离的(非聚集&#xf…

大数据开发中级练习题目(python超详细)

给定长度为m的非重复数组p&#xff0c;以及从其中取n&#xff08;n<m&#xff09;个数字组成新的子数组q。现要对p进行排序&#xff0c;要求&#xff1a;q在数组的最前方&#xff0c;其余数字按从小到大的顺序依次排在后面 输入样例&#xff1a; q [3, 5, 4] p [5, 4, 3…

37. 解数独

37. 解数独 编写一个程序&#xff0c;通过填充空格来解决数独问题。 数独的解法需 遵循如下规则&#xff1a; 数字 1-9 在每一行只能出现一次。 数字 1-9 在每一列只能出现一次。 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。&#xff08;请参考示例图&#xff…

SAP 财务年结操作宝典

目录 一 、后台操作篇 1.1 维护会计凭证编号范围 2.2 维护CO版本 1.3 维护利润中心版本 1.4 维护物料分类账文档的编号范围 (如 1.5 复制合并凭证编号范围(如果公司没有这个业务的) 1.6 维护发票凭证的编号范围间隔 (如果不针对年度则不用维护) 1.7 维护发票凭证的编号范…

MCU-51:单片机串口详解

目录一、计算机通信简介二、串口通信简介2.1 同步通信2.2 异步通信三、串行通信的传输方式四、串口通信硬件电路五、常见接口介绍六、串口相关寄存器详解6.1 特殊功能寄存器SCON6.2 PCON寄存器6.3 TMOD寄存器七、代码演示-单片机和电脑通信7.1 串口向电脑发送数据7.2 电脑通过串…

YOLO-V5 算法和代码解析系列(二)—— 【train.py】核心内容

文章目录调试设置整体结构代码解析ModelTrainloader分布式训练FreezeOptimizerSchedulerEMA调试设置 调试平台&#xff1a;Ubuntu&#xff0c;VSCode 调试设置&#xff0c;打开【/home/slam/kxh-1/2DDection/yolov5/.vscode/launch.json】&#xff0c;操作如下图所示&#xff…

GNN基础知识

1. 泰勒公式 背景background 有一个很复杂的方程&#xff0c;我们直接计算方程本身的值可能非常麻烦。 所以我们希望能够找到一个近似的方法来获得一个足够近似的值 本质&#xff1a; 近似&#xff0c;求一个函数的近似值 one point is 近似的方法another point is 近似的…

【Java 数据结构】-优先级队列以及Java对象的比较

作者&#xff1a;学Java的冬瓜 博客主页&#xff1a;☀冬瓜的主页&#x1f319; 专栏&#xff1a;【Java 数据结构】 分享&#xff1a;美妙人生的关键在于你能迷上什么东西。——《球状闪电》 主要内容&#xff1a;优先级队列底层的堆&#xff0c;大堆的创建&#xff0c;插入&a…

Openssl 生成自签名证书

最近在调试Ingress需要使用多份证书&#xff0c;对证书的生成和使用做了简单的整理。 不用翻垃圾桶一条过 #!/bin/sh output_dir"/opt/suops/k8s/ingress-files/certs/fanht-create-ssl/" read -p "Enter your domain [www.example.com]: " DOMAIN echo…

C++11特性-线程

并发 一个程序执行多个独立任务&#xff0c;提高性能 单核cpu是通过(任务切换)&#xff0c;即上下文切换&#xff0c;有时间开销 多核cpu(当核数>任务数)&#xff0c;硬件并发 进程 运行起来的一个可执行程序&#xff08;一段程序的运行过程&#xff09; 资源分配的最小单…

百数应用中心上新了——餐饮门店管理系统

随着智能化时代的来临&#xff0c;传统的餐饮门店管理方式逐渐暴露出缺陷。不少餐饮业的掌门人都纷纷对管理方式进行了转型&#xff0c;由传统模式转变为数字化系统的管理。然而数字化管理方式也没那么容易进行&#xff0c;想要百分百满足需求的系统耗时耗力耗钱&#xff0c;成…

不懂PO 设计模式?这篇实战文带你搞定 PO

1080442 73.1 KB 为UI页面写测试用例时&#xff08;比如web页面&#xff0c;移动端页面&#xff09;&#xff0c;测试用例会存在大量元素和操作细节。当UI变化时&#xff0c;测试用例也要跟着变化&#xff0c; PageObject 很好的解决了这个问题&#xff01; 使用UI自动化测试工…

钉钉 ANR 治理最佳实践 | 定位 ANR 不再雾里看花

作者&#xff1a;姜凡(步定) 本文为《钉钉 ANR 治理最佳实践》系列文章首篇《定位 ANR 不再雾里看花》&#xff0c;主要介绍了钉钉自研的 ANRCanary 通过监控主线程的执行情况&#xff0c;为定位 ANR 问题提供更加丰富的信息。 后续将在第二篇文章中讲述钉钉基于分析算法得出 …