事情是這樣的, 前些日子在 FS2410 (核心板為三星 s3c2410)開發(fā)板上實現(xiàn)了中斷,包括
響應(yīng)時鐘 Timer0, 響應(yīng)按鍵,并實現(xiàn)了串口通信,能把任何數(shù)據(jù)通過 UART0 發(fā)送到 PC 機
上的超級終端上進行顯示,這樣也便于調(diào)試。前兩天又實現(xiàn)了 MMU 的啟用代碼,歡呼雀躍
啊..., 可就在這個時候問題來了...
MMU 啟用后中斷不能響應(yīng)了!, 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 放入一條長跳轉(zhuǎn)指 ldr pc, =handle_irq 來響應(yīng)中斷的,在
沒有啟用 MMU 之前,代碼工作的很好。通過如下兩個函數(shù)啟用了 MMU 實現(xiàn)虛擬內(nèi)存管理:
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, 可以看到:
通過設(shè)置頁表,將 0xffff0000 為首地址的 32 個字節(jié)的中斷向量表被映射到 0x33ff0000 為首地址的 32 字節(jié)區(qū)域,
既然設(shè)置了高端中斷向量,我們就要在 0x33ff0000 處設(shè)置中斷跳轉(zhuǎn)指令,通過調(diào)用下面這個函數(shù):
bl flash_to_sdram
把程序自身復(fù)制到 SDRAM 的 0x30004000為首地址的區(qū)域,而 0x30004000 前面的 16K 即 0x30000000~0x30003FFF
用來放置頁表,并且把 nand flash 的前 512byte 復(fù)制到 SDRAM 0x33ff0000 處, 因為nand flash 的前32byte 是8 個
中斷跳轉(zhuǎn)指令,這樣中斷向量表就在 0x33ff0000 處被設(shè)置。
如何讓 ARM 使用高端的中斷跳轉(zhuǎn)指令呢?通過設(shè)置 CP15 協(xié)處理器的一些寄存器來啟用。以下是內(nèi)聯(lián)匯編片段
/*
* 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"
萬事俱備,我們可以實驗了,可不幸的中斷不能響應(yīng)了?!...
......經(jīng)過了一天的折騰,發(fā)現(xiàn)將中斷跳轉(zhuǎ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
中斷就能響應(yīng)了,代碼運行的很好,可百思不得其解,為什么不能用 ldr pc, =handle_irq
設(shè)置而非要用下面這種形式呢
ldr pc, handle_irq_addr
NOP
handle_irq_addr:
.long handle_irq
我在 main 函數(shù)里通過如下代碼將所有的中斷跳轉(zhuǎn)指令(高端的和低端的)都打印出來了:
#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;
}
代碼將數(shù)據(jù)通過串口在超級終端上進行顯示后,發(fā)現(xiàn)確有微妙不同:
中斷不能響應(yīng)時(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
中斷可以響應(yīng)時(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
難道編譯器對這兩種跳轉(zhuǎn)產(chǎn)生了不同的影響? 將代碼反匯編來看個究竟:
中斷不能響應(yīng)時(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)
發(fā)現(xiàn) ldr pc, =handle_irq 被匯編成
ldr pc, [pc, #508] ; 0x21c
我們又發(fā)現(xiàn)編譯器為這條指令生成了一條注釋: ; 0x21c, 它的意思是說去地址0x21c 處加載
數(shù)據(jù),怎么算的呢? 當(dāng)前地址是 0x18, 加上 508 即 0x1fc 得出 0x214,好像不是0x21c,
慢著... ARM 采用三級流水結(jié)構(gòu),那么讀 pc 時得到的得數(shù)值會是相對當(dāng)前指令的第二條指
令的地址, 即當(dāng)前地址值加上 0x8,這么算來:
0x18 + 0x1fc + 0x8 = 0x21c
去 0x21c 處看看:
21c: 30004208 andcc r4, r0, r8, lsl #4
是 0x30004208 這么個值,也就是說 ARM 把 0x30004208 裝進了 pc
......
再來看看將中斷跳轉(zhuǎn)指令改成
ldr pc, handle_irq_addr
NOP
handle_irq_addr:
.long handle_irq
也就是中斷能響應(yīng)時的反匯編代碼:
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
發(fā)現(xiàn) ARM 把 0x3000420c 這個數(shù)值裝進了 pc
前后對比一下, 一個是 0x30004208, 另一個是 0x3000420c, 兩者相差 4 個字節(jié)
而后一種情況多出的4個字節(jié)是由于 定義
handle_irq_addr:
.long handle_irq
而占用的 4 個字節(jié)。
看來編譯器忠實的匯編了代碼,那么很可能是 MMU 啟用后對 ldr pc, =handle_irq 這樣
的中斷跳轉(zhuǎn)指令產(chǎn)生了影響?
......現(xiàn)在還沒有找到合理的解釋,請高手不吝賜教