Operating Systems for Practical Programmers [2] – Bootstrapping

/ 0评 / 0

再一次学习汇编。

之前仔细考虑过使用 C 来编写启动代码的可能性。但是最终得出了这样一个结论:不太可能。因为在刚刚启动的状态,栈指针还没有设置好,那么任何一个调用都会导致不可预料的事情发生。所以这部分代码必须使用汇编来写。当然,我们也会本着《30 天》的精神尽量少地使用汇编。

如果使用 CubeMX 生成项目的话,这部分其实已经写好了。但本着探究的精神,我们还是继续探索自己写启动代码的可能性。以及默认情况下,生成的代码是不使用 CCM 的,所以之后如果我们需要使用的话对代码进行改动是不可避免的。

So let's jump into it.


目标配置

从《编程手册》中我们可以知道,STM32F4 系列支持 Thumb 指令集。那么我们需要要求汇编器使用对应的指令集。同时,我们将使用统一助记符(即 ARM 指令集和 Thumb 指令集共享一套助记符)。

.sytax unified
.cpu cortex-m4
.thumb

/* 接下来的代码在这里 */

导入符号

在上一节,我们在链接脚本内定义了一些符号。现在我们需要将它们导入进来:

.word _sbss
.word _ebss
.word _sdata
.word _edata

中断向量表

首先我们关心的是中断向量表的设置。从《参考手册》中可以知道 STM32F40x 的中断向量表共有 97 个条目。我们需要为这 97 个条目挨个设置向量值。

.section .isr_vector, "a", @progbits /* "a": 可分配, @progbits 输出到二进制 */
.type IsrVector, @object /* @object 对象 */
IsrVector:
/* 核心中断 */
.word 0 /* 条目 0 保留 */
.word ResetHandler /* 1: 重置中断 */
.word NmiHandler /* 2: 不可屏蔽中断 */
.word HardFaultHandler /* 3: 硬错误 */
.word MemoryManagementFaultHandler /* 4: 内存管理错误 */
.word BusFaultHandler /* 5: 总线错误 */
.word UsageFaultHandler /* 6: 违例错误 */
.word 0 /* 7-10: 保留 */
.word 0
.word 0
.word 0
.word ServiceCallHandler /* 11: 系统调用 */
.word 0 /* 12-13: 保留 */
.word 0
.word PendingServiceHandler /* 14: 内核调用 */
.word SysTickHandler /* 15: 内部计时器中断 */
/* 外设中断 */
.word WatchDogHandler /* 16: 看门狗中断 */
.word VoltageDropHandler /* 17: 低电压中断 */
.word TamperHandler /* 18: 侵入中断 */
.word RtcWakeUpHandler /* 19: 实时时钟中断 */
.word FlashHandler /* 20: 闪存访问中断 */
.word RccHandler /* 21: 复位与时钟控制中断 */
.word Exti0Handler /* 22: 外部中断控制器 0 线路 */
.word Exti1Handler /* 23: 1 线路 */
.word Exti2Handler /* 24: 2 线路 */
.word Exti3Handler /* 25: 3 线路 */
.word Exti4Handler /* 26: 4 线路 */
.word Dma1Stream0Handler /* 27: 内存直通控制器 1 通道 0 */
.word Dma1Stream1Handler /* 28: 通道 1 */
.word Dma1Stream2Handler
.word Dma1Stream3Handler
.word Dma1Stream4Handler
.word Dma1Stream5Handler
.word Dma1Stream6Handler /* 33: 通道 6 */
.word AdcHandler /* 34: 模数转换器中断 */
.word Can1TxHandler /* 35: CAN 总线 1 发送中断 */
.word Can1Rx0Handler /* 36: CAN 总线 1 接收中断 0 */
.word Can1Rx1Handler /* 37: CAN 总线 1 接收中断 1 */
.word Can1SceHandler /* 38: CAN 总线 1 SCE 中断 */
.word Exti9To5Handler /* 39: 外部中断控制器 9..5 线路 */
.word Tim1BreakHandler
.word Tim1UpdateHandler
.word Tim1CaptureCompareHandler
.word Tim2Handler
.word Tim3Handler
.word Tim4Handler
.word I2C1EventHandler
.word I2C1ErrorHandler
.word I2C2EventHandler
.word I2C2ErrorHandler
.word Spi1Handler
.word Spi2Handler
.word Usart1Handler
.word Usart2Handler
.word Usart3Handler
.word Exti15To10Handler
.word RtcAlarmHandler
.word UsbFsWakeUpHandler
.word Tim8BreakHandler
.word Tim8UpdateHandler
.word Tim8TriggerCommunicationHandler
.word Tim8CaptureCompareHandler
.word Dma1Stream7Handler
.word FsmcHandler
.word SdIoHandler
.word Tim5Handler
.word Spi3Handler
.word Uart4Handler
.word Uart5Handler
.word Tim6DacHandler
.word Tim7Handler
.word Dma2Stream0Handler
.word Dma2Stream1Handler
.word Dma2Stream2Handler
.word Dma2Stream3Handler
.word Dma2Stream4Handler
.word EthernetHandler
.word EthernetWakeUpHandler
.word Can2TxHandler
.word Can2Rx0Handler
.word Can2Rx1Handler
.word Can2SceHandler
.word UsbFsHandler
.word Dma2Stream5Handler
.word Dma2Stream6Handler
.word Dma2Stream7Handler
.word Usart6
.word I2C3EventHandler
.word I2C3ErrorHandler
.word UsbHs1OutHandler
.word UsbHs1InHandler
.word UsbHsWakeUpHandler
.word UsbHsHandler
.word CameraHandler
.word CryptoHandler
.word EntrophyHandler
.word FpuHandler

