开发板
购于00IC,与北京恒丰瑞科开发的S3C44B0开发板类似,外观上只少一个音频输入口。基本配置如下:
CPU Samsung S3C44B0X
ROM AM29LV160DB, 2M * 8-Bit Flash, BANK0
RAM HY57V641620ETP-7, 4Banks * 1M * 16-Bit SDRAM, BANK6
编译环境
Windows XP SP2
Cygwin, version 2.194.2.24
Arm-elf-gcc, version 2.95.3 20010315
U-Boot 版本1.1.1
S3C44B0的异常类型
S3C44B0采用了ARM7TDMI核心,支持共7种类型的异常。发生异常时,CPU保存异常处理的返回地址到link register,进入与异常类型对应的处理模式,然后从对应的异常向量取指。
Address
|
Exception
|
Mode in Entry
|
0x0000 0000
|
Reset
|
Supervisor
|
0x0000 0004
|
Undef Instruction
|
Undefined
|
0x0000 0008
|
Software Interrupt
|
Supervisor
|
0x0000 000c
|
Abort (prefetch)
|
Abort
|
0x0000 0010
|
Abort (data)
|
Abort
|
0x0000 0014
|
Reserved
|
Reserved
|
0x0000 0018
|
IRQ
|
IRQ
|
0x0000 001c
|
FIQ
|
FIQ
|
U-Boot提供的默认异常处理
U-Boot为异常提供了一种默认处理,从启动代码(入口地址0x00000000)开始看:
b reset
add pc, pc, #0x0c000000 /* will jump to 0x0c00000c */
add pc, pc, #0x0c000000 /* will jump to 0x0c000010 */
add pc, pc, #0x0c000000 /* will jump to 0x0c000014 */
add pc, pc, #0x0c000000 /* will jump to 0x0c000018 */
b . /* jump here, system halt */
add pc, pc, #0x0c000000 /* will jump to 0x0c000020 */
add pc, pc, #0x0c000000 /* will jump to 0x0c000024 */
对于复位异常,CPU通过b reset进入复位代码,重新启动系统。发生复位异常通常意味着出现了较严重的错误,必须对系统重新引导。复位代码总在ROM中执行,而b指令可寻址32MB,应付本例中的2MB Flash绰绰有余。
其它类型异常则比较自由,如何处理完全由用户决定。因此它们的处理常常在RAM中进行,而本例中RAM挂接在BANK6,地址范围为0x0c000000 ~ 0x0c7fffff,已经超出了b指令的寻址范围。于是通过add指令重置pc。目标地址已经在代码注释中说明,注意这里pc读出来的值等于当前指令的地址加0x08。
好了,发生异常(复位异常除外)后会从ROM的异常向量跳转到RAM,接下来会发生什么呢?有理由猜测RAM中也存放了一系列的跳转指令,进入到异常处理服务例程中去。来看U-Boot的这段代码:
/* now copy to sram the interrupt vector */
adr r0, real_vectors
add r2, r0, #1024
ldr r1, =0x0c000000
add r1, r1, #0x08
vector_copy_loop:
ldmia r0!, {r3-r10}
stmia r1!, {r3-r10}
cmp r0, r2
ble vector_copy_loop
这段代码将把ROM中从real_vectors开始的1024字节内容,复制0x0c000008开始的目标区域中去,相当于在RAM中建立一个二级跳转表。看看这张表的内容:
/* interrupt vectors */
real_vectors:
b reset
b undefined_instruction
b software_interrupt
b prefetch_abort
b data_abort
b not_used
b irq
b fiq
undefined_instruction:
mov r6, #3
b reset
software_interrupt:
mov r6, #4
b reset
...
于是,异常处理从RAM的二次跳转表进入服务例程,这样就和前面ROM中的首次跳转连接起来了。以SWI为例,从0x00000008跳转到0x0c000010,然后跳转到software_interrupt开始执行,最终reset。
异常处理的定制
这些异常服务例程是U-Boot所提供的一种默认处理,有时候这可能不是你想要的。比如,发生IRQ显然用不着系统复位。如何自定义异常处理呢?直接修改U-Boot的异常处理例程是显然是可以的,但是这里有一个更灵活的处理方法:修改RAM中的二次跳转表,从这里跳转到你自己编写的异常服务例程中去。
为了让这种方法实施起来更方便,在RAM的高端开辟了一块区域,用于存储每个异常服务例程的入口地址,我把这块区域称为“异常服务例程地址表”。进行异常处理的时候,通过一小段“加载程序”,把服务例程的入口地址写入pc。这看起来有些复杂,但是一旦理解这个过程,编写或是自己的异常服务程序就变得很简单了。
假设通过U-Boot把用户程序下载到RAM中0x0c008000开始的地方,用户程序首先执行必要的初始化步骤,在44binit.s中:
ENTRY
b ResetHandler ;for debug
b HandlerUndef ;handlerUndef
b HandlerSWI ;SWI interrupt handler
b HandlerPabort ;handlerPAbort
b HandlerDabort ;handlerDAbort
b . ;handlerReserved
b HandlerIRQ
b HandlerFIQ
看起来这些HandlerXXX有点像是异常服务例程的入口了,其实不是,HandlerXXX只是上面说的那个“加载程序”。它是带有一个参数的宏,参数即存放着相应服务例程入口地址的地址,这个宏的作用就是把这个入口地址加载到pc:
MACRO
$HandlerLabel HANDLER $HandleLabel
sub sp,sp,#4
stmfd sp!,{r0}
ldr r0,=$HandleLabel
ldr r0,[r0]
str r0,[sp,#4]
ldmfd sp!,{r0,pc}
MEND
...
HandlerFIQ HANDLER HandleFIQ
HandlerIRQ HANDLER HandleIRQ
HandlerUndef HANDLER HandleUndef
HandlerSWI HANDLER HandleSWI
...
这里的HandleXXX标记了存放各个服务例程地址的地址,它们从_ISR_STARTADDRESS开始,以word为单位,足以存放64项。事实上,如果仅使用non-vectored irq模式的话,只需要8项就足够了:
^ _ISR_STARTADDRESS /* 0x0c7fff00, in option.s */
HandleReset # 4
HandleUndef # 4
HandleSWI # 4
HandlePabort # 4
HandleDabort # 4
HandleReserved # 4
HandleIRQ # 4
HandleFIQ # 4
...
至此,用户程序需要完成的工作已经很清楚了:
1、编写异常处理例程
Void SWI_ISR(void)
{
Uart_Printf(“SWI Exception Captured!”);
...
}
2、将异常处理例程的入口地址写入到RAM高端相应的地址处
#define pISR_SWI (*(unsigned *)(_ISR_STARTADDRESS+0x8))/*44b.h*/
...
pISR_SWI =(unsigned) SWI_ISR;
注意IRQ处理例程已在44binit.s中定义并写入RAM高端相应位置了:
/* Setup IRQ handler */
ldr r0,=HandleIRQ ;This routine is needed
ldr r1,=IsrIRQ ;if there isn't 'subs pc,lr,#4' at 0x18, 0x1c
str r1,[r0]
3、修改RAM 0x0c000008开始的二级跳转表,使其跳转到44binit.s开始处建立的“加载程序”。这一步最简单的方法就是向二级跳转表处直接写b指令的机器码0xeaXXXXXX。比如对SWI,起跳地址为0x0c000010,目标地址为0x0c008008,则:
XXXXXX = (0x0c008008 – 0x0c000010 – 0x08) >> 2
= 7ff0 / 4
= 001ffc
写机器码的方法:
*((volatile unsigned*)0x0c000008) = 0xea001ffc
用下表总结整个跳转过程:
开始地址
|
目标地址
|
完成者
|
当前程序流
|
0x0000 0008
|
CPU硬件逻辑
|
0x0000 0008
|
0x0c00 0010
|
U-Boot的一级跳转表
|
0x0c00 0010
|
0x0c008008
|
用户程序的二级跳转表
|
0x0c00 8008
|
异常服务例程
|
44binit.s中的加载程序
|
另一种处理方法
不过我想,既然有了异常服务例程地址表,也可以选择从U-Boot的一级跳转表直接加载服务历程入口地址,只要按照GNU Assembler的语法重写“加载程序”就可以了,修改start.S的开始部分:
.macro Handler Addr
sub sp,sp,#4
stmfd sp!,{r0}
ldr r0,=\Addr
ldr r0,[r0]
str r0,[sp,#4]
ldmfd sp!,{r0,pc}
.endm
_start:
b reset
b HandlerUndef
b HandlerSWI
...
/* 加载程序 */
HandlerUndef:
Handler HandleUndef
HandlerSWI:
Handler HandleSWI
...
/* 地址表 */
.equ HandleReset, 0xc7fff00
.equ HandleUndef,0xc7fff04
.equ HandleSWI, 0xc7fff08
并注释掉now copy to sram the interrupt vector那段代码。
参考资料
1. S3C44B0X Datasheet, Samsung
2. 基于S3C44B0X嵌入式uCLinux系统原理及应用,李岩,荣盘祥编著
3. 关于S3C44b启动代码中中断初始化部分的讨论和问题,hufeng