ARM64 Linux 6.10内核启动流程7-ioremap和readl writel

§1.1 ioremap:寄存器为什么能 readl / writel

平台:QEMU virt,UART 物理基址常见 0x09000000,RAM 从 0x40000000 起。


0. 这条要明白什么

CPU 取指、load/store 用的是 虚拟地址 。设备树 reg 给的是 物理地址。两者不是一回事。

所以驱动不能写 *(u32 *)0x09000000。必须先在内核页表里开一扇窗:虚拟地址 → 这块 MMIO,属性是 Device / 非缓存。readl/writel 打的是这扇窗,MMU 再翻译到物理寄存器。

复制代码
DT / 手册:UART @ 0x09000000          物理地址
ioremap                                在页表里开窗
返回 void __iomem *                    内核虚拟地址
readl/writel(base + offset)            访存指令打虚拟地址
MMU → 0x09000000                       硬件寄存器

没有这扇窗,readl 没有合法的内核地址可用。这就是清单那句「寄存器为什么能 readl/writel」。


1. 两份 ioremap.c 各干什么

ARM64 选了 GENERIC_IOREMAP。驱动看到的 ioremap() 是头文件里的 inline。

文件 函数 角色
include/asm-generic/io.h ioremap() 皮:补上默认 prot = _PAGE_IOREMAP
arch/arm64/mm/ioremap.c ioremap_prot() 架构门禁(地址宽度、禁止映射 RAM)
mm/ioremap.c generic_ioremap_prot() 占虚拟窗口 + 填页表
mm/vmalloc.c __get_vm_area_caller / ioremap_page_range 被通用层调用;看到职责即可,不跟 PGD 循环

arch/arm64/include/asm/io.h_PAGE_IOREMAP = PROT_DEVICE_nGnRE(设备内存、非缓存、写可 posted)。变体:ioremap_wcPROT_NORMAL_NCioremap_npPROT_DEVICE_nGnRnE

正规驱动优先 devm_ioremap_resource() / devm_platform_ioremap_resource(),最后仍进 ioremap_prot。virt 上 PL011:amba-pl011.cdevm_ioremap_resource(dev, mmiobase)


2. 调用链(probe 之后)

复制代码
probe
  devm_ioremap_resource() / ioremap(phys, size)
    include/asm-generic/io.h
      ioremap_prot(phys, size, _PAGE_IOREMAP)
        arch/arm64/mm/ioremap.c          ★ 门禁
          generic_ioremap_prot()         ★ mm/ioremap.c
            1. __get_vm_area_caller()    占 VA(页表还空)
            2. ioremap_page_range()      填 phys → vaddr
            返回 (vaddr + 页内 offset)
  readl(base + 0x18)                     下一条 io.h 才展开

ioremap() 本体:

c 复制代码
/* include/asm-generic/io.h,CONFIG_GENERIC_IOREMAP */
static inline void __iomem *ioremap(phys_addr_t addr, size_t size)
{
	return ioremap_prot(addr, size, _PAGE_IOREMAP);
}

3. 架构门禁:ioremap_prot

arch/arm64/mm/ioremap.c 自己不建页表,只拦非法请求。

  1. last_addr & ~PHYS_MASK

    超出芯片物理地址宽度(PHYS_MASK,跟 CONFIG_ARM64_PA_BITS)。无法映射,返回 NULL

  2. pfn_is_map_memory()

    这段已经是内核线性映射里的 RAM。禁止再 ioremap 成 Device 属性(别名、缓存错乱)。会 WARN_ON

    virt:RAM @0x40000000,UART @0x09000000,后者不是 memory,能过。

通过后:generic_ioremap_prot(phys, size, __pgprot(prot))

同文件 early_ioremap_init()另一条路setup_archearly_fixmap_init 之后调用,给启动早期用。DTB 的 fixmap_remap_fdt 走 fixmap,不是本函数。


4. 真正开窗:generic_ioremap_prot

mm/ioremap.c。依赖 slab 已可用vmalloc 要分配 vm_struct)。!slab_is_available()WARN 并返回 NULL------早期平台代码误闯到这里会失败,应走 early_ioremap

4.1 页对齐

