2020-11-30

转载——cortex-M4与cortex-A7内核启动流程分析

文章目录

  • 一.概述
  • 二.cortex-M4内核启动流程
    • 1.在代码最开始进行定义中断向量表
    • 2.编写中断服务函数(重点分析Reset_Handler复位中断函数)
    • 3.分析SystemInit()函数
    • 4. 启动过程总结
  • 三.cortex-A7内核启动流程
    • 1.定义中断向量表
    • 2.Reset_Handler中断服务函数
    • 3.IRQ_Handler中断服务函数
  • 三.总结

一.概述

  在系统上电时需要先执行一段引导程序,也称bootloader程序,来完成对系统运行环境的初始化工作。就和 PC 上的 BIOS程序一样,bootloader 就相当于 BIOS。
  基于ARM架构的处理器其内核启动流程大同小异,本文就基于cortex-M4内核的MCU以及基于cortex-A7内核的MPU,对其启动流程做简要分析。

二.cortex-M4内核启动流程

  在我们进行单片机编程的时候,通常没有考虑过自己写bootloader程序,直接编写main函数就能运行。那是因为半导体厂商给我们写好了bootloader,通常不需要我们修改。以STM32F4系列MCU为例,可以看到在我们的工程中存在startup_stm32f40_41xxx.s这个文件,这个文件是使用汇编写的,也就是bootloader程序。
  在单片机上电时,首先指向的就是startup_stm32f40_41xxx.s这个文件,因为在这个文件中完成了对系统的初始化工作。下面具体看看做了哪些事情。在这个文件上方给出了这样一段描述:

This module performs:
;*        - Set the initial SP                                               //初始化SP指针
;*        - Set the initial PC == Reset_Handler                              //设置PC指针指向Reset_Handler
;*        - Set the vector table entries with the exceptions ISR address     //设置中断向量表
;*        - Configure the system clock and the external SRAM mounted on      //调用SystemInit函数进行系统初始化,包括时钟、偏移地址等,且执行SystemInit函数后跳转回来
;*        	STM324xG-EVAL board to be used as data memory (optional,         
;*        	to be enabled by user) 
;*        - Branches to __main in the C library (which eventually            //最终跳转到main函数执行,且不再跳转回来,因此汇编初始化只执行一次,一旦跳出就不会回来。
;*          calls main()).                                                 
;*          After Reset the CortexM4 processor is in Thread mode, 
;*          priority is Privileged, and the Stack is set to Main.

从上面的描述中可以大概看出初始化工作做了哪些事情:包括堆栈初始化、SP初始化、PC初始化、系统时钟初始化并最终跳转到main函数。

1.在代码最开始进行定义中断向量表

__Vectors       DCD     __initial_sp               ; Top of Stack           //初始化SP指针指向栈顶,栈顶地址:0X08000000
                DCD     Reset_Handler              ; Reset Handler          //复位中断向量(中断向量表起始地址):0X08000004
                DCD     NMI_Handler                ; NMI Handler            //非可屏蔽中断向量:0X08000008
                DCD     HardFault_Handler          ; Hard Fault Handler      ..................
                DCD     MemManage_Handler          ; MPU Fault Handler
                DCD     BusFault_Handler           ; Bus Fault Handler       ..................
                DCD     UsageFault_Handler         ; Usage Fault Handler
                DCD     0                          ; Reserved
                DCD     0                          ; Reserved
                DCD     0                          ; Reserved                ..................
                DCD     0                          ; Reserved
                DCD     SVC_Handler                ; SVCall Handler
                DCD     DebugMon_Handler           ; Debug Monitor Handler
                DCD     0                          ; Reserved
                DCD     PendSV_Handler             ; PendSV Handler
                DCD     SysTick_Handler            ; SysTick Handler
            ; External Interrupts                                       //外部中断
            DCD     WWDG_IRQHandler                   ; Window WatchDog                                        
            DCD     PVD_IRQHandler                    ; PVD through EXTI Line detection                        
            DCD     TAMP_STAMP_IRQHandler             ; Tamper and TimeStamps through the EXTI line            
            DCD     RTC_WKUP_IRQHandler               ; RTC Wakeup through the EXTI line                       
            DCD     FLASH_IRQHandler                  ; FLASH                                           
            DCD     RCC_IRQHandler                    ; RCC                                             
            DCD     EXTI0_IRQHandler                  ; EXTI Line0                                             
            DCD     EXTI1_IRQHandler                  ; EXTI Line1                                             
            DCD     EXTI2_IRQHandler                  ; EXTI Line2               
                   ..................
                   ..................
                   ..................

2.编写中断服务函数(重点分析Reset_Handler复位中断函数)

