天天看點

S5PV210-uboot解析(五)-do_bootm函數分析

在main_loop函數中倒計時結束後就執行 bootcmd 指令跳轉到 do_bootm函數引導核心啟動。

typedef   struct   image_header {      uint32_t    ih_magic;        uint32_t    ih_hcrc;         uint32_t    ih_time;         uint32_t    ih_size;         uint32_t    ih_load;         uint32_t    ih_ep;           uint32_t    ih_dcrc;         uint8_t     ih_os;           uint8_t     ih_arch;         uint8_t     ih_type;         uint8_t     ih_comp;         uint8_t     ih_name[IH_NMLEN];  } image_header_t;   typedef   struct   bootm_headers {           image_header_t  *legacy_hdr_os;          image_header_t  legacy_hdr_os_copy;      ulong       legacy_hdr_valid;   #if defined(CONFIG_FIT)      const   char *fit_uname_cfg;        void         *fit_hdr_os;         const   char *fit_uname_os;       int      fit_noffset_os;        void         *fit_hdr_rd;         const   char *fit_uname_rd;       int      fit_noffset_rd;   #if defined(CONFIG_PPC)      void         *fit_hdr_fdt;        const   char *fit_uname_fdt;      int      fit_noffset_fdt; #endif #endif        int      verify;          struct   lmb *lmb;       } bootm_headers_t;

這兩個結構體都是用來存儲鏡像的頭資訊的, image_header 用于 Legacy 方式啟動的鏡像,而 bootm_headers 用于 Legacy 或 裝置樹(FDT)方式啟動的鏡像。這裡隻分析 Legacy 方式啟動的鏡像,在 image_header 中需要注意這幾個成員:    uint32_t    ih_magic;      uint32_t    ih_ep;     

ih_magic 記憶體儲的是鏡像的魔數,用來給uboot判斷是什麼格式的鏡像(zImage、uImage等)

先看九鼎添加的這一段用zImage啟動的代碼:

#define LINUX_ZIMAGE_MAGIC 0x016f2818           if   (argc < 2) {          addr = load_addr;          debug ( "*  kernel: default image load address = 0x%08lx\n" ,                  load_addr);      }  else   {          addr = simple_strtoul(argv[1], NULL, 16);          debug ( "*  kernel: cmdline image address = 0x%08lx\n" , img_addr);      }   如果 argc<2,也就是沒有傳參的情況, uboot 使用預設的 kernel 加載位址,如果有傳參,就會使用傳遞的位址。 load_addr 是在之前用宏定義指派的一個 unsigned long 變量。        if   (*(ulong *)(addr + 9*4) ==  LINUX_ZIMAGE_MAGIC ) {          printf ( "Boot with zImage\n" );          addr = virt_to_phys(addr);          hdr = (image_header_t *)addr;          hdr->ih_os =  IH_OS_LINUX ;          hdr->ih_ep = ntohl(addr);

從 kernel 的起始位址後的36個位元組,也就是第37-40位元組中存儲的是鏡像的魔數,如果等于 LINUX_ZIMAGE_MAGIC 就說明這個鏡像是 zImage 的鏡像。 之後進行了一個虛拟位址到實體位址的轉換,然後将 addr 類型轉換為 image_header_t,之後指派, hdr->ih_os 代表鏡像的系統, hdr->ih_ep 代表鏡像的入口(entry point)。 ntohl函數是用來轉換網絡位元組序到主機位元組序的,與大小端有關,追了幾層都是__開頭的函數,系統調用的函數,一般不用管。            memmove   (&images.legacy_hdr_os_copy, hdr,  sizeof (image_header_t));                     images.legacy_hdr_os = hdr;            images.legacy_hdr_valid = 1;

把 image_header_t 的資訊複制到 bootm_headers_t 中。            goto   after_header_check;

頭資訊校驗完畢,跳轉到 after_header_check 标号執行引導代碼      } #endif

之後再看下uboot中自帶的檢查鏡像頭資訊的一部分,和九鼎添加的這段代碼差不多,隻是封裝更好,基本都調用函數來從頭資訊中擷取鏡像的資訊來完成檢查的操作。

