事情是这样的, 前些日子在 FS2410 (核心板为三星 s3c2410)开发板上实现了中断,包括
响应时钟 Timer0, 响应按键,并实现了串口通信,能把任何数据通过 UART0 发送到 PC 机
上的超级终端上进行显示,这样也便于调试。前两天又实现了 MMU 的启用代码,欢呼雀跃
啊..., 可就在这个时候问题来了...
MMU 启用后中断不能响应了!, start.S 的代码片段如下(arm-linux-gcc 汇编格式):
text
.global _start
_start:
b reset
NOP
NOP
NOP
NOP
NOP
ldr pc ,=handle_irq
NOP
reset:
ldr r0, =0x53000000 @ Close Watch-dog Timer
mov r1, #0x0
str r1, [r0]
@ init stack
ldr sp,=4096
@ disable all interrupts
mov r1, #0x4A000000
mov r2, #0xffffffff
str r2, [r1, #0x08]
ldr r2, =0x7ff
str r2, [r1, #0x1c]
bl memory_setup @ Initialize memory setting
bl flash_to_sdram @ Copy code to sdram
ldr pc, =run_on_sdram
run_on_sdram:
ldr sp, =0x33000000
bl init_mmu_tlb @ setup page table
bl init_mmu @ MMU enabled
msr cpsr_c, #0xd2 @ set the irq mode stack
ldr sp, =0x31000000
msr cpsr_c, #0xdf @ set the system mode stack
ldr sp, =0x32000000
bl init_irq
msr cpsr_c, #0x5f @ set the system mode open the irq
ldr sp, =0x33000000 @ Set stack pointer
bl main
loop:
b loop
我是通过在地址 0x00000018 放入一条长跳转指 ldr pc, =handle_irq 来响应中断的,在
没有启用 MMU 之前,代码工作的很好。通过如下两个函数启用了 MMU 实现虚拟内存管理:
bl init_mmu_tlb @ setup page table
bl init_mmu @ MMU enabled
通过下面代码来映射低端和高端中断向量表:
*(tb_base + 0x00000000) = (0x00000000)|(0x03<<10)|(0<<5)|(1<<4)|(1<<3)|0x02;
*(tb_base + (0xffff0000>>20)) = VECTORS_PHY_BASE|(0x03<<10)|(0<<5)|(1<<4)|(0<<3)|0x02;
其中 VECTORS_PHY_BASE=0x33f00000, 可以看到:
通过设置页表,将 0xffff0000 为首地址的 32 个字节的中断向量表被映射到 0x33ff0000 为首地址的 32 字节区域,
既然设置了高端中断向量,我们就要在 0x33ff0000 处设置中断跳转指令,通过调用下面这个函数:
bl flash_to_sdram
把程序自身复制到 SDRAM 的 0x30004000为首地址的区域,而 0x30004000 前面的 16K 即 0x30000000~0x30003FFF
用来放置页表,并且把 nand flash 的前 512byte 复制到 SDRAM 0x33ff0000 处, 因为nand flash 的前32byte 是8 个
中断跳转指令,这样中断向量表就在 0x33ff0000 处被设置。
如何让 ARM 使用高端的中断跳转指令呢?通过设置 CP15 协处理器的一些寄存器来启用。以下是内联汇编片段
/*
* turn on what we want
* base location of exception = 0xffff0000
*/
"orr r0, r0, #0x2000\n"
"orr r0, r0, #0x0002\n"
/* MMU enabled*/
"orr r0, r0, #0x0001\n"
/* write control register*/
"mcr p15, 0, r0, c1, c0, 0\n"
万事俱备,我们可以实验了,可不幸的中断不能响应了?!...
......经过了一天的折腾,发现将中断跳转指令由
b reset
NOP
NOP
NOP
NOP
NOP
ldr pc,=handle_irq
NOP
改为
b reset
NOP
NOP
NOP
NOP
NOP
ldr pc, handle_irq_addr
NOP
handle_irq_addr:
.long handle_irq
中断就能响应了,代码运行的很好,可百思不得其解,为什么不能用 ldr pc, =handle_irq
设置而非要用下面这种形式呢
ldr pc, handle_irq_addr
NOP
handle_irq_addr:
.long handle_irq
我在 main 函数里通过如下代码将所有的中断跳转指令(高端的和低端的)都打印出来了:
#include "serl.h"
#include "printf.h"
#define GPFCON (*(volatile unsigned long *)0x56000050)
#define GPFDAT (*(volatile unsigned long *)0x56000054)
int main()
{
init_uart();
GPFDAT = 0x0;
uart_printf("starting:\n");
unsigned long *ptr = (unsigned long *)0x30004000;
unsigned long *ptr2 =(unsigned long *)0x33ff0000;
unsigned long *ptr3 = (unsigned long *)0x00000000;
unsigned long *ptr4 = (unsigned long *)0xffff0000;
int i= 8;
while (i--) {
uart_printf("%x %x %x %x\n", *ptr++, *ptr2++, *ptr3++, *ptr4++);
}
while (1);
return 0;
}
代码将数据通过串口在超级终端上进行显示后,发现确有微妙不同:
中断不能响应时(ldr pc, =handle_irq):
0x30004000 0x33ff0000 0x00000000 0xffff0000
-------------------------------------------
ea000006 ea000006 ea000006 ea000006
e1a00000 e1a00000 e1a00000 e1a00000
e1a00000 e1a00000 e1a00000 e1a00000
e1a00000 e1a00000 e1a00000 e1a00000
e1a00000 e1a00000 e1a00000 e1a00000
e1a00000 e1a00000 e1a00000 e1a00000
e59ff1fc e59ff1fc e59ff1fc e59ff1fc
e1a00000 e1a00000 e1a00000 e1a00000
中断可以响应时(ldr pc, handle_irq_addr):
0x30004000 0x33ff0000 0x00000000 0xffff0000
-------------------------------------------
ea000007 ea000007 ea000007 ea000007
e1a00000 e1a00000 e1a00000 e1a00000
e1a00000 e1a00000 e1a00000 e1a00000
e1a00000 e1a00000 e1a00000 e1a00000
e1a00000 e1a00000 e1a00000 e1a00000
e1a00000 e1a00000 e1a00000 e1a00000
e59ff000 e59ff000 e59ff000 e59ff000
e1a00000 e1a00000 e1a00000 e1a00000
难道编译器对这两种跳转产生了不同的影响? 将代码反汇编来看个究竟:
中断不能响应时(ldr pc, =handle_irq):
---------------------------------------------------------------------
0: ea000006 b 0x20
4: e1a00000 nop (mov r0,r0)
8: e1a00000 nop (mov r0,r0)
c: e1a00000 nop (mov r0,r0)
10: e1a00000 nop (mov r0,r0)
14: e1a00000 nop (mov r0,r0)
18: e59ff1fc ldr pc, [pc, #508] ; 0x21c
1c: e1a00000 nop (mov r0,r0)
发现 ldr pc, =handle_irq 被汇编成
ldr pc, [pc, #508] ; 0x21c
我们又发现编译器为这条指令生成了一条注释: ; 0x21c, 它的意思是说去地址0x21c 处加载
数据,怎么算的呢? 当前地址是 0x18, 加上 508 即 0x1fc 得出 0x214,好像不是0x21c,
慢着... ARM 采用三级流水结构,那么读 pc 时得到的得数值会是相对当前指令的第二条指
令的地址, 即当前地址值加上 0x8,这么算来:
0x18 + 0x1fc + 0x8 = 0x21c
去 0x21c 处看看:
21c: 30004208 andcc r4, r0, r8, lsl #4
是 0x30004208 这么个值,也就是说 ARM 把 0x30004208 装进了 pc
......
再来看看将中断跳转指令改成
ldr pc, handle_irq_addr
NOP
handle_irq_addr:
.long handle_irq
也就是中断能响应时的反汇编代码:
0: ea000007 b 0x24
4: e1a00000 nop (mov r0,r0)
8: e1a00000 nop (mov r0,r0)
c: e1a00000 nop (mov r0,r0)
10: e1a00000 nop (mov r0,r0)
14: e1a00000 nop (mov r0,r0)
18: e59ff000 ldr pc, [pc, #0] ; 0x20
1c: e1a00000 nop (mov r0,r0)
可以看到 0x18 处为
18: e59ff000 ldr pc, [pc, #0] ; 0x20
与上面分析同理,去 0x20 处看看:
20: 3000420c andcc r4, r0, ip, lsl #4
发现 ARM 把 0x3000420c 这个数值装进了 pc
前后对比一下, 一个是 0x30004208, 另一个是 0x3000420c, 两者相差 4 个字节
而后一种情况多出的4个字节是由于 定义
handle_irq_addr:
.long handle_irq
而占用的 4 个字节。
看来编译器忠实的汇编了代码,那么很可能是 MMU 启用后对 ldr pc, =handle_irq 这样
的中断跳转指令产生了影响?
......现在还没有找到合理的解释,请高手不吝赐教