打印
[应用相关]

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

[复制链接]
1933|11
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
tfqi|  楼主 | 2021-7-7 12:53 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
一.概述
  在系统上电时需要先执行一段引导程序,也称bootloader程序,来完成对系统运行环境的初始化工作。就和 PC 上的 BIOS程序一样,bootloader 就相当于 BIOS。
  基于ARM架构的处理器其内核启动流程大同小异,本文就基于cortex-M4内核的MCU以及基于cortex-A7内核的MPU,对其启动流程做简要分析。


使用特权

评论回复
沙发
tfqi|  楼主 | 2021-7-7 12:54 | 只看该作者
二.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函数。


使用特权

评论回复
板凳
tfqi|  楼主 | 2021-7-7 12:55 | 只看该作者
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               
                       ..................
                       ..................
                       ..................


使用特权

评论回复
地板
tfqi|  楼主 | 2021-7-7 12:56 | 只看该作者
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)


使用特权

评论回复
5
tfqi|  楼主 | 2021-7-7 12:57 | 只看该作者
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
}


使用特权

评论回复
6
tfqi|  楼主 | 2021-7-7 12:58 | 只看该作者
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函数继续指向。


使用特权

评论回复
7
tfqi|  楼主 | 2021-7-7 12:59 | 只看该作者
三.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是用户级中断。


使用特权

评论回复
8
tfqi|  楼主 | 2021-7-7 13:00 | 只看该作者
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函数执行,且不会再返回。


使用特权

评论回复
9
tfqi|  楼主 | 2021-7-7 13:01 | 只看该作者
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继续执行。


使用特权

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


使用特权

评论回复
11
木木guainv| | 2021-8-6 12:34 | 只看该作者
二者有可以互相借鉴的东西吗

使用特权

评论回复
12
xiaoqizi| | 2021-8-6 12:39 | 只看该作者
如何编辑引导程序呢

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

56

主题

3316

帖子

4

粉丝