然后,为向量表填值。我们先建立一个默认中断处理函数:

.section .text
DefaultHandler:
Loop: b Loop /* goto Loop; */

就一直循环下去就好了。

我们一般期望之后能用 C 语言的同名函数覆盖默认值,这时候就需要用到弱引用。

弱引用的格式为:

.weak <名字>
.thumb_set <名字>, DefaultHandler

这 90 多行我是显然不打算手动打一遍的,所以我们可以用 awk 来自动生成。首先删除开始的两行声明和 .word 0 部分,保存到 isr.txt, 然后执行指令:

cat isr.txt | awk '{ print ".weak " $2; print ".thumb_set " $2 ", DefaultHandler\n" }' > isr_set.txt

得到 isr_set.txt 就是我们需要追加的部分了:

.weak ResetHandler
.thumb_set ResetHandler, DefaultHandler

.weak NmiHandler
.thumb_set NmiHandler, DefaultHandler

.weak HardFaultHandler
.thumb_set HardFaultHandler, DefaultHandler

.weak MemoryManagementFaultHandler
.thumb_set MemoryManagementFaultHandler, DefaultHandler

.weak BusFaultHandler
.thumb_set BusFaultHandler, DefaultHandler

.weak UsageFaultHandler
.thumb_set UsageFaultHandler, DefaultHandler

.weak ServiceCallHandler
.thumb_set ServiceCallHandler, DefaultHandler

.weak PendingServiceHandler
.thumb_set PendingServiceHandler, DefaultHandler

.weak SysTickHandler
.thumb_set SysTickHandler, DefaultHandler

.weak WatchDogHandler
.thumb_set WatchDogHandler, DefaultHandler

.weak VoltageDropHandler
.thumb_set VoltageDropHandler, DefaultHandler

.weak TamperHandler
.thumb_set TamperHandler, DefaultHandler

.weak RtcWakeUpHandler
.thumb_set RtcWakeUpHandler, DefaultHandler

.weak FlashHandler
.thumb_set FlashHandler, DefaultHandler

.weak RccHandler
.thumb_set RccHandler, DefaultHandler

.weak Exti0Handler
.thumb_set Exti0Handler, DefaultHandler

.weak Exti1Handler
.thumb_set Exti1Handler, DefaultHandler

.weak Exti2Handler
.thumb_set Exti2Handler, DefaultHandler

.weak Exti3Handler
.thumb_set Exti3Handler, DefaultHandler

.weak Exti4Handler
.thumb_set Exti4Handler, DefaultHandler

.weak Dma1Stream0Handler
.thumb_set Dma1Stream0Handler, DefaultHandler

.weak Dma1Stream1Handler
.thumb_set Dma1Stream1Handler, DefaultHandler

.weak Dma1Stream2Handler
.thumb_set Dma1Stream2Handler, DefaultHandler

.weak Dma1Stream3Handler
.thumb_set Dma1Stream3Handler, DefaultHandler

.weak Dma1Stream4Handler
.thumb_set Dma1Stream4Handler, DefaultHandler

