天天看點

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處理器上函數調用寄存器的使用規則

二. 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");
    }      

繼續閱讀