/**************************************************************************** * * ASM with C,MMU,Exception,GIC * * 声明: * 1. 本系列文档是在vim下编辑,请尽量是用vim来阅读,在其它编辑器下可能会 * 不对齐,从而影响阅读. * 2. 以下所有的shell命令都是在root权限下运行的; * 3. 文中在需要往文件中写入内容的时候使用了如下2方式: * 1.如果文件不存在,创建文件;如果存在,以覆盖的方式往文件中添加内容: * cat > 文件名 << EOF (结束符) * ... * 文件内容... * ... * EOF (输入遇到EOF,cat指令结束,内容将保存在前面指定的文件中) * 2.如果文件不存在,创建文件;如果存在,将内容追加到文件尾: * cat >> 文件名 << EOF (结束符) * ... * 文件内容... * ... * EOF * * 2015-3-7 阴 深圳 尚观 Sbin 曾剑锋 ****************************************************************************/ \\\\\\\\\\\\\\\--*目录*--////////////// | 一. 预热文章; | 二. C语言中插入ARM汇编; | 三. U-Boot下汇编裸板开发基本流程; | 四. U-Boot下C语言裸板开发基本流程; | 五. MMU 配置流程; | 六. Exception 配置及处理; | 七. 主程序对异常的处理; \\\\\\\\\\\\\\\\\\\\/////////////////// 一. 预热文章: 1. Make 命令教程 url: http://www.ruanyifeng.com/blog/2015/02/make.html 2. ATPCS和内嵌汇编: arm处理器上函数调用寄存器的使用规则 url: http://bog.csdn.net/yypony/article/details/17633323 二. C语言中插入ARM汇编: 1. cat > test.c << EOF #include <stdio.h> int main(void) { volatile unsigned int a ; int b ; __asm__ __volatile__ ( "mov r0, #11 \n" // 如果立即数小于256直接附值 "mov %0, r0 \n" "mov %1, #125 \n" :"=r"(a),"=r"(b) // 输出 : // 输入 :"r0" // 已经使用过的寄存器 ); printf("a:%d b:%d \n" , a , b); return 0 ; } EOF 2. arm-linux-gcc test.c -o test 3. minicom(U-Boot)中运行编译好的test程序: ./test 三. U-Boot下汇编裸板开发基本流程: 1. 编译好U-Boot后,在其根目标录下会生成一个System.map文件,这是U-Boot中提供的 函数及其地址(符号表),我们可以把U-Boot当作一个函数库来使用. 2. cat > test.S << EOF .global _start _start: stmfd sp! , {r0-r12 , lr} @寄存器入栈 @ 0x43e11434是U-Boot中printf地址,这个地址不是固定,这是我编译的U-Boot中 @ printf的地址, 因为如果修改了U-Boot的源码,printf地址会变,U-Boot其他 @ 函数地址也会变,所以大家以各自编译U-Boot后产生的System.map文件中的 @ 地址为准. ldr r1 , =0x43e11434 ldr r0 , =str mov lr , pc mov pc , r1 ldmfd sp! , {r0-r12 , pc} @寄存器出栈 str: .string "hello world\n" .align 5 EOF 3. cat > Makefile << EOF all: arm-linux-gcc -c test.S -o test.o arm-linux-ld -Ttext=0x40008000 test.o -o test # 0x40008000是加载代码的起始地址 arm-linux-objcopy -O binary test test.bin # 获取二进制可运行文件 clean: rm -rf test.o test test.bin EOF 4. make 5. 将test.bin烧入开发板,运行程序,得到结果. 6. 如果不使用默认的连接文件,采用自己编写的连接文件,操作如下: 1. 获取链接脚本模板: arm-linux-ld --verbose > test.lds ,修改模板文件为如下文件内容: ============================================================================= /* Script for -z combreloc: combine and sort reloc sections */ OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm", "elf32-littlearm") OUTPUT_ARCH(arm) ENTRY(_start) SEARCH_DIR("=/usr/local/lib"); SEARCH_DIR("=/lib"); SEARCH_DIR("=/usr/lib"); SECTIONS { . = 0x40008000 ; /* 运行代码的起始地址 */ .text : { test.o(.text) ; /* _start标号在这个文件里 */ *(.text) ; } align = 4 ; } 2. 修改Makefile如下: cat > Makefile << EOF all: arm-linux-gcc -c test.S -o test.o arm-linux-ld -T test.lds *.o -o test arm-linux-objcopy -O binary test test.bin clean: rm -rf test.o test test.bin EOF 四. U-Boot下C语言裸板开发基本流程: 1. 编译好U-Boot后,在其根目标录下会生成一个System.map文件,这是U-Boot中提供的 函数及其地址(符号表),我们可以把U-Boot当作一个函数库来使用. 2. cat > test.c << EOF int num = 1; int array[10] = {0}; //0x43e11434是U-Boot中printf地址,这个地址不是固定,如果修改了源码,地址可能会变 int (*printf)(const char *fmt , ...) = (void *)0x43e11434; int _start(void) // 这里不能是main,因为裸板运行的其实函数是_start,和汇编一样 { printf("num:%d \n" , num); int i ; for(i = 0 ; i < 10; i++) { printf("array[%d]: %d \n" , i , array[i]); } return 0 ; } EOF 3. cat > Makefile << EOF all: arm-linux-gcc -c test.c -o test.o -fno-builtin arm-linux-ld -T test.lds *.o -o test #采用第三部分的lds文件 arm-linux-objcopy -O binary test test.bin clean: rm -rf test test.bin *.o EOF 4. make 5. 将test.bin烧入开发板,运行程序,得到结果. 五. MMU 配置流程: void memset(int *ttb , char ch , int size ) { int i ; for(i = 0 ; i < size ; i++) { ((char *)ttb)[i] = ch; } } void default_map(int *ttb) { unsigned int va , pa; //IROM RAM for(va = 0x00000000 ; va < 0x10000000 ; va+=0x100000) { pa = va; ttb[va >> 20] = (pa & 0xfff00000) | 2; } //SFR for(va = 0x10000000 ; va < 0x14000000 ; va+=0x100000) { pa = va; ttb[va >> 20] = (pa & 0xfff00000) | 2; } //DRAM 内存 for(va = 0x40000000 ; va < 0x80000000 ; va+=0x100000) { pa = va; ttb[va >> 20] = (pa & 0xfff00000) | 2; } } void memory_map(int *ttb , unsigned int va , unsigned int pa) { ttb[va >> 20] = (pa & 0xfff00000) | 2 ; } void enable_mmu(unsigned int virtualaddress , unsigned int physicsaddress) { unsigned int systemctl = 0; unsigned int *ttb = (void *)0x73000000 ; unsigned int *va = (void *)virtualaddress; unsigned int *pa = (void *)physicsaddress; //1. 清空ttb所在的地址 16K = 4G/1M*4(最后乘以4是因为每个地址占用4个字节) memset(ttb, 0, 16*1024); //2. IROM SFR DRAM default_map(ttb); //3. memmap memory_map(ttb , virtualaddress, physicsaddress); //4. enable_mmu(); systemctl = 1 | (1 << 11) | (1 << 13) | ( 1 << 28) ; __asm__ __volatile__ ( //Domain Acess c3 c0 "mvn r0 , #0 \n" "MCR p15, 0, r0, c3, c0, 0 \n" //write ttb "MCR p15, 0, %0, c2, c0, 0 \n" //enable mmu system control "MRC p15, 0, r0, c1, c0, 0 \n" "orr r0 , r0 , %1 \n" "MCR p15, 0, r0, c1, c0, 0 \n" : :"r"(ttb),"r"(systemctl) //外部传的参数 :"r0" ); } 六. Exception 配置及处理: 1. cat > vector.S << EOF .global _start _start: b reset @复位异常 b undef @指令未定义异常 b svc @软件中断 b PrefetchAbt @取指令异常 b DataAbt @取数据异常 nop @保留 b irq @外部普通中断 b fiq @外部快速中断 reset: @执行指令的时候触发的异常,但因为是复位,返回pc指针无效 stmfd sp! , {r0-r12 , lr} ldr r0 , =0x60000000 @保存当前执行位置下+8的地址,也就是下2行ldmfd sp! , {r0-r12 , pc}^地址 @所以当执行完r0代表的函数返回时,接着到这个位置执行--- mov lr , pc | ldr pc , [r0] | V ldmfd sp! , {r0-r12 , pc}^ @"^"的意思是指令完成后,把SPSR拷贝到CPSR undef: @指令编译的时候触发的异常,此时的pc指针正好指向异常指令的后面一条指令 stmfd sp! , {r0-r12 , lr} @-------------------test ldr r0 , =str @获取字符串,第一个参数保存在r0中 ldr r2 , =printf @获取printf符号的地址 ldr r1 , [lr , #-4] @把发生指令异常指令对应的数字打印出来 mov lr , pc ldr pc , [r2] @获取printf符号地址里的值,并调用对应值的函数(调用printf) @-------------------test ldr r0 , =0x60000004 mov lr , pc ldr pc , [r0] ldmfd sp! , {r0-r12 , pc}^ svc: @指令编译的时候触发的异常 stmfd sp! , {r0-r12 , lr} @处理函数需要知道SVC指令的调用号,把整条指令当传输传给C函数处理 ldr r0 , [lr , #-4] ldr r2 , =0x60000008 mov lr , pc ldr pc , [r2] ldmfd sp! , {r0-r12 , pc}^ PrefetchAbt: @取指令的时候引发的异常 stmfd sp! , {r0-r12 , lr} ldr r0 , =0x6000000C mov lr , pc ldr pc , [r0] ldmfd sp! , {r0-r12 , pc}^ DataAbt: @取数据的时候引发的异常 stmfd sp! , {r0-r12 , lr} ldr r0 , =0x60000010 mov lr , pc ldr pc , [r0] ldmfd sp! , {r0-r12 , pc}^ irq: @会执行完当前正在编译的指令,再去处理异常 stmfd sp! , {r0-r12 , lr} ldr r0 , =0x60000014 mov lr , pc ldr pc , [r0] ldmfd sp! , {r0-r12 , pc}^ fiq: @会执行完当前正在编译的指令,再去处理异常 stmfd sp! , {r0-r12 , lr} ldr r0 , =0x60000018 mov lr , pc ldr pc , [r0] ldmfd sp! , {r0-r12 , pc}^ str: .string "hello world \n" .align 5 printf: .word 0x43e11434 EOF 七. 主程序对异常的处理: int (*printf)(const char *fmt , ...) = (void *)0x43e11434 ; void do_reset(void); void do_undef(void); void do_svc(void); void do_PrefetchAbt(void); void do_DataAbt(void); void do_irq(void); void do_fiq(void); int _start(void) { unsigned int *va = (void *)0xfff00000 ; unsigned int *pa = (void *)0x50000000 ; //这里决定异常向量表从0x500f0000开始 /* 对应vector.S中的地址调用 */ *(U32 *)0x60000000 = (U32)do_reset; *(U32 *)0x60000004 = (U32)do_undef ; *(U32 *)0x60000008 = (U32)do_svc; *(U32 *)0x6000000C = (U32)do_PrefetchAbt; *(U32 *)0x60000010 = (U32)do_DataAbt ; *(U32 *)0x60000014 = (U32)do_irq ; *(U32 *)0x60000018 = (U32)do_fiq ; //开启mmu enable_mmu((int)va , (int)pa); __asm__ __volatile__ ( "mov r0 , r0 \n" "nop \n" ".word 0x12345678 \n" //正常的指令 ".word 0x77777777 \n" //异常的指令 "swi #0x1234 \n" //软件中断: 以前是swi,现在改成svc "svc #0x2345 \n" ); //设置cpsr第I位,打开外部中断,要不然GIC无效 __asm__ __volatile__ ( "mrs r0 , cpsr \n" "bic r0 , r0 , #(1 << 7) \n" "msr cpsr , r0 \n" ); //---------------------------cpu //指定哪个CPU接收 ICCICR_CPU0 |= 1 ; //配置CPU的优先级最低 //ICCPMR_CPU0 &= ~0xff ; ICCPMR_CPU0 |= 0xff ; //数字越小,优先级越高 //开启GIC enable ICDDCR |= 1 ; //---------------------------- //设置GIC 1号中断的优先级为0,也就是最高 ICDIPR0_CPU0 &= ~(0xff << 8); //指定CPU处理中断 ICDIPTR0_CPU0 |= (1 << 8); //允许GIC 1号中断 ICDISER0_CPU0 |= 1 << 1; //发1号内部GIC中断 ICDSGIR = (1 << 16) | 1 ; } void do_reset(void) { printf("this is in reset ... \n"); } void do_undef(void) { printf("this is in do_undef... \n"); } void do_svc((int SystemCallNo) { /* 软件中断的参数在Linux就是系统调用号的意思 */ SystemCallNo &= 0xffffff ; //获取系统调用号 printf("this is in svc...No:%p \n" , SystemCallNo); } void do_PrefetchAbt(void) { printf("this is in PrefetchAbt... \n"); } void do_DataAbt(void) { printf("this is in DataAbt... \n"); } void do_irq(void) { /* 经测试,不能和其他的中断一起使用,只能作为测试GIC 1号中断这样处理 */ int ID = ICCIAR_CPU0 & 0x3ff ; int CPUID = ((ICCIAR_CPU0) >> 10) & 0x7 ; printf("this is in irq...ID:%d CPUID:%d \n" , ID , CPUID); } void do_fiq(void) { printf("this is in fiq... \n"); }