Linux内核6.6 内存管理 (16)early_init_dt_scan_memory详解

https://elixir.bootlin.com/linux/v6.11/source/drivers/of/fdt.c#L964

1.扫描内存/非内存节点

详解这一段函数的内容,这一段代码主要分为两部分

首先是打印了设备树所有的顶层子节点,但是只会停留在memory,这里打印了memory节点的值。

问题1:为什么160字节?

这里表示用2个cell表示地址,2个cell表示大小,每个cell固定是4个字节。4*4=16

下面又打印了memory range, 一共10个,16*10就是160个字节

bootloader中填充了这些内存范围的内容

2.内存分块

内存分块后解析出了具体的内容

地址不连续(剩余部分可能为硬件准备)/内存大小具有差异

可以使用 cat /proc/iomem 看到硬件的地址空间布局

举个小例子:

[ 0.000000] OF: fdt: LX Parsed memory range: Base=0x80000000, Size=0x5f20000 (99745792 bytes)

80000000-805fffff : reserved

80600000-85cfffff : System RAM

85d00000-85efffff : reserved

85f40000-85f7ffff : reserved

这里又把内存块区分成了,reserved和system ram, 加粗部分为拆开分布。最后一小部分是下块内存的预留部分

3.CPU是如何访问外设的

核心逻辑:硬件设计师把 UFS 控制器的寄存器分配到物理地址 01d84000-01d86fff(MMIO 物理地址)→ 内核用 ioremap 把这个物理地址映射成内核虚拟地址(比如 0xffff000012340000)→ 内核用 ioread/iowrite 读写这个虚拟地址→ MMU 把虚拟地址翻译成物理地址→ 地址译码器把物理地址转发到 UFS 控制器→ 控制器执行对应的硬件动作(返回状态 / 传输数据)

1) 将物理地址映射为虚拟地址

https://elixir.bootlin.com/linux/v6.11/source/drivers/ufs/host/ufshcd-pltfrm.c#L471

访问寄存器:映射完成后,就可以通过指针base来访问外设的寄存器。比如要读取某个寄存器的值,可以使用readl(base + offset)函数(假设是 32 位读取,offset是寄存器相对于基地址base的偏移量);要写入值,可以使用writel(value, base + offset)。 以是否调用函数ufs_qcom_device_reset为例

liuxin@liuxin-gv:/local/mnt/workspace/92_talos_upstream/linux-sheepdog/linux-next$ git diff drivers/ufs/host/ufs-qcom.c
diff --git a/drivers/ufs/host/ufs-qcom.c b/drivers/ufs/host/ufs-qcom.c
index a5a0646bb80a..4705ffc72e17 100644
--- a/drivers/ufs/host/ufs-qcom.c
+++ b/drivers/ufs/host/ufs-qcom.c
@@ -1549,12 +1549,27 @@ static int ufs_qcom_device_reset(struct ufs_hba *hba)
         * The UFS device shall detect reset pulses of 1us, sleep for 10us to
         * be on the safe side.
         */
+       dev_info(hba->dev, "ufs_qcom_device_reset entry\n");//确定函数被调用
+       int reg_val;
+       void __iomem *vir_addr;
+
+       vir_addr = ioremap(0x359F004,4); //这时候通过ioremap转换实际需要的物理地址已经需要获取的大小,转换成内核可以访问的地址
+
+       reg_val = readl(vir_addr);
+       dev_info(hba->dev, "0x%x\n", reg_val);
+
        ufs_qcom_device_reset_ctrl(hba, true);
        usleep_range(10, 15);

+       reg_val = readl(vir_addr);
+       dev_info(hba->dev, "0x%x\n", reg_val);
+
        ufs_qcom_device_reset_ctrl(hba, false);
        usleep_range(10, 15);

+       reg_val = readl(vir_addr);
+       dev_info(hba->dev, "0x%x\n", reg_val);
+
        return 0;
 }

最后输出:验证成功
[    5.121632] ufshcd-qcom 1d84000.ufshc: 0x0
[    5.144866] ufshcd-qcom 1d84000.ufshc: 0x0
[    5.163031] ufshcd-qcom 1d84000.ufshc: 0x1

要理解「内存映射(Memory-Mapped I/O,简称 MMIO)」,核心是先打破一个误区:CPU 不能直接访问外设硬件,只能访问 “地址”——MMIO 的本质就是把「外设的寄存器 / 缓冲区」“伪装” 成一段普通的物理内存地址,让 CPU 像读写内存一样,完成对外设的控制和数据交互。

2) 问题1

CPU为什么不能直接访问外设?

CPU核心功能就是执行命令和读写地址,只有一套内存地址总线。CPU只能通过这套流程,无法直接识别 “这是内存” 还是 “这是 UFS 控制器”

而外设(如 UFS 控制器)的硬件逻辑是由「寄存器」控制的(比如 “数据寄存器” 存要传输的数据,“状态寄存器” 表示设备是否就绪)。这些寄存器本质是 “硬件电路的开关”,需要 CPU 来 “拨动”—— 但 CPU 只能通过地址访问,所以硬件设计师想出了一个办法:把外设的寄存器,分配一段专属的「物理地址空间」(比如 01d84000-01d86fff),这段地址不对应实际的内存芯片,而是对应外设的寄存器电路。当 CPU 读写这段地址时,硬件会自动把 “地址信号” 转发到外设,而不是内存 —— 这就是 MMIO 的核心思路。

