Uboot启动流程

    首先要說的是平臺是基於三星S5PV210的,因為不同的平臺啟動方式不一樣。
    先說具體運行的流程,再分析這些流程的代碼實現。
    Inand接在MMC0,MMC1上;sd card接在MMC2上;wifi接在MMC3上。我們從產線第一次打板開始分析,這時候inand為空,沒有任何代碼,我們要啟動幾台,那就必須有代碼運行,总不能把inand拆下来拷贝一份代码进去吧,所以就只能从电脑拷贝代碼到sd card中,插入sd card到机器上, 然后从sd card搬到內存中,如何做到這一點呢?

    原來三星為S5PV210固化了一段代碼在CPU中,不管怎麼樣,CPU每次启动肯定是先執行這段代碼,這段代碼做的事情就是判斷從哪裡哪個外設啟動(如從usb,nand,inand,sd card還是onenand等),如何判斷呢?從OM pin腳?先看CPU手冊534頁:

    電路連接第4頁:


   我们电路R102, R103连接了,为高电平,其它四个电阻没有焊接,为空,即低电平,也就是001100,对照原理图,即为SD/MMC启动, 所以我們的連接決定先從MMC啟動,MMC有MMC0,MMC1,MMC2,MMC3,所以會先從MMC0,這一組我們接的是inand,所以先去找inand,可是CPU啟動代碼判斷到的inand並沒有可以啟動的代碼,接著找MMC2,發現SD card中有可以啟動的代碼,他是怎麼找的呢?原來是直接從SD card偏移地址為512 bytes的地方開始copy,copy多少呢?這個與CPU有關,PV210 copy的是32K(好像是?手冊有說明),copy到哪裡去呢?Copy到CPU內部的ram中(因為這個時候外部的memery還沒有初始化呀,肯定只能copy到CPU內部),這段CPU內部的memery已經被映射到地址為0的地方開始,所以從0x0000000開始執行copy進來的uboot代碼(要說明的是這個時候cpu這段內部的固化代碼已經把從哪個MMC啟動記憶下來了,後面我們要用到它來copy剩下的uboot代碼),接著就開始真正分析uboot代碼,uboot從start.S開始執行,爲什麽是從start.S執行呢?看board/venus/pdc2/u-boot.lds文件:

我們的start.o放在最前面呀,從start.S中那一句開始執行呢?ENTRY(_start)說明了一定要從lable 為_start的地方開始(不要再问我为什么ENTRY就能决定了从_start开始执行,人家编译器就是这么设计的呀):

    直接跳到reset中去了,不過要說明一下7個中斷向量的定義,ARM 的CPU規定從0地址開始的7個32位地址就是中斷的入口地址,所以這裡存放的是各個中斷的跳轉地址,我們看其中一條:
     ldr     pc, _undefined_instruction
      _undefined_instruction
          .word undefined_instruction

    undefined_instruction:
    get_bad_stack
    bad_save_user_regs
    bl    do_undefined_instruction
    第一條爲什麽不直接用 ldr     pc, undefined_instruction 呢?而是要經過一個中間變量來在跳過去?原來如果直接用這條的話跳轉的地址必須放到指令碼中去(放到指令碼的0-11位),這麼一算跳轉的範圍只有4K呀!可是我們用了一個中間變量,就開闢了一個32位的地址空間來訪這個地址,這樣就可以實現4G範圍內的跳轉了(當然這個32位的地址空間和前面的那條中斷跳轉指令也不能相隔超過4K了,我們一般情況下是像上面一样,紧跟連著的啦)
分析完中斷向量接著往下:

這裡要說一下bl    lowlevel_init這一句,在lowlevel_init.S中,我們看初始化memery的函數bl mem_ctrl_asm_init,在cpu_init.S中:


就是我們的memery地址為設麼是從0x30000000開始,從其他地方如0x20000000開始可以不?答案是可以的,配置好這裡的DMC0_MEMCONFIG_0和DMC0_MEMCONFIG_1就好了,至於這個寄存器怎麼用,看手冊,這裡我也加了註釋可以參考。

    讀取OM pin腳的狀態,前面分析CPU內部固化代碼部份的時候有讀過,這裡讀取的作用和前面差不多,保存到一個特定的寄存器中(個人認為前面CPU啟動的代碼如果有保存的話這裡可能就不用再讀一次了),我們這裡肯定是判斷到是從MMC啟動的,接著往下:

    初始化完一些外設后(當然包括memery了),開始copy剩下的uboot到memery中,所以提出剛才保存的OM pin值,這裡也肯定是跳到movi_bl2_copy函數去執行了,在movi.c中:

    看if的判斷語句ch = *(volatile u32 *)(0xD0037488);這個地址是什麽?看手冊也沒有呀?原來它就是前面提到的CPU代碼判斷從MMC0啟動還是MMC2啟動時候保存的標記,這裡讀出來的是ch == ELFIN_HSMMC_2_BASE,所以進入if語句中:

    這裡CONFIG_MMC_PARTITION有定義,MOVI_UBOOT1_START=512;MOVI_UBOOT1_BLKCNT=256K; CFG_PHY_UBOOT_BASE=0X33E00000
