`startup_gcc.S` 详细介绍(D13x):从复位到内核的完整路径
startup_gcc.S详细介绍(D13x)
ArtInChip D13x在GCC 工具链下的RISC-V 启动汇编(典型为平头哥E907内核),负责:中断向量表、复位入口、早期硬件与 C 运行时准备,最后跳入内核或main。它与链接脚本、vectors.S、boot_param_gcc.S、system.c共同构成“上电 → 跑 RT-Thread/裸机”的第一段路径。
1. 在工程中的位置
| 角色 | 说明 |
|---|---|
| 链接入口 | gcc_aic.ld中ENTRY(Reset_Handler),复位后 CPU 从Reset_Handler开始执行 |
| 代码段 | KEEP(*startup_gcc.o(*.text*))放在.text最前,保证复位代码不被优化掉 |
| 向量表 | KEEP(*startup_gcc.o(*.vectors*))放在.data段,与__global_pointer$等同在 SRAM |
| 配套文件 | boot_param_gcc.S(保存启动参数)、vectors.S(默认/陷阱处理)、system.c(SystemInit) |
上电后的调用链可概括为:
2. 文件结构概览
文件分三块:
.section .vectors—__Vectors中断向量表(100 项)Reset_Handler— 真正的启动逻辑__exit— 死循环兜底(正常不应到达)
3. 中断向量表__Vectors(第 11–118 行)
.section .vectors, "aw", @progbits .align 6 .globl __Vectors .type __Vectors, @object __Vectors: .long Default_Handler /* 0 */ ... .long PendSV_Handler /* 3 */ ... .long Default_IRQHandler /* 7 */ ... /* External interrupts */ .long Default_IRQHandler /* 16 */ ...3.1 表项含义
- 0–15:系统类异常/内部事件入口(多数先落到
Default_Handler,再由vectors.S区分 trap / NMI)。 - 3:
PendSV_Handler— 在KERNEL_BAREMETAL下由vectors.S实现,最终进trap;RTOS 场景下由内核提供真实实现。 - 7:固定为
Default_IRQHandler(与其它外部 IRQ 共用向量中断入口风格)。 - 16–99:外部中断,默认均为
Default_IRQHandler。
表长100 × 4 字节,与 D13x CLIC 向量中断模型配合使用(具体 IRQ 号与芯片手册、aic_drv_irq.c中drv_irq_vectors_init一致)。
3.2 与mtvt的关系
后面Reset_Handler会执行:
la a0, __Vectors csrw mtvt, a0这是T-Head 扩展 CSRmtvt:向量表基址。发生向量中断时,硬件按 IRQ 号从__Vectors取处理函数地址。
实际业务 IRQ 还会在SystemInit→drv_irq_vectors_init()里写入g_irqvector[],与Default_IRQHandler里的分发逻辑配合。
3.3 链接放置
向量表在.data(可写 RAM),不是只读 Flash。Bootloader/SPL 把镜像拷到 SRAM 后,表在运行地址上有效;若需要运行时改向量,也可写(本工程主要靠g_irqvector做二次分发)。
4.Reset_Handler:逐步解析
4.1 保存 Boot 参数(第 129–133 行)
Reset_Handler: .option push .option norelax j save_boot_params save_boot_params_ret:- 先
j save_boot_params(实现在boot_param_gcc.S)。 - SPL/Bootloader 跳转 OS 时会把a0–a7、s0–s11、sp、ra等写入
boot_params_stash。 - 若a1 ≠ 0,还会把256 字节
boot_arg从 SPL 拷到 OS 的boot_arg(DDR/资源等启动信息)。 - 完成后
j save_boot_params_ret回到本文件继续。
这样 OS 能保留上一阶段的启动上下文,供aic_get_boot_resource()、reloc_private_params等使用。
4.2 全局指针与中断控制器初配(第 134–140 行)
la gp, __global_pointer$ .option pop la a0, Default_Handler ori a0, a0, 3 csrw mtvec, a0 la a0, __Vectors csrw mtvt, a0| 步骤 | 作用 |
|---|---|
la gp, __global_pointer$ | 初始化 RISC-Vgp,访问小数据/.sdata;.option norelax避免早期la被错误松弛 |
mtvec = Default_Handler | 3 | 向量模式(低 2 位为 mode);未走mtvt的路径先进Default_Handler |
mtvt = __Vectors | 绑定向量表,外部 IRQ 按表跳转 |
Default_Handler在vectors.S里会检查mcause:若为NMI(24)走.NMI_Handler,否则j trap进统一异常/系统调用路径。
4.3 栈指针(第 142–148 行)
la sp, g_top_irqstack #ifdef CONFIG_THEAD_EXT_SPSWAPEN csrw mscratch, sp #endif #ifdef KERNEL_BAREMETAL la sp, g_top_normalstack #endif- 默认
sp = g_top_irqstack(vectors.S里g_base_irqstack+AIC_INTERRUPTSTACK_SIZE的栈顶)。 CONFIG_THEAD_EXT_SPSWAPEN:把该栈顶写入mscratch,配合 T-HeadSPSWAPEN在中断里换栈。- 裸机
KERNEL_BAREMETAL:再切到g_top_normalstack,中断与主流程栈分离。
4.4 使能 I/D Cache(第 150–153 行)
la a5, icache_enable jalr a5 la a5, dcache_enable jalr a5调用system.c中的icache_enable()/dcache_enable()(内部走aicos_*)。SystemInit()里还会再次csi_icache_enable()/csi_dcache_enable(),属于“尽早开缓存 + 正式初始化再确认”的常见做法。
4.5 芯片相关早期硬件(第 155–198 行,#ifndef QEMU_RUN)
仅在真实芯片编译,QEMU 仿真跳过。
| 宏 | 行为 |
|---|---|
AIC_TCM_EN | 写mitcmcr/mdtcmcr使能 ITCM/DTCM;开 SYSCFG 时钟;SYSCFG 寄存器使能 TCM |
AIC_SRAM1_EN | 配置 SYSCFG 中 SRAM1 大小/使能位(AIC_SRAM_S1_REG_SIZE) |
PSRAM_UNCACHED_EN | 写SYSMAP_ADDR6_ATTR,把 PSRAM 标为non-cacheable |
地址注释在源码中(如 ITCM0x30040000、DTCM0x30060000、CMU/SYSCFG 基址),与aic_soc.h一致。
4.6 QEMU 下的.data拷贝(第 201–215 行)
#if defined(QEMU_RUN) la a0, __erodata la a1, __data_start__ la a2, __data_end__ ... la t0, dcache_clean jalr t0 #endifQEMU 启动时可能没有 Bootloader 做LMA→VMA拷贝,因此在启动代码里把已初始化数据从 rodata 末尾(加载地址)拷到__data_start__~__data_end__,再dcache_clean。真机通常由 SPL 完成,故用QEMU_RUN隔开。
4.7 BSS 清零(第 217–225 行)
标准 C 运行时:对__bss_start__~__bss_end__按字写 0,保证未初始化全局/静态变量为 0。
4.8 私有参数重定位(第 227–229 行)
la a5, reloc_private_params jalr a5实现在bsp/common/private_param/private_param.c:从 boot 资源里解析PDAT/私有配置(如 DDR 参数等),拷入private_params_stash并aic_set_boot_resource(),供后续驱动/内存初始化读取。
4.9SystemInit(第 231–234 行)
#ifndef __NO_SYSTEM_INIT la a5, SystemInit jalr a5 #endifsystem.c中主要做:
- FPU、
MXSTATUS(THEADISAEE、MM)、MEXSTATUS(SPUSHEN/SPSWAPEN) - CLIC配置、清 pending、设向量中断属性
- 再次开 cache、使能Machine Software(用于上下文切换/软中断)
drv_irq_vectors_init()、aic_gtc_enable()、时钟低功耗、CoreT/SysTick(非 baremetal)
4.10 进入内核或应用(第 236–242 行)
#if (defined(KERNEL_RTTHREAD) || defined(KERNEL_FREERTOS)) la a5, entry jalr a5 #else la a5, main jalr a5 #endif- RT-Thread:
entry()→rtthread_startup()(kernel/rt-thread/src/components.c),再建主线程跑main()。 - FreeRTOS:
entry()建任务并vTaskStartScheduler()。 - 裸机/其它:直接
main()。
entry/main返回后不会继续执行有用代码;文件末尾__exit: j __exit为死循环。
5. 与vectors.S的分工
| 符号 | 所在文件 | 职责 |
|---|---|---|
__Vectors | startup_gcc.S | 硬件向量表(指针数组) |
Default_IRQHandler | vectors.S | 外部 IRQ:ipush、保存 FPU/上下文、查g_irqvector分发 |
Default_Handler | vectors.S | mtvec入口:NMI 或trap |
PendSV_Handler | vectors.S(baremetal) | 软件 PendSV →trap |
g_top_irqstack | vectors.S | 中断栈顶 |
startup_gcc.S只建表、不配具体外设 ISR;外设驱动通过drv_irq_register()等改g_irqvector(aic_drv_irq.c)。
6. 关键编译宏小结
| 宏 | 影响 |
|---|---|
QEMU_RUN | 跳过 TCM/SRAM1/PSRAM 硬件初始化;增加.data拷贝 |
AIC_TCM_EN/AIC_SRAM1_EN/PSRAM_UNCACHED_EN | 内存子系统早期配置 |
KERNEL_BAREMETAL | 使用g_top_normalstack;PendSV_Handler在vectors.S |
KERNEL_RTTHREAD/KERNEL_FREERTOS | 跳转entry |
CONFIG_THEAD_EXT_SPSWAPEN | 设置mscratch |
__NO_SYSTEM_INIT | 跳过SystemInit(特殊镜像/调试) |
7. 与标准 RISC-V 启动的差异(便于对照学习)
- 双 CSR:
mtvec(带 mode 3)+mtvt向量表,属 T-Head/E907 常见组合。 - Boot 参数链:先
save_boot_params再 C 环境,适配 SPL → OS 跳转。 - 向量表在 RAM
.data,且项数固定 100。 - SoC 寄存器初始化写在汇编里(TCM/SYSMAP),而不是全部放在
SystemInit。 - 内核入口统一为
entry,与 ARM 上-eentry习惯类似。
8. 调试时可关注的点
- 若卡在复位:查 SPL 是否已拷
.data、BSS 范围是否与gcc_aic.ld一致。 - 若 IRQ 全进默认处理:查
mtvt是否指向__Vectors、SystemInit是否执行、drv_irq_vectors_init是否注册。 - 若栈溢出:确认
AIC_INTERRUPTSTACK_SIZE与g_top_irqstack/mscratch配置。 - QEMU 与真机构建选项不同,注意是否定义
QEMU_RUN。
