设备树中reg属性和地址映射怎么理解

浏览量:1
时间: 2026-08-11 17:48:34
分类:嵌入式Linux经验级别:1-3年难度:中级题型:原理+实践

嵌入式Linux工程师查看设备树dts源代码
嵌入式Linux工程师在调试设备树dts源码文件和驱动代码

本文概述

设备树中的reg属性是描述硬件地址资源的核心机制,它的格式由父节点的#address-cells和#size-cells两个属性共同决定。#address-cells表示地址用几个32位单元,#size-cells表示长度用几个32位单元。32位系统通常是1个地址单元加1个大小单元,64位系统则需要2个地址单元。地址映射是从子节点到根节点逐层翻译的过程,通过每层的ranges属性将本地总线地址翻译为父总线地址,最终得到CPU物理地址。驱动中通过of_address_to_resource或ioremap来获取和映射这些地址。

核心要点速览
  • reg属性描述设备的地址资源,格式是"地址 + 长度"的多组数据

  • #address-cells和#size-cells是父节点的属性,决定子节点reg的格式

  • 64位地址用2个cells表示高32位和低32位,高32位在前

  • 地址翻译:子节点地址 → 经父节点ranges翻译 → 逐层向上直到根节点

  • 驱动中用of_address_to_resource获取物理地址,ioremap映射到虚拟地址

一、面试题

面试官问:"设备树中的reg属性你理解吗?#address-cells和#size-cells是什么意思?地址映射是怎么工作的?"

二、面试官考察点

考察方向

第一,是否真正理解设备树的地址表示机制,还是只会复制粘贴别人的dts;第二,是否搞清楚了#address-cells和#size-cells属于哪个节点,这是一个经典的易错点;第三,是否理解地址翻译的层级关系和ranges属性的作用;第四,是否有实际的驱动开发经验,知道驱动代码中怎么获取和使用这些地址。

三、回答思路(对话式展开)

面试官

先说说reg属性是干什么的吧,格式是什么样的?

候选人

reg属性是用来描述一个设备的地址资源的,也就是这个设备在它所在的总线地址空间里占据哪一段地址范围。格式是一个由多组"地址 + 长度"组成的数组,用尖括号括起来。比如常见的32位处理器平台,一个设备的寄存器基地址是0x12340000,大小是4KB,那reg就写成<0x12340000 0x1000>。

面试官

那#address-cells和#size-cells呢?它们和reg是什么关系?

候选人

这两个属性是父节点的属性,用来规定子节点的reg怎么解析。这个点挺容易搞混的,我刚开始学时也经常搞错。#address-cells表示子节点reg中的"地址"部分用几个32位单元来表示,#size-cells表示"长度"部分用几个32位单元。

举个例子,父节点如果设了#address-cells = <1>,#size-cells = <1>,那每个子节点的reg就是"一个地址加一个长度"的格式。如果是64位地址系统,#address-cells就等于2,高32位在前,低32位在后。

1. 具体例子说明

举一个典型的32位ARM平台的设备树片段:

