1. 程式人生 > >深入嵌入式系統的 BootLoader【轉】

深入嵌入式系統的 BootLoader【轉】

///////////////////////////////////////////////////////////////2011.5.13--黑米/////////////////////////////////////////////////////////////////////////////////

這周在除錯新的處理器和記憶體。遇到了一些問題。解決了,回想這篇文章,大概的思路很有借鑑性!

“設定核心的啟動引數”

是啊  我原來以為只要傳一個大小就可以了,不用管幾段的。。結果掛載ramdisk的時候 就是識別不到第2個控制器上的映像。。但是UBOOT確是可以使用的。

呵呵 這也是經驗吧

// 記憶體段tag的設定
static void setup_memory_tags (bd_t *bd)
{
 int i;
// 系統中存在的記憶體段來設定此引數, 存在多個記憶體段是需要單獨設定,不然會造成核心混亂,譬如DMC0控制器掛256MB記憶體,DMC1上掛256記憶體,如果這個時候CONFIG_NR_DRAM_BANKS你在.h中的定義還是1,那麼UBOOT正常啟動,但是核心啟動掛載檔案系統不行,提示超出範圍或者核心崩潰。因為這個時候的512MB 核心認為是在20000000~40000000!而實際的實體記憶體範圍應該是:DMC0上的20000000~30000000;DMC1上的40000000~50000000;!!!!!

 for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) {
  params->hdr.tag = ATAG_MEM;   // 記憶體段型別設定
  params->hdr.size = tag_size (tag_mem32); // 設定大小

  params->u.mem.start = bd->bi_dram[i].start; // 起始地址
  params->u.mem.size = bd->bi_dram[i].size; // 大小

  params = tag_next (params);     // 取得下一個tag地址
 }
}

作者:不詳,如果原創看到了,請告訴我。

核心映像所佔用的記憶體範圍; 根檔案系統所佔用的記憶體範圍。

    在規劃記憶體佔用的佈局時,主要考慮基地址和映像的大小兩個方面。
    對於核心映像,一般將其拷貝到從(MEM_START+0x8000) 這個基地址開始的大約1MB大小的記憶體範圍內(嵌入式 Linux 的核心一般都不超過 1MB)。為什麼要把從 MEM_START 到MEM_START+0x8000 這段 32KB 大小的記憶體空出來呢?這是因為 Linux 核心要在這段記憶體中放置一些全域性資料結構,如:啟動引數和核心頁表等資訊。
    而對於根檔案系統映像,則一般將其拷貝到 MEM_START+0x0010,0000 開始的地方。如果用 Ramdisk 作為根檔案系統映像,則其解壓後的大小一般是1MB。

(2)從 Flash 上拷貝
    由於像 ARM 這樣的嵌入式 CPU 通常都是在統一的記憶體地址空間中定址 Flash 等固態儲存裝置的,因此從 Flash 上讀取資料與從 RAM 單元中讀取資料並沒有什麼不同。用一個簡單的迴圈就可以完成從 Flash 裝置上拷貝映像的工作:
while(count) {
*dest++ = *src++; /* they are all aligned with word boundary */
count -= 4; /* byte number */
};

3.2.4 設定核心的啟動引數
    應該說,在將核心映像和根檔案系統映像拷貝到 RAM 空間中後,就可以準備啟動 Linux 核心了。
    但是在呼叫核心之前,應該作一步準備工作,即:設定 Linux 核心的啟動引數。
    Linux 2.4.x 以後的核心都期望以標記列表(tagged list)的形式來傳遞啟動引數。啟動引數標記列表以標記 ATAG_CORE 開始,以標記 ATAG_NONE 結束。每個標記由標識被傳遞引數的 tag_header 結構以及隨後的引數值資料結構來組成。

資料結構 tag 和 tag_ header 定義在 Linux 核心原始碼的include/asm/setup.h 標頭檔案中:
/* The list ends with an ATAG_NONE node. */
#define ATAG_NONE 0x00000000