.weak Dma1Stream5Handler
.thumb_set Dma1Stream5Handler, DefaultHandler

.weak Dma1Stream6Handler
.thumb_set Dma1Stream6Handler, DefaultHandler

.weak AdcHandler
.thumb_set AdcHandler, DefaultHandler

.weak Can1TxHandler
.thumb_set Can1TxHandler, DefaultHandler

.weak Can1Rx0Handler
.thumb_set Can1Rx0Handler, DefaultHandler

.weak Can1Rx1Handler
.thumb_set Can1Rx1Handler, DefaultHandler

.weak Can1SceHandler
.thumb_set Can1SceHandler, DefaultHandler

.weak Exti9To5Handler
.thumb_set Exti9To5Handler, DefaultHandler

.weak Tim1BreakHandler
.thumb_set Tim1BreakHandler, DefaultHandler

.weak Tim1UpdateHandler
.thumb_set Tim1UpdateHandler, DefaultHandler

.weak Tim1CaptureCompareHandler
.thumb_set Tim1CaptureCompareHandler, DefaultHandler

.weak Tim2Handler
.thumb_set Tim2Handler, DefaultHandler

.weak Tim3Handler
.thumb_set Tim3Handler, DefaultHandler

.weak Tim4Handler
.thumb_set Tim4Handler, DefaultHandler

.weak I2C1EventHandler
.thumb_set I2C1EventHandler, DefaultHandler

.weak I2C1ErrorHandler
.thumb_set I2C1ErrorHandler, DefaultHandler

.weak I2C2EventHandler
.thumb_set I2C2EventHandler, DefaultHandler

.weak I2C2ErrorHandler
.thumb_set I2C2ErrorHandler, DefaultHandler

.weak Spi1Handler
.thumb_set Spi1Handler, DefaultHandler

.weak Spi2Handler
.thumb_set Spi2Handler, DefaultHandler

.weak Usart1Handler
.thumb_set Usart1Handler, DefaultHandler

.weak Usart2Handler
.thumb_set Usart2Handler, DefaultHandler

.weak Usart3Handler
.thumb_set Usart3Handler, DefaultHandler

.weak Exti15To10Handler
.thumb_set Exti15To10Handler, DefaultHandler

.weak RtcAlarmHandler
.thumb_set RtcAlarmHandler, DefaultHandler

.weak UsbFsWakeUpHandler
.thumb_set UsbFsWakeUpHandler, DefaultHandler

.weak Tim8BreakHandler
.thumb_set Tim8BreakHandler, DefaultHandler

.weak Tim8UpdateHandler
.thumb_set Tim8UpdateHandler, DefaultHandler

.weak Tim8TriggerCommunicationHandler
.thumb_set Tim8TriggerCommunicationHandler, DefaultHandler

.weak Tim8CaptureCompareHandler
.thumb_set Tim8CaptureCompareHandler, DefaultHandler

.weak Dma1Stream7Handler
.thumb_set Dma1Stream7Handler, DefaultHandler

.weak FsmcHandler
.thumb_set FsmcHandler, DefaultHandler

.weak SdIoHandler
.thumb_set SdIoHandler, DefaultHandler

.weak Tim5Handler
.thumb_set Tim5Handler, DefaultHandler

.weak Spi3Handler
.thumb_set Spi3Handler, DefaultHandler

.weak Uart4Handler
.thumb_set Uart4Handler, DefaultHandler

.weak Uart5Handler
.thumb_set Uart5Handler, DefaultHandler

.weak Tim6DacHandler
.thumb_set Tim6DacHandler, DefaultHandler

.weak Tim7Handler
.thumb_set Tim7Handler, DefaultHandler

.weak Dma2Stream0Handler
.thumb_set Dma2Stream0Handler, DefaultHandler

.weak Dma2Stream1Handler
.thumb_set Dma2Stream1Handler, DefaultHandler

.weak Dma2Stream2Handler
.thumb_set Dma2Stream2Handler, DefaultHandler

.weak Dma2Stream3Handler
.thumb_set Dma2Stream3Handler, DefaultHandler

.weak Dma2Stream4Handler
.thumb_set Dma2Stream4Handler, DefaultHandler

.weak EthernetWakeUpHandler
.thumb_set EthernetWakeUpHandler, DefaultHandler

.weak Can2TxHandler
.thumb_set Can2TxHandler, DefaultHandler

.weak Can2Rx0Handler
.thumb_set Can2Rx0Handler, DefaultHandler

