新聞中心

        EEPW首頁 > 嵌入式系統 > 設計應用 > s3c6410 uboot代碼分析

        s3c6410 uboot代碼分析

        作者: 時間:2016-11-09 來源:網絡 收藏
        以下用以記錄uboot代碼的分析過程,目標是s3c6410,如有錯誤,歡迎指正。

        強調,內容與三星原廠提供的uboot-1.1.6有更改的地方,因為外接外設的區別,特別是nand_flash、外接網卡芯片和LCD芯片

        本文引用地址:http://www.104case.com/article/201611/317904.htm

        以下純代碼情景分析,請結合uboot的功能結構圖和內存分布圖查看代碼,這樣會更加容易理解。

        s3c-u-boot-1.1.6源代碼可以在三星下面的網站獲得,但前提是你有官方的email。

        還是百度google搜一下吧,當然我這也是有的哦。

        http://www.samsung.com/global/business/semiconductor/productInfo.do?fmly_id=835&partnum=S3C6410




        功能結構圖(上圖) uboot內存分布圖(上圖)

        1.start.s代碼分析(第一階段)

        /*以下是具有arm特色的異常向量表,為中斷異常準備 */

        --------------------

        .globl _start
        _start: breset
        ldrpc, _undefined_instruction
        ldrpc, _software_interrupt
        ldrpc, _prefetch_abort
        ldrpc, _data_abort
        ldrpc, _not_used
        ldrpc, _irq
        ldrpc, _fiq

        _undefined_instruction:
        .word undefined_instruction
        _software_interrupt:
        .word software_interrupt
        _prefetch_abort:
        .word prefetch_abort
        _data_abort:
        .word data_abort
        _not_used:
        .word not_used
        _irq:
        .word irq
        _fiq:
        .word fiq
        _pad:
        .word 0x12345678 /* now 16*4=64 */
        .global _end_vect
        _end_vect:

        .balignl 16,0xdeadbeef

        --------------------

        /*當發生中斷異常時,pc會跳轉到.word的后面地址處 處理異常,

        undefined異常由arm核譯碼單元檢測,并觸發未定義指令異常請求,硬件設置pc的值為0x4,強制程序從內存0x4地址執行指令;

        0x8存放軟件中斷處理指令,arm中使用swi指令時觸發軟件中斷,硬件設置PC的值為0x8,同時進入系統模式,多用在系統庫的編寫;

        prefetch異常,預取指中止異常,導致正在取的指令無法正常取出,這里需要注意流水線造成的pc值;

        data中止,無法獲取數據,產生的原因有可能是內存未準備好、內存無讀或寫權限等一些原因產生的異常;

        0x14暫時未使用;

        0x18提供系統硬件中斷跳轉接口,一般我們的處理器都會引出很多的外部中斷線,在這里能做的就是判斷系統中斷線產生的中斷,注冊中斷,初始化中斷,調用中斷函數等等;

        0x1c地址為_fiq快速中斷,一個系統在中斷流水線上可能產生很多中斷,但快中斷只會有一個

        */

        --------------------

        _undefined_instruction:
        .word undefined_instruction
        _software_interrupt:
        .word software_interrupt
        _prefetch_abort:
        .word prefetch_abort
        _data_abort:
        .word data_abort
        _not_used:
        .word not_used
        _irq:
        .word irq
        _fiq:
        .word fiq
        _pad:
        .word 0x12345678 /* now 16*4=64 */
        .global _end_vect
        _end_vect:

        .balignl 16,0xdeadbeef

        --------------------

        /*

        _TEXT_BASE標號所代表的是uboot代碼的運行地址,對于s3c6410

        系統來說,如果nand flash啟動方式,系統會把0xc000000里面前4KB的內容映射到引導鏡像區,即0x0地址,但是我們需要把

        uboot代碼放到我們的SDRAM,原因是我們代碼里面需要對變量做更改并且增加代碼執行效率等

        下面代碼的含義是定義uboot程序執行的運行地址,值為0xc7e00000,.word后面的值TEXT_BASE在編譯的時候,

        通過向編譯器傳遞參數獲得,-DTEXT_BASE方式向編譯器傳遞宏參,在編譯的時候可以注意下編譯的時候都會指定它的值,值得定義在

        config.mk中,Makefile會包含它。

        */

        --------------------

        _TEXT_BASE:
        .wordTEXT_BASE

        --------------------

        /*

        在uboot里面會開啟MMU,下面是在MMU開啟前uboot在內存存放的真實物理地址,值為0x57e00000。強調一下,我們做的開發板的SDRAM在DMC1上,即訪問物理內存的實際物理地址從0x50000000開始,SDRAM的大小為256M,正好是一個DMC1,所以內存的訪問地址就是0x50000000-0x6FFFFFFF之間了。

        */

        --------------------

        _TEXT_PHY_BASE:
        .wordCFG_PHY_UBOOT_BASE

        --------------------

        /*

        這個不解釋也是可以的,但是還是要解釋。很多人對_start的值有疑惑,認為是0x0,因為看到_start的標號在代碼段最開始處,其實是錯誤的,匯編代碼里面的標號是和編譯時指定的運行地址有關系的。我們在編譯程序的時候會通過-DTEXT_BASE=0xc7e00000參數告訴編譯器我們程序將會運行在0xc7e00000地址,那么自然編譯器會認為代碼開始的時候就運行在這個地址,那么_start的值自然就是0xc7e00000了。總結之,標號的值與編譯時指定的程序地址有關系,而與程序實際存放在內存出的位置無關。小心使用哦。特別在使用一些偽指令的時候

        */

        --------------------

        .globl _armboot_start
        _armboot_start:
        .word _start

        --------------------

        /*

        下面的代碼__bss_start的值是在u-boot.lds腳本里面定義的,雖然沒給值,但是你要知道文件的大小和位置是由

        編譯器指定的,那么還需要我們告訴它值嗎?所以沒值勝有值啦,由編譯時編譯器決定它們的值

        */

        --------------------

        .globl _bss_start
        _bss_start:
        .word __bss_start

        .globl _bss_end
        _bss_end:
        .word _end

        --------------------

        /*

        uboot開始執行的第二條代碼處即在這里了,下面的代碼使得cpu的模式為管理模式,如果想使得為cpu為管理模式,需要保證cpsr寄存

        器的最低5位為10011,下面是把0xd3的值賦值給cpsr,0xd3即1101 0011,最高兩位置1的意思為關閉中斷和快中斷,這是為了防止代碼

        正在執行時,產生外部中斷,導致程序跳轉到異常向量表而無法正常按順序執行。5位為0的意思是cpu的狀態為arm狀態,如果是1則cpu進入thumb態,thumb態處理16位指令代碼和數據。

        */

        --------------------

        reset:

        mrsr0,cpsr
        bicr0,r0,#0x1f
        orrr0,r0,#0xd3
        msrcpsr,r0

        --------------------

        /* 以下標號所在處的代碼比較多,將做逐步分析,這段代碼主要的工作也就是改了一些硬件寄存器和內存初始化工作 */

        --------------------

        cpu_init_crit:

        --------------------

        /*
        指令的含義為刷新指令和數據緩存。mcr的意思是把arm寄存器的值賦值給coprocesser寄存器,拿第一條指令來說,

        p15代表協處理器,0為一定的值,指令中0b0000四位來表示,現在無具體作用,如果不是0則結果未知,后面的r0是即將寫入

        c7目標寄存器中的值,后面還有個c7所代表的意思為額外操作碼,如果不是c0,則表示的是同一個寄存器的不同物理寄存器,因為

        同一個寄存器的名字并不代碼通一個物理內存,我們在學rpsr的時候應該知道這點,最后的0提供附加信息,用于區分同一寄存器的

        不同物理寄存器,如無附加信息,請保持為0值,否則結果不可預測

        下面三行代碼不難看出,c7、c8的值被清為0,為什么要清為零呢,你需要去看arm1176jzf-s芯片手冊了,其中是有說明的,不再累述,

        arm1176jzf-sarm核芯片手冊下載地址httop://www.arm.com,也可以與本人聯系獲取。
        */

        --------------------
        movr0, #0
        mcrp15, 0, r0, c7, c7, 0/* flush v3/v4 cache */
        mcrp15, 0, r0, c8, c7, 0/* flush v4 TLB */

        --------------------

        /*
        實在不想解釋這段,因為以前看過芯片手冊的解釋,且不止一遍,對于這里面要更改的內容就是不能全部記下來,和工作有關了,

        不能全心搞這塊內容,最多2個月左右的時間能回來回顧一下了。總之還是去查arm11核芯片手冊,因為以下改的內容是協處理器

        c1,那么你就該去查c1是用來干什么的。查看得知,是控制寄存器,查看手冊是online books12.2.2 Primary register allocation

        一節,其中13,9,8位為V、R、S:V位是對高端異常向量表的支持,如果選擇0異常向量表為0x00000000-0x0000001c,如果選擇

        1異常向量表就是FFFF0000-FFFF001c;R位用于ROM保護的,具體的還要與c5里面的配合,這都是MMU惹的禍,很煩,但是現在

        我們還沒有講到MMU,所以為什么這樣做,也必須到講到MMU的時候才見分曉了,S在這里面的意思也是用于系統保護的,和MMU

        又是有很大的關系,好吧,后面會找MMU算賬的,這里就先不深入了,接下來再分析下下面的指令含義

        bicr0, r0, #0x00000087@ clear bits 7, 2:0(B--- -CAM) 的B位為0表示支持小little-endian,1表示支持big-endian格式的系統內存

        CAM為第三位,M為0代表禁止MMU,反之打開,A代表地址對齊檢查,0代表禁止,C代表指令數據cache控制,0為禁止

        orrr0, r0, #0x00000002@ set bit 2 (A) Align 這段指令又比較犯賤了,打開地址對齊檢查了,這是應該的O(∩_∩)O~,后面又

        設置12位為1,含義是如果數據cache和指令cache是分開的話,這里面置1的含義將會打開指令緩存

        */

        --------------------
        mrcp15, 0, r0, c1, c0, 0
        bicr0, r0, #0x00002300@ clear bits 13, 9:8 (--V- --RS)
        bicr0, r0, #0x00000087@ clear bits 7, 2:0 (B--- -CAM)
        orrr0, r0, #0x00000002@ set bit 2 (A) Align
        orrr0, r0, #0x00001000@ set bit 12 (I) I-Cache
        mcrp15, 0, r0, c1, c0, 0

        --------------------

        /*

        以下代碼的作用是為了給256M的內存在MMU開啟的時候把0x70000000作為重映射的基地址

        c15協處理器寄存器在s3c6410上有特殊作用,它是外部內存端口映射寄存器,32位,在開關MMU的時候發生作用,且優先級最高

        這里的0x70000000為外部端口的基地址,0x13的二進制為0x10011,0x10011的意思為256M,代表映射的

        大小為256M,0x10010為128M。假如你沒開MMU,PHY和Peri port映射的地址將相同。通過下面的內容后,我們知道我們原來uboot

        代碼是放置到0x57e00000的,現在便只能通過0x57e00000+0x70000000虛擬地址來訪問uboot起始地址了。

        使用C15的方法是:

        1.Opcode_1 set to 0

        2.CRn set to c15

        3.CRm set to c2

        4.Opcode_2 set to 4

        還有問題請參考arm1176jzfs芯片手冊



        */

        --------------------

        /* Peri port setup */
        ldrr0, =0x70000000
        orrr0, r0, #0x13
        mcrp15,0,r0,c15,c2,4 @ 256M(0x70000000-0x7fffffff)

        --------------------

        /*

        下面是一條跳轉指令,代碼這里不貼,但是其中的代碼很重要,在lowlevel.S中實現比如說點亮LED燈、關閉watchdog、關閉中斷、系統

        時鐘初始、nand flash初始化、內存控制器初始化。不過說實在的,去仔細分析這些初始化的過程,對于你對如何控制硬件有很大的幫

        助,對于這個函數,所要說的東西太多,會在后面的文章中單獨分析它,現在先知道功能就好,沒有它代碼無法啟動。

        */

        --------------------

        bllowlevel_init

        --------------------

        /*跳轉出來以后,繼續執行下面的代碼,下面的代碼是判斷程序是否已經在ram中了,在的話就不拷貝,直接跳轉到after_copy了,否則

        繼續執行下面的代碼*/

        --------------------

        ldrr0, =0xff000fff
        bicr1, pc, r0/* r0 <- current base addr of code */
        ldrr2, _TEXT_PHY_BASE/* r1 <- original base addr in ram */
        bicr2, r2, r0/* r0 <- current base addr of code */
        cmp r1, r2 /* compare r0, r1 */
        beq after_copy/* r0 == r1 then skip flash copy */

        --------------------

        /*

        下面代碼通過函數copy_from_nand函數把代碼拷貝到ram中。steppingstone只能拷貝4KB,我們需要把所有的代碼搬運到內存中哦

        我們知道s3c6410可以通過SD、onenand、nand啟動,但是我們這里做了簡化,先只從nand啟動,以后會再增加SD卡啟動

        copy_from_nand代碼也在start.S中,做了修改以適合大頁訪問,如有需要請留言告知,將添加copy_from_nand代碼分析

        */

        --------------------

        #ifdef CONFIG_BOOT_NAND
        movr0, #0x1000
        blcopy_from_nand
        #endif

        --------------------

        /*SD卡啟動方式,這個宏我沒有定義,先保留吧*/

        --------------------

        #ifdef CONFIG_BOOT_MOVINAND
        ldrsp, _TEXT_PHY_BASE
        blmovi_bl2_copy
        bafter_copy
        #endif

        --------------------

        /*這里我啥都沒做*/

        after_copy:

        /*

        打開MMU功能

        協處理器c3的作用是存儲的保護和控制,用在MMU中為內存的域訪問控制

        c3為32位寄存器,每兩位為一個訪問控制特權,0x00代表沒有訪問權限,這時候訪問將失效;0x01為客戶類型,將根據

        地址變換條目中的訪問控制位決定是否允許特定內存訪問;0x10是保留的,暫時沒有使用,0x11為管理者權限,不考慮

        地址變換條目中的權限控制位,將不會訪問內存失效。

        ldrr5, =0x0000ffff
        mcrp15, 0, r5, c3, c0, 0,代碼的含義為設置高8個域無訪問權限,低8個域為管理者權限。

        接著下面通過mcrp15, 0, r1, c2, c0, 0指令給c2賦值,c2用于保存頁表基地址。所謂頁表基地址即是虛實轉換的內存頁表的首地址。

        這里r1的值賦值給了c2,r1的值為0x57exxxxx,c2高14位是儲存頁表的基地址

        最后代碼很簡單,打開MMU。

        */

        --------------------

        #ifdef CONFIG_ENABLE_MMU
        enable_mmu:
        /* enable domain access */
        ldrr5, =0x0000ffff
        mcrp15, 0, r5, c3, c0, 0@ load domain access register

        /* Set the TTB register */
        ldrr0, _mmu_table_base
        ldrr1, =CFG_PHY_UBOOT_BASE
        ldrr2, =0xfff00000
        bicr0, r0, r2
        orrr1, r0, r1
        mcrp15, 0, r1, c2, c0, 0

        /* Enable the MMU */
        mmu_on:
        mrcp15, 0, r0, c1, c0, 0
        orrr0, r0, #1/* Set CR_M to enable MMU */
        mcrp15, 0, r0, c1, c0, 0
        nop
        nop
        nop
        nop
        #endif

        --------------------

        /*

        堆棧初始化代碼,我們在這里定義了CONFIG_MEMORY_UPPER_CODE

        sp的值為0xC7FFFFE8

        */

        --------------------

        skip_hw_init:
        /* Set up the stack */
        stack_setup:
        #ifdef CONFIG_MEMORY_UPPER_CODE
        ldrsp, =(CFG_UBOOT_BASE + CFG_UBOOT_SIZE - 0xc)
        #else
        ldrr0, _TEXT_BASE/* upper 128 KiB: relocated uboot */
        subr0, r0, #CFG_MALLOC_LEN/* malloc area */
        subr0, r0, #CFG_GBL_DATA_SIZE /* bdinfo */
        #ifdef CONFIG_USE_IRQ
        subr0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
        #endif
        subsp, r0, #12/* leave 3 words for abort-stack */

        #endif

        --------------------

        /*清零BSS段內容為0 */

        --------------------

        clear_bss:
        ldrr0, _bss_start/* find start of bss segment */
        ldrr1, _bss_end/* stop here */
        mov r2, #0x00000000/* clear */

        clbss_l:
        strr2, [r0]/* clear loop... */
        addr0, r0, #4
        cmpr0, r1
        bleclbss_l

        --------------------

        /*跳轉到uboot代碼的第二個階段,第二階段基本上都是用C實現的,幸好前面sp的值已經設置好了 */

        --------------------

        ldrpc, _start_armboot

        _start_armboot:
        .word start_armboot

        --------------------

        2.第二階段代碼分析(代碼在lib_arm目錄下的board.c里面,start_armboot函數)

        1)初始化CPU及外圍硬件


        init_fnc_t init_fnc_ptr;
        char *s;
        #ifndef CFG_NO_FLASH
        ulong size;
        #endif

        #if defined(CONFIG_VFD) || defined(CONFIG_LCD)
        unsigned long addr;
        #endif

        #if defined(CONFIG_BOOT_MOVINAND)
        uint *magic = (uint *) (PHYS_SDRAM_1);
        #endif

        /* Pointer is writable since we allocated a register for it */
        #ifdef CONFIG_MEMORY_UPPER_CODE /* by scsuh */
        ulong gd_base;

        gd_base = CFG_UBOOT_BASE + CFG_UBOOT_SIZE - CFG_MALLOC_LEN - CFG_STACK_SIZE - sizeof(gd_t);
        #ifdef CONFIG_USE_IRQ
        gd_base -= (CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ);
        #endif
        gd = (gd_t*)gd_base;
        #else
        gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));
        #endif

        /* compiler optimization barrier needed for GCC >= 3.4 */
        __asm__ __volatile__("": : :"memory");

        memset ((void*)gd, 0, sizeof (gd_t));
        gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));
        memset (gd->bd, 0, sizeof (bd_t));

        monitor_flash_len = _bss_start - _armboot_start;

        for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
        if ((*init_fnc_ptr)() != 0) {
        hang ();
        }
        }

        解釋:定義二級指針init_fnc_ptr指向一個存放函數指針的數組,init_fnc_ptr是typedef int (init_fnc_t) (void)類型,即函數類型,init_fnc_ptr可以指向一個沒有參數,返回值為int型的函數指針的地址(很繞哦,),我們看上面代碼最后的for循環init_fnc_ptr = init_sequence,if中會使用(*init_fnc_ptr)()方式調用init_sequence中的函數(函數名可以看為一個地址),如果返回值不是0,則執行hang報錯。

        因為我們定義了CONFIG_MEMORY_UPPER_CODE宏,所以gd = (gd_t*)gd_base,由gd_base的值我們知道,malloc區域、stack區域、bdinfo數據在內存的位置是放在upper of uboot。

        __asm__ __volatile__("": : :"memory");這條是內嵌匯編,請查看另一篇介紹內嵌匯編的博文。

        gd->bd指針指向數據類型為bd_t的結構體,bd_t結構體記錄開發板的參數,例如串口波特率、ip地址、機器類型、啟動參數、環境變量位置等。

        下面分析for循環執行的函數:

        cpu_init:因為我們沒有定義CONFIG_USE_IRQ,所以這個函數直接返回0

        board_init:


        函數內首先執行dm9000_pre_init()函數,因為我們把DM9000AEP網卡映射到內存的Xm0CSn[1]上,所以我們要設置訪問CSn[1]的方式,SROM_BW_REG &= ~(0xf << 4);SROM_BW_REG |= (1<<7) | (1<<6) | (1<<4);兩條代碼的含義為設置0x70000000控制器的CSn[1]訪問方式為nBE enable、wait enable、16位數據總線訪問模式,SROM_BC1_REG = ((DM9000_Tacs<<28)+(DM9000_Tcos<<24)+(DM9000_Tacc<<16)+(DM9000_Tcoh<<12)+(DM9000_Tah<<8)+(DM9000_Tacp<<4)+(DM9000_PMC));這句話的意思是設置訪問的時序,s3c6410 datasheet中已經給出了時序代碼,歡迎查看哦。因為我的zc6410開發箱接了4.3的TFT LCD,所以在代碼中增加了對LCD的配置工作,代碼如下:

        writel(readl(MIFPCON) & (~(1 << 3)), MIFPCON);

        writel(readl(MIFPCON) & (~(3 << 0)) | 0x1, SPCON);

        writel(0xaaaaaaaa, GPICON);
        writel(0xaaaaaa, GPJCON);

        以上四行代碼分別對MIFPCON、SPCON、GPICON、GPJCON四個寄存器賦值。為什么賦值?查看s3c6410 datasheet手冊14.5.1節,給出了DisplayController的引腳配置,MIFPCON的[3]位設置為0(normal mode)instead of “1”(by-pass mode),SPCON[1:0]位的值設置為“01”(useRGB I/F Style)or “00” to use Host I/F Style,我們設置的是01。GPICON、GPJCON賦值的原因請看下面圖:

        第一張圖是LCD控制器接口連接原理圖,后面的是圖是芯片手冊,通過兩個圖我們就知道為什么要寫后面兩行代碼了吧。

        好了,73-74行一個是記錄機器類型,一個是指定向內核傳參的地址。

        interrupt_init:


        184行:值0x0101的含義是設置env_init:如下圖,因為沒有定義ENV_IS_EMBEDDED,所有只是執行了142-143,把環境變量的首地址賦值給gd->env_addr。

        init_baudrate:

        139行使用getenv_r函數在default_environment里找baudrate關鍵字,找到后把“=”號后面的值賦值給gd->baudrate,然后

        再放到gd->bd->bi_baudrate里面。simple_strtoul是uboot實現的字符串轉UL類型。

        serial_init:什么都沒做,保持默認的8位數據、無奇偶校驗、1 停止位、無開始位。

        console_init_f:gd->have_console = 1就這一句話

        display_banner:串口打印uboot信息,就是uboot啟動的時候我們看到的信息,這里使用的是printf,但是我們追進去后,關注的函數

        應該是serial_putc,它是真實向串口輸出一個字符的函數,這個函數會遞歸調用,應該說自己調用自己,遇到n結束。

        print_cpuinfo:打印CPU信息,CPU型號和速度 CPU:...

        checkboard:打印開發板信息 BOARD:...


        dram_init:

        記錄dram的起始地址,0x50000000,size為256M

        display_dram_config:因為沒有定義DEBUG,所以打印DRAM:256M

        2)配置malloc空間

        (因為CONFIG_LCD、CONFIG_VFD沒有定義,所以跳過這一部分)


        我們定義了UPPER_CODE,所以執行第一個mem_malloc_init。這個函數的作用是記錄堆棧空間的起始地址、結束地址、當前地址。

        3)啟動設備初始化(SD、NAND、ONENAND)

        系統一開始嘗試從SD卡啟動,因為本篇介紹的是NANDFLASH啟動方式,所以SD卡部分暫不分析,會單獨開辟章節介紹s3c6410

        的SD卡啟動方式(包括windows下SD flasher應用程序的編寫、SD卡硬件電路分析、SD寄存器操作和啟動流程)。

        下面分析nand啟動方式,我們在自己的頭文件里定義了CONFIG_COMMANDS和CFG_CMD_NAND,所以會執行nand_init函數

        分析代碼中的368行nand_init函數,我們知道在uboot啟動起來之后,會顯示NAND: 512M(你nandflash的大小),所以不難想象,

        nand_init會向終端打印NAND大小的信息,以下是nand_init的實現:


        nand_init函數的實現體在drivers/nand/nand.c中,nand_init函數不僅會打印出nandflash的大小,還會初始化描述nand的結構體

        nand_info以及代表“nand”的設備結構體nand_chip,這兩個結構體前者是mtd層對設備的抽象和對塊設備的接口統一,后者是

        設備的實體,所有對設備的讀寫控制操作都最終通過這個結構體完成,下面我們開始分析nand_init函數:

        64~69行:從外表看,最后會執行size += nand_info[i].size,由此最起碼可以猜測到這個函數會計算出nand的大小。那么是怎樣計算出

        來的呢,我們需要看nand_init_chip函數,注意,在進入這個函數之前,先看一下傳入的三個參數,前兩個參數我們已經介紹過,第三

        個參數是nand的數據寄存器,訪問地址為0x70200010。nand_init_chip函數會根據我們傳入的參數,去查找對應的nand設備,并初

        始化一些功能接口為以后對nand操作做準備,下面看圖:

        47行:mtd->priv實現了mtd中間層對底層nand設備的接口,我們以后在訪問nand硬件時,通過mtd的priv成員可以快速找到我們的

        nand設備。

        49行:nand成員中保存了讀寫nand數據的數據寄存器基地址,我們通過讀寫base_addr中的數據,實現對nand中數據的讀和寫,

        后面的__iomem是個宏定義,這樣定義的#define __iomem,只定義并沒有給值,所以沒有任何功能意義,但是對于我們在看代碼

        的時候,很容易能判斷出后面的變量是IO地址空間的寄存器地址。

        50行:是對nand設備的初始化操作,我們進入函數體

        820~823行:判斷是否是從nand_flash啟動。看s3c6410寄存器就會明白:

        820行:NFCONF定義的宏,其實是取0x70200000地址里面的內容,那么如果我們把OM跳線設置為nand啟動,這個[31]

        位的值就會為1,這樣的話NFCONF & 0x80000000的值就是1了,因而boot_nand的值為1;

        825行:清除0x70200004的[16]位,關閉軟件鎖存,如果此位設置為1,則NFSBLK(0x70200020)到NFEBLK

        (0x70200024)-1被開啟,除了這部分區域,寫或擦除命令是無效的,只有讀命令是有效的(NFSBLK和NFEBLK)為可編程

        可編程開始和結束塊地址寄存器;

        826~827行:我們在進入這個函數的時候就做過了;

        828行:nand->cmd_ctrl = s3c_nand_hwcontrol,這個函數是用于向nand硬件發送命令的,比如發送00h,代表的是讀命令。

        829行:nand->dev_ready = s3c_nand_device_ready,是用于判斷nand芯片處于忙/可讀狀態的。s3c_nand_device_ready

        用一個循環去判斷NFSTAT(0x70000028)的最低位(RnB輸入引腳狀態),如果是1表示現在nand可讀,0代表正在忙不可讀

        830行:bbt(bad block table)壞塊表,因為我們沒用到,所以s3c_nand_scan_bbt函數會直接返回;

        我們沒有定義宏CFG_NAND_FLASH_BBT,所以nand->options|= NAND_SKIP_BBTSCAN,后面宏的含義代表在初始化期間

        將跳過壞塊掃描;

        839行:CFG_NAND_HWECC需要我們自己定義,含義代表使用錯誤糾錯碼;

        840行:代表使用NAND_FLASH模塊內部的ECC模塊產生糾錯碼;

        841行:nand->ecc.hwctl= s3c_nand_enable_hwecc,設置對ECC的控制,這個函數應該在產生ECC編碼前被調用。這個函數的

        功能為確認SLC FLASH或是MLC FLASH,SLC代表single layer cell,MCL為multi-level cell,有關SLC和MLC的區別在容量、可

        讀寫總次數、讀寫速度上,SLC的讀寫速度要快于MLC,但MCL的容量要比SLC大很多,因為1cell可以容納4bits,有興趣可以查閱

        相關手冊。此函數還有一個功能為:1.初始化主區ECC解碼器/編碼器(向0x70200004的[5]位寫1)2.開啟主區ECC

        (向0x70200004的[7]位寫0);

        842行:nand->ecc.calculate= s3c_nand_calculate_ecc,用于存儲產生的校驗糾錯碼;

        843行:nand->ecc.correct= s3c_nand_correct_data,用生成的ECC碼檢測是否有錯誤,沒有則返回,具體內容看函數說明就好;

        845~849行:向nand發送4條命令,849行為等待設備準備好;

        851~852行:將讀取到nand芯片的廠商信息和芯片ID編號;

        854~859行:nand_flash_ids結構體保存了很多公司生產的nand芯片信息和編號,for循環將通過ID找到和我們板子匹配的NAND芯片;

        再往下雖然代碼挺多的,但不用擔心,只會執行877行的nand->ecc.layout = &s3c_nand_oob_16,這是在定義oob信息;

        以下是nand芯片可以處理的命令以及命令的含義(下面是三星K9F4G08U0A-PCB0芯片的命令集)


        分析完board_nand_init函數后,我們繼續看nand_init_chip第52行,nand_scan函數:

        這個函數的主要功能就是2768行的nand_scan_ident函數,功能是填充mtd結構體,配置對nand的接口。這樣下次在訪問設備時,可

        通過mtd層找到對應的底層設備,我們看下nand_scan_ident函數:


        到此,我們不再往下追函數了,

        2501行在設置mtd設備層的接口函數,

        2504行nand_get_flash_type函數代碼比較多,主要的功能還是在獲得nand芯片的廠商信息和ID,并判斷是否支持,如果支持

        為這個nand設備和mtd填充一些功能接口函數,

        我們再來看nand_scan_ident函數的后面代碼,2512行是for循環,maxchips的值是nand_scan函數傳遞進來的1,所以我們最后

        看到的i值為1,在2526行chip->numchips=1,mtd->size=512(我的nandflash型號是K9F4G08U0A-PCB0,大小512M,

        我們在board_nand_init函數中已經在結構體nand_flash_ids中找到我們的nand型號,chipsize的值為512)。

        nand初始化分析完畢。。。


        4)環境變量初始化

        環境變量初始化,即start_armboot函數第379行的env_relocate ()函數,這個函數實現體在env_common.c中,我們看真相:

        這個函數的功能其實就是讓env_ptr指向存放環境變量的首地址,并且填充env_ptr->data成員變量。

        208和212的兩個宏我們沒有定義,所以直接看223行,223行malloc一個CFG_ENV_SIZE的空間用于存放環境變量

        230行是在環境變量被遷移到內存后,我們可以使用內存中的環境變量

        由于gd->env_valid我們前面沒有賦值,所以232行if會執行,打印236行的內容

        240~244行是判斷默認的環境變量是否大于我們限定的環境變量大小

        247行是把uboot代碼中已經存在的default_environment指針所指向的環境變量拷貝到env_ptr->data中

        253行是crc32的校驗,會對環境變量的內容使用CRC32校驗表做每8字節的DO1校驗,校驗表定義在crc32.c的77行

        259行是把內存中可操作的環境變量記錄在全局變量


        5)網絡初始化

        網絡初始化分為IP地址初始化和MAC地址初始化,看下圖:


        387行:IP地址初始化,getenv_IPaddr會返回IPaddr_t(unsigned long)類型的IP地址賦值給gd->bd->bi_ip_addr全局變量。

        進入getenv_IPaddr函數會執行return (string_to_ip(getenv(var))),我們先看getenv(var):

        497行:是給軟件狗準備的,我的ZC6410板子上有硬件狗,所以這個WATCHDOG_RESET其實就是一個空的macro。

        499~510行:會查找環境變量中的name,name是傳遞進來的ipaddr,進入查找旅程env_get_char函數,如果我們沒有忘記的話

        我們在env_relocate ()函數的230行執行過env_get_char = env_get_char_memory,所以會執行env_get_char_memory函數,

        這個函數在env_common.c中實現:


        186行的值同樣在env_relocate正確執行過后,被賦值為1,返回gd->env_addr+index,gd->env_addr指向default_environment

        因為default_environment是個指針數組,index代表數組的索引,即環境變量名得索引,看default_environment數組就能夠明白

        如果你已經自己看過了這兩段for循環,你可能知道外層for循環在遍歷default_environment數組,內層判斷是否超出環境變量的

        大小。后面外層循環會執行507行的envmatch函數,這個函數是真正匹配getenv傳遞進去的ipaddr參數的,里面使用while循環

        去找環境變量中的‘=’號前面的是否有ipaddr字符串,有則返回ipaddr在環境變量中的索引,比如ipaddr=“。。。”

        主站蜘蛛池模板: 仲巴县| 滦平县| 宜兰县| 阿图什市| 洪雅县| 柳江县| 忻州市| 青浦区| 防城港市| 汪清县| 翁源县| 仁寿县| 横山县| 河源市| 扎赉特旗| 南陵县| 英吉沙县| 织金县| 长垣县| 上饶县| 河西区| 兖州市| 穆棱市| 深泽县| 贞丰县| 抚远县| 大关县| 葫芦岛市| 刚察县| 许昌市| 水富县| 滦平县| 莱西市| 永州市| 和平区| 镇沅| 锡林浩特市| 聊城市| 鹤山市| 楚雄市| 来宾市|