Samsung_tiny4412(驱动笔记02)----ASM with C,MMU,Exception,GIC

时间:2023-03-08 20:21:59
/****************************************************************************
*
* 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 配置及处理;
| 七. 主程序对异常的处理;
\\\\\\\\\\\\\\\\\\\\/////////////////// 一. 预热文章:
. Make 命令教程
url: http://www.ruanyifeng.com/blog/2015/02/make.html
. ATPCS和内嵌汇编: arm处理器上函数调用寄存器的使用规则
url: http://bog.****.net/yypony/article/details/17633323 二. C语言中插入ARM汇编:
. 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 ;
}
EOF
. arm-linux-gcc test.c -o test
. minicom(U-Boot)中运行编译好的test程序: ./test 三. U-Boot下汇编裸板开发基本流程:
. 编译好U-Boot后,在其根目标录下会生成一个System.map文件,这是U-Boot中提供的
函数及其地址(符号表),我们可以把U-Boot当作一个函数库来使用.
. 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
EOF
. 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
. make
. 将test.bin烧入开发板,运行程序,得到结果.
. 如果不使用默认的连接文件,采用自己编写的连接文件,操作如下:
. 获取链接脚本模板: 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 = ;
}
. 修改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语言裸板开发基本流程:
. 编译好U-Boot后,在其根目标录下会生成一个System.map文件,这是U-Boot中提供的
函数及其地址(符号表),我们可以把U-Boot当作一个函数库来使用.
. cat > test.c << EOF
int num = ;
int array[] = {};
//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 = ; i < ; i++)
{
printf("array[%d]: %d \n" , i , array[i]);
} return ;
}
EOF
. 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
. make
. 将test.bin烧入开发板,运行程序,得到结果. 五. MMU 配置流程:
void memset(int *ttb , char ch , int size )
{
int i ;
for(i = ; 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 >> ] = (pa & 0xfff00000) | ;
}
//SFR
for(va = 0x10000000 ; va < 0x14000000 ; va+=0x100000) {
pa = va;
ttb[va >> ] = (pa & 0xfff00000) | ;
}
//DRAM 内存
for(va = 0x40000000 ; va < 0x80000000 ; va+=0x100000) {
pa = va;
ttb[va >> ] = (pa & 0xfff00000) | ;
}
}
void memory_map(int *ttb , unsigned int va , unsigned int pa)
{
ttb[va >> ] = (pa & 0xfff00000) | ;
}
void enable_mmu(unsigned int virtualaddress , unsigned int physicsaddress)
{
unsigned int systemctl = ;
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, , *);
//2. IROM SFR DRAM
default_map(ttb);
//3. memmap
memory_map(ttb , virtualaddress, physicsaddress);
//4. enable_mmu();
systemctl = | ( << ) | ( << ) | ( << ) ; __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 配置及处理:
. 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 , #-] @把发生指令异常指令对应的数字打印出来 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 , #-]
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 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 |= ; //配置CPU的优先级最低
//ICCPMR_CPU0 &= ~0xff ;
ICCPMR_CPU0 |= 0xff ; //数字越小,优先级越高
//开启GIC enable
ICDDCR |= ;
//----------------------------
//设置GIC 1号中断的优先级为0,也就是最高
ICDIPR0_CPU0 &= ~(0xff << );
//指定CPU处理中断
ICDIPTR0_CPU0 |= ( << );
//允许GIC 1号中断
ICDISER0_CPU0 |= << ;
//发1号内部GIC中断
ICDSGIR = ( << ) | ; }
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) >> ) & 0x7 ;
printf("this is in irq...ID:%d CPUID:%d \n" , ID , CPUID); }
void do_fiq(void)
{
printf("this is in fiq... \n");
}