意思就是從SD卡512字節開始copy 256k內容到memery的0X33E00000地址中,我們的uboot大小剛好是256K呀(前面說的是copy uboot剩下的部份看來也不是門準確,應該是copy全部),至於爲什麽要放到memery 0X33E00000中?其他位置不行嗎?先看Makefile:

TEXT_BASE在start.S中:

可以這樣理解:標號為_TEXT_BASE這個地址中放的是TEXT_BASE的值33e00000標號為_TEXT_PHY_BASE的地址方的是CFG_PHY_UBOOT_BASE的值,是多少?

靠,原來是同一個地址呀,大概只不過是做不同的標記來增加易讀性吧!再看uboot目錄下的u-boot.dis文件(研究uboot一定看要這個文件才能看的懂^_^),


    這麼一看可明白的,我們的uboot代碼就是從0x33e00000的地方開始執行的呀!現在只不過是原樣的搬到內存中同樣的起始地址,這樣uboot代碼本身根部意識不到自己什麼時候從CPU內部跳到memery中執行了(當然我知道一定是這一句ldr    pc, _start_armboot),問題又來了?我們剛開始是在CPU內部的ram中0x0000000執行代碼的呀,根本不是0x33e00000,百思不得騎姐^_^,原來start.S中前面的這一段代碼是與地址無關的(就是ldr    pc, _start_armboot這一句和他之前的部份,包括這一句哦!),意思就是你把這段代碼放到內存中任何位置他都可以執行,我們可以拿一段出來分析看看是不是這樣的? u-boot.dis中:

    首先要明白CPU的執行過程,就像單片機,上電或者復位后CPU跳到鏈接時指定的入口地址去執行,前面說過,u-boot.lds中的_start就是入口,所以執行ea000013這條指令碼,譯碼器翻譯出這是一條相對跳轉指令b,跳轉的偏移量是低24字節,這裡就是0x13(十進制19)呀,所以跳到相對當前地址增加19*4=76=0x4c,我們往前加0x4c,

    怎麼是_bss_start,不是我們想要的reset呀?難道錯了?原來所謂相對跳轉是相對當前PC的跳轉,當前PC永遠比當前指令執行的地址超前8個字節,這就是ARM的流水線作業了(取址-譯碼-執行),就是你執行時候他已經跑到前面去取址了,你永遠跟不上呀,所以這裡0x4c+0x08=0x54,剛好就是我們想要的reset地址。
扯了很遠呀,回到前面的movi.c中,它把uboot從SD card搬到內存中0x33e00000,然後回到了start.S中,要理解這時候代碼還是在CPU內部的ram中運行。繼續往下:

    使能MMU:

    設置棧寄存器SP指向的地址,CFG_UBOOT_SIZE為2M,這是UBOOT階段所需要的棧大小,清除BBS段,它的起始位置和結束為止放在那裡是由u-boot.lds連接決定的,所謂的BBS段就是我們uboot定義的全局變量,它的空間分配在u-boot.bin文件中,可以說如果你定義了很多全局變量,那麼你編譯出來的u-boot.bin會隨著變大。


可以看到是放在uboot的最後。

