/****************************************************************************
*
* 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處理器上函數調用寄存器的使用規則
二. 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");
}