; Reset handler
Reset_Handler    PROC                                           //执行Reset_Handler中断服务函数          
                 EXPORT  Reset_Handler             [WEAK]
                 IMPORT  SystemInit                             //输入SystemInit
                 IMPORT  __main                                 //输入__main
             LDR     R0, =SystemInit                        //将SystemInit函数的地址放在R0寄存器中
             BLX     R0                                     //跳转到SystemInit函数执行,并将返回地址放在LR寄存器中,执行完返回!!!
             LDR     R0, =__main                            //将main函数的地址放在R0寄存器中
             BX      R0                                     //跳转到main函数执行,并且不会返回!!!
             ENDP

可以看出,在Reset handler中断服务函数中主要完成了两件事:
(1)调用SystemInit进行系统初始化
(2)跳转到mian处执行并不再跳回(因为BX R0并未保存返回地址至LR)

3.分析SystemInit()函数

SystemInit()系统初始化函数主要做了一下几件事情:
(1)设置是否开启FPU
(2)进行系统时钟初始化配置
(3)设置中断向量表偏移地址(ARM处理器默认中断向量表地址为:0x00000000,这里FLASH起始地址是0x08000000,因此设置偏移地址为0x08000000)

void SystemInit(void)
{
  /* FPU settings ------------------------------------------------------------*/
  #if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
    SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2));  /* set CP10 and CP11 Full Access */
  #endif
  /* Reset the RCC clock configuration to the default reset state ------------*/
  /* Set HSION bit */
  RCC->CR |= (uint32_t)0x00000001;

/* Reset CFGR register */
RCC->CFGR = 0x00000000;

/* Reset HSEON, CSSON and PLLON bits */
RCC->CR &= (uint32_t)0xFEF6FFFF;

/* Reset PLLCFGR register */
RCC->PLLCFGR = 0x24003010;

/* Reset HSEBYP bit */
RCC->CR &= (uint32_t)0xFFFBFFFF;

/* Disable all interrupts */
RCC->CIR = 0x00000000;

#if defined (DATA_IN_ExtSRAM) || defined (DATA_IN_ExtSDRAM)
SystemInit_ExtMemCtl();
#endif /* DATA_IN_ExtSRAM || DATA_IN_ExtSDRAM */

/* Configure the System clock source, PLL Multiplier and Divider factors,
AHB/APBx prescalers and Flash settings ----------------------------------*/

SetSysClock();

/* Configure the Vector Table location add offset address ------------------/
#ifdef VECT_TAB_SRAM
SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; / Vector Table Relocation in Internal SRAM /
#else
SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; / Vector Table Relocation in Internal FLASH */
#endif
}

4. 启动过程总结

<1>初始化堆栈、SP指针、令PC指向Reset_Handler(也就是最先执行复位中断服务函数)
<2>定义中断向量表(后面在中断发送时会根据中断向量表找到中断服务函数的入口地址)
<3>由于最开始PC指针指向了Reset_Handler中断,因此先执行Reset_Handler中断服务函数(进行初始化工作:包括初始化时钟,FPU,设置VTOR中断向量表偏移地址等)
<4>在Reset_Handler服务函数中SystemInit执行完返回并跳转到mian函数(采用BX跳转,不会返回了),进入用户程序执行,至此,系统启动,boot程序作用终结。
<5>通常main函数是while死循环执行的方式,那么当中断来的时候怎样执行呢?别忘了我们在bootloader程序的一开始就定义了中断向量表,将各中断都注册了(包括外部中断和内部中断),并且有系统或用户实现其中断服务函数。当有中断来临时系统会自动跳转到中断向量表,并根据中断号查找中断向量表获得中断服务子程序的入口地址,并跳转指向,中断指向完成后跳回main函数继续指向。

三.cortex-A7内核启动流程

  通常ARM的A系列处理器会基于linux系统做开发,由专门的bootloader程序做引导。常用的例如U-BOOT。这里我们不分析U-BOOT,只是按照上面的MCU裸机开发的方式分析cortex-A7内核启动流程。具体过程和上面大同小异。

1.定义中断向量表

_start:                             //此处为定义中断向量表,后面需写其执行部分
	ldr pc, =Reset_Handler		/* 复位中断 					*/	
	ldr pc, =Undefined_Handler	/* 未定义中断 					*/
	ldr pc, =SVC_Handler		/* SVC(Supervisor)中断 		                */
	ldr pc, =PrefAbort_Handler	/* 预取终止中断 					*/
	ldr pc, =DataAbort_Handler	/* 数据终止中断 					*/
	ldr pc, =NotUsed_Handler	/* 未使用中断					*/
	ldr pc, =IRQ_Handler		/* IRQ中断 					*/
	ldr pc, =FIQ_Handler		/* FIQ(快速中断)未定义中断 			*/

  可以看出Cortex-A中断向量表有8个中断,比上面的M4内核中断少了很多,而且我们常见的中断都没有了,这是为什么?这是因为Cortex-A内核有9种运行模式,其中断系统也更加复杂,因此中间又封装了一层,将所有中断分为了以上几类,我们常用的一些中断都在IRQ_Handler这一大类里面。在上面的表中我们重点关注Reset_Handler和IRQ_Handler。因为Reset_Handler完成了系统初始化,IRQ_Handler是用户级中断。

