eCos启动过程详解,基于Cortex-M架构

来源:互联网 发布:阿里云域名交易 编辑:程序博客网 时间:2024/04/28 14:24

eCos是开源免版税的抢占式实时操作系统。其最大亮点是可配置,与其配套的图形化配置工具提供组件管理、选项配置、自动化单元测试等。eCos核心组件包括硬件抽象层(HAL)、设备驱动(IO)、实时内核(两种调度算法可选)、线程安全的C库、POSIX兼容层、文件系统(FAT、JFFS2、ROMFS)、协议栈(lwIP、OpenBSD、FreeBSD)、图形系统(Nano-X)等,同时支持第三方扩展组件。官网http://ecos.sourceware.org,中文专业论坛http://www.52ecos.net。

mingdu.zheng <at> gmail <dot> com
http://blog.csdn.net/zoomdy/article/details/12789535

Cortex-M基础

Cortex-M将执行模式分成handler模式和thread模式,进入异常或中断处理则进入handler模式,其他情况则为thread模式。Cortex-M有两个运行级别,分别为特权级和用户级(非特权级),handler模式总是运行在特权级,而thread模式可以运行在特权级也可以运行在用户级,这通过CONTROL特殊寄存器控制。Cortex-M的堆栈寄存器SP对应两个物理寄存器MSP和PSP,MSP为主堆栈,PSP为进程堆栈,handler模式总是使用MSP作为堆栈,thread模式可以选择使用MSP或PSP作为堆栈,同样通过CONTROL特殊寄存器控制。

复位后,Cortex-M进入thread模式、特权级、使用MSP堆栈,并从向量表项0处取SP寄存器值,从向量表项1处取PC寄存器值,然后从PC寄存器值处开始执行,一般情况下默认向量表存储在地址0x00000000处,不同的变种其默认向量表可能不同。

向量表

Cortex-M复位后首先从默认向量表处读取SP初始值和PC初始值。为了让Cortex-M复位后立即有可用的复位向量,必须将向量表存储在ROM中,然后在初始化过程可选地将向量表地址映射到其它区域。eCos为Cortex-M准备的向量表主要是存储在RAM中,因为eCos需要支持修改向量表项,但是仍然要为复位动作准备存储在ROM中的向量表,ROM中的向量表仅有两项,分别为SP初始值和PC初始值,刚好满足Cortex-M复位的需求。

[cpp] view plaincopy
  1. // hal/cortexm/arch/<version>/src/vectors.S:79  
  2.         .section        ".vectors","ax"  
  3.         .global         hal_vsr_table  
  4. hal_vsr_table_init:  
  5.         .long           hal_startup_stack   
  6.         .long           hal_reset_vsr  

(2)通过.section伪指令将向量表存储在.vectors节,.vectors会在target.ld链接控制文件中特殊对待,将其定位到指定的地址,一般情况下为0x00000000,这是Cortex-M复位后的向量表起始地址。

(5)向量表项0为堆栈地址,Cortex-M的堆栈为递减的满栈,因此这里的值应当为堆栈最高地址+1,例如堆栈空间分配在0x20001000~0x20001FFF,那么这里应当为0x20002000。hal_startup_stack符号是在target.ld中定义的。

(6)向量表项1为复位向量,存储初始化函数地址,这里为hal_reset_vsr,是在hal_misc.c中定义的C函数。

伪入口

在调试过程中,不会产生复位来初始化SP和PC,为了能够配合调试软件模拟复位过程的SP和PC寄存器的初始化,eCos提供了伪入口,伪入口将初始化SP,并跳转到初始化函数hal_reset_vsr。

[cpp] view plaincopy
  1. // hal/cortexm/arch/<version>/src/vectors.S:100  
  2.         .type   reset_vector, %function  
  3. reset_vector:  
  4.         ldr     sp,=hal_startup_stack  
  5.         b       hal_reset_vsr  

(2)reset_vector是个普通函数,不需要定位到特殊位置,target.ld将reset_vector设置为入口地址,调试器加载程序后会将PC指向reset_vector所在的地址。

(4)模拟复位时读取向量0初始化SP的过程。

(5)模拟复位时跳转到向量1所指函数的过程,这里是hal_reset_vsr。

复位向量hal_reset_vsr

整个初始化过程主要由hal_reset_vsr函数完成,hal_reset_vsr函数的基本流程:调用平台相同的系统初始化、初始化异常向量表、更改运行模式、初始化数据区、初始化中断向量表、调用变种初始化函数、调用平台初始化函数、调用全局静态对象构造函数、调用cyg_start进入用户代码。

调用平台相同的系统初始化

