gd32vf103 启动文件分析
/**
* Reset Handler called on controller reset
*/
_start:
/* ===== Startup Stage 1 ===== */
/* csrc是伪指令
* csrc csr rs -> csrrc x0, csr, rs : Clear bits in CSR
* csrrc指令格式 csrrc rd, csr, rs1
* CSRRC(CSR 中的原子读取和清除位)指令读取 CSR 的值,将值零扩展到 XLEN
* 位,并将其写入整数寄存器 rd。 整数寄存器 rs1 中的初始值被视为位掩码,
* 指定要在 CSR 中清除的位位置。 如果 CSR 位可写,rs1 中的任何高位都会导致
* CSR 中的相应位被清除。 CSR 中的其他位不受影响。
* MSTATUS_MIE MIE bit
* machine状态下的全局中断控制位 1:enable 0: disable
* 这里是关闭中断
*/
/* Disable Global Interrupt */
csrc CSR_MSTATUS, MSTATUS_MIE
/* Jump to logical address first to ensure correct operation of RAM region */
/* 如果是flash启动的话,_start地址是 0x0800xxxx
* 如果是ram启动,__start地址是0x2000xxxx
*/
la a0, _start
li a1, 1
/* 这里的值是 1<<29 = 0x20000000 就是ram的地址*/
slli a1, a1, 29
/* 如果_start大于0x20000000 则说明是从ram启动
* 这里跳到_start0800去执行
*/
bleu a1, a0, _start0800
/* 0x20000000>>2 = 0x08000000 这里对应flash的基地址 */
srli a1, a1, 2
/* 如果_start大于0x08000000,则说明是从flash启动
* 同样跳到_start0800去执行
*/
bleu a1, a0, _start0800
/* 走到这里的话,说明_start地址是小于0x08000000的
* 这是有问题的,所以强制执行 0x08000000+_start0800
* 也就是定位到flash中
*/
la a0, _start0800
add a0, a0, a1
jr a0
_start0800:
/* Initialize GP and Stack Pointer SP */
.option push
.option norelax
/* 将标签__global_pointer$所处的地址赋值给gp寄存器
* _global_pointer在链接脚本中定义参见链接脚本
*/
la gp, __global_pointer$
.option pop
/* 设置堆栈指针
* _sp在链接脚本中定义
*/
la sp, _sp
/*
* Set the the NMI base mnvec to share
* with mtvec by setting CSR_MMISC_CTL
* bit 9 NMI_CAUSE_FFF to 1
*/
/* #define MMISC_CTL_NMI_CAUSE_FFF (1<<9)
* #define CSR_MMISC_CTL 0x7D0
* mmisc_ctl 自定义寄存器用于控制NMI的处理程序地址入口
* bit 9置1后,mnvec 的值与 mtvec 一致,NMI 的 mcause.EXCCODE 为
* 0xfff
* mnvec 寄存器用于配置 NMI 的入口地址
* mtvec 寄存器用于配置中断和异常处理程序的入口地址
*/
li t0, MMISC_CTL_NMI_CAUSE_FFF
csrs CSR_MMISC_CTL, t0
/*
* Intialize ECLIC vector interrupt
* base address mtvt to vector_base
*/
/* mtvt ECLIC 中断向量表的基地址
* 填充基地址
*/
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.
*/
/* 自定义
* mtvt2 用于指定 ECLIC 非向量模式的中断 common-code 入口地址
* bit2~bit31 用于存放common-code入口地址 所以改地址需要4bytes对齐
* bit0:
* 0: 入口地址有mtvec决定
* 1: 入口地址就是设置的值
*/
la t0, irq_entry
csrw CSR_MTVT2, t0
csrs CSR_MTVT2, 0x1
/*
* Set Exception Entry MTVEC to exc_entry
* Due to settings above, Exception and NMI
* will share common entry.
*/
/* 异常处理程序入口地址
* 同样要4bytes对齐
*/
la t0, exc_entry
csrw CSR_MTVEC, t0
/* Set the interrupt processing mode to ECLIC mode */
/* mtvee中bit0~bit5 值为3的时候,中断处理模式为ECLIC中断模式
* 其他值的话则是 默认中断模式
*/
li t0, 0x3f
csrc CSR_MTVEC, t0
csrs CSR_MTVEC, 0x3
/* mtvc中记录了中断向量表的地址
* 都知道有向量中断和非向量中断,两者的区别这些不多说
* 下面通过寄存器说一下两者之前的区别和联系
* 1. 无论中断是向量模式还是非向量模式,硬件都会通过查询中断向量表中的
* 地址跳到其对应的中断服务程序中。
* 2. mtvec 中记录了异常入口地址
* mtvt2 中记录了非向量中断入口地址
* 这2个地址的区别?
* mtvt2中有个有个bit0
* 值为0的时候,非向量中断入口地址是mtvec中的地址
* 这个是异常和中断使用一个入口地址
* 值为1的时候,非向量中断入口地址是mtvt2中记录的地址
* 这个时候,非向量中断的入口地址是mtvt2,而异常的入口地址是mtevc
* 异常和中断的入口地址是分开的
* 3. 怎么设置使用向量中断和非向量中断?
* ECLIC的每个中断源均可以设置成向量或者非向量处理
* (通过寄存器clicintattr[i]的shv域)
* 3.1 如果被配置成为向量处理模式 (clicintattr[i].shv==1),
* 则该中断被处理器内核响应后,处理器直接跳入该中断的向量入口
* (Vector Table Entry)存储的目标地址
* 3.2 如果被配置成为非向量处理模式 (clicintattr[i].shv==0),
* 则该中断被处理器内核响应后,处理器直接跳入所有中断共享的入口
* 地址
* 默认是非向量中断
*/
/* __riscv_flen没有定义,所以这里不会执行 */
#ifdef __riscv_flen
/* Enable FPU */
li t0, MSTATUS_FS
csrs mstatus, t0
csrw fcsr, x0
#endif
/* Enable mcycle and minstret counter */
csrci CSR_MCOUNTINHIBIT, 0x5
/* ===== Startup Stage 3 ===== */
/*
* Load code section from FLASH to ILM
* when code LMA is different with VMA
*/
/* LMA: 内存装载地址
* 比如程序原来是在flash中,如果可以在flash中执行,那么LMA
* 就是flash地址。如果需要搬运到ram中运行,那么LMA就是ram地址
* VMA: 虚拟内存地址
* 一般来说启用了MMU之后,才有虚拟地址和实地址。
* 这里不考虑MMU,可以认为就是实际运行的地址
* _ilm_lma 和 _ilm 是在链接脚本中定义的
* ld文件:
* MEMORY
* {
* flash (rxai!w) : ORIGIN = __ROM_BASE, LENGTH = __ROM_SIZE
* ram (wxa!ri) : ORIGIN = __RAM_BASE, LENGTH = __RAM_SIZE
* }
* .ilalign :
* {
* . = ALIGN(4);
* /* Create a section label as _ilm_lma which located at flash */
* PROVIDE( _ilm_lma = . );
* } >flash AT>flash
*
* .ialign :
* {
* /* Create a section label as _ilm which located at flash */
* PROVIDE( _ilm = . );
* } >flash AT>flash
* 语法中AT前的一个flash表示该段的运行地址,AT后的flash表示该段的物
* 理地址
* 注意如果后面没有设置>ram
* 则说明后面的段都是在flash中的
*/
/* 可以看到都是在flash中,所以_ilm_lma和_ilm值一样
* 跳到下一个2执行
*/
la a0, _ilm_lma
la a1, _ilm
/* If the ILM phy-address same as the logic-address, then quit */
beq a0, a1, 2f
la a2, _eilm
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 */
/* _data_lma是在flash中的
* _data和_edata是在ram中
* a1 < a2的话,说明data段中有数据
*/
la a0, _data_lma
la a1, _data
la a2, _edata
bgeu a1, a2, 2f
1:
/* 下面将data段从flash中拷贝到ram */
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:
/* 清空bss段 */
sw zero, (a0)
addi a0, a0, 4
bltu a0, a1, 1b
2:
/*
* Call vendor defined SystemInit to
* initialize the micro-controller system
*/
/* 调用C函数 SystemInit
* 主要是初始化时钟
*/
call SystemInit
/*__libc_fini_array 和 __libc_init_array 是库里面定义的
* 主要作用就是 执行fini_array段和init_aray段中的函数
*/
/* Call global constructors */
la a0, __libc_fini_array
call atexit
/* Call C/C++ constructor start up code */
call __libc_init_array
/* _premain_init 这里主要是初始化 gd32vf103的外设
* 比如 gpio i2c uart等等
*/
/* do pre-init steps before main */
call _premain_init
/* 默认main函数无参数*/
/* ===== Call Main Function ===== */
/* argc = argv = 0 */
li a0, 0
li a1, 0
/* 如果编译RTT,则入口函数是enrty
* 非RTT,入口函数是main
*/
#ifdef RTOS_RTTHREAD
// Call entry function when using RT-Thread
call entry
#else
call main
#endif
/* 从main函数退出的话,则执行_postmain_fini */
/* do post-main steps after main */
call _postmain_fini
/* 跳到清bss 重新执行 */
1:
j 1b
以上汇编代码可以借鉴
https://www.nucleisys.com/product/n100/6_sdk/