struct tag_header {
    u32 size; /* 注意,這裡size是字數為單位的 */
    u32 tag;
};
……
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;
        /*
        * Acorn specific
        */
    struct tag_acorn acorn;
    /*
    * DC21285 specific
    */
    struct tag_memclk memclk;
} u;
};
在嵌入式 Linux 系統中,通常需要由 Boot Loader 設定的常見啟動引數有:ATAG_CORE、ATAG_MEM、ATAG_CMDLINE、ATAG_RAMDISK、ATAG_INITRD等。
比如,設定 ATAG_CORE 的程式碼如下:
params = (struct tag *)BOOT_PARAMS;
params->hdr.tag = ATAG_CORE;
params->hdr.size = tag_size(tag_core);
params->u.core.flags = 0;
params->u.core.pagesize = 0;
params->u.core.rootdev = 0;
params = tag_next(params);
其中,BOOT_PARAMS 表示核心啟動引數在記憶體中的起始基地址,指標 params 是一個 struct tag 型別的指標。
巨集 tag_next() 將以指向當前標記的指標為引數,計算緊臨當前標記的下一個標記的起始地址。

注意,核心的根檔案系統所在的裝置ID就是在這裡設定的。

下面是設定記憶體對映情況的示例程式碼:
for(i = 0; i < NUM_MEM_AREAS; i++) {
    if(memory_map[i].used) {
        params->hdr.tag = ATAG_MEM;
        params->hdr.size = tag_size(tag_mem32);
        params->u.mem.start = memory_map[i].start;
        params->u.mem.size = memory_map[i].size;
        params = tag_next(params);
    }
}
可以看出,在 memory_map[]陣列中,每一個有效的記憶體段都對應一個 ATAG_MEM 引數標記。

Linux 核心在啟動時可以以命令列引數的形式來接收資訊,利用這一點我們可以向核心提供那些核心不
能自己檢測的硬體引數資訊,或者過載(override)核心自己檢測到的資訊。
比如,我們用這樣一個命令列引數字串"console=ttyS0,115200n8"來通知核心以ttyS0 作為控制檯,且串列埠採用 "115200bps、無奇偶校驗、8位資料位"這樣的設定。下面是一段設定呼叫核心命令列引數字串的示例程式碼:
    char *p;
    /* eat leading white space */
    for(p = commandline; *p == ' '; p++)
    ;
    /* skip non-existent command lines so the kernel will still
    * use its default command line.
    */
    if(*p == '')
    return;
    params->hdr.tag = ATAG_CMDLINE;
    params->hdr.size = (sizeof(struct tag_header) + strlen(p) + 1 + 4) >> 2;
    strcpy(params->u.cmdline.cmdline, p);
    params = tag_next(params);
請注意在上述程式碼中,設定 tag_header 的大小時,必須包括字串的終止符'',此外還要將位元組數向上圓整4個位元組,因為 tag_header 結構中的size 成員表示的是字數。

下面是設定 ATAG_INITRD 的示例程式碼,它告訴核心在 RAM 中的什麼地方可以找到 initrd 映象(壓縮格式)以及它的大小:
params->hdr.tag = ATAG_INITRD2;
params->hdr.size = tag_size(tag_initrd);
params->u.initrd.start = RAMDISK_RAM_BASE;
params->u.initrd.size = INITRD_LEN;
params = tag_next(params);

下面是設定 ATAG_RAMDISK 的示例程式碼,它告訴核心解壓後的 Ramdisk 有多大(單位是KB):
params->hdr.tag = ATAG_RAMDISK;
params->hdr.size = tag_size(tag_ramdisk);
params->u.ramdisk.start = 0;
params->u.ramdisk.size = RAMDISK_SIZE; /* 請注意,單位是KB */
params->u.ramdisk.flags = 1; /* automatically load ramdisk */
params = tag_next(params);
最後,設定 ATAG_NONE 標記,結束整個啟動引數列表:

static void setup_end_tag(void)
{
params->hdr.tag = ATAG_NONE;
params->hdr.size = 0;
}

3.2.5 呼叫核心
Boot Loader 呼叫 Linux 核心的方法是直接跳轉到核心的第一條指令處,
也即直接跳轉到 MEM_START+0x8000 地址處。在跳轉時,下列條件要滿足:

  1. CPU 暫存器的設定:
    R0=0;
    R1=機器型別 ID;關於 Machine Type Number,可以參見 linux/arch/arm/tools/mach-types。
    R2=啟動引數標記列表在 RAM 中起始基地址;
  2. CPU 模式:
        必須禁止中斷(IRQs和FIQs);
        CPU 必須 SVC 模式;
  3. Cache 和 MMU 的設定:
        MMU 必須關閉;
        指令 Cache 可以開啟也可以關閉;
        資料 Cache 必須關閉;

如果用 C 語言,可以像下列示例程式碼這樣來呼叫核心:
void (*theKernel)(int zero, int arch, u32 params_addr) = (void (*)(int, int,u32))KERNEL_RAM_BASE;
……
theKernel(0, ARCH_NUMBER, (u32) kernel_params_start);
注意,theKernel()函式呼叫應該永遠不返回的。如果這個呼叫返回,則說明出錯。

四. 關於串列埠終端
    在 boot loader 程式的設計與實現中,沒有什麼能夠比從串列埠終端正確地收到列印資訊能更令人激動了。此外,向串列埠終端列印資訊也是一個非常重要而又有效的除錯手段。但是,我們經常會碰到串列埠終端顯示亂碼或根本沒有顯示的問題。造成這個問題主要有兩種原因:

  1.  boot loader 對串列埠的初始化設定不正確。
  2.  執行在 host 端的終端模擬程式對串列埠的設定不正確,這包括:波特率、奇偶校驗、資料位和停止位等方面的設定。

    此外,有時也會碰到這樣的問題,那就是:在 boot loader 的執行過程中我們可以正確地向串列埠終端輸出資訊,但當 boot loader 啟動核心後卻無法看到核心的啟動輸出資訊。對這一問題的原因可以從以下幾個方面來考慮:

  •  首先請確認你的核心在編譯時配置了對串列埠終端的支援,並配置了正確的串列埠驅動程式。
  •  你的 boot loader 對串列埠的初始化設定可能會和核心對串列埠的初始化設定不一致。此外,對於諸如 s3c44b0x 這樣的 CPU,CPU 時鐘頻率的設定也會影響串列埠,因此如果boot loader 和核心對其 CPU 時鐘頻率的設定不一致,也會使串列埠終端無法正確顯示資訊。
  •  最後,還要確認 boot loader 所用的核心基地址必須和核心映像在編譯時所用的執行基地址一致,尤其是對於 uClinux 而言。假設你的核心映像在編譯時用的基地址是0xc0008000,但你的 boot loader 卻將它載入到 0xc0010000 處去執行,那麼核心映像當然不能正確地執行了。

五. 結束語
    Boot Loader 的設計與實現是一個非常複雜的過程。如果不能從串列埠收到那激動人心的"uncompressing linux.................. done, booting the kernel……"核心啟動資訊,恐怕誰也不能說:"嗨,我的 boot loader 已經成功地轉起來了!"。

///...............................................................................................................................................................................................................

///........................................................................................

///////////////////////////////////////////////////U-boot給kernel傳引數和kernel讀取引數—struct tag [轉]///////////////////////////////////////////////////////////////////////////////////////////////////

.......................................................................................................................

文章來自:http://blog.chinaunix.net/u3/90973/showart_1925725.html

U-boot 會給 Linux Kernel 傳遞很多引數,如:串列埠, RAM , videofb 等。而 Linux kernel 也會讀取和處理這些引數。兩者之間通過 struct tag 來傳遞引數。 U-boot 把要傳遞給 kernel 的東西儲存在 struct tag 資料結構中,啟動 kernel 時,把這個結構體的實體地址傳給 kernel ; Linux kernel 通過這個地址,用 parse_tags 分析出傳遞過來的引數。