[cpp] view plaincopy
  1. // hal/cortexm/arch/<version>/src/hal_misc.c:131  
  2. void hal_reset_vsr( void )  
  3. {  
  4.     hal_system_init();  

(4)hal_system_init函数通常由平台层HAL提供,主要进行基础设施的初始化,例如时钟、外扩存储器、IO配置等,后续的初始化过程将依赖于这些基础设施,因此必须最先被初始化,平台层HAL还提供非基础设置的初始化函数hal_platform_init,hal_platform_init函数将在初始化的末尾调用。

初始化异常向量表

[cpp] view plaincopy
  1. // hal/cortexm/arch/<version>/src/hal_misc.c:157  
  2.     for( i = 2; i < 15; i++ )  
  3.         hal_vsr_table[i] = (CYG_ADDRESS)hal_default_exception_vsr;  
  4.   
  5.     hal_vsr_table[CYGNUM_HAL_VECTOR_SERVICE] = (CYG_ADDRESS)hal_default_svc_vsr;  
  6.     hal_vsr_table[CYGNUM_HAL_VECTOR_PENDSV] = (CYG_ADDRESS)hal_pendable_svc_vsr;  
  7.   
  8.     for( i = CYGNUM_HAL_VECTOR_SYS_TICK ;  
  9.          i < CYGNUM_HAL_VSR_MAX;  
  10.          i++ )  
  11.         hal_vsr_table[i] = (CYG_ADDRESS)hal_default_interrupt_vsr;  
  12.   
  13.     HAL_WRITE_UINT32( CYGARC_REG_NVIC_BASE+CYGARC_REG_NVIC_VTOR,  
  14.                       CYGARC_REG_NVIC_VTOR_TBLOFF(0)|  
  15.                       CYGARC_REG_NVIC_VTOR_TBLBASE_SRAM );  

(2)初始化异常向量表不包括堆栈指针和复位向量,因为这两项仅在系统复位时需要,而系统复位后默认从ROM中读取,因此这里不需要初始化,此外向量15为SysTick对应的向量,eCos将SysTick作为中断处理而不是异常,这是非常合理的。

(3)初始化成默认的异常处理函数hal_default_exception_vsr。

(5,6)SVC和PendSV使用专门的异常处理函数hal_default_svc_vsr和hal_pendable_svc_vsr。

(8)从SysTick开始的向量作为中断处理,默认处理函数为hal_default_interrupt_vsr。

(13)将初始化完成的向量表地址写入NVIC的VTOR寄存器,从这一刻起,Cortex-M将从hal_vsr_table读取异常向量。

更改运行模式

Cortex-M复位后,进入特权级thread模式,使用MSP堆栈。eCos希望初始化过程以及线程使用PSP,而把MSP留给异常或中断处理单独使用。因此接下来eCos需要进行一次运行模式的改变,将当前使用的SP由MSP更改为PSP,并且关闭中断,eCos初始化过程是要求关中断的。

[cpp] view plaincopy
  1. // hal/cortexm/arch/<version>/src/hal_misc.c:201  
  2.     hal_vsr_table[CYGNUM_HAL_VECTOR_SERVICE] = (CYG_ADDRESS)hal_switch_state_vsr;  
  3.     __asm__ volatile"swi 0" );  
  4.     hal_vsr_table[CYGNUM_HAL_VECTOR_SERVICE] = (CYG_ADDRESS)hal_default_svc_vsr;  

(2)将SVC异常向量更改为hal_switch_state_vsr,这是汇编实现的异常处理函数,后面再解释。

(3)插入swi指令,swi是系统调用指令,执行该指令将产生SVC异常,上一行代码刚刚SVC异常处理向量更改为hal_switch_state_vsr,因此执行swi指令后将会通过异常处理机制跳转到hal_switch_state_vsr执行。

(4)恢复SVC向量。

[cpp] view plaincopy
  1. // hal/cortexm/arch/<version>/src/vectors.S:130  
  2.         .type   hal_switch_state_vsr, %function  
  3. hal_switch_state_vsr:  
  4.   
  5.         mov     r0,#CYGNUM_HAL_CORTEXM_PRIORITY_MAX  
  6.         msr     basepri,r0  
  7.   
  8.         mov     r0,#2                   // Set CONTROL register to 2  
  9.         msr     control,r0  
  10.         isb                             // Insert a barrier  
  11.   
  12.         mov     r0,sp  
  13.         msr     psp,r0                  // Copy SP to PSP  
  14.   
  15.         orr     lr,#0xD                 // Adjust return link  
  16.         bx      lr                      // Return to init code on PSP now  