os_hdr = boot_get_kernel (cmdtp, flag, argc, argv,          &images, &os_data, &os_len); if   (os_len == 0) {      puts   ( "ERROR: can't get kernel image!\n" );      return   1; }   switch   (genimg_get_format (os_hdr)) { case   IMAGE_FORMAT_LEGACY:      type = image_get_type (os_hdr);      comp = image_get_comp (os_hdr);      os = image_get_os (os_hdr);        image_end = image_get_image_end (os_hdr);      load_start = image_get_load (os_hdr);      break ;

---

跳轉到 after_header_check 标号,Legacy方式就是一個switch語句,根據鏡像的系統來進入到對應的引導核心啟動的函數中。

after_header_check:      os = hdr->ih_os; #endif        switch   (os) {      default :                 case   IH_OS_LINUX : #ifdef CONFIG_SILENT_CONSOLE          fixup_silent_linux(); #endif          do_bootm_linux (cmdtp, flag, argc, argv, &images);          break ;

這裡進入到 do_bootm_linux 函數引導核心啟動。

void   do_bootm_linux (cmd_tbl_t *cmdtp,  int   flag,  int   argc,  char   *argv[],               bootm_headers_t *images) {      ulong   initrd_start, initrd_end;      ulong   ep = 0;      bd_t    *bd = gd->bd;      char     *s;      int machid = bd->bi_arch_number;      void     (*theKernel)( int   zero,  int   arch, uint params);      int ret;   這裡定義的幾個變量, ep 是鏡像入口,最後會指派給 theKernel ,theKernel 所在的位址就是核心啟動的第一句代碼,還會接受 uboot 給他傳遞的幾個參數。

#ifdef  CONFIG_CMDLINE_TAG      char   *commandline =  getenv   ( "bootargs" ); #endif             if   (images->legacy_hdr_valid) {          ep = image_get_ep (&images->legacy_hdr_os_copy); #if defined(CONFIG_FIT)      }  else   if   (images->fit_uname_os) {          ret = fit_image_get_entry (images->fit_hdr_os,                      images->fit_noffset_os, &ep);          if   (ret) {              puts   ( "Can't get entry point property!\n" );              goto   error;          } #endif      }  else   {          puts   ( "Could not find kernel entry point!\n" );          goto   error;      }

這裡用來找到 kernel 的入口,Legacy 方式就直接用 image_get_ep 函數擷取之前在 do_bootm 函數中得到的 ep,但是這裡有點問題,我用SI找不到 image_get_ep 這個函數的定義,隻找得到這個函數被調用,很奇怪。後來在uboot/include/image.h 檔案中找到了這個函數的定義,這個函數是用宏定義的,和U_BOOT_CMD宏類似。

#define image_get_hdr_l(f) \      static   inline   uint32_t image_get_##f(image_header_t *hdr) \      { \          return   uimage_to_cpu (hdr->ih_##f); \      } image_get_hdr_l (ep);

追進 uimage_to_cpu 函數後發現就是 ntohl 函數,效果就是将 hdr->ih_ep 進行關于大小端的轉換後傳回這個值。

     theKernel = ( void   (*)( int ,  int , uint))ep;

這裡就是将 ep 指派給 theKernel,theKernel 将是核心啟動的第一句代碼的位址。        s =  getenv   ( "machid" );      if   (s) {          machid = simple_strtoul (s, NULL, 16);          printf   ( "Using machid 0x%x from environment\n" , machid);      }

擷取 machid,之後會作為參數傳遞給核心進行比對。如果不同就不能啟動。        ret = boot_get_ramdisk (argc, argv, images,  IH_ARCH_ARM ,              &initrd_start, &initrd_end);      if   (ret)          goto   error;   這裡和 ramdisk (虛拟記憶體盤?)有關,在函數定義處發現大部分代碼和FIT有關,與Legacy方式關系有限。

     show_boot_progress (15);        debug ( "## Transferring control to Linux (at address %08lx) ...\n" ,             (ulong) theKernel);   #if defined ( CONFIG_SETUP_MEMORY_TAGS ) || \      defined ( CONFIG_CMDLINE_TAG ) || \      defined ( CONFIG_INITRD_TAG ) || \      defined (CONFIG_SERIAL_TAG) || \      defined (CONFIG_REVISION_TAG) || \      defined (CONFIG_LCD) || \      defined (CONFIG_VFD) || \      defined ( CONFIG_MTDPARTITION )      setup_start_tag (bd); #ifdef CONFIG_SERIAL_TAG      setup_serial_tag (&params); #endif #ifdef CONFIG_REVISION_TAG      setup_revision_tag (&params); #endif #ifdef  CONFIG_SETUP_MEMORY_TAGS      setup_memory_tags (bd); #endif #ifdef  CONFIG_CMDLINE_TAG      setup_commandline_tag (bd, commandline); #endif #ifdef  CONFIG_INITRD_TAG      if   (initrd_start && initrd_end)          setup_initrd_tag (bd, initrd_start, initrd_end); #endif #if defined (CONFIG_VFD) || defined (CONFIG_LCD)      setup_videolfb_tag ((gd_t *) gd); #endif   #ifdef  CONFIG_MTDPARTITION      setup_mtdpartition_tag(); #endif        setup_end_tag (bd); #endif   這一段代碼和 uboot 向核心傳參有關,uboot 向核心的傳參方式是 tag 傳參,tag 是 linux中定義的一種資料結構,uboot也定義了相同的資料結構以便于向 kernel 傳參。

struct   tag {          struct   tag_header hdr;          union   {                   struct   tag_core         core;                  struct   tag_mem32        mem;                  struct   tag_videotext    videotext;                  struct   tag_ramdisk      ramdisk;                  struct   tag_initrd       initrd;                  struct   tag_serialnr     serialnr;                  struct   tag_revision     revision;                  struct   tag_videolfb     videolfb;                  struct   tag_cmdline      cmdline;                                                      struct   tag_acorn        acorn;                                                      struct   tag_memclk       memclk;                                     struct   tag_mtdpart      mtdpart_info;          } u; };

struct   tag_header {      u32 size;      u32 tag; };

這個 tag 資料結構中定義了兩個成員,一個是 tag_header 結構體,其中的 tag 成員用來表示有效信 息(比如 tag為 ATAG_CORE 就是起始,ATAG_NONE 就是結束,其他的 ATAG_XX 就是表示下面的 聯合體中具體是哪一個結構體)。還有就是這個 tag 結構體沒有定義一個具體的變量,在操作時是事 先定義的 tag* 類型的指針 params 來操作的。

這一段代碼中的 setup_xxx_tag 函數實作方式、作用都非常類似,都是首先給 hdr 指派 tag參數的名稱和大小,随後給聯合體中寫入之前存放闆子參數資訊的bd變量中的值。

          printf   ( "\nStarting kernel ...\n\n" );   #ifdef CONFIG_USB_DEVICE      {          extern   void   udc_disconnect ( void );          udc_disconnect ();      } #endif        cleanup_before_linux ();

這個是在啟動核心之前清 cache。        theKernel (0, machid, bd->bi_boot_params);           return ;   跳轉到 theKernel,附帶三個參數,第一個是0,第二個是機器碼,第三個是一系列 tag 結構體的首位址(即 params = ( struct   tag *) bd->bi_boot_params;params->hdr.tag = ATAG_CORE 的結構體的位址)。在這裡跳轉到 theKernel 後就正式進入到核心了,是以這裡的注釋寫的 does not return ,不會傳回了。

error:      do_reset (cmdtp, flag, argc, argv);      return ; }

到這裡,uboot 已經基本解析完畢,還剩下關于裝置樹啟動方式的分析,這個以後再說吧。 回顧總結整個 uboot 啟動過程,從 Makefile 開始配置編譯uboot,随後從 start.S 開始啟動 跳轉到start_armboot 又到main_loop 最後到達 do_bootm 最終引導啟動核心。這次對 uboot 代碼的分析,一方面是鍛煉我閱讀代碼的能力,另一方面是加強對整個 uboot 啟動的了解,還有就是在這個過程中了解 uboot 代碼的實作思路,uboot 整個代碼的可移植性非常強,幾乎考慮到了所有方面,大量的使用條件編譯來增強其可移植性。uboot 中指令集的巧妙實作方式、對字元數組的解析、使用函數指針來跳轉等技巧也使我受益匪淺。 接下來就準備開始移植 uboot 了。