这章主要分析启动开始文件startup_Device.s以及相关的文件
一、定义向量表
/* If BOOT_HARTID is not defined, default value is 0 */
#ifndef BOOT_HARTID
.equ BOOT_HARTID, 0
#endif
.macro DECLARE_INT_HANDLER INT_HDL_NAME
#if defined(__riscv_xlen) && (__riscv_xlen == 32)
.word \INT_HDL_NAME
#else
.dword \INT_HDL_NAME
#endif
.endm
这里一个汇编宏定义,主要用于定义向量表,在存储器里面开辟的一段连续的地址空间;其中各个中断服务函数(Interrupt Service Routine,ISR)的PC地址,32位机地址长度是32位,使用.word关键字,64位机地址长度是64位,使用.dword关键字;
.section .vtable
.weak eclic_msip_handler
.weak eclic_mtip_handler
使用.section 伪操作指明将接下来的代码汇编链接到名为vtable的段,根据链接脚本ld文件可知,.vtable是属于.init的子段;
使用.weak 伪操作定义一个弱属性的符号内容为空,为了使得其能够通过汇编器语法检查,但是在后续的程序中定义符号的真正实体,并且在链接阶段将空符号覆盖并链接。
例如:假设eclic_mtip_handler在整个工程中都没有定义,则向量表中此中断服务函数的PC地址为0x00000000;
.globl vector_base
.type vector_base, @object
vector_base:
#ifndef VECTOR_TABLE_REMAPPED
j _start /* 0: Reserved, Jump to _start when reset for vector table not remapped cases.*/
.align LOG_REGBYTES /* Need to align 4 byte for RV32, 8 Byte for RV64 */
#else
DECLARE_INT_HANDLER default_intexc_handler /* 0: Reserved, default handler for vector table remapped cases */
#endif
DECLARE_INT_HANDLER default_intexc_handler /* 1: Reserved */
DECLARE_INT_HANDLER default_intexc_handler /* 2: Reserved */
DECLARE_INT_HANDLER eclic_msip_handler /* 3: Machine software interrupt */
DECLARE_INT_HANDLER default_intexc_handler /* 4: Reserved */
DECLARE_INT_HANDLER default_intexc_handler /* 5: Reserved */
DECLARE_INT_HANDLER default_intexc_handler /* 6: Reserved */
DECLARE_INT_HANDLER eclic_mtip_handler /* 7: Machine timer interrupt */
DECLARE_INT_HANDLER default_intexc_handler /* 8: Reserved */
DECLARE_INT_HANDLER default_intexc_handler /* 9: Reserved */
DECLARE_INT_HANDLER default_intexc_handler /* 10: Reserved */
DECLARE_INT_HANDLER default_intexc_handler /* 11: Reserved */
DECLARE_INT_HANDLER default_intexc_handler /* 12: Reserved */
DECLARE_INT_HANDLER default_intexc_handler /* 13: Reserved */
DECLARE_INT_HANDLER default_intexc_handler /* 14: Reserved */
DECLARE_INT_HANDLER default_intexc_handler /* 15: Reserved */
DECLARE_INT_HANDLER default_intexc_handler /* 16: Reserved */
DECLARE_INT_HANDLER default_intexc_handler /* 17: Reserved */
DECLARE_INT_HANDLER default_intexc_handler /* 18: Reserved */
DECLARE_INT_HANDLER default_intexc_handler /* 19: Interrupt 19 */
这里正式开始分配向量表;
.globl伪操作定义一个全局的符号vector_base,使得链接器能够全局识别它,即一个程序文件中定义的符号能够被所有其他程序文件可见。
.type伪操作用于定义符号vector_base的类型。即将vector_base的符号定义为一个数据对象(object)。
vector_base: vector_base数据对象标签
j _start:跳转到_start标签的函数;
.align LOG_REGBYTES :接下来以4字节对齐;其中LOG_REGBYTES值 = 2;
DECLARE_INT_HANDLER default_intexc_handler:DECLARE_INT_HANDLER这个宏为“.word”;.word伪操作将从当前PC地址处开始分配若干个字(word)的空间,每个字填充的值由分号分隔开的expression指定。空间分配的地址一定与字对齐(word aligned),这里值分配了一个word空间。
接下来就是继续分配向量表的空间,先是19个RISCV预留的中断;之后是厂商自己定义的中断;
注意,为了方便,这里分配的中断服务函数PC地址空间,需要在上面定义相同的一个弱属性的符号,否则在没有定义中断服务函数时编译会出错;
二、start代码
.section .init
.globl _start
.type _start, @function
/**
* Reset Handler called on controller reset
*/
_start:
/* ===== Startup Stage 1 ===== */
/* Disable Global Interrupt */
csrc CSR_MSTATUS, MSTATUS_MIE
/* Initialize GP and Stack Pointer SP */
.option push
.option norelax
la gp, __global_pointer$
la tp, __tls_base
.option pop
#if defined(SMP_CPU_CNT) && (SMP_CPU_CNT > 1)
/* Set correct sp for each cpu
* each stack size is __STACK_SIZE
* defined in linker script */
la t0, __STACK_SIZE
la sp, _sp
csrr a0, CSR_MHARTID
li a1, 0
1:
beq a0, a1, 2f
sub sp, sp, t0
addi a1, a1, 1
j 1b
2:
#else
/* Set correct sp for current cpu */
la sp, _sp
#endif
/*
* Set the the NMI base mnvec to share
* with mtvec by setting CSR_MMISC_CTL
* bit 9 NMI_CAUSE_FFF to 1
*/
li t0, MMISC_CTL_NMI_CAUSE_FFF
csrs CSR_MMISC_CTL, t0
/*
* Intialize ECLIC vector interrupt
* base address mtvt to vector_base
*/
la t0, vector_base
csrw CSR_MTVT, t0
/*
* Set ECLIC non-vector entry to be controlled
* by mtvt2 CSR register.
* Intialize ECLIC non-vector interrupt
* base address mtvt2 to irq_entry.
*/
la t0, irq_entry
csrw CSR_MTVT2, t0
csrs CSR_MTVT2, 0x1
/*
* Set Exception Entry MTVEC to early_exc_entry
* Due to settings above, Exception and NMI
* will share common entry.
* This early_exc_entry is only used during early
* boot stage before main
*/
la t0, early_exc_entry
csrw CSR_MTVEC, t0
/* Set the interrupt processing mode to ECLIC mode */
li t0, 0x3f
csrc CSR_MTVEC, t0
csrs CSR_MTVEC, 0x3
/* ===== Startup Stage 2 ===== */
/* Enable FPU and Vector Unit if f/d/v exist in march */
#if defined(__riscv_flen) && __riscv_flen > 0
/* Enable FPU, and set state to initial */
li t0, MSTATUS_FS
csrc mstatus, t0
li t0, MSTATUS_FS_INITIAL
csrs mstatus, t0
#endif
#if defined(__riscv_vector)
/* Enable Vector, and set state to initial */
li t0, MSTATUS_VS
csrc mstatus, t0
li t0, MSTATUS_VS_INITIAL
csrs mstatus, t0
#endif
/* Enable mcycle and minstret counter */
csrci CSR_MCOUNTINHIBIT, 0x5
#if defined(SMP_CPU_CNT) && (SMP_CPU_CNT > 1)
csrr a0, CSR_MHARTID
li a1, BOOT_HARTID
bne a0, a1, __skip_init
#endif
__init_common:
/* ===== Startup Stage 3 ===== */
/*
* Load text section from CODE ROM to CODE RAM
* when text LMA is different with VMA
*/
la a0, _text_lma
la a1, _text
/* If text LMA and VMA are equal
* then no need to copy text section */
beq a0, a1, 2f
la a2, _etext
bgeu a1, a2, 2f
1:
/* Load code section if necessary */
lw t0, (a0)
sw t0, (a1)
addi a0, a0, 4
addi a1, a1, 4
bltu a1, a2, 1b
2:
/* Load data section */
la a0, _data_lma
la a1, _data
/* If data vma=lma, no need to copy */
beq a0, a1, 2f
la a2, _edata
bgeu a1, a2, 2f
1:
lw t0, (a0)
sw t0, (a1)
addi a0, a0, 4
addi a1, a1, 4
bltu a1, a2, 1b
2:
/* Clear bss section */
la a0, __bss_start
la a1, _end
bgeu a0, a1, 2f
1:
sw zero, (a0)
addi a0, a0, 4
bltu a0, a1, 1b
2:
__start:
1、关闭总中断
2、初始化GP TP SP
(2、如果没有定义SMP_CPU_CNT,则判断启动BOOT_HARTID和当前运行的核心MHARTID是否一致,一致则继续初始化,否则执行休眠;(多核才需要此代码))
3、初始化NMI和其他异常共享入口地址,MNVEC和MTVEC相同;
4、初始化ECLIC向量表基地址寄存器 MTVT = vector_base;
5、设置ECLIC非向量模式共享入口地址寄存器 MTVT2 且地址与异常入口地址不共享,(ECLIC非向量模式共享入口地址函数为"irq_entry");
6、设置异常入口地址寄存器MTVEC,函数为"early_exc_entry";
7、设置中断处理模式为 ECLIC模式,将中断模式修改为CLIC模式(ECLIC模式),不再是CLINT模式;
8、使能FPU,如果有;
9、使能Vector扩展,如果有;
10、打开mcycle和minstret的计数
(RISC-V架构定义了一个64位宽的时钟周期计数器mcycle,用于反映处理器执行了多少个时钟周期。只要处理器处于执行状态时,此计数器便会不断自增计数;RISC-V架构定义了一个64位宽的指令完成计数器minstret,用于反映处理器成功执行了多少条指令。只要处理器每成功执行完成一条指令,此计数器便会自增计数。)
(11、如果定义了SMP_CPU_CNT,且SMP_CPU_CNT>1,则判断启动BOOT_HARTID和当前允许的核心MHARTID是否一致,一致则继续初始化,否则__skip_init;(多核才需要此代码))
__init_common:
初始化内存,code section ,data section, bss section
_start_premain:
调用厂商系统初始函数(时钟配置),调用C/C++构造函数;
SystemInit、atexit(__libc_fini_array) ->_postmain_fini、__libc_init_array->_init;
__skip_init:
1、调用 __sync_harts,同步硬件线程(单核 函数为空);
2、调用_premain_init, main函数之前的初始化函数 ,初始化系统内部部件模块基地址;其他初始化(printf )
3、重新设置MTVEC 寄存器,设置MTVEC 异常入口地址为exc_entry,并设置中断处理模式为 ECLIC模式;
5、使能BPU,如果有
5、如果定义了SMP_CPU_CNT调用smp_main,否则如果定义了RTOS_RTTHREAD则调用entry,否则调用main,
5、调用_postmain_fini
6、死循环;
smp_main:(如果定义了SMP_CPU_CNT,且SMP_CPU_CNT>1)
多核处理函数;
early_exc_entry:
进入低功耗
死循环early_exc_entry;
N300 启动分析大纲:
启动第一条指令是“j _start”:伪指令 跳转到_start段开始执行,流程如下:
-
初始化流程
-
__start
-
关闭总中断
-
初始化GP TP SP
-
初始化NMI和其他异常共享入口地址,即MNVEC和MTVEC相同;
-
ECLIC向量表基地址寄存器 MTVT = vector_base
-
设置ECLIC非向量模式共享入口地址寄存器 MTVT2 且地址与异常入口地址不共享,(ECLIC非向量模式共享入口地址函数为"irq_entry");
-
设置异常入口地址寄存器MTVEC,函数为"early_exc_entry";
-
设置中断处理模式为 ECLIC模式,将中断模式修改为CLIC模式(ECLIC模式),不再是CLINT模式;
-
使能FPU,如果有;
-
使能Vector扩展,如果有;
-
打开mcycle和minstret的计数
-
-
__init_common
-
初始化内存,code section ,data section, bss section
-
-
_start_premain
-
SystemInit
-
atexit(__libc_fini_array)
-
__libc_init_array
-
_init 空
-
-
-
__skip_init
-
_premain_init
-
获取IRegion 地址
-
Exception_Init
-
异常初始化
-
-
ECLIC_Init
-
mth=0 不屏蔽中断
-
设置CLICINTCTL中LEVEL的位数
-
-
Trap_Init 空
-
-
重新设置MTVEC 寄存器,设置MTVEC 异常入口地址为exc_entry,并设置中断处理模式为 ECLIC模式;
-
使能BPU,如果有
-
调用main函数
-
死循环
-
-