(2)hal_switch_state_vsr是个异常处理函数,在Cortex-M中异常处理函数跟普通函数没有区别,因为Cortex-M的异常处理机制会模拟C函数调用过程来调用异常处理函数,看起来就像是调用了一个普通函数。

(6)首先将最高优先级写入basepri寄存器,这起到关中断的作用,因为所有优先级低于等于basepri的中断都被屏蔽了,在Cortex-M中优先级越高其对应的数值越小,因此从数值上讲,所有优先级数值大于等于basepri值的中断都被屏蔽,但是basepri不会屏蔽HardFault、NMI,因此初始化过程不可能会产生外部中断(包括SysTick异常)但是可能会产生异常,这也是需要在切换运行状态之前首先初始化向量表的原因。eCos没有使用primask和faultmask寄存器来关中断,因为eCos希望即使是在关中断的情况下也能处理异常。

(9)将control寄存器设置为2,即:选择PSP作为thread模式指针,thread运行在特权级上。

(10)插入isb指令,清洗流水线,确保前面的所有指令都执行完成后再执行后续的指令,这保证刚刚设置起作用后在执行后续的指令。

(13)将MSP的值复制给PSP,这个函数是通过异常机制调用的,处理异常时使用handler模式,handler模式总是使用MSP作为堆栈指针,也就是说这里的SP实际上是映射到MSP的,SP的值就是MSP的值,至此,PSP的值与MSP的值一模一样,因此从MSP变更到PSP不会有任何问题。

(16)从异常状态返回,因为lr的低4位被修改成0xD,因此返回的过程从PSP中处栈,回到thread模式后使用PSP作为堆栈寄存器。

疑问:为什么要通过SVC系统调用异常来调用这个函数,如果按照普通函数的形式调用会有什么问题吗?

初始化数据区

更改模式后,继续回到hal_misc.c,接下来的代码是在特权级thread模式,使用PSP的情况下执行的。初始化数据过程将初始化数据从ROM拷贝到RAM,并将初始化为0的区域清除为0。初始化数据区的内容包括定义时带有初始值的全局变量或静态变量,不包括全局或静态的C++对象实例,全局或静态C++对象实例的初始化见后文。

[cpp] view plaincopy
  1. // hal/cortexm/arch/<version>/src/hal_misc.c:211  
  2.     {  
  3.         register cyg_uint32 *p, *q;  
  4.         for( p = &__ram_data_start, q = &__rom_data_start;  
  5.              p < &__ram_data_end;  
  6.              p++, q++ )  
  7.             *p = *q;  
  8.     }  
  9.   
  10.     {  
  11.         register cyg_uint32 *p, *q;  
  12.         for( p = &__sram_data_start, q = &__srom_data_start;  
  13.              p < &__sram_data_end;  
  14.              p++, q++ )  
  15.             *p = *q;  
  16.     }  
  17.   
  18.     {  
  19.         register cyg_uint32 *p;  
  20.         for( p = &__bss_start; p < &__bss_end; p++ )  
  21.             *p = 0;  
  22.     }  

(4)初始化RAM中.data段,初始化数据被存储在ROM中,因此将ROM中的初始化数据拷贝到RAM即可,这里RAM可能是芯片内部SRAM的一部分,也有可能是外部扩展的SRAM或DRAM,根据系统配置不同而不同。

(12)初始化SRAM中的.data段,初始化数据被存储在ROM中,因此将ROM中的初始化数据拷贝到RAM即可,SRAM指的是芯片内部的SRAM。

(20)将.bss段的数据清零,凡是存储在.bss段的数据,要么其定义时的初始值被赋值为0,要么没有定义初始值,这两种情况下,均将其清零。

疑问:.data被分成内部RAM和外部RAM两种情况,为什么.bss字段没有考虑两部分呢?

初始化中断向量表

[cpp] view plaincopy
  1. // hal/cortexm/arch/<version>/src/hal_misc.c:240  
  2.     {  
  3.         register int i;  
  4.   
  5.         HAL_WRITE_UINT32( CYGARC_REG_NVIC_BASE+CYGARC_REG_NVIC_SHPR0, 0x00000000 );  
  6.         HAL_WRITE_UINT32( CYGARC_REG_NVIC_BASE+CYGARC_REG_NVIC_SHPR1, 0xFF000000 );  
  7.         HAL_WRITE_UINT32( CYGARC_REG_NVIC_BASE+CYGARC_REG_NVIC_SHPR2, 0x00FF0000 );  
  8.   
  9.         hal_interrupt_handlers[CYGNUM_HAL_INTERRUPT_SYS_TICK] = (CYG_ADDRESS)hal_default_isr;  
  10.   
  11.         for( i = 1; i < CYGNUM_HAL_ISR_COUNT; i++ )  
  12.         {  
  13.             hal_interrupt_handlers[i] = (CYG_ADDRESS)hal_default_isr;  
  14.             HAL_WRITE_UINT8( CYGARC_REG_NVIC_BASE+CYGARC_REG_NVIC_PR(i-CYGNUM_HAL_INTERRUPT_EXTERNAL), 0x80 );  
  15.         }  
  16.     }  

