Starterware工程与自建工程默认ARM Mode的区别由来
3-22-2016 Tony Tang
针对有不少用户对TI提供的starterware工程,以及自建的CCS工程里的ARM模式存在疑问,觉得有必要做个简单总结,以理清思路,节省大家的时间为目的。
#1. ARMV4以上的内核有7个不同的模式,其状态可以通过CPSR寄存器确定。(至于为什么要分这么多种模式,不是本小结的讨论内容)
(1) User Mode:用户模式。操作系统的Task一般以这种模式执行。User Mode是ARM唯一的非特权模式,这表示如果CPU处于这种模式下,很多指令将不能够执行,因此操作系统的资源得以保护。 (2) System Mode:这是V4及其以上版本所引入的特权模式。、 (3) IRQ Mode:中断模式。中断(不包括软中断)处理函数在这种模式下执行。 (4) FIQ Mode:快速中断模式。除了多了几个寄存器外,其他同IRQ一样。 (5) Supervisor Mode:管理模式。软中断(SWI)处理函数在这种模式下执行。 (6) Abort Mode:所有同内存保护相关的异常均在这种模式下执行。 (7) Undefined Mode:处理无效指令的异常处理函数在这种模式下执行
#2. ARM在不同的模式下有不同的权限,以OMAP-L138为例,修改系统寄存器需要在supervisor或system mode这种privilege模式下才行,比如配置PLL,PINMUX等。
#3. 大家发现用starterware提供的工程,一切都是那么顺利,似乎这些模式与自己无关,所以从未关心过。但是一旦自己用CCS新建一个工程,就什么也配置不了了,一看CPSR原来是在用户模式,但怎么进入privilege模式呢~~~
#4. 百度~~~ARM在user mode下要通过SWI才能进入supervisor模式,spnu151L文档里也提供了call_swi()函数接口,还可以带个参数,可是为什么一调用就跑飞了呢?还有这个参数是啥意思?
#5. 不管查什么资料,基本上都说ARM上电启动后是supervisor模式,而且上电后连上仿真器,也可以看到CPSR显示的是supervisor模式。可是运行到main就变成user mode了,那么在main之前干了啥~~~
#6. C工程从来都不是从main开始运行的,注意到map文件没,里面的entry_point是_c_int00, 如果不知道它是干啥的,看一下spnu151l的第6章,简而言之,就是C环境初始化,(写汇编可从来没这个说话). 还是看看_c_int00这个函数做了啥吧(这个函数在RTS库里,源文件在CCS的编译器安装目录下boot.asm)。
;*——————————————————
;* SET TO USER MODE
;*——————————————————
MRS r0, cpsr
BIC r0, r0, #0x1F ; CLEAR MODES
ORR r0, r0, #0x10 ; SET USER MODE
MSR cpsr_cf, r0
简单来看,就在这里切换到了user mode,然后初始化了user mode的堆栈,再是对初始化段做初始化(__TI_auto_init),再就跳到我们的main了
;*——————————————————
;* INITIALIZE THE USER MODE STACK
;*——————————————————
.if __TI_AVOID_EMBEDDED_CONSTANTS
MOVW sp, __stack
MOVT sp, __stack
MOVW r0, __STACK_SIZE
MOVT r0, __STACK_SIZE
.else
LDR sp, c_stack
LDR r0, c_STACK_SIZE
.endif
ADD sp, sp, r0
;*—————————————————–
;* ALIGN THE STACK TO 64-BITS IF EABI.
;*—————————————————–
.if __TI_EABI_ASSEMBLER
BIC sp, sp, #0x07 ; Clear upper 3 bits for 64-bit alignment.
.endif
;*—————————————————–
;* SAVE CURRENT STACK POINTER FOR SDP ANALYSIS
;*—————————————————–
.if __TI_AVOID_EMBEDDED_CONSTANTS
MOVW r0, MAIN_FUNC_SP
MOVT r0, MAIN_FUNC_SP
.else
LDR r0, c_mf_sp
.endif
STR sp, [r0]
;*——————————————————
;* Perform all the required initilizations:
;* – Process BINIT Table
;* – Perform C auto initialization
;* – Call global constructors
;*——————————————————
BL __TI_auto_init
;*——————————————————
;* CALL APPLICATION
;*——————————————————
BL ARGS_MAIN_RTN
#6. 怎么办,最简单的办法就是把这个文件加到工程里去,然后把切换模式这段代码删掉就行了(因为在工程里源文件与库文件存在同样的lable,会优先调用源文件的),但是后面想切换到user,又想从user切换到supervisor怎么办呢,再说吧~~~。
#7. 现在再来看看starterware的工程,注意一下cmd文件,里面都加了一句-e Entry,也就是说starterware的工程不是从_c_int00开始执行的,所以编译输出信息里也相应多了一句警告,这没关系,只要在main之前把该做的做了就行,_c_int00只是提供了一个简便的现成的函数,因为通常都是做同样的事件,没必要大家每次自己写一遍嘛。
#8. 对比一下Entry与_c_int00的区别吧。Entry在starterware的system_config\armv5\下对应的编译器类型目录下的init.asm文件,
Entry:
;
; Set up the Stack for Undefined mode
;
LDR r0, _stackptr ; Read and align the stack pointer
SUB r0, r0, #8
BIC r0, r0, #7
MSR cpsr_c, #MODE_UND|I_F_BIT ; switch to undef mode
MOV sp,r0 ; write the stack pointer
SUB r0, r0, #UND_STACK_SIZE ; give stack space
;
; Set up the Stack for abort mode
;
MSR cpsr_c, #MODE_ABT|I_F_BIT ; Change to abort mode
MOV sp, r0 ; write the stack pointer
SUB r0,r0, #ABT_STACK_SIZE ; give stack space
;
; Set up the Stack for FIQ mode
;
MSR cpsr_c, #MODE_FIQ|I_F_BIT ; change to FIQ mode
MOV sp,r0 ; write the stack pointer
SUB r0,r0, #FIQ_STACK_SIZE ; give stack space
;
; Set up the Stack for IRQ mode
;
MSR cpsr_c, #MODE_IRQ|I_F_BIT ; change to IRQ mode
MOV sp,r0 ; write the stack pointer
SUB r0,r0, #IRQ_STACK_SIZE ; give stack space
;
; Set up the Stack for SVC mode
;
MSR cpsr_c, #MODE_SVC|I_F_BIT ; change to SVC mode
MOV sp,r0 ; write the stack pointer
SUB r0,r0, #SVC_STACK_SIZE ; give stack space
;
; Set up the Stack for USer/System mode
;
MSR cpsr_c, #MODE_SYS|I_F_BIT ; change to system mode
MOV sp,r0 ; write the stack pointer
原来在这里初始化了各模式下的堆栈,顺便在最后设定在了system mode。再往下看:
;
; Clear the BSS section here
;
Clear_Bss_Section:
LDR r0, _bss_start ; Start address of BSS
LDR r1, _bss_end ; End address of BSS
SUB r1,r1,#4
MOV r2, #0
Loop:
STR r2, [r0], #4 ; Clear one word in BSS
CMP r0, r1
BLE Loop ; Clear till BSS end
BL __TI_auto_init ; Call TI auto init
;
; Enter the start_boot function. The execution still happens in system mode
;
LDR r10, _start_boot ; Get the address of start_boot
MOV lr,pc ; Dummy return
BX r10 ; Branch to start_boot
SUB pc, pc, #0x08 ; looping
; MSR cpsr_c, #MODE_SVC|I_F_BIT ; change to SVC mode
; BX lr
;
; End of the file
上面的_TI_auto_init还是一样调用的RTS库的,然后调用了start_boot(startup.c),
unsignedintstart_boot(void)
{
/* Enable write-protection for registers of SYSCFG module. */
SysCfgRegistersLock();
/* Disable write-protection for registers of SYSCFG module. */
SysCfgRegistersUnlock();
PSCModuleControl(SOC_PSC_1_REGS,HW_PSC_UART2, 0, PSC_MDCTL_NEXT_ENABLE);
PSCModuleControl(SOC_PSC_0_REGS,HW_PSC_AINTC, 0, PSC_MDCTL_NEXT_ENABLE);
/* Initialize the vector table with opcodes */
CopyVectorTable();
/* Calling the main */
main();
while(1);
}
上面在调用main之前这一步很关键了。将异常向量表搬到了0xFFFF0000开始处(在L138上规定ARM的异常向量表只能放这,可以看一下L138 TRM手册的2.4节)。
staticunsignedintconst vecTbl[14]=
{
0xE59FF018, // 0x00: RESET
0xE59FF018, // 0x04: Undefined
0xE59FF018, // 0x08: SWI
0xE59FF018, // 0x0C: Abort prefetch
0xE59FF014, // 0x10: Abort Data
0xE24FF008, // 0x14: reserved
0xE59FF010, // 0x18: IRQ
0xE59FF010, // 0x1C: FIQ
(unsignedint)Entry, //0x20
(unsignedint)UndefInstHandler, //0x24
(unsignedint)SWIHandler, //0x28
(unsignedint)AbortHandler, //0x2C
(unsignedint)IRQHandler, //0x30
(unsignedint)FIQHandler //0x34
};
staticvoidCopyVectorTable(void)
{
unsignedint *dest = (unsignedint *)0xFFFF0000;
unsignedint *src = (unsignedint *)vecTbl;
unsignedint count;
for(count = 0; count <sizeof(vecTbl)/sizeof(vecTbl[0]); count++)
{
dest[count] = src[count];
}
}
#9. 至于上面那个数组的trick,在内存上显示其代表的汇编就明白了,它巧妙的在向量表上加上了跳转到各异常向量的处理handler(这要通过对指定格式的了解才能整出这么个数值来):
后面带着system mode进入main后,就由用户自由发挥了。但是这里跟前面那个改boot.asm进入main后的区别是什么呢?前面我也提到如果想进切换模式怎么办。
现在的基于starterware的工程可以调用cpu.c文件里的函数进行模式切换,但是前面那种修改的工程是不行了,一调用就跑飞。原因在于,在调用swi时,程序会跳转到SWI位置即0xFFFF0008, 然后跳转到SWIHandle(见exceptionhandler.asm),
SWIHandler:
STMFD r13!, {r0-r1, r14} ; Save context in SVC stack
LDR r0, [r14, #-4] ; R0 points to SWI instruction
BIC r0, r0, #MASK_SWI_NUM ; Get the SWI number
CMP r0, #458752
MRSEQ r1, spsr ; Copy SPSR
ORREQ r1, r1, #0x1F ; Change the mode to System
MSREQ spsr_cf, r1 ; Restore SPSR
LDMFD r13!, {r0-r1, pc}^ ; Restore registers from IRQ stack
这里就进入了supervisor mode,而且对传进来的参数做了对比确认,理论上可以做一长串的参数对比,做不同分支的处理。
为什么自己建的CCS工程不行呢,因为还没有做异常向量表的初始化,调用SWI时,系统自动跳转到0xFFFF0008,谁知道这时候这地方是一条什么指令呢,所以在调用之前必需要先初始化异常向量表等等,有兴趣自己实现吧。
附上我基于starterware简化过来的工程。
weihua li:
好贴,加精。
只可惜附件工程导入失败:提示
See details below. Error: Import failed for project 'arm9_previlege_Mode' because its meta-data cannot be interpreted. Please contact support.
Tony Tang:
回复 weihua li:
我是用CCSV6. Version: 6.1.2.00015建的工程。目前电脑没装CCSV5.
weihua li:
回复 Tony Tang:
上传一个我的CCS V5.5下面的最小工程,供大家参考
其中:
arm端,使用定时器点了个灯,一秒一次。
dsp端,带dsp/bios系统,点灯,同样一秒一次。
Zhihua Ge:
学习了。
另外:
文中提到的,spru151L文档 应该是spnu151L(ARM Optimizing C/C++ Compiler v15.12.0.LTS User's Guide)吧。
Tony Tang:
回复 Zhihua Ge:
谢谢提醒,已更正。
luo qi:
写的非常好,如果早3年看到这篇文章,当时就不会绕太多弯路了
user1552761:
你好,我将startware的工程移植到 CCS3.3环境下,编译提示 找不到 __TI_auto_init ,请问是要添加哪个库呢?
Tony Tang:
回复 user1552761:
上面总结里说了,这是RTS库里的函数,了解一下什么是RTS库:
上面的_TI_auto_init还是一样调用的RTS库的,然后调用了start_boot(startup.c),
user4699890:
我在我自己新建的工程里面添加的 -e Entry 编译出错,请问是什么原因?
**** Build of configuration Debug for project test ****
"C:\\ti\\ccsv5\\utils\\bin\\gmake" -k all 'Building target: test.out''Invoking: ARM Linker'"C:/ti/ti-cgt-arm_16.9.3.LTS/bin/armcl" -mv5e –code_state=32 -me –advice:power="all" –define=omapl132 -g –diag_warning=225 –diag_wrap=off –display_error_number –abi=eabi -z -m"test.map" –heap_size=0x800 –stack_size=0x800 -i"C:/ti/ti-cgt-arm_16.9.3.LTS/lib" -i"C:/ti/OMAPL138_StarterWare_1_10_04_01/binary/armv5/cgt_ccs/omapl138/system_config/Debug" -i"C:/ti/OMAPL138_StarterWare_1_10_04_01/binary/armv5/cgt_ccs/utils/Debug" -i"C:/ti/OMAPL138_StarterWare_1_10_04_01/binary/armv5/cgt_ccs/omapl138/drivers/Debug" -i"C:/ti/ti-cgt-arm_16.9.3.LTS/include" –reread_libs –diag_wrap=off –display_error_number –warn_sections –xml_link_info="test_linkInfo.xml" –rom_model -o "test.out" "./main.obj" "../OMAPL132.cmd" -l"libc.a" -l"system_config.lib" -lutils.lib -ldrivers.lib <Linking>
undefined first referenced symbol in file ——— —————- SysCfgRegistersLock C:/ti/OMAPL138_StarterWare_1_10_04_01/binary/armv5/cgt_ccs/omapl138/system_config/Debug/system_config.lib<startup.obj> SysCfgRegistersUnlock C:/ti/OMAPL138_StarterWare_1_10_04_01/binary/armv5/cgt_ccs/omapl138/system_config/Debug/system_config.lib<startup.obj>
error #10234-D: unresolved symbols remainremark #10371-D: (ULP 1.1) Detected no uses of low power mode state changing instructionswarning #10063-D: entry-point symbol other than "_c_int00" specified: "Entry"error #10010: errors encountered during linking; "test.out" not built
>> Compilation failuregmake: *** [test.out] Error 1gmake: Target `all' not remade because of errors.
**** Build Finished ****