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

/****************************************************************************
 *
 *                        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  
"      // 如果立即数小于256直接附值
             "mov %0, r0   
"
             "mov %1, #125 
"
             :"=r"(a),"=r"(b)       // 输出
             :                      // 输入
             :"r0"                  // 已经使用过的寄存器
             );
             printf("a:%d b:%d 
" , 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
"
             .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 
" , num);
            int i ; 
            for(i = 0 ; i < 10; i++)
            {
                printf("array[%d]: %d 
" , 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     
"
        "MCR p15, 0, r0, c3, c0, 0     
"
        
        //write ttb
        "MCR p15, 0, %0, c2, c0, 0     
"

        //enable mmu system control 
        "MRC p15, 0, r0, c1, c0, 0     
"
        "orr    r0 , r0 , %1           
"
        "MCR p15, 0, r0, c1, c0, 0     
"
        :
        :"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 
"
            .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   
"
        "nop    
"
        ".word  0x12345678   
"    //正常的指令
        ".word  0x77777777   
"    //异常的指令

        "swi    #0x1234      
"    //软件中断: 以前是swi,现在改成svc
        "svc    #0x2345      
"
        );

        //设置cpsr第I位,打开外部中断,要不然GIC无效
        __asm__ __volatile__ (
        "mrs    r0 , cpsr             
"
        "bic    r0 , r0 , #(1 << 7)   
"
        "msr    cpsr , r0             
"
        );
     
        //---------------------------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 ... 
");
    }
    void do_undef(void)
    {
        printf("this is in do_undef... 
");
    }
    void do_svc((int SystemCallNo)
    {
        /* 软件中断的参数在Linux就是系统调用号的意思 */
        SystemCallNo &= 0xffffff ;  //获取系统调用号
        printf("this is in svc...No:%p  
" , SystemCallNo);
    }
    void do_PrefetchAbt(void)
    {
        printf("this is in PrefetchAbt... 
");
    }
    void do_DataAbt(void)
    {
        printf("this is in DataAbt... 
");
    }
    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  
" , ID , CPUID);

    }
    void do_fiq(void)
    {
        printf("this is in fiq... 
");
    }
原文地址:https://www.cnblogs.com/zengjfgit/p/4320885.html