页表只能按页映射。若物理地址不是页头:

复制代码
offset    = phys_addr & ~PAGE_MASK     页内偏移
phys_addr -= offset                    从页头开始映射
size      = PAGE_ALIGN(size + offset)  覆盖到请求末尾

返回时加回 offset,驱动拿到的仍是「寄存器那个字节」,不是页头。virt 上 0x09000000 通常已页对齐,offset 为 0。

长度为 0,或 phys + size 溢出绕回,直接 NULL

4.2 两步

第一步:占虚拟地址(还不译地址)

复制代码
__get_vm_area_caller(size, VM_IOREMAP, IOREMAP_START, IOREMAP_END, caller)
  mm/vmalloc.c:3139
    → __get_vm_area_node()           :3090
         kzalloc(struct vm_struct)
         alloc_vmap_area()           在 [start, end) 抠一段 VA

ARM64 未另定义 IOREMAP_START,就是 vmalloc 区(VMALLOC_START ... VMALLOC_END)。VM_IOREMAP 只是标记这块给 ioremap 用。此时 load 这个 vaddr 仍会出错。

alloc_vmap_area 内部红黑树不必跟。

第二步:填页表

复制代码
ioremap_page_range(vaddr, vaddr+size, phys_addr, prot)
  mm/vmalloc.c:329
    find_vm_area()                   必须是刚占的那块 VM_IOREMAP
    → vmap_page_range()              :315
        → vmap_range_noflush()       :286  ★ 写 PGD...PTE
        flush_cache_vmap()

vmap_range_noflush()phys_addrprot 写进内核页表。再往下 vmap_p4d_range / set_pte 是同一件事的分层,看到 286 行停

失败则 free_vm_area,返回 NULL

成功:(void __iomem *)(vaddr + offset)。之后 readl 用的就是这个指针。


5. 用 virt 的 UART 对号

复制代码
物理 0x09000000(DT uart0.reg)
  → ioremap_prot 门禁通过(不是 RAM)
  → vmalloc 区某个 vaddr(例如 ffff8000xxxxxxxx,具体随运行变化)
  → PTE:vaddr → 0x09000000,PROT_DEVICE_nGnRE
  → 驱动 pl011->base = vaddr
  → readl(base + UARTDR)  访问虚拟地址,MMU 打到 UART 数据寄存器

gdb:在 PL011 probedevm_ioremap_resource 返回后看指针;再断 generic_ioremap_protphys_addr == 0x09000000-s -S + nokaslr 方便对地址。


6. 生命周期、失败、和早期映射

正规 ioremap 早期 early_ioremap / fixmap
何时 slab / vmalloc 起来之后(驱动 probe start_kernel 很早(扫 DTB、earlycon)
窗口 vmalloc 区,按需分配 固定的 fixmap 槽,数量有限
释放 iounmap / devm_* 设备卸载时自动 用完要 early_iounmap,槽要让出来
本条 精读这条 已在 setup_arch DTB 笔记里见过

失败都是返回 NULL(或 devm_ioremap_resource 返回 ERR_PTR)。驱动必须查返回值,不能对空指针 readl

generic_iounmap():把地址按页对齐,确认落在 ioremap 区后 vunmap,拆页表、还虚拟窗口。新驱动用 devm_*,一般不手写 iounmap


相关推荐
小羊没烦恼!1 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
bwz999@88.com2 小时前
Ubuntu Server 24.04 设置中文
linux·运维·ubuntu
fly-972 小时前
Ubuntu FTP/SFTP 报错:找不到匹配的 hostkey 算法 完整解决
linux·ubuntu
Cx330❀2 小时前
【Linux网络】网络层协议 IP :从网络层原理到 Linux 内核源码
linux·运维·服务器·网络·tcp/ip·ai·ai编程
Gl�ria3 小时前
Linux 清理日志常用命令
linux·运维·服务器
天道jimmy4 小时前
VulnHub 系列:HA, Wordy
linux·服务器·web安全
君生我老4 小时前
Linux中调式器gbd/cgdb
linux
两斤牛肉一壶酒5 小时前
opencode 技能加载全挂背后,竟是不用系统的ripgrep
linux