/* 根节点 */ / { #address-cells = <1>; // 地址用1个cell(32位) #size-cells = <1>; // 大小用1个cell(32位) soc { #address-cells = <1>; #size-cells = <1>; ranges; // 1:1映射,地址和父节点一样 uart@12340000 { compatible = "fsl,imx6q-uart"; reg = <0x12340000 0x4000>; // 地址0x12340000,大小16KB interrupts = <0 26 4>; }; }; };

在这个例子里,uart节点的reg是<0x12340000 0x4000>,意思是它在soc总线上的地址是0x12340000,大小是0x4000(16KB)。因为soc节点的#address-cells是1,#size-cells是1,所以每组就是一个地址加一个长度。

2. 64位地址的情况

如果是ARM64平台,地址是64位的,就要用两个cells来表示地址:

/* ARM64 根节点 */ / { #address-cells = <2>; // 地址用2个cell(64位) #size-cells = <2>; // 大小用2个cell(64位) memory@80000000 { device_type = "memory"; reg = <0x00000000 0x80000000 0x00000000 0x40000000>; // 地址: 0x00000000_80000000 (高32位在前) // 大小: 0x00000000_40000000 (1GB) }; };
补充说明

这里要注意地址的字节序。设备树中都是大端格式,高32位在前,低32位在后。所以<0x0 0x80000000>表示的地址就是0x0000000080000000,也就是2GB的位置。如果地址超过4GB,比如0x1_00000000,就写成<0x1 0x00000000>。

3. 地址映射的过程

地址映射就是把设备节点中的本地地址,通过各级总线的ranges属性,一层层翻译到CPU能直接访问的物理地址。这个过程是从子节点向根节点逐步推进的:

设备本地地址子节点reg中的值
父总线地址通过父节点ranges翻译
根节点地址逐层翻译到根
CPU物理地址驱动ioremap使用

ranges属性是地址翻译的关键。它的格式是:子地址 + 父地址 + 长度。每一项描述一段地址空间的映射关系。如果ranges是空的(只有关键字没有值),表示子地址空间和父地址空间是完全一样的,1比1映射,不需要翻译。

举个有地址翻译的例子,比如PCIe总线:

pcie@10000000 { #address-cells = <3>; // PCIe地址用3个cell #size-cells = <2>; // PCIe大小用2个cell ranges = <0x82000000 0 0x40000000 0x40000000 0 0x20000000>; // 含义:PCIe地址0x40000000映射到CPU物理地址0x40000000,大小512MB // 前3个是子地址(PCIe),中间2个是父地址(CPU),最后2个是大小 };

4. 驱动中怎么使用reg地址

驱动代码中,通常用下面的步骤获取和使用设备树中的地址:

/* 方法一:用 of_address_to_resource */ struct resource res; if (of_address_to_resource(np, 0, &res)) { // 获取第0个reg段失败 return -EINVAL; } // res.start 是物理起始地址 // res.end 是物理结束地址 base = ioremap(res.start, resource_size(&res)); /* 方法二:用 of_iomap 一步到位 */ base = of_iomap(np, 0); // 直接返回映射好的虚拟地址 /* 方法三:用 platform_get_resource */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); base = devm_ioremap_resource(&pdev->dev, res);

5. 几个容易出错的点

面试官

那你写设备树的时候,在reg相关的地方踩过什么坑吗?

候选人

有几个印象比较深的坑。第一个是#address-cells和#size-cells搞反了归属,刚开始总以为是当前节点的,其实是父节点的,决定子节点的reg格式。第二个是64位地址的高低位顺序,一开始把低32位写前面了,结果地址完全不对。第三个是ranges属性写了但格式不对,子地址单元数和父地址单元数搞混,翻译出来的地址就错了。

还有一个是reg的索引顺序,如果一个设备有好几段地址空间,reg里写的顺序就是驱动里用索引0、1、2获取的顺序,不能搞反,不然ioremap出来的地址对应的寄存器段不对。

四、面试官追问(5个)

Q1
追问1:reg可以描述多段地址吗?怎么表示?

可以。reg可以包含多组地址和长度,组和组之间是连续排列的。比如有两段地址空间就写reg = 。驱动中用索引0、1分别获取。设备的名字里如果有@,@后面跟的是第一段地址的起始值。

Q2
追问2:#size-cells可以是0吗?

可以。如果子节点描述的是一个单独的地址点而不是一段范围,#size-cells就设为0,这时候reg只有地址部分没有长度部分。比如中断控制器的CPU接口,只有一个地址没有长度概念,或者某些配置寄存器的单地址描述。

Q3
追问3:什么情况下需要ranges属性?什么情况下不需要?

当子总线的地址空间和父总线不一样的时候需要ranges来做翻译。比如PCIe、USB等外部总线,总线内部的地址和CPU物理地址不是一个空间。如果子总线的地址空间和父总线完全一样(1比1映射),可以写一个空的ranges属性。如果节点不是总线节点,只是个普通设备,就不需要ranges。

Q4
追问4:reg和interrupts的解析方式有什么类似?

思路很像。interrupts的格式也是由父中断控制器的#interrupt-cells决定的,也是父节点决定子节点的属性格式。这是设备树的一个设计思路:子节点描述自己的资源,资源的格式由父总线(或父控制器)来规定。

Q5
追问5:设备树编译后地址信息存在哪里?内核怎么解析?

设备树编译成dtb后,节点和属性以扁平结构存储在内核指定的内存区域。内核启动时解析dtb,建立设备树节点树结构。驱动匹配设备后,通过of_系列函数读取属性值,按父节点的cells数来解析成地址和长度。of_address_to_resource函数内部会完成逐级地址翻译的工作。

五、失分表达

  • "#address-cells是当前节点的属性" —— 经典错误,这是父节点的属性,用来描述子节点reg的格式

  • "reg就是一个地址加一个长度" —— 太绝对,要看父节点的cells设置,可以是多个cells,也可以有多组

  • "设备树中的地址就是物理地址" —— 不一定,子节点的地址是相对于父总线的,需要经过翻译才是物理地址

  • "64位地址低32位在前" —— 设备树用大端格式,高32位在前,低32位在后

  • "ranges没用,有reg就行" —— 不理解地址翻译的概念,ranges是总线地址映射的关键

六、准备动作

  1. 亲手写几个设备树节点,练习reg的不同格式(32位、64位、多段地址)

  2. 理解设备树地址翻译的原理,画一个地址翻译的层级图帮助记忆

  3. 熟悉驱动中获取设备树地址的几种常用函数和区别

  4. 回顾自己项目中设备树相关的调试经历,总结几个踩过的坑

  5. 了解ePAPR规范中关于设备树寻址的基本约定

七、核心关键词

设备树DTS、reg属性、#address-cells、#size-cells、地址映射、ranges属性、地址翻译、物理地址、of_address_to_resource、of_iomap、ioremap、64位地址、总线地址空间


声明:本网站所收集的部分公开资料来源于互联网,转载的目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。仅供学习交流使用,不构成商业目的。版权归原作者所有,如果您发现网站上有侵犯您的知识产权的作品,请与我们取得联系,我们会及时删除。侵权投诉
相关推荐HOT
Linux指的是什么,有什么区别?

Linux是指由Linux本人维护并不断更新的内核 。一个嵌入式Linux系统指的是一个基于Linux内核的,但不包含有关这个内核的任何专业的库或是用户工具的嵌... 详情>>

2024-03-27 11:27:09
Boost电源输出二极管为什么有反向恢复尖峰?电源工程师面试题

📂 分类:电源设计📊 经验级别:中级⭐ 难度:★★★☆☆📝 题型:电源拓扑题本文概述Boost电路中输出二极管的反向恢复尖峰是开关电源里的经典问题,根本原... 详情>>

2026-08-10 11:36:48
ADC采样值一直跳,硬件工程师面试应该怎样排查?

题库分类:硬件设计 适合经验:0—5年 难度:中高级 题型:故障排查题 + 混合信号题 面试题板子上... 详情>>

2026-07-29 11:11:38
高速过孔残桩什么时候需要背钻?PCB面试别直接回答“速率高就背钻”

题库分类:PCB设计适合经验:1—5年难度:中高级题型:高速PCB场景题 + 工艺决策题面试题高速串行通道中,什么情况下需要对过孔做背钻?如果项目不做背钻,你会... 详情>>

2026-08-01 17:36:34
面试结束后怎样跟进能提高录用概率?工程师求职跟进指南

分类:面试技巧经验要求:通用难度:基础题型:求职方法📖 本文概述面试结束不是求职的终点。恰当的跟进方式可以让面试官对你印象更深,甚至扭转面试中的小失误。本文按... 详情>>

2026-08-04 16:03:44
开班信息