1、 User Mode:用户模式。
2、FIQ Mode:快速中断模式。
3、 IRQ Mode:中断模式。中断(不包括软中断)处理函数在这种模式下执行。
4、 Supervisor Mode:监视模式。软中断(SWI)处理函数在这种模式下执行。
5、 Abort Mode:所有同内存保护相关的异常均在这种模式下执行。
6、 Undefined Mode:处理无效指令的异常处理函数在这种模式下执行。
7、 System Mode:特权模式。
今天的任务是实现3,5和6模式的跳转。要实现跳转,就要制造异常,下面是异常向量表:
由上图可知,当发生软中断时,程序会到0x00000008地址执行,并处于监视模式,当发生取数据异常时,程序会到0x00000010处执行,并处于Abort模式,当发生未定义指令异常的时候,程序会到0x00000004处执行,并处于Undefined模式。
当发生异常的时候,就会跳到特定的地址去执行异常处理 程序,地址是从0x00000000d到0x00000001C。那么问题来了。我们可以根据下表可知,0x00000000~0x00010000地址是iROM,是不能写入处理的。这时候就需要利用到上文讲解的MMU,将0x000000000的地址映射到其它可以访问的地方。
我们先来模拟未定义指令异常,当程序发生未定义指令异常的时候,会跳转到0x000000004处运行,因为C语言不能指定指令存放的地址。所以需要使用内嵌汇编实现。先使用汇编实现处理指令,将指令放到0x60000004地址中,再开启MMU,将0x60000004地址映射到0x00000004地址中。
下面是具体代码的实现:
1 int (*printf)(char *, ...) = 0xc3e114d8; 2 3 void init_ttb(unsigned long *ttb); 4 void enable_mmu(void); 5 void memcpy(unsigned char *dest, unsigned char *source, int len); 6 7 int main() 8 { 9 unsigned long source; 10 __asm__ __volatile__( 11 "ldr %0, =vector_start\n" 12 : "=r" (source) 13 ); 14 memcpy(0x60000004, source, 0x100); 15 16 enable_mmu(); 17 18 __asm__ __volatile__( 19 ".word 0x77777777\n" 20 ); 21 } 22 23 24 void init_ttb(unsigned long *ttb) 25 { 26 unsigned long va = 0; 27 unsigned long pa = 0; 28 29 for(va=0x00000000; va<0x10000000; va+=0x100000){ 30 pa = va + 0x60000000; 31 ttb[va >> 20] = pa | 2; 32 } 33 34 //10000000~14000000 -> 10000000~14000000 35 for(va=0x10000000; va<0x14000000; va+=0x100000){ 36 pa = va; 37 ttb[va >> 20] = pa | 2; 38 } 39 40 //40000000~80000000 -> 40000000~80000000 41 for(va=0x40000000; va<0x80000000; va+=0x100000){ 42 pa = va; 43 ttb[va >> 20] = pa | 2; 44 } 45 46 //30000000~40000000 -> 50000000~60000000 47 for(va=0x30000000; va<0x40000000; va+=0x100000){ 48 pa = va + 0x20000000; 49 ttb[va >> 20] = pa | 2; 50 } 51 } 52 53 void enable_mmu(void) 54 { 55 unsigned long ttb = 0x70000000; 56 init_ttb(ttb); 57 unsigned long mmu = 0; 58 mmu = 1 | (1 << 3) | (1 << 8); 59 __asm__ __volatile__( 60 "mov r0, #3\n" 61 "mcr p15, 0, r0, c3, c0, 0\n" 62 "mcr p15, 0, %0, c2, c0, 0\n" 63 "mcr p15, 0, %1, c1, c0, 0\n" 64 : 65 : "r" (ttb), "r" (mmu) 66 ); 67 } 68 69 __asm__( 70 "vector_start:\n" 71 "mov sp, #0x66000000\n" 72 "stmfd sp!, {r0-r12, lr}\n" 73 74 75 "ldr r0, =string\n" 76 "ldr r2, show\n" 77 "blx r2\n" 78 79 "loop:\n" 80 "b loop\n" 81 82 83 "show:\n" 84 ".word 0xc3e114d8\n" 85 86 "string:\n" 87 ".asciz \"hello undefined\\n\" \n" 88 ".align 2\n" 89 ); 90 91 void memcpy(unsigned char *dest, unsigned char *source, int len) 92 { 93 int i = 0; 94 for(i=0; i) 95 dest[i] = source[i]; 96 }
输出结果为:
开启MMU代码和建映射表的内容在前文章介绍过了,这里就解释内嵌汇编中异常处理函数。要解释前,还要贴一张表格:
在跳转中cpu要完成三件事情,一、将PC保存到新模式下的lr中,二、将CPSR保存在SPSR中,三,初始化SP。
前两步由硬件完成,第三步要我们完成,56行就是初始化SP代码,因为不同模式下的r1~r12是相同的,所以57行是入栈保护寄存器。后面的代码就是输出一段字符。
将代码拷到开发板中运行,输出mode undefined 说明模式跳转成功。
跳转过去了,这么回来呢?将这三步逆着执行就可以了。下面的代码是为了更好的了解将上面的代码分解成三个代码,完成软中断异常的跳转 ,最后还会跳回来。
分成三个代码思想更加简单,第一个是开启MMU,第二个是将异常处理代码放到0x00000008处,第三个是软中断触发程序。
1 nt (*printf)(char *, ...) = 0xc3e114d8; 2 3 void init_ttb(unsigned long *ttb); 4 void enable_mmu(void); 5 void memcpy(unsigned char *dest, unsigned char *source, int len); 6 7 int main() 8 { 9 enable_mmu(); 10 11 } 12 13 14 void init_ttb(unsigned long *ttb) 15 { 16 unsigned long va = 0; 17 unsigned long pa = 0; 18 19 for(va=0x00000000; va<0x10000000; va+=0x100000){ 20 pa = va + 0x60000000; 21 ttb[va >> 20] = pa | 2; 22 } 23 24 //10000000~14000000 -> 10000000~14000000 25 for(va=0x10000000; va<0x14000000; va+=0x100000){ 26 pa = va; 27 ttb[va >> 20] = pa | 2; 28 } 29 30 //40000000~80000000 -> 40000000~80000000 31 for(va=0x40000000; va<0x80000000; va+=0x100000){ 32 pa = va; 33 ttb[va >> 20] = pa | 2; 34 } 35 36 //30000000~40000000 -> 50000000~60000000 37 for(va=0x30000000; va<0x40000000; va+=0x100000){ 38 pa = va + 0x20000000; 39 ttb[va >> 20] = pa | 2; 40 } 41 } 42 43 void enable_mmu(void) 44 { 45 unsigned long ttb = 0x70000000; 46 init_ttb(ttb); 47 unsigned long mmu = 0; 48 mmu = 1 | (1 << 3) | (1 << 8); 49 __asm__ __volatile__( 50 "mov r0, #3\n" 51 "mcr p15, 0, r0, c3, c0, 0\n" 52 "mcr p15, 0, %0, c2, c0, 0\n" 53 "mcr p15, 0, %1, c1, c0, 0\n" 54 : 55 : "r" (ttb), "r" (mmu) 56 ); 57 }
1 vector_start: 2 mov sp, #0x66000000 3 stmfd sp!, {r0-r12, lr} 4 sub r3,lr,#4 5 ldr r2,[r3] 6 7 ldr r0, =string 8 bic r1,r2,#0xff000000 9 ldr r2, show 10 blx r2 11 12 mov sp,#0x66000000 13 ldmea sp,{r0-r12,pc}^ 14 show: 15 .word 0xc3e114d8 16 string: 17 .asciz "hello swi %d\n" 18 .align 2
1 int (*printf)(char *, ...) = 0xc3e114d8; 2 int main() 3 { 4 __asm__ __volatile__( 5 "swi #88\n" 6 ); 7 printf("welcome back\n"); 8 }
运行代码后输出
因为处理的汇编函数直接放到0x000000008地址处,就不需要内存拷贝函数了。输出的88 表示软中断号为88.异常处理结束后成功回来。输出welcome back.
取数据异常的模式跳转和上面的程序类似,就是处理异常存放的地址改为0x00000010.这里就不贴代码了。
最后老刘留了个问题:如果这三个异常同时发生会怎么样?(提示:二级跳转)