本文主要以 U-boot 傳遞 RAM 和 Linux kernel 讀取 RAM 引數為例進行說明。

1 、u-boot 給kernel 傳RAM 引數

       ./common/cmd_bootm.c 檔案中, bootm 命令對應的 do_bootm 函式,當分析 uImage 中資訊發現 OS 是 Linux 時 , 呼叫 ./lib_arm/bootm.c 檔案中的 do_bootm_linux 函式來啟動 Linux kernel 。

       在 do_bootm_linux 函式中:

void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],

                   ulong addr, ulong *len_ptr, int verify)

{

......

#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)

       setup_start_tag (bd);      // 初始化 tag 結構體開始

#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);      // 設定 RAM 引數

#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

       setup_end_tag (bd);              // 初始化 tag 結構體結束

#endif

......

......

       theKernel (0, machid, bd->bi_boot_params);

// 傳給 Kernel 的引數= (struct tag *) 型的 bd->bi_boot_params

//bd->bi_boot_params 在 board_init 函式中初始化如對於 at91rm9200 ,初始化在 at91rm9200dk.c 的 board_init 中進行: bd->bi_boot_params =PHYS_SDRAM + 0x100;

// 這個地址也是所有 taglist 的首地址,見下面的 setup_start_tag 函式

}

       對於 setup_start_tag 和 setup_memory_tags 函式說明如下。

       函式 setup_start_tag 也在此檔案中定義,如下:

static void setup_start_tag (bd_t *bd)

{

       params = (struct tag *) bd->bi_boot_params;

// 初始化 (struct tag *) 型的全域性變數 params 為bd->bi_boot_params 的地址,之後的setup tags 相關函式如下面的 setup_memory_tags 就把其它 tag 的資料放在此地址的偏移地址上。

       params->hdr.tag = ATAG_CORE;

       params->hdr.size = tag_size (tag_core);

       params->u.core.flags = 0;

       params->u.core.pagesize = 0;

       params->u.core.rootdev = 0;

       params = tag_next (params);

}

RAM 相關引數在 bootm.c 中的函式 setup_memory_tags 中初始化:

static void setup_memory_tags (bd_t *bd)

{

       int i;

       for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) {

              params->hdr.tag = ATAG_MEM;

              params->hdr.size = tag_size (tag_mem32);

              params->u.mem.start = bd->bi_dram[i].start;

              params->u.mem.size = bd->bi_dram[i].size;

              params = tag_next (params);

       }                   // 初始化記憶體相關 tag

}

2 、Kernel 讀取U-boot 傳遞的相關引數

對於 Linux Kernel , ARM 平臺啟動時,先執行 arch/arm/kernel/head.S ,此檔案會呼叫 arch/arm/kernel/head-common.S 中的函式,並最後呼叫 start_kernel :

......

b     start_kernel

......

init/main.c 中的 start_kernel 函式中會呼叫 setup_arch 函式來處理各種平臺相關的動作,包括了 u-boot 傳遞過來引數的分析和儲存:

start_kernel()

{

......

       setup_arch(&command_line);

......

}

       其中, setup_arch 函式在 arch/arm/kernel/setup.c 檔案中實現,如下:

void __init setup_arch(char **cmdline_p)

