在路上...

The development of life
我们一直都在努力,有您的支持,将走得更远...

站内搜索: Google

  :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::
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 这样
的中断跳转指令产生了影响?

===========================================================
508 + 0x8 即 516, 大于 512, 前面我提到把 Nand flash 的前 512 字节复制到 0x33ff0000 处用以设置高端中断向量表,这样当发生 IRQ 中断时,程序会去 0x33ff0000 + 508 + 0x8 处把 0x30004208 装进 pc 并进行跳转来响应中断,但不幸的是我只复制了512字节,所以 IRQ 中断是不能响应的。总结一下,对比上面提到的两种设置中断跳转指令的方式:

(1)
b reset
NOP
NOP
NOP
NOP
NOP
ldr pc,=handle_irq
NOP

(2)
b reset
NOP
NOP
NOP
NOP
NOP
ldr pc, handle_irq_addr
NOP
handle_irq_addr:
.long handle_irq

第二种方式的好处是显而易见的,只需要偏移 0x8 就能取到目的地址了;对第一种方式,ARM 要去通过一个偏移来取到目的地址,具体数值要看编译如何设置了。

posted on 2009-08-26 23:24  palam  阅读(496)  评论(0编辑  收藏  举报