2440(ARM9)中Ucos移植相关
Keil 中irq作用
如果在中断函数上使用__irq,编译器会自动将一些寄存器压栈,等该中断函数返回时自动出栈。关于keil中__irq说明参考网址:http://www.keil.com/support/man/docs/armccref/armccref_babicjbc.htm。
针对手动保存寄存器的函数,不用使用__irq。参考:http://hi.baidu.com/chongxing01/item/3c23c1ca9ad3183399b49827。
Ucos中是手动保存中断寄存器内容,支持中断嵌套。因此中断函数不使用__irq。若使用__irq会使程序运行错误。
Note:
2440系统刚起时是管理SVC模式。针对ucos各个任务,网上的程序一般都运行在SVC模式。但我觉得在user/system模式应该也是可以的。可参考uCOS-II的中断-ARM7实现中断嵌套的方法探究。但得修改os_cpu_c.s中断函数的代码。
2440启动后默认是小端模式,但可以改成大端模式
ARM9部分命令解释
在理解文件之前,应该先简要记住下面几个在文件中用到的命令。
MRS:Move PSR to register,将程序状态寄存器的值复制到通用寄存器
例:MRS R0,CPSR; 传送CPSR 的内容到 R0
MSR:Move Register to PSR,将通用寄存器的值复制到到程序状态寄存器
例:MSR CPSR_c,R0; 传送 R0 的内容到 SPSR,但仅仅修改 CPSR 中的控制位域
LDR:Load word,装载一个字数据(立即数/内容)到一个寄存器
例:LDR R0, [R1,R2]!; 将存储器地址为 R1+R2 的字数据读入R0,并将新地址R1+R2 写入 R1
LDRB:Load byte,装载一个字节数据(立即数/内容)到一个寄存器
例:LDRB R0,[R1,#8]; 将存储器地址为R1+8的字节数据读入R0,并将R0的高 24 位清零
STR:store,装载寄存器的字数据到地址的内容里
例:STR R0,[R1], #8; 将R0中的字数据写入R1为地址的存储器中,并将新地址R1+8 写入 R1
STRB:Store byte,装载寄存器的字节数据到地址的内容里
例:STRB R0, [RS#8] ;将寄存器 R0 中的字节数据写入以 R1+8 为地址的存储
器中
LDM:load multiple,将寄存器地址的内容顺序恢复(出栈)到一系列寄存器组中。
例:LDMFD R13!,{R0,R4-R12, PC}^;R13即SP。将堆栈内容顺序弹出(此时堆栈递增)到寄存器R0,R4-R12,LR。后缀!表示最后的地址写会到R13内,末尾^意味这cpsr的值将从spsr中得到恢复,这只有在pc同时也被装载的情况下才有效。如果pc没有被装载,那么^只恢复用户模式的备份寄存器组。
STM:store multiple,将一系列寄存器值顺序存入(压栈)到某一寄存器地址的内容中
STMFD R13!, {R0, R4-R12, LR}; 将寄存器列表中的寄存器LR, R12-R4, R0
顺序压入(此时堆栈递减)堆栈。后缀!表示最后的地址写回到R13中。
BL:Branch with link,带返回的跳转指令。
例:BL Label; 当程序无条件跳转到标号Label处执行时,同时将当前的PC值保存到R14(LR)中
Arm9寄存器
ARM9共有37个32位寄存器,其中,有31个通用寄存器,1个CPSR,5个SPSR。
http://bdxnote.blog.163.com/blog/static/8444235201121413413947/。
ARM9处理器的运行模式有7种,用户模式user,系统模式sys,管理模式svc,中止模式abt,未定义模式und,中断模式irq,快速中断模式fiq。各种模式下所拥有的寄存器信息如下图:
寄存器的别名:摘自《ARM ProcedureCall Standard for the ARM® Architecture》
关于CPSR和SPSR(http://blog.sina.com.cn/s/blog_641a37da01017cjm.html)
CPSR:程序状态寄存器(current program status register) (当前程序状态寄存器),在任何处理器模式下被访问。它包含了条件标志位、中断禁止位、当前处理器模式标志以及其他的一些控制和状态位。CPSR在用户级编程时用于存储条件码。
SPSR:程序状态保存寄存器(saved program status register),每一种处理器模式下都有一个状态寄存器SPSR,SPSR用于保存CPSR的状态,以便异常返回后恢复异常发生时的工作状态。当特定的异常中断发生时,这个寄存器用于存放当前程序状态寄存器的内容。在异常中断退出时,可以用SPSR来恢复CPSR。由于用户模式和系统模式不是异常中断模式,所以他没有SPSR。当用户在用户模式或系统模式访问SPSR,将产生不可预知的后果。
最高优先级任务运行
os_cpu_a.s文件解释
先从OSStartHighRdy开始,OSStartHighRdy完成的是,将OSRunning置为TRUE,将最高优先级任务栈里面的数据顺序恢复(出栈)到各个寄存器(包括PC)中。
分析:
OSStartHighRdy
;----------------------------------------------------------------------------------
; OSRunning = TRUE;
;----------------------------------------------------------------------------------
MSR CPSR_cxsf,#SVCMODE:OR:NOINT ;Switch to SVC mode with IRQ&FIQ disable
;解释:将SVCMODE:OR:NOINT的值存到CPSR中,其中cxsf(字母必须小写)意为c是控制域屏蔽字节[0-7],x为扩展域屏蔽字节[8-15],s为状态域屏蔽字节[16-23],f为状态域屏蔽字[24-31]。可参考http://19831028.blog.51cto.com/1333653/411832。
此句主要意为进入SVC模式并屏蔽中断。NOINT为0xc0,屏蔽IRQ和FRQ中断。
BL OSTaskSwHook ;Call user define Task switch hook
;解释:调用OSTaskSwHook函数,并将下一条指令地址存入LR(R14)寄存器中。BL指令在编译时,是以当前指令地址为基准相对跳转。由于指令中地址区域为16位,其中1位作前后标志,剩下15位作为跳转范围。所以跳转地址范围为当前地址前后32MB地址。
LDR R0,=OSRunning ; OSRunning =TRUE
MOV R1, #1
STRB R1, [R0]
;解释:第1句将OSRunning地址放入到R0;第2句将立即数1存入R1;第3句将R1的字节数据存入到以R0为地址的存储器中。一句话解释就是OSRunning=1
;----------------------------------------------------------------------------------
; SP =OSTCBHighRdy->OSTCBStkPtr;
;----------------------------------------------------------------------------------
LDR R0,=OSTCBHighRdy
LDR R0, [R0]
LDR SP, [R0]
;解释:OSTCBHighRdy是指针。第1句将指针OSTCBHighRdy的地址存入R0;第2句将OSTCBHighRdy本身存入R0;第3句就是将OSTCBHighRdy指向的内容存入SP。OSTCBHighRdy和OSTCBHighRdy->OSTCBStkPtr属于同一地址,因此就是将OSTCBHighRdy->OSTCBStkPtr值存入SP。
一句话就是另SP =OSTCBHighRdy->OSTCBStkPtr;
OSTCBHighRdy->OSTCBStkPtr的值是多少?是在Os_cpu_c.c的OSTaskStkInit赋值的,在这个函数中最后返回的值就是OSTCBHighRdy->OSTCBStkPtr初值。
;----------------------------------------------------------------------------------
; Prepare to return to proper mode
;----------------------------------------------------------------------------------
LDMFD SP!,{R0}
MSR SPSR_cxsf, R0
LDMFD SP!,{R0-R12, LR, PC}^
;解释:第1句将SP的值弹出到R0中。根据OSTaskStkInit函数可知,SP最后的一个值为CPSR。第2句就是将R0存入到SPSR寄存器。第3句是顺序将SP的值弹出到R0-R12, LR, PC。PC值在OSTaskStkInit被设置为当前任务的指针。后缀!表示最后的SP地址还会写回到SP内。末尾^意味这cpsr的值将从spsr中得到恢复,这只有在pc同时也被装载的情况下才有效。如果pc没有被装载,那么^只恢复用户模式的备份寄存器组。《ARM嵌入式系统开发:软件设计与优化》9章-异常和中断处理,例9.8。
Ucos的任务切换有两种方式,一种是任务级上下文切换,一种是中断级上下文切换。参考:http://blog.csdn.net/zhanglianpin/article/details/7350712
Ucos中任务上下文切换会发生在两种情况下。
第一是任务调用了ucos的一些系统函数,比如:延时函数,pend,post函数等等,此时会发生任务切换。这叫做任务级的上下文切换;
第二是有更高优先级的任务进入就绪状态,在Time tick中断或者其他中断发生时,在中断里进行任务切换。这叫做中断级的上下文切换。
任务级上下文切换
任务级的上下文切换可总结为(摘自《嵌入式实时操作系统ucos-II原理及应用第2版 任哲》 第三章 第4节):
1> 把被中止任务的断点指针保存到任务堆栈中;
2> 把CPU通用寄存器的内容保存到任务堆栈中;
3> 把被中止任务的任务堆栈指针当前值保存到该任务的任务控制块的OSTCBStkPtr中;
4> 获得待运行任务的任务控制块;
5> 使CPU通过任务控制块获得待运行任务的任务堆栈指针;
6> 把待运行任务堆栈中通用寄存器的内容恢复到CPU的通用寄存器中;
7> 使CPU获得待运行任务的断点指针(该指针是待运行任务在上一次被调度器中止运行时保留在任务堆栈中的)。
代码如下,可对照上面步骤进行理解。
OSCtxSw
;解释:此时SP表示的是当前(被中止)任务的堆栈指针
STMFD SP!, {LR} ;PC
;解释:将LR压入(递减)SP内。后缀!表示SP递减后写回到SP中。一句话:--SP=LR
STMFD SP!, {R0-R12,LR} ;R0-R12 LR
;解释:将寄存器列表中的寄存器LR, R12-R0, 顺序压入(堆栈递减)堆栈。后缀!表示SP递减后写回到SP中。可表示:--SP=LR;--SP=R12;--SP=R11;…………;--SP=R0;
MRS R0, CPSR ;Push CPSR
;解释:将CPSR的值存入R0
STMFD SP!, {R0}
;解释:将R0(此时为CPSR的值)值压入SP,即:--SP=R0
;可以看出入栈的顺序为LR(PC),LR,R12-R0,CPSR,和OSTaskStkInit入栈的顺序完
;全一致。
;----------------------------------------------------------------------------------
; OSTCBCur->OSTCBStkPtr= SP
;----------------------------------------------------------------------------------
LDR R0,=OSTCBCur
LDR R0, [R0]
STR SP, [R0]
;解释:将当前SP的值存入到当前任务堆栈指针中,即:OSTCBCur->OSTCBStkPtr= SP
;----------------------------------------------------------------------------------
; OSTaskSwHook();
;---------------------------------------------------------------------------------
BL OSTaskSwHook
;解释:调用OSTaskSwHook函数,并将下一条指令地址存入LR(R14)寄存器中
;----------------------------------------------------------------------------------
; OSTCBCur = OSTCBHighRdy;
;----------------------------------------------------------------------------------
LDR R0, =OSTCBHighRdy
LDR R1,=OSTCBCur
LDR R0, [R0]
STR R0, [R1]
;解释:当前任务控制块指针等于最高优先级任务控制块指针,即:OSTCBCur = OSTCBHighRdy
;----------------------------------------------------------------------------------
; OSPrioCur = OSPrioHighRdy;
;----------------------------------------------------------------------------------
LDR R0,=OSPrioHighRdy
;解释:OSPrioHighRdy地址存入R0
LDR R1,=OSPrioCur
;解释:OSPrioCur地址存入R1
LDRB R0, [R0]
;解释:将地址为R0对应的内容(字节)存入R0寄存器中
STRB R0, [R1]
;解释:将R0的内容(字节)存入以R1为地址对应的内容中
;解释:当前任务任务优先级等于最高优先级,即:OSPrioCur = OSPrioHighRdy
;----------------------------------------------------------------------------------
; OSTCBHighRdy->OSTCBStkPtr;
;----------------------------------------------------------------------------------
LDR R0,=OSTCBHighRdy
LDR R0, [R0]
LDR SP, [R0]
;解释:将最高优先级任务堆栈指针赋值给SP,即:SP= OSTCBHighRdy->OSTCBStkPtr
;----------------------------------------------------------------------------------
;Restore New task context
;----------------------------------------------------------------------------------
LDMFD SP!, {R0} ;POP CPSR
;解释:将SP的值弹出到R0,此时SP指向的是最高优先级任务的堆栈。可表示为:R0=SP++
MSR SPSR_cxsf, R0
;解释:各个任务堆栈里面最顶层存的是当前模式下的CPSR,因此首先弹出的值为CPSR,先放到SPSR中。
LDMFD SP!,{R0-R12, LR, PC}^
;解释:顺序将SP的值弹出到R0-R12, LR, PC。后缀!表示最后的SP地址还会写回到SP内。可表示:R0=SP++;R1=SP++;……;R12=SP++;LR=SP++;PC=SP++。
;末尾^意味这cpsr的值将从spsr中得到恢复,这只有在pc同时也被装载的情况下才有效。如果pc没有被装载,那么^只恢复用户模式的备份寄存器组。《ARM嵌入式系统开发:软件设计与优化》9章-异常和中断处理,例9.8。
中断级上下文切换
首先必须要知道的是ARM9怎么去找到相对应的中断服务函数。当已经使能的IRQ中断发生时,会跳转到下面的OS_CPU_IRQ_ISR处,这是因为已经与地址0x00000018相关联了。
中断级任务切换简要流程为:进入中断,调用中断函数,调用中断退出函数进行任务切换(如果最高优先级任务不等于当前任务)。
下面代码中实现的IRQ中断流程,
1> 将R1-R3寄存器值压入IRQ堆栈;
2> 切换到SVC模式,按照OSTaskStkInit初始化堆栈的顺序将寄存器顺序压入当前任务堆栈;其中LR存的是被中断函数中断处的下一条指令;
3> OSIntNesting加1后与1比较,若等于1,则将当前SP的值存入当前任务控制块的堆栈指针中;
4> 切换到IRQ模式,执行真正的中断函数;
5> 切换到SVC模式,调用OSIntExit函数。若OSIntNesting-1==0且最高优先级任务不为当前任务优先级,调用OSIntCtxSw执行任务切换,此处即刻返回到被中断函数中断运行的下一条地址;
6>若OSIntNesting-1!=0或者最高优先级任务等于当前任务优先级,不动作,退出OSIntExit,接着返回到被中断函数中断运行的下一条指令处。
OS_CPU_IRQ_ISR
;解释:此处SP的值为ISR的堆栈首地址
STMFD SP!, {R1-R3} ; Wewill use R1-R3 as temporary registers
;----------------------------------------------------------------------------
; R1--SP
; R2--PC
; R3--SPSR
;------------------------------------------------------------------------
;解释:将R3-R1压入(递减)SP内。后缀!表示SP递减后写回到SP中。一句话:--SP=R3;--SP=R2;--SP=R1; --SP等于SP-4,因为寄存器是32位的。
MOV R1, SP
;解释:将SP的值存入R1
ADD SP, SP,#12 ;Adjust IRQ stack pointer
;解释:SP=SP+12,将SP的值调回到ISR的堆栈首地址
SUB R2, LR, #4 ;Adjust PC for return address totask
;解释:R2=LR-4,LR-4表示的是被中断函数中下一条该运行的地址,为什么是LR-4?
;《ARM嵌入式系统开发:软件设计与优化》第9章表9.4列出了各个模式返回时的PC地址。
MRS R3, SPSR ; Copy SPSR (TaskCPSR)
;解释:R3=SPSR,将切换到ISR模式前运行模式下的CPSR状态存入R3。因为在进入ISR模式时已经将之前模式下的CPSR存入到了当前模式的SPSR中。
;R1存的是ISR模式下寄存器入栈后的SP值
;R2存的是被中断函数中下一条该运行的地址
;R3存的是之前模式下的CPSR值。
MSR CPSR_cxsf,#SVCMODE:OR:NOINT ;Change to SVC mode
;解释:切换到SVC模式并禁止中断,修改CPSR的值就可以切换到对应的模式。
;切换到SVC模式后,SP指向的是被中断任务的堆栈指针。每个任务都运行在SVC模式下。
; SAVE TASK''S CONTEXT ONTO OLD TASK''S STACK
STMFD SP!, {R2} ; Push task''s PC
;解释:--SP=R2,R2存的是被中断函数中下一条该运行的地址,将其压入当前任务的堆栈。
STMFD SP!, {R4-R12,LR} ; Push task''s LR,R12-R4
;解释:--SP=LR,--SP=R12,…….,--SP=R4;将这些寄存器值压入当前任务堆栈
LDMFD R1!, {R4-R6} ; Load Task''s R1-R3 from IRQstack
;解释:R4=R1++;R5=R1++;R6=R1++;此处R1存的是ISR模式下寄存器入栈后的SP值,在ISR模式下最后入栈的是R1的值,其次是R2,最先入栈的是R3,因此将压入在ISR堆栈的参数弹出到R4(R1),R5(R2),R6(R3)。
STMFD SP!, {R4-R6} ; Push Task''s R1-R3 to SVCstack
;解释:--SP=R6(R3),--SP=R5(R2),--SP=R4(R0),将这三个寄存器压入当前任务堆栈
STMFD SP!, {R0} ; Push Task''s R0 to SVC stack
;解释:--SP=R0,R0入栈,R0一直未使用,因此也不用恢复。
STMFD SP!, {R3} ; Push task''s CPSR
;解释:--SP=R3。R3存的是中断之前模式的CPSR。
;此处可以看到压入堆栈的寄存器顺序为PC,LR,R12-R0,CPSR,跟OSTaskStkInit函数的入栈顺序完全一致。
;另:此处被中断之前运行的那个任务所有寄存器参数已经全部入栈。
LDR R0,=OSIntNesting ;OSIntNesting++
;解释:R0=&OSIntNesting,将OSIntNesting地址存入R0中
LDRB R1,[R0]
;解释:R1=OSIntNesting&0x0FF,将以R0为地址的字节内容存入R1
ADD R1,R1,#1
;解释:R1=R1+1
STRB R1,[R0]
;解释:OSIntNesting=R1&0x0FF,将R1以字节形式存入以R0为地址的内容
CMP R1,#1 ;if(OSIntNesting==1){
;解释:将寄存器 R1 的值与1相减,并根据结果设置CPSR 的标志位。若1==R1,z=0,若1!=R1,z=1。
BNE %F1
;解释:
LDR R4,=OSTCBCur ;OSTCBHighRdy->OSTCBStkPtr=SP;
LDR R5,[R4]
STR SP,[R5] ;}
;解释:将当前SP的位置保存到当前任务堆栈指针。OSTCBHighRdy->OSTCBStkPtr=SP。
;此处,之前被中断之前运行的那个任务各个寄存器参数已经全部保存。
1
MSR CPSR_c,#IRQMODE:OR:NOINT ;Change to IRQ mode to use IRQ stack to handle interrupt
;解释:切换到ISR模式并禁止中断,修改CPSR的值就可以切换到对应的模式。
;切换到ISR模式后,SP指向的是ISR模式下的堆栈,此时SP的值指向的是ISR的堆栈首地址
LDR R0, =INTOFFSET
LDR R0, [R0]
;解释:INTOFFSET= 0x4A000014,中断偏移寄存器。中断偏移寄存器中的值表明了是哪个IRQ模式的中断请求在INTPND寄存器中。此位可以通过清除SRCPND和INTPND自动清除。FIQ模式中断不会影响INTOFFSET寄存器因为该寄存器只对IRQ模式中断有效。此时R0存的是INTOFFSET寄存器的内容。
LDR R1, IRQIsrVect
;解释,IRQIsrVect =HandleEINT0=0x33FFFF20,0x33FFFF20地址处放的是EINT0中断函数,HandleEINT1=0x33FFFF24地址处方的是EINT1中断函数,往后依次4个字节存的是各个中断函数。
MOV LR, PC ; Save LR befor jump tothe C function we need return back
;解释:ARM9处理器是五级流水线。即:取指--译码--执行--缓冲/数据--回写。在执行第1句指令的时候,PC指向的是第3条指令,即PC始终指向要取指令的地址。这一点可以参考《Uboot中start.S源码的指令级的详尽解析》3.4。即:PC=PC+8。此句执行后LR里存的是下面MSR CPSR_c,#SVCMODE:OR:NOINT指令的地址。
LDR PC, [R1, R0, LSL #2] ;Call OS_CPU_IRQ_ISR_handler();
;解释:R1是中断函数的基地址,R0存的是INTOFFSET寄存器的内容,左移2即乘以4是因为每4个字节存一个中断函数。一句话,就是将对应的中断函数放入PC,执行完这一句后就会跳转到对应的中断函数。
;解释:中断函数运行完后,会根据LR返回到下面的这条指令
MSR CPSR_c,#SVCMODE:OR:NOINT ;Change to SVC mode
;解释:切换到SVC模式并禁止中断,修改CPSR的值就可以切换到对应的模式。
;切换到SVC模式后,SP指向的是被中断任务的堆栈指针。每个任务都运行在SVC模式下。
;这条指令的执行同时也说明了此时切换到了被中断之前运行的那个任务,并且该任务的各个寄存器参数也已经全部入栈。
BL OSIntExit ;Call OSIntExit
;解释:BL是Branch Link,跳转到OSIntExit,并将下面的指令地址放入LR寄存器,以备函数返回时使用。在OSIntExit函数中,若OSIntNesting-1不等于0,则退出此函数;否则查找最高优先级任务,若最高优先级与当前任务优先级相同,则此函数不进行任务切换;若最高优先级任务不等于当前任务优先级,则跳转到OSIntCtxSw函数进行任务切换。若调用OSIntCtxSw进行任务切换,下面的代码就不会被执行,在OSIntCtxSw函数里就将PC指针返回到被中断函数中断运行的下一条代码,开始执行。
LDMFD SP!,{R4} ;POP the task''s CPSR
;解释:R4=SP++;
MSR SPSR_cxsf,R4
;解释:SPSR=R4;
LDMFD SP!,{R0-R12,LR,PC}^ ;POP new Task''s context
;解释:顺序将SP的值弹出到R0-R12, LR, PC。后缀!表示最后的SP地址还会写回到SP内。可表示:R0=SP++;R1=SP++;……;R12=SP++;LR=SP++;PC=SP++。
;末尾^意味这cpsr的值将从spsr中得到恢复,这只有在pc同时也被装载的情况下才有效。如果pc没有被装载,那么^只恢复用户模式的备份寄存器组。《ARM嵌入式系统开发:软件设计与优化》9章-异常和中断处理,例9.8。
;运行到此处说明了不需要进行任务切换。被中断函数之前那个任务的所有寄存器参数已经全部压栈,因此现在只要全部出栈即可恢复到被中断函数之前那个任务运行的地址,上面3条指令实现的就是这个功能。
OSIntCtxSw代码如下,和OSCtxSw代码部分相同,可参考。
OSIntCtxSw
;----------------------------------------------------------------------------------
; Call OSTaskSwHook();
;----------------------------------------------------------------------------------
BL OSTaskSwHook
;----------------------------------------------------------------------------------
; OSTCBCur = OSTCBHighRdy;
;----------------------------------------------------------------------------------
LDR R0,=OSTCBHighRdy
LDR R1,=OSTCBCur
LDR R0,[R0]
STR R0,[R1]
;----------------------------------------------------------------------------------
; OSPrioCur = OSPrioHighRdy;
;----------------------------------------------------------------------------------
LDR R0,=OSPrioHighRdy
LDR R1,=OSPrioCur
LDRB R0,[R0]
STRB R0,[R1]
;----------------------------------------------------------------------------------
; SP= OSTCBHighRdy->OSTCBStkPtr;
;----------------------------------------------------------------------------------
LDR R0,=OSTCBHighRdy
LDR R0,[R0]
LDR SP,[R0]
;----------------------------------------------------------------------------------
; Restore New Task context
;----------------------------------------------------------------------------------
LDMFD SP!,{R0} ;POP CPSR
MSR SPSR_cxsf,R0
LDMFD SP!,{R0-R12, LR, PC}^
为什么中断级上下文切换复杂?(待写)
为什么中断级上下文切换复杂?这个中断级上下文切换目前不支持中断嵌套,怎么修改使其支持中断嵌套?。这个等有时间再去分析。