.weak Can2Rx1Handler
.thumb_set Can2Rx1Handler, DefaultHandler

.weak Can2SceHandler
.thumb_set Can2SceHandler, DefaultHandler

.weak UsbFsHandler
.thumb_set UsbFsHandler, DefaultHandler

.weak Dma2Stream5Handler
.thumb_set Dma2Stream5Handler, DefaultHandler

.weak Dma2Stream6Handler
.thumb_set Dma2Stream6Handler, DefaultHandler

.weak Dma2Stream7Handler
.thumb_set Dma2Stream7Handler, DefaultHandler

.weak Usart6
.thumb_set Usart6, DefaultHandler

.weak I2C3EventHandler
.thumb_set I2C3EventHandler, DefaultHandler

.weak I2C3ErrorHandler
.thumb_set I2C3ErrorHandler, DefaultHandler

.weak UsbHs1OutHandler
.thumb_set UsbHs1OutHandler, DefaultHandler

.weak UsbHs1InHandler
.thumb_set UsbHs1InHandler, DefaultHandler

.weak UsbHsWakeUpHandler
.thumb_set UsbHsWakeUpHandler, DefaultHandler

.weak UsbHsHandler
.thumb_set UsbHsHandler, DefaultHandler

.weak CameraHandler
.thumb_set CameraHandler, DefaultHandler

.weak CryptoHandler
.thumb_set CryptoHandler, DefaultHandler

.weak EntrophyHandler
.thumb_set EntrophyHandler, DefaultHandler

.weak FpuHandler
.thumb_set FpuHandler, DefaultHandler

堆栈设置

接下来设置堆栈。尽管在原理上,ARM 并不需要堆栈就能实现调用子过程和从子过程中返回,但是只能有一层深度。如果我们要调用更深的子过程,那么使用堆栈是必要的。

有一个很有趣的事情是在《编程手册》上,它会告诉你在上电时,主堆栈会从 0x0000_0000 处取得,而这个位置正好对应中断向量表的第 0 个条目。那么我们直接改写对应位置即可。

.section .isr_vector
.word _estack /* 改写这里 */
...

数据复制

之后我们需要将 .data 段内的数据复制到内存当中,并将 .bss 段填 0.

.section .text
.type ResetHandler, @function
ResetHandler:
  movs r1, #0      /* r1 = 0; */
  b DataCopy       /* goto DataCopy; */

/* 复制 .data 段 */
DataCopyLoop:
  ldr r3, =_sidata /* r3 = *_sidata; */
  ldr r3, [r3, r1] /* r3 = *(r3 + r1); */
  str r3, [r0, r1] /* *(r0 + r1) = r3; */
  adds r1, r1, #4  /* r1 += 4; */
DataCopy:
  ldr r0, =_sdata  /* r0 = *_sdata; */
  ldr r3, =_edata  /* r3 = *_edata; */
  adds r2, r0, r1  /* r2 = r0 + r1; */
  cmp r2, r3       
  blo DataCopyLoop /* if (r2 < r3) goto DataCopyLoop; */

ldr r2, =_sbss     /* r2 = *_sbss; */
b ZeroFill         /* goto ZeroFill; */

/* 填充 .bss 段 */
ZeroFillLoop:
  movs r3, #0      /* r3 = 0; */
  str r3, [r2], #4 /* *(r2 + 4) = r3; */
ZeroFill:
  ldr r3, =_ebss   /* r3 = *_ebss; */
  cmp r2, r3       
  blo ZeroFillLoop /* if (r2 < r3) goto ZeroFillLoop

/* 接下来的代码放在这里 */ 

你可能会注意到,明明有 r4 - r12 等通用寄存器,为什么一直只用 r0 - r3 呢?私以为这是因为只有这四个寄存器是保证可用且会被自动恢复的。

第二阶段引导

完成上面的操作后我们就可以调用 C 语言函数了。那么我们调用进一步的设置函数:

bl SystemSetup
bl __libc_init_array /* 来自 libc 的初始化函数 */

进一步设置函数将会在下一节继续讨论。

进入内核

初始化核心外设之后,我们就可以进入内核了。

bl main
bx lr /* 应该永远不会到这里 */

而内核则会在第二阶段引导之后讨论。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

Your comments will be submitted to a human moderator and will only be shown publicly after approval. The moderator reserves the full right to not approve any comment without reason. Please be civil.