{

       struct tag *tags = (struct tag *)&init_tags;

       struct machine_desc *mdesc;

       char *from = default_command_line;

       setup_processor();

       mdesc = setup_machine(machine_arch_type);

       machine_name = mdesc->name;

       if (mdesc->soft_reboot)

              reboot_setup("s");

       if (__atags_pointer)             

// 指向各種 tag 起始位置的指標,定義如下:

//unsigned int __atags_pointer  __initdata;

// 此指標指向 __initdata 段,各種 tag 的資訊儲存在這個段中。

              tags = phys_to_virt(__atags_pointer);

       else if (mdesc->boot_params)

              tags = phys_to_virt(mdesc->boot_params);

       if (tags->hdr.tag != ATAG_CORE)

              convert_to_tag_list(tags);

       if (tags->hdr.tag != ATAG_CORE)

              tags = (struct tag *)&init_tags;

       if (mdesc->fixup)

              mdesc->fixup(mdesc, tags, &from, &meminfo);

       if (tags->hdr.tag == ATAG_CORE) {

              if (meminfo.nr_banks != 0)

                     squash_mem_tags(tags);

              save_atags(tags);

              parse_tags(tags); 

// 處理各種 tags ,其中包括了 RAM 引數的處理。

// 這個函式處理如下 tags :

__tagtable(ATAG_MEM, parse_tag_mem32);

__tagtable(ATAG_VIDEOTEXT, parse_tag_videotext);

__tagtable(ATAG_RAMDISK, parse_tag_ramdisk);

__tagtable(ATAG_SERIAL, parse_tag_serialnr);

__tagtable(ATAG_REVISION, parse_tag_revision);

__tagtable(ATAG_CMDLINE, parse_tag_cmdline);

       }

       init_mm.start_code = (unsigned long) &_text;

       init_mm.end_code   = (unsigned long) &_etext;

       init_mm.end_data   = (unsigned long) &_edata;

       init_mm.brk       = (unsigned long) &_end;

       memcpy(boot_command_line, from, COMMAND_LINE_SIZE);

       boot_command_line[COMMAND_LINE_SIZE-1] = '/0';

       parse_cmdline(cmdline_p, from);  // 處理編譯核心時指定的 cmdline 或 u-boot 傳遞的 cmdline

       paging_init(&meminfo, mdesc);

       request_standard_resources(&meminfo, mdesc);

#ifdef CONFIG_SMP

       smp_init_cpus();

#endif

       cpu_init();

       init_arch_irq = mdesc->init_irq;

       system_timer = mdesc->timer;

       init_machine = mdesc->init_machine;

#ifdef CONFIG_VT

#if defined(CONFIG_VGA_CONSOLE)

       conswitchp = &vga_con;

#elif defined(CONFIG_DUMMY_CONSOLE)

       conswitchp = &dummy_con;

#endif

#endif

       early_trap_init();

}

對於處理 RAM 的 tag ,呼叫了 parse_tag_mem32 函式:

static int __init parse_tag_mem32(const struct tag *tag)

{

......

       arm_add_memory(tag->u.mem.start, tag->u.mem.size);

......

}

__tagtable(ATAG_MEM, parse_tag_mem32);

       上述的 arm_add_memory 函式定義如下:

static void __init arm_add_memory(unsigned long start, unsigned long size)

{

       struct membank *bank;

       size -= start & ~PAGE_MASK;

       bank = &meminfo.bank[meminfo.nr_banks++];

       bank->start = PAGE_ALIGN(start);

       bank->size  = size & PAGE_MASK;

       bank->node  = PHYS_TO_NID(start);

}

       如上可見, parse_tag_mem32 函式呼叫 arm_add_memory 函式把 RAM 的 start 和 size 等引數儲存到了 meminfo 結構的 meminfo 結構體中。最後,在 setup_arch 中執行下面語句:

       paging_init(&meminfo, mdesc);

       對有 MMU 的平臺上呼叫 arch/arm/mm/nommu.c 中的 paging_init ,否則呼叫 arch/arm/mm/mmu.c 中的 paging_init 函式。這裡暫不分析 mmu.c 中的 paging_init 函式。

3 、關於U-boot 中的bd 和gd

U-boot 中有一個用來儲存很多有用資訊的全域性結構體-- gd_t ( global data 縮寫),其中包括了 bd 變數,可以說 gd_t 結構體包括了 u-boot 中所有重要全域性變數。最後傳遞給核心的引數,都是從 gd 和 bd 中來的,如上述的 setup_memory_tags 函式作用就是用 bd 中的值來初始化 RAM 相應的 tag 。

