开发板
购于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,进入与异常类型对应的处理模式,然后从对应的异常向量取指。
ddress
xception
Mode in Entry
x0000 0000
eset
Supervisor
x0000 0004
ndef Instruction
Undefined
x0000 0008
oftware Interrupt
Supervisor
x0000 000c
bort (prefetch)
Abort
x0000 0010
bort (data)
Abort
x0000 0014
eserved
Reserved
x0000 0018
RQ
IRQ
x0000 001c
IQ
FIQ
U-Boot提供的默认异常处理
U-Boot为异常提供了一种默认处理,从启动代码(入口地址0x00000000)开始看:
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 */
dd 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
ub sp,sp,#4
tmfd sp!,{r0}
ldr r0,=$HandleLabel
dr 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
用下表总结整个跳转过程:
始地址
标地址
完成者
前程序流
x0000 0008
CPU硬件逻辑
x0000 0008
x0c00 0010
U-Boot的一级跳转表
x0c00 0010
x0c008008
用户程序的二级跳转表
x0c00 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
这时对于刚开始学习uboot移植的人非常有帮助,可以搞清楚uboot的中断机制是怎样的,现在用的开发板带来的代码真是#@$#$,uboot可以用,但启动uclinux时就不行。。。