接著往下,一句ldr     pc, _start_armboot,執行完這一句,uboot的代碼終於擺脫了CPU內部ram的束縛,從內部ram跳到外部memery中,從此一去不複返。


    這一句意思就是將數值0x33e02b44放到PC寄存器中,PC當然就跑到 地址為0x33e02b44地方去取指令了,這里很容易混淆:ldr pc, [pc, #-4]這是相對跳轉,沒有錯,可是跳到的地方(0x33e001b8)方的是一個數值,就相當於是ldr pc, =0x33e02b44,這個地址就是內存中的地址了,是地址相關的,之前的代碼都沒有用到這樣的跳轉,如果你用到了,那么,恭喜你,uboot掛掉了。start_armboot定義在board.c中,接著後面分析的就是C部份的代碼了,如果到這裡,前面的都弄明白了,uboot你已經會了一半了。
這裡強調一下前面的SD card啟動,uboot知道是從SD card啟動時,會設置標誌位,具體位置在borad.c中,我們不妨先看一下:


    看bootcmd_smt的定義就知道,不想多說了,這樣重啟動時候會進入recovey升級模式,將update.zip包燒寫到inand中,以後啟動就不用sdcard啦!

    跳過board.c中的一些初始化部份,直接進入main_loop函數,在c2/common/main.c:

Abortboot()等待用戶鍵盤輸入,超過bootdelay秒后按默認環境參數啟動,這裡遇到兩個函數run_command和parse_string_outer,先看它們傳進去的字符串參數s:

它是在cmd環境變量的存放區取出參數,cmd環境變量存放的位置是在u-boot.lds連接腳本中定義的,編譯時分配好這裡s=“run bootcmd_android”,所以run_command在這個區域中一個個的查找對比字符”run”; parse_string_outer通過一種有效的算法查找,可以節省時間,其實兩者的執行線路是一樣的,我們這裡分析第一個。先看run 命令,在common/cmd_nvedit.c中有:

do_run 定義在common/main.c中:

我們看到for循環從第二個參數開始分析,這裡第二個參數為bootcmd_android,它定義在include/configs/pdc2.h:

最終被包含在common/env_common.c中:

所以這裡會用run_command依次執行bootargs_android、load_kernel、load_ramdisk、${kernel_start}、${ramdisk_addr},而這些參數又分別等於:

它們都定義在pdc2.h中,可見前面那些命令其實是一些縮寫,是爲了更方便我們的操作。最終結果都是要細化為具體的U_BOOT_CMD命令,如run load_kernel -> load kernel ${kernel_addr} ->load kernel 30008000, 來看load命令,在common/load.c中:

又調用U_BOOT_CMD命令movi,最終的結果就是把kernel搬到30008000地址上去,最後執行的是bootm ${kernel_start} ${ramdisk_addr},這才是uboot跳到kernel的地方。Bootm命令定義在common/cmd_bootm.c中:

可見,這里傳進來的argc = 3, argv[0] = “bootm”, argv[1] = “30008040”, argv[2] = “31000000”,具體分析該函數:

這裡argc =3,執行else部份,simple_strtoul先將字符串“30008040”轉化成16進制數 0x30008040,memmove複製的是uImage的頭文件信息,它是多大呢:

剛好64字節,它就是我們用mkimage工具生成的uImage, 它與zImage的區別就是多了這64字節的信息,沒有這些信息uboot將無法正確啟動kernel,這裡是mkimage的用法mkimage -A arm -O linux -T kernel -C none -a 0x30008000 -e 0x30008000 -n ‘Linux-2.6.32.9’ -d zImage uImage,地址定義在arch/arm/mach-s5pv210/Makefile.boot中(修改裏面的地址與自己的板內存一致),在arch/arm/boot/makefile 中賦值給ZRELADDR 和PARAMS_PHYS ;kernel的log信息done, booting the kernel.在arch/arm/boot/compressed/misc.c中,machine-type定義在arch/arm/tools/mach-types 中,要和uboot中的一致;內存的PHYS_OFFSET在arch/arm/kernel/head.S中用到 ,定義在在arch/arm/mach-s5pv210/include/mach/memory.h中所以從kernel2.6移植到kernel3.0,要修改的地方:
1.arch/arm/mach-s5pv210/Makefile.boot
2.arch/arm/mach-s5pv210/include/mach/memory.h
3.arch/arm/mach-s5pv210/include/mach/map.h
4.arch/arm/tools/mach-types(與uboot一致)

kernel運行mkimage 在 bw-kn/arch/arm/boot/Makefile中。接著看代碼,LINUX_ZIMAGE_MAGIC是linux kernel中加進去的,定義在arch/arm/boot/compressed/head.S中,用來判斷image是不是 linux 的kernel。memmove,複製64byte內容到images.legacy_hdr_os_copy中。最後直接跳到after_header_check去:

switch語句,看do_bootm_linux函數:

bi_arch_number在start_armboot中初始化,調用smdkc110.c的board_init:

在kernel中要判斷該machine id與kenel中的一致才能正常啟動,getenv (“bootargs”);這個前面說過,不記得的回頭看一下:

theKernel = (void (*)(int, int, uint))ep;將地址30008040轉換成函數指針。接著往下看:

結構體struct tag存放的是傳遞給kernel的參數地址,uboot和kernel約定好了一個地方存放這些參數,它們約定的地址在哪呢?

這個地址存放的一系列struct tag結構以setup_start_tag函數初始化為開始,setup_end_tag函數為結束, params->hdr.tag = ATAG_CORE就是開始的標記;(struct tag *) bd->bi_boot_params前面有出現過,不知道有人注意到沒有,smdkc110.c中:

    原來它們約定的地址就是0x30000100,正好是偏移內存起始的地方0x100個byte。params->hdr.size存放的是該結構體的大小;tag_next指向下一個結構體起始位置,後面的自己分析吧,最後一句theKernel (0, machid, bd->bi_boot_params); theKernel 函數帶著machid信息和uboot辛辛苦苦設置好的環境變量,背叛了uboot,投靠kernel去了,從此kernel控制著系統的一切!

我们分析其中一个ramdisk传递的参数看看:

看看开始和结束地址在哪里:

主要事先说一下image_get_data_size这类函数的定义,这些函数都是宏定义得来的,在image.h中:

而这里的ramdisk长度等信息是mkiamge时候算出来并写进头文件里面去的。







  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值