3)问题2

MMIO 的硬件原理:地址怎么 “转发” 到外设?

MMIO 能工作,靠的是硬件层面的「地址译码器」和「总线仲裁」,这是理解 MMIO 的关键:

  1. 地址分配:硬件设计时,会给每个外设分配一段不重叠的「物理地址范围」(比如 UFS 控制器 01d84000-01d86fff,串口 00880000-00883fff),这些地址范围会避开普通内存的地址(比如内存从 80000000 开始),避免冲突。
  2. 地址译码:CPU 发送一个物理地址(比如 01d84004)时,地址总线会把这个地址传给「地址译码器」。译码器会判断:如果地址属于「内存地址范围」(如 80000000 以上)→ 通知内存芯片,CPU 要读写内存;如果地址属于「MMIO 地址范围」(如 01d84004)→ 通知对应的外设(UFS 控制器),CPU 要读写它的寄存器。
  3. 数据交互:当外设收到 “地址命中” 的信号后,会根据 CPU 的 “读写信号”(读:CPU 要获取数据;写:CPU 要发送指令),操作自己的寄存器:读操作:外设把寄存器里的数据(比如 UFS 设备的状态)放到数据总线,CPU 从总线读取;写操作:CPU 把数据(比如 “开始传输” 的指令)放到数据总线,外设从总线读取并写入寄存器,触发硬件动作。

举个具体例子:CPU 要读 UFS 控制器的 “状态寄存器”(假设偏移 0x04,物理地址 01d84004):

  1. CPU 执行指令:read 0x01d84004
  2. 地址总线发送 01d84004 到译码器;
  3. 译码器识别到这是 UFS 控制器的地址,通知 UFS 控制器 “准备数据”;
  4. UFS 控制器把 “状态寄存器” 的值(比如 0x01 表示就绪)放到数据总线;
  5. CPU 从数据总线读取 0x01,知道 UFS 设备已就绪。

4)问题3

内核中为什么要 “二次映射”?(物理地址 → 虚拟地址)

MMIO 的物理地址(如 01d84000)属于「未被 MMU 管理的地址」,内核要访问它,必须先通过 ioremap 函数做两件事:

1.告诉 MMU:“把物理地址 01d84000-01d86fff 映射到内核的虚拟地址空间(比如 0xffff000012340000)”;

2.禁用该虚拟地址的 “缓存”:因为外设寄存器的值是实时变化的(比如状态寄存器会随硬件状态更新),如果开启缓存,CPU 可能读的是缓存里的旧值,而不是实际硬件的状态 ——ioremap 会自动标记这段虚拟地址为 “非缓存”。

ioremap 是内核通过配置 MMU 实现 “物理地址→内核虚拟地址” 映射的关键函数

#通信硬件人笔面经互助##嵌入式##嵌入式软开##嵌入式笔面经分享#
全部评论

相关推荐

头像
昨天 17:44
中南大学 Java
刚才跟表哥打了一个多小时的电话,他是13年的中2本科毕业,学的是电气工程,毕业进了当地一家大型的国企,虽然薪资尚可,但是最近两年都在降薪,最大的好处就是稳定性还可以。他跟我说了这样一些话,总结分享一下:1. 他的个人看法:考公>longduan国央企>银行>大厂,原因是现在找一份稳定的工作太难了。现在也越来越卷了,他所在的那个企业在他那个时候本科211都有的看不上,但是现在招应届的话,基本要硕9起了,好一点的哈工大,上交,甚至清华的也来卷了。2. 现在不要想着去做小生意,创业啥的,基本都是亏,要去做的话也建议去一些偏远的地区去做。3. 本科双非的话,基本上省会城市的国家电网,烟草局这种就不用去浪费时间了,除非你运气真的爆棚或者有关系。4. 银行其实也不大建议去,除非是政策行+农发,储蓄这些,之前还有房产行业贷款盖房,普通人贷款买房啥的,现在这行业快坍塌了,普通人甚至都没啥钱存在银行了。所以里面的待遇自然相比原来会有降低啥的。5. 最重要看自己想要什么,要成长就去私企锻炼,但是要接受不稳定的事实,要稳定就去考公,但是没背景和关系的话要接受被分到一个差的地方或者碰到不好的领导。6. 你看到的透明可能都只是表象,你以为官网挂招聘了通知了,其实招聘的人就已经内定好了,甚至还有一些不对外招聘,当然校招可能这种情况稍微好一点。有很多事情都没你想的那么公平,这本身也不是一个公平的社会。7. 有的时候你没过,真不是你的问题,千万不要怀疑自己。有的时候真就是运气的问题,找工作本身也有很大运气成分在的,所以千万不要内耗。上面就是他跟我说的一些点,当然也只是代表他个人的看法,如果大家觉得有什么不对的欢迎评论指出
拥抱太阳的哈士奇很勇...:国央企透明招聘这个词本身就不透明(手动狗头
大厂VS公务员你怎么选
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务