2.Reset_Handler中断服务函数

/******编写复位中断服务函数Reset_Handler,内容如下:*******/
Reset_Handler:
/*1)关闭I,D Cache和MMU。
	CP15寄存器:
	MRC将CP15协处理器中的寄存器数据读到ARM寄存器中。
	MRC就是读CP15寄存器,MCR就是写CP15寄存器,MCR指令格式如下:*/
	MCR{cond} p15,<opc1>,<Rt>,<CRn>,<CRm>,<opc2>
	MRC p15,0,r0,c0,c0,0
	/*现在要关闭I,D Cache和MMU,打开Cortex-A7参考手册到105页,找到SCTLR寄存器。也就是系统控制寄存器,此寄存器bit0用于打开和关闭MMU,bit1	对其控制位,bit2控制D Cache的打开与关闭。bit11用于控制分支预测,bit12用于控制I Cache。*/
/* 关闭I,DCache和MMU 
 * 采取读-改-写的方式。
 */
mrc     p15, 0, r0, c1, c0, 0     /* 读取CP15的C1寄存器到R0中       		        	*/
bic     r0,  r0, #(0x1 << 12)     /* 清除r0寄存器的bit12位(I位),关闭I Cache            	*/
bic     r0,  r0, #(0x1 <<  2)     /* 清除r0寄存器的bit2(C位),关闭D Cache    	        */
bic     r0,  r0, #0x2             /* 清除r0寄存器的bit1(A位),关闭对齐		 	*/
bic     r0,  r0, #(0x1 << 11)     /* 清除r0寄存器的bit11(Z位),关闭分支预测			*/
bic     r0,  r0, #0x1             /* 清除r0寄存器的bit0(M位),关闭MMU			*/
mcr     p15, 0, r0, c1, c0, 0     /* 将r0寄存器中的值写入到CP15的C1寄存器中	 		*/

/2)设置中断向量表偏移
将新的中断向量表首地址写入到CP15协处理器的VBAR寄存器。
/

MCR{ cond} p15,<opc1>,<Rt>,<CRn>,<CRm>,<opc2>
MRC p15,0,r0,c12,c0,0 //读CP15寄存器
MCR p15,0,r0,c12,c0,0 //写CP15寄存器

/* 汇编版本设置中断向量表偏移,一定要在中断发生之前设置! */
    //注:此处屏蔽掉是因为在C语言进行中断初始化中进行了中断向量表的偏移,因此汇编和C只需初始化一个即可
ldr r0, =0X87800000

dsb                               /* 数据同步指令                */
isb                               /* 指令同步指令                */
mcr p15, 0, r0, c12, c0, 0	  /* 将r0寄存器中的值写入到CP15的C12寄存器中,设置VBAR寄存器=0x87800000*/
dsb                               /* 数据同步指令                */
isb                               /* 指令据同步指令                */

/3)设置处理器9种工作模式对应的SP指针。要使用中断那么必须设置IRQ模式下的SP指针。索性直接设置所有模式下的SP指针。/
/* 设置各个模式下的栈指针(SP指针),
* 注意:IMX6UL的堆栈是向下增长的!
* 堆栈指针地址一定要是4字节地址对齐的!!!
* DDR范围:0X80000000~0X9FFFFFFF
/

/ 进入IRQ模式 /
mrs r0, cpsr
bic r0, r0, #0x1f / 将r0寄存器中的低5位清零,也就是cpsr的M0~M4 /
orr r0, r0, #0x12 / r0或上0x12,表示使用IRQ模式 /
msr cpsr, r0 / 将r0 的数据写入到cpsr_c中 /
ldr sp, =0x80600000 / 设置IRQ模式下的栈首地址为0X80600000,大小为2MB */

/* 进入SYS模式 */
mrs r0, cpsr
bic r0, r0, #0x1f 	/* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4 	*/
orr r0, r0, #0x1f 	/* r0或上0x1f,表示使用SYS模式					*/
msr cpsr, r0		/* 将r0 的数据写入到cpsr_c中 					*/
ldr sp, =0x80400000	/* 设置SYS模式下的栈首地址为0X80400000,大小为2MB */

