Linux 内核在 ZTE zx297520v3 SoC 上的引导实践:硬件剖析、U-Boot 构建与 CPU/GIC 初始化
【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux
本篇技术指南以 Linux 内核源码树中的 Documentation/arch/arm/zte/zx297520v3.rst 为骨架,系统讲解如何在 ZTE zx297520v3 SoC 上引导 Linux:从该 SoC 的硬件组成(Cortex-A53 32 位模式、GICv3、ZSP880 基带 DSP、Cortex-M0 协处理器)出发,依次覆盖 USB Boot ROM 下载引导、基于设备自带 U-Boot 的 NAND 镜像构建九步流程,以及满足 Linux 引导要求的 CPU/GICv3 汇编初始化代码。读完本文,你将掌握为这类廉价 LTE 路由器 SoC 制作可引导内核镜像的完整方法,并能理解为何需要手工完成 GIC 安全配置与 PPI 极性修正。
1. 硬件背景:一台“32 位内核、64 位硬件”的廉价路由器 SoC
zx297520v3 是一颗使用64 位能力 Cortex-A53 CPU 与 GICv3的 SoC,但实际仅以arm32 模式运行(对应内核配置ARCH_MULTI_V7,见 arch/arm/mach-zte/Kconfig)。CPU 支持 EL3,但没有 EL2 虚拟化层(hypervisor),且看起来缺少 VFP 与 NEON 浮点单元——这意味着内核构建时需避免依赖这些特性。
该 SoC 被大量用于廉价 LTE 转 WiFi 路由器,包括电池供电的 MiFi 与固定式 CPE(Customer Premises Equipment,客户驻地设备)。典型硬件配置为:
- 64 MB RAM(其中一部分与 LTE 芯片共享;也存在 32 MB 与 128 MB 的变体);
- 128 MB NAND flash(也存在 8 MB / 16 MB NOR flash 的变体);
- SDIO 连接的RTL8192 类 WiFi 芯片,仅支持 2.4 GHz;
- USB 2.0 与按键;
- 部分固定式设备带 100 Mbit 以太网及以太网交换机;
- 多数设备有状态 LED,个别使用 SPI 或 I2C 连接的显示屏;
- 部分设备带SD 卡槽——若存在,建议将根文件系统放在 SD 卡上,因其性能明显优于板载 NAND。
1.1 LTE 基带 DSP:ZSP880
LTE 接口运行在独立 DSPZSP880上,其指令集未公开(推测源自 LSI ZSP 系列)。ZSP 与主 CPU 通过SRAM、DRAM 及一个可在两端产生 IRQ 的 mailbox 硬件通信。这意味着内核侧对 LTE 的控制需要遵循该 mailbox 协议。
1.2 Cortex-M0 协处理器
SoC 内还有一个Cortex M0 CPU,负责早期硬件初始化并启动 Cortex-A53。一旦 U-Boot 启动,它对系统运行已无必要用途;不过存在基于 SRAM 的交接协议,可以在其上运行自定义代码。
1.3 仓库中的对应支持代码
主线上游对该 SoC 的支持已落地,可从源码结构中印证:
- 设备树公共定义 arch/arm/boot/dts/zte/zx297520v3.dtsi:声明单核
arm,cortex-a53、26 MHz 固定时钟的 UART、arm,armv7-timer(PPI 13/14/11/10,IRQ_TYPE_LEVEL_LOW)以及 GICv3 中断控制器(reg = <0xf2000000 0x10000>, <0xf2040000 0x20000>); - 具体设备 arch/arm/boot/dts/zte/zx297520v3-dlink-dwr932m.dts:D-Link DWR-932M,
compatible = "dlink,dwr932m", "zte,zx297520v3",内存 0x20000000 起 64 MB,并使能uart1; - 板级机器描述 arch/arm/mach-zte/zx297520v3.c:
DT_MACHINE_START匹配zte,zx297520v3; - Kconfig 开关
SOC_ZX297520V3(arch/arm/mach-zte/Kconfig)默认启用,并select ARM_GIC_V3 / ARM_PSCI_FW / ARM_AMBA / HAVE_ARM_ARCH_TIMER; - 构建规则 arch/arm/boot/dts/zte/Makefile:
CONFIG_SOC_ZX297520V3使能zx297520v3-dlink-dwr932m.dtb的生成。
2. 通过 USB 引导:Boot ROM 下载模式
Boot ROM 原生支持通过USB 下载自定义代码。进入该模式有两种方式:
- 将Boot PIN 接地(GND);
- 修改 NAND 上第三个字节(将其设为除
0x5A(即字符'Z')以外的任何值)。
配套的自由软件工具链有两个(均为社区维护的外部项目,仓库中未包含,可按名称检索):
- zx297520v3-loader:用于启动自定义 U-Boot 与内核的 USB 加载工具;
- u-boot-mainline:可与 USB loader 配合使用的 U-Boot 版本,负责按 Linux 的引导要求配置 CPU 与中断控制器。
一个实用技巧:若进入 USB 下载模式但数秒内未通过 USB 收到任何引导命令,设备会自动继续正常引导。因此可以把 USB 引导永久开启,同时保留默认启动文件不动——一旦需要刷机或救砖,插入 USB 即可中断,平时则不影响正常开机。
3. 为内置 U-Boot 构建镜像(NAND 启动)
设备出厂自带古老 U-Boot:它从 NAND 加载 legacy uImage 并直接启动,用户无法中断。镜像存放在 jffs2 分区imagefs(通常为mtd4)中的两个文件:
ap_cpuap.bin:正常启动模式;ap_recovery.bin:恢复模式(开发推荐,见下文);- 文件
fotaflag用于切换这两种模式。
3.1 384 字节签名头
在 uImage 头之外,镜像还带一个384 字节签名头用于部分设备上的镜像认证。大多数设备默认关闭认证,因此只需在 uImage 文件前填充 384 个零字节即可。
3.2 内置 U-Boot 的缺陷与对策
内置 U-Boot 对 CPU 的设置也很糟糕(详见第 4 节),并且不支持加载 DTB,因此内核必须开启CONFIG_ARM_APPEND_DTB(追加式设备树)。
3.3 九步构建流程
按原文档,构建一个可从 NAND 启动的镜像需要以下步骤:
1) 将第 4 节的汇编代码补丁打入 arch/arm/kernel/head.S 2) make zx29_defconfig 3) make [-j x] 4) cat arch/arm/boot/zImage arch/arm/boot/dts/zte/[device].dtb > kernel+dtb 5) mkimage -A arm -O linux -T kernel -C none -a 0x20008000 -d kernel+dtb uimg 6) dd if=/dev/zero bs=1 count=384 of=ap_recovery.bin 7) cat uimg >> ap_recovery.bin 8) 将该文件放到设备上的 imagefs 分区;若空间不足,删除 ap_cpuap.bin 9) 创建 fotaflag 文件:echo -n FOTA-RECOVERY > fotaflag要点说明:
- 第 4 步的
[device]对应设备树文件,例如本仓库已收录的zx297520v3-dlink-dwr932m.dtb(由 arch/arm/boot/dts/zte/Makefile 生成); - 第 5 步
mkimage使用 legacy uImage 格式(-T kernel -C none),加载地址0x20008000与本仓库 DTS 中memory@20000000的起始地址一致(zx297520v3-dlink-dwr932m.dts); - 开发阶段强烈建议引导
ap_recovery.bin,因为正常启动模式会在启动内核前启用看门狗(watchdog),不利于调试。
4. CPU 与 GIC 设置:让中断真正工作起来
zx297520v3 的 CPU 与 GICv3 需要按照 Documentation/arch/arm64/booting.rst 的要求进行初始化。针对本 SoC,具体为:
GICD_CTLR.DS=1,禁用 GIC 安全(security)功能;- 开放对
ICC_SRE的访问; - 禁止将 IRQ 陷入 monitor 模式(trapping IRQs into monitor mode);
- 将EL2 及以下配置为非安全模式(insecure mode);
- 将定时器 PPIs 配置为低电平有效(active-low)。
原文档特别指出:ZTE 官方提供的内核源码本身无法启动(中断完全不工作),且在其他方面也不完整,因此可以推断在官方二进制 blob 中必然存在与本文档描述类似的某种 workaround。
4.1 汇编初始化代码(示例)
以下是实现上述配置的汇编代码(来自原文档,可补丁进arch/arm/kernel/head.S的开头引导阶段):
#include <linux/irqchip/arm-gic-v3.h> #include <asm/assembler.h> #include <asm/cp15.h> @ Detect sane bootloaders and skip the hack ldr r3, =0xf2000000 ldr r3, [r3] ldr r4, =(GICD_CTLR_ARE_NS | GICD_CTLR_DS) cmp r3, r4 beq skip_zx_hack @ This allows EL1 to handle ints hat are normally handled by EL2/3. ldr r3, =0xf2000000 str r4, [r3] cps #MON_MODE @ Work in non-secure physical address space: SCR_EL3.NS = 1. At least the UART @ seems to respond only to non-secure addresses. I have taken insipiration from @ Raspberry pi's armstub7.S here. mov r3, #0x131 @ non-secure, Make F, A bits in CPSR writeable @ Allow hypervisor call. mcr p15, 0, r3, c1, c1, 0 @ AP_PPI_MODE_REG: Configure timer PPIs (10, 11, 13, 14) to active-low. ldr r3, =0xF22020a8 ldr r4, =0x50 str r4, [r3] ldr r3, =0xF22020ac ldr r4, =0x14 str r4, [r3] @ Enable EL2 access to ICC_SRE (bit 3, ICC_SRE_EL3.Enable). Enable system reg @ access to GICv3 registers (bit 0, ICC_SRE_EL3.SRE) for EL1 and EL3. mrc p15, 6, r3, c12, c12, 5 @ ICC_SRE_EL3 orr r3, #0x9 @ FIXME: No defines for SRE_EL3 values? mcr p15, 6, r3, c12, c12, 5 mrc p15, 0, r3, c12, c12, 5 @ ICC_SRE_EL1 orr r3, #(ICC_SRE_EL1_SRE) mcr p15, 0, r3, c12, c12, 5 @ Like ICC_SRE_EL3, enable EL1 access to ICC_SRE and system register access @ for EL2. mrc p15, 4, r3, c12, c9, 5 @ ICC_SRE_EL2 aka ICC_HSRE orr r3, r3, #(ICC_SRE_EL2_ENABLE | ICC_SRE_EL2_SRE) mcr p15, 4, r3, c12, c9, 5 isb @ Back to SVC mode cps #SVC_MODE skip_zx_hack:关键点逐段解读:
- 检测与跳过逻辑:先读
0xf2000000(GICD_CTLR 寄存器),若已等于GICD_CTLR_ARE_NS | GICD_CTLR_DS,说明引导加载程序已正确初始化(如主线的 u-boot-mainline),直接跳到skip_zx_hack,避免重复配置; - GIC 安全禁用:将
GICD_CTLR_ARE_NS | GICD_CTLR_DS写入 GICD_CTLR,使 EL1 能处理通常由 EL2/EL3 处理的中断; - 进入 monitor 模式并设置
SCR_EL3.NS=1(写入值0x131):工作于非安全物理地址空间——至少 UART 只响应非安全地址;同时使 CPSR 的 F、A 位可写,并允许 hypervisor 调用; - 定时器 PPI 极性修正:向
0xF22020a8写入0x50、向0xF22020ac写入0x14,把定时器 PPI(10、11、13、14)配置为低电平有效。这一点与设备树中timer节点声明的IRQ_TYPE_LEVEL_LOW相呼应(见 zx297520v3.dtsi); - ICC_SRE 访问使能:分别对
ICC_SRE_EL3(bit3 使能 EL2 访问、bit0 使能 EL1/EL3 系统寄存器访问)、ICC_SRE_EL1(设置ICC_SRE_EL1_SRE)、ICC_SRE_EL2(akaICC_HSRE,设置ICC_SRE_EL2_ENABLE | ICC_SRE_EL2_SRE)进行 orr 后写回,并执行isb同步屏障; - 最后
cps #SVC_MODE回到 SVC 模式继续正常启动流程。
4.2 为何这些工作在仓库的 DTS 中可见其必要性
在 arch/arm/boot/dts/zte/zx297520v3.dtsi 的 GIC 节点注释中明确说明:该 GIC 在0xf2202070及之后地址存在非标准的电平/边沿极性配置寄存器(对应 ZTE 内核中的AP_INT_MODE_BASE与AP_PPI_MODE_REG,且 ZTE 源码中的偏移疑似有误);一切默认是高电平有效/上升沿,唯独定时器是低电平有效——目前依赖引导加载程序代为完成定时器 IRQ 极性切换。这正是上述汇编代码第 5 步(配置定时器 PPI 为 active-low)存在的根本原因。
5. 常见问题与排障要点
- 内核无法接收任何中断:优先检查第 4 节的 5 项 GIC/CPU 配置是否全部执行——尤其是
GICD_CTLR.DS与定时器 PPI 极性。ZTE 官方内核同样存在此问题,说明这不是个别板卡差异,而是 SoC 级缺陷,必须由引导代码修复; - UART 无输出:确认运行在非安全物理地址空间(
SCR_EL3.NS=1),UART 仅响应非安全地址; - 正常启动模式被杀:内置 U-Boot 在启动内核前会武装看门狗,开发阶段请改用
ap_recovery.bin; - DTS 未生效:内置 U-Boot 不支持加载 DTB,务必开启
CONFIG_ARM_APPEND_DTB并把 zImage 与 dtb 拼接后制作 uImage; - 启动卡在 armv7-timer:若引导加载程序未配置
CNTVOFF,由于是单 CPU 系统,偏移随机通常不影响使用;设备树已声明arm,cpu-registers-not-fw-configured以告知内核不要依赖固件完成寄存器配置。
6. 小结
zx297520v3 的上游 Linux 支持目前覆盖了设备树、板级机器代码与 Kconfig 配置(见 arch/arm/boot/dts/zte/、arch/arm/mach-zte/),但引导环节仍高度依赖外部工具链:USB 模式下用 zx297520v3-loader + u-boot-mainline 最省事;从 NAND 启动则需遵循本文的九步流程,并用第 4 节的汇编代码弥补 SoC 的 GIC/CPU 初始化缺陷。对开发者而言,推荐路径是:USB 下载模式调试 → 恢复分区镜像开发 → 最后再固化到正常启动分区。
【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考