(5)将存储器管理fault异常、总线fault异常、用法fault异常的优先级设置为0,在eCos中,中断的优先级至少大于0,因此这三个异常比任何中断的优先级都高,这也是合理的,异常比中断更要紧。优先级寄存器是8位的,这里使用32位方式写,一次写4个优先级。

(6)将SVC异常优先级设置为0xFF,这是Cortex-M的最低优先级,也就是说SVC异常的优先级低于任何中断优先级。

(7)将PendSVC异常优先级设置为0xFF,SysTick异常优先级设置为0。

(9)设置SysTick的中断服务函数为默认中断服务函数hal_default_isr,上文提到过,eCos将Cortex-M中16个系统异常中的SysTick异常作为中断处理,而且是第0号中断。

(13)将其余的中断服务函数均设置成默认中断服务函数hal_default_isr。

(14)设置中断优先级初始值为0x80,这个优先级高于SVC异常和PendSVC异常的优先级,低于Fault异常的优先级。但SysTick的优先级被设置为0。

疑问:SysTick是否应该和其它中断一致对待,设置其默认优先级为0x80?

使能异常

接下来使能用法fault异常、总线fault异常和存储器管理fault异常,这让eCos有机会捕获异常并通知调试器或内核。在fault异常屏蔽的情况,如果发生异常将会上访成硬件fault异常。

[cpp] view plaincopy
  1. // hal/cortexm/arch/<version>/src/hal_misc.c:278  
  2.     HAL_READ_UINT32( base+CYGARC_REG_NVIC_SHCSR, shcsr );  
  3.     shcsr |= CYGARC_REG_NVIC_SHCSR_USGFAULTENA;  
  4.     shcsr |= CYGARC_REG_NVIC_SHCSR_BUSFAULTENA;  
  5.     shcsr |= CYGARC_REG_NVIC_SHCSR_MEMFAULTENA;  
  6.     HAL_WRITE_UINT32( base+CYGARC_REG_NVIC_SHCSR, shcsr );  

变体初始化

同样是基于Cortex-M,不同的芯片系列会有不同的外设,每个系列有其对应的变体HAL层,通过hal_variant_init初始化变体层HAL。

[cpp] view plaincopy
  1. // hal/cortexm/arch/<version>/src/hal_misc.c:287  
  2.     hal_variant_init();  

平台初始化

相同的芯片还被应用于不同的目标机,每个目标机都有其对应的平台HAL层,通过hal_platform_init初始化平台层HAL,平台层初始化可以包括部分外围器件的初始化。

[cpp] view plaincopy
  1. // hal/cortexm/arch/<version>/src/hal_misc.c:288  
  2.     hal_platform_init();  

初始化滴答定时器

滴答定时器是操作系统的脉搏,这里初始化滴答定时器,需要注意的是,这里进行的初始化工作进行设置合适的寄存器值,然后让滴答定时器自由运行,并不会产生中断,滴答定时器的中断向量安装和中断使能通过内核的时钟模块完成。

[cpp] view plaincopy
  1. // hal/cortexm/arch/<version>/src/hal_misc.c:291  
  2.     HAL_CLOCK_INITIALIZE( CYGNUM_HAL_RTC_PERIOD );  

初始化C++全局或静态对象

参考《eCos组件初始化》

[cpp] view plaincopy
  1. // hal/cortexm/arch/<version>/src/hal_misc.c:307  
  2.     cyg_hal_invoke_constructors();  

进入用户代码

[cpp] view plaincopy
  1. // hal/cortexm/arch/<version>/src/hal_misc.c:307  
  2.     cyg_start();  
  3.     for(;;);  

(2)进入用户代码cyg_start,如果用户代码入口不是cyg_start,那么eCos将提供默认实现的cyg_start,并在cyg_start中调用用户入口代码cyg_user_start或者main。

(3)嵌入式系统中,进入用户代码后,无论有没有包括内核支持都不应该返回到这里,如果万一返回到这里,那么将进入死循环,除了消耗CPU周期啥也不做。


原文见:http://blog.csdn.net/zoomdy/article/details/12789535

0 0
原创粉丝点击