/* 进入SVC模式 */
mrs r0, cpsr
bic r0, r0, #0x1f 	/* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4 	*/
orr r0, r0, #0x13 	/* r0或上0x13,表示使用SVC模式					*/
msr cpsr, r0		/* 将r0 的数据写入到cpsr_c中 					*/
ldr sp, =0X80200000	/* 设置SVC模式下的栈首地址为0X80200000,大小为2MB */

/*这里只设置了三种模式的栈指针,至于其余六种模式可自行设置!*/

/4)跳到c函数,也就是main函数。/
b main /* 跳转到 main 函数 */

  可以看出,系统初始化工作也是在Reset_Handler复位中断中完成的,并且在完成系统初始化之后就会跳转到main函数执行,且不会再返回。

3.IRQ_Handler中断服务函数

/************************1.功能:保护现场!!!***********************************/
IRQ_Handler:
111 push {lr} /* 保存 lr 地址 */
112 push {r0-r3, r12} /* 保存r0-r3,r12寄存器 ,触发中断的机制会自动保存其他寄存器,对于不能自动保存的需要手动保存*/
113
114 mrs r0, spsr /* 读取 spsr 寄存器 */
115 push {r0} /* 保存 spsr 寄存器 */

/************************2.功能:获取当前IRQ中断中特定中断的中断号,并保存在R0寄存器中,作为C语言调用的一个参数!!!**************/
117 mrc p15, 4, r1, c15, c0, 0 / 将 CP15 的 C0 内的值到 R1 寄存器中,读取CP15的CBAR寄存器。CBAR寄存器保存了GIC控制器的寄存器组首地址。
118 * 参考文档 ARM Cortex-A(armV7)编程手册 V4.0.pdf P49
119 * Cortex-A7 Technical ReferenceManua.pdf P68 P138
120 /

121 add r1, r1, #0X2000 / GIC 基地址加 0X2000,得到 CPU 接口端基地址 /
122 ldr r0, [r1, #0XC] / CPU 接口端基地址加 0X0C 就是 GICC_IAR 寄存器,
123 * GICC_IAR 保存着当前发生中断的中断号,我们要根据
I.MX6U 嵌入式 Linux 驱动开发指南
124 * 这个中断号来绝对调用哪个中断服务函数
125 /

126 push { r0, r1} / 保存 r0,r1 */
/此时r0,r1分别保存了当前发生中断的中断号,CPU 接口端基地址/

128 cps #0x13 /* 进入 SVC 模式,允许其他中断再次进去 /
129
130 push { lr} / 保存 SVC 模式的 lr 寄存器 */

/3.功能:跳转到C语言处去执行具体的中断服务函数,执行完返回!!!***********/
131 ldr r2, =system_irqhandler /* 加载 C 语言中断处理函数到 r2 寄存器中*/
132 blx r2 /* 运行 C 语言中断处理函数,带有一个参数:中断号R0 ,在跳转到C语言函数执行时,自动的将RO作为参数传递给c语言函数*/

/*******4.功能:执行完C语言中断服务函数各寄存器出栈恢复现场,重新返回到曾经被中断打断的地方运行!!!/
134 pop { lr} / 执行完 C 语言中断服务函数,lr 出栈 /
135 cps #0x12 / 进入 IRQ 模式 /
136 pop { r0, r1}
137 str r0, [r1, #0X10] / 中断执行完成,写 EOIR /
138
139 pop { r0}
140 msr spsr_cxsf, r0 / 恢复 spsr /
141
142 pop { r0-r3, r12} / r0-r3,r12 出栈 /
143 pop { lr} / lr 出栈 /
144 subs pc, lr, #4 / 将 lr-4 赋给 pc */

  可以看出IRQ_Handler中断服务函数总体上完成了三件事,在发送通用级用户中断时,系统会在中断向量表中找到IRQ_Handler中断服务函数的入口地址并指向上述代码:上述代码时是汇编写的,大概流程是:1.保护现场----->2.获取更具体的中断号------>3.调用更具体的中断服务函数system_irqhandler,这个函数是有参数的,就是中断号,保存在R0寄存器中---->4.指定具体的中断服务程序,完成后返回------>5.恢复现场,返回main继续执行。

三.总结

  总体来看cortex-M4与cortex-A7的启动流程基本一致:都是定义中断向量表---->执行复位中断服务Reset_Handler并在此中完成系统初始化----->跳转至main函数。
  对于后面中断的处理过程也基本一致:都是查找中断向量表获取中断服务函数入口地址---->执行中断服务函数(保护现场、具体指向、恢复现场)------>指向完返回main。M4与A7的唯一不同可能就是对于系统中断的封装上,M4直接将所有中断都列在了中断向量表中。而A7将所有中断分为了两级,其中我们常见的中断都放在IRQ_Handler中,中断发生时先执行IRQ_Handler,在IRQ_Handler中再具体指向中断处理。

你可能感兴趣的:(转载,嵌入式)