對於 ARM 平臺這個結構體的定義大致如下:

include/asm-arm/global_data.h

typedef    struct      global_data {

       bd_t        *bd;

       unsigned long  flags;

       unsigned long  baudrate;

       unsigned long  have_console; /* serial_init() was called */

       unsigned long  reloc_off;       /* Relocation Offset */

       unsigned long  env_addr;       /* Address  of Environment struct */

       unsigned long  env_valid;       /* Checksum of Environment valid? */

       unsigned long  fb_base;  /* base address of frame buffer */

       void        **jt;        /* jump table */

} gd_t;

在 U-boot 中使用 gd 結構之前要用先用巨集 DECLARE_GLOBAL_DATA_PTR 來宣告。這個巨集的定義如下:

include/asm-arm/global_data.h

#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")

從這個巨集的定義可以看出, gd 是一個儲存在 ARM 的 r8 暫存器中的 gd_t 結構體的指標。

說明:本文的版本為U-boot-1.3.4 、Linux-2.6.28 ,平臺是ARM 。

//補充一下:

來自:http://hi.baidu.com/armfans/blog/item/306cd5035f24ff084afb514b.html


bootloader巧妙地利用函式指標及傳參規範將R0:0x0,R1:機器號,R2:引數地址傳遞給核心.由於R0,R1比較簡單,不需要再作說明.需要花點時間瞭解的是R2暫存器.
  R2暫存器傳遞的是一個指標,這個指標指向一個TAG區域.UBOOT和Linux核心之間正是通過這個擴充套件了的TAG區域來進行復雜引數的傳遞,如 command line,檔案系統資訊等等,使用者也可以擴充套件這個TAG來進行更多引數的傳遞.TAG區域存放的地址,也就是R2的值,是在/board /yourboard/youboard.c裡的board_init函式中初始化的,如在UB4020中初始化為:gd->bd->bi_boot_params = 0x30000100;,這是一個絕對地址.
  TAG區的結構比較簡單,可以視為一個一個TAG的排列(陣列?),每一個TAG傳遞一種特定型別的引數.各種系統TAG的定義可以參考./include/asm-arm/setup.h.
  下面是一個TAG區的例子:
  0x30000100 00000005 54410001 00000000 00000000
  0x30000110 00000000 0000000F 54410009 746F6F72
  0x30000120 65642F3D 61722F76 7220306D 6F632077
  0x30000130 6C6F736E 74743D65 2C305379 30303639
  0x30000140 696E6920 6C2F3D74 78756E69 EA006372
  0x30000150 00000004 54420005 30300040 00200000
  0x30000160 00000000 00000000
  我們可以看到一共有三個TAG:
  第一個TAG的長度是5個字,型別是ATAG_CORE(54410001),有三個元素,均為全零.TAG區必須以這個TAG開頭.
  第二個TAG的長度是F個字,型別是ATAG_CMDLINE(54410009),這是一個字串,是向核心傳遞的kernel command line
  第三個TAG的長度是4個字,型別是ATAG_INITRD2(54410005),有兩個元素,第一個是start:30300040(30300000+40),第二個是size:200000(2M)
  如果說還有第四個TAG,那就是末尾的兩個全零,這是TAG結束的標誌.
  這些TAG是在./lib_arm/arm_linux.c中的do_bootm_linux函式中建立起來的.具體建立哪些TAG,由相應的控制巨集決定.具體可以參考相應程式碼.例子中第一個TAG是起始TAG,如果環境變數中有bootargs,則建立第二個TAG,如果bootm有兩個引數(引導檔案系統),則會讀取檔案系統頭部的必要資訊,建立第三個TAG.
  核心啟動後,將根據R2暫存器的值找到這些TAG,並根據TAG型別,呼叫相應的處理函式進行處理,從而獲取核心執行的必要資訊.