Chinaunix首页 | 论坛 | 博客
  • 博客访问: 453643
  • 博文数量: 72
  • 博客积分: 3186
  • 博客等级: 中校
  • 技术积分: 1039
  • 用 户 组: 普通用户
  • 注册时间: 2009-03-07 16:53
文章分类

全部博文(72)

文章存档

2012年(1)

2011年(5)

2010年(10)

2009年(56)

我的朋友

分类: 嵌入式

2011-04-09 14:32:52

swi 是 arm 的软件中断指令,大概是 software interrupt 的意思 执行完swi指令后,cpu会做几件事情:

  1. 将swi的下一条指令地址保存到 r14_svc 中
  2. 将当前 cpsr 保存到 spsr_svc 中
  3. 将cpu模式改为特权模式svc_mode, 即更改 cpsr 的低五位, cspr[4:0]=0b10011
  4. 切换到ARM状态, cspr[5]=0
  5. 禁止IRQ, cspr[7]=1
  6. 将 pc 置为 0x00000008, 即跳到中断向量的地方开始执行

其于CPU的这种行为,我们需要考虑几件事情:


  • cpu最后跳到0x00000008地址执行,而且该地址处只有4个字节可以使用。 
    这4个字节正好可以放置一条指令。那么显然这个指令要将pc跳到另一个地方。


如何实现呢?

首先考虑当前的环境和需求。

在我的这块开发板上,SRAM的地址空间为 0x30000000 ~ 0x34000000 所以最开始要将测试程序放到 0x30000000 开始的地方。当程序从 0x30000000 地址跑 起来之后,我们再去设置中断向量表,即在在代码中去设置 0x00000000 ~ 0x0000001c 这块地址空间的指令。

于是想到可以如下来实现:

  1. .global _start
  2. _start:
  3.     mov r8, #0
  4.     adr r9, vector_init_block
  5.     ldmia {r0-r7}
  6.     stmia {r0-r7}

  7. vector_init_block:
  8.     b    reset_addr
  9.     b    undefined_addr
  10.     b    swi_addr
  11.     b    prefetch_addr
  12.     b    abort_addr
  13.     b    notused_addr
  14.     b    irq_addr
  15.     b    fiq_addr

程序从 _start 开始执行,第一件事情即将 vector_init_block 开始的8条指令复制到 0x00000000 开始的地方,每条指令占4字节,共 8*4=32字节。

假想现在程序从 0x30000000 开始执行,之后调用 swi 发生了软件中断,pc开始跳到 0x00000008 开始执行,这个位置的指令是 b swi_addr , 然后再跳到swi_addr去继续 执行。

这里有一个问题,跳转指令 b 只能在 32M 空间范围内跳转。 而 swi_addr 肯定是在 0x30000000 之后的,所以这里会跳转失败。

要想在32位(4G)地址空间实现跳转,可以使用 ldr 指令。 于是可以如下实现

  1. .global _start
  2. _start:
  3.     mov r8, #0
  4.     adr r9, vector_init_block
  5.     ldmia {r0-r7}
  6.     stmia {r0-r7}
  7.     ldmia {r0-r7}
  8.     stmia {r0-r7}

  9. vector_init_block:
  10.     ldr pc, reset_addr
  11.     ldr pc, undefined_addr
  12.     ldr pc, swi_addr
  13.     ldr pc, prefetch_addr
  14.     ldr pc, abort_addr
  15.     ldr pc, notused_addr
  16.     ldr pc, irq_addr
  17.     ldr pc, fiq_addr

  18. reset_addr:     .word reset_handler
  19. undefined_addr: .word undefined_handler
  20. swi_addr:     .word swi_handler
  21. prefetch_addr:     .word prefetch_handler
  22. abort_addr:     .word abort_handler
  23. notused_addr:    .word 0
  24. irq_addr:     .word irq_handler
  25. fiq_addr:     .word fiq_handler

ldr 指令是将某个地址里的值读到寄存器中,而紧接着我们将几个中断处理函数 的地址放到内存中供 ldr 读取。

reset_addr .word reset_handler 就是很典型的指针用法, 将处理函数 reset_handler 的地址放到内存中存起来。

与上面不同的时,这里 _start 中不仅复制了指令,而且复制了后面的‘指针’



  • 上面构造好中断向量表之后,swi 就可以正确跳到 swi_handler 地址处开始执行了。 
    那么在 swi_handler 处理函数中,需要做些什么事情呢?


swi处理通常分为两级:

  1. 汇编实现,保存现场,计算swi中的24位立即数
  2. 可以用c实现,具体实现swi各个功能

细分如下:

  1. 保存环境,将要用到的寄存器和返回地址保存到栈中,供退出时恢复
  2. 计算中断号,swi指令中包括一个24位的立即数,用于指定特定功能
  3. 进入swi处理程序。这部分可以用c实现
  4. 中断返回,恢复寄存器
  1. swi_handler:
  2.     stmfd     {r0-r12, lr}

  3.     ldr    r0, [lr, #-4]
  4.     bic    r0, r0, #0xff000000
  5.     bl    c_swi_handler

  6.     ldmfd     {r0-r12, pc}^

看到 swi 是怎么返回的了么! ldmfd sp!, {r0-r12, pc}^ ^ 表示恢复 spsr_svc -> cpsr



  • c语言部分。 首先要注意的是上面 bl c_swi_handler 之前计算好了 r0 的值,跳到 
    c_swi_handler时,r0将作为第一个实参传给c_swi_handler, 所以在c_swi_handler中 
    可以得到中断号

    1. void c_swi_handler(unsigned int nr)
    2. {
    3.     /* print swi exception number */
    4.     puts("c_swi_handler\t");
    5.     put_dex(nr);
    6.     
    7.     return;
    8. }


  • 如何触发 swi 中断呢? 这好像不是个问题,直接使用 swi 不就可以了么。 
    那在c语言里呢? 嵌入汇编!

  1. static inline void swi(void)
  2. {
  3.     __asm__ __volatile__ (
  4.         "swi 0x1\n\t"
  5.     );
  6. }



swi 带的参数 0x1 即为中断号

附件是本文例子的完整代码,运行于2410上,其中通过串口打印来方便看到测试结果

讲了这么多,swi倒底可以用来做什么呢? 通过上面的测试,对swi运行流程有了一个大概的了解。之后第一映像就是swi可以用来实 现系统调用。 如同在x86下linux中使用的 int 0x80

 2410-exception-swi.rar   

阅读(6192) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~