u-boot分析五 向linux內核傳遞啓動參數(詳細)

原文鏈接:https://blog.csdn.net/itxiebo/article/details/50986253

u-boot 向linux 內核傳遞啓動參數(詳細)

U-BOOT 在啓動內核時,會向內核傳遞一些參數.BootLoader 可以通過兩種方法傳遞參數給內核,一種是舊

的參數結構方式(parameter_struct),主要是 2.6 之前的內核使用的方式。另外一種就是現在的 2.6 內核

在用的參數鏈表 (tagged list) 方式。這些參數主要包括,系統的根設備標誌,頁面大小,內存的起始地址

和大小,RAMDISK 的起始地址和大小,壓縮的RAMDISK 根文件系統的起始地址和大小,當前內核命令

參數等而這些參數是通過 struct tag 來傳遞的。U-boot 把要傳遞給 kernel 的東西保存在 struct tag 數據結構中,啓

動 kernel 時,把這個結構體的物理地址傳給 kernel;Linux kernel 通過這個地址分析出u-boot 傳遞的參數。大家都知道U-Boot

啓動的時候會將啓動參數的地址放入R2 中,然後再啓動內核。

首先看兩個重要的數據結構:

第一個是 global_data,定義在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 */

#ifdef CONFIG_VFD

unsigned char vfd_type; /* display type */

#endif

#if 0

unsigned long cpu_clk; /* CPU clock in Hz! */

unsigned long bus_clk;

unsigned long ram_size; /* RAM size */

unsigned long reset_status; /* reset status register at boot */

#endif

void **jt; /* jump table */

} gd_t;

在同一個文件中有如下定義:

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

在需要使用gd 指針的時候,只需要加入DECLARE_GLOBAL_DATA_PTR 這句話就可以了。可以知道,gd 指針始終是放在r8

中的。

其中的第一個變量,bd_t 結構體,定義於include/asm-arm/u-boot.h 中:

typedef struct bd_info {

int bi_baudrate; /* serial console baudrate */

unsigned long bi_ip_addr; /* IP Address */

unsigned char bi_enetaddr[6]; /* Ethernet adress */

struct environment_s *bi_env;

ulong bi_arch_number; /* unique id for this board */

ulong bi_boot_params; /* where this board expects params */

struct /* RAM configuration */

{

ulong start;

ulong size;

} bi_dram[CONFIG_NR_DRAM_BANKS];

#ifdef CONFIG_HAS_ETH1

/* second onboard ethernet port */

unsigned char bi_enet1addr[6];

#endif

} bd_t;

bd_t 中的變量bi_boot_params,表示傳遞給內核的參數的位置。

然後看看 gd 和bd 的初始化,在lib_arm/board.c 中:

gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));

memset ((void*)gd, 0, sizeof (gd_t));

gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));

memset (gd->bd, 0, sizeof (bd_t));

說明這兩個結構體在內存中的位置是在 uboot 的代碼在往下的地址處,所以進行操作的時候不要覆蓋了這個位置!

在 board/smdk2410/smdk2410.c 中,有如下初始化:

gd->bd->bi_boot_params = 0x30000100; 說明參數位置在0x30000100。

現在,具體看看uboot 是如何(按什麼格式)把參數放入內存中。

內核參數鏈表的格式和說明可以從內核源代碼目錄樹中的 include/asm-arm/setup.h 中找到,參數鏈表必

須以ATAG_CORE 開始,以ATAG_NONE 結束。這裏的 ATAG_CORE,ATAG_NONE 是各個參數的

標記,本身是一個32 位值,例如:ATAG_CORE=0x54410001。

其它的參數標記還包括: ATAG_MEM32 , ATAG_INITRD , ATAG_RAMDISK ,ATAG_COMDLINE

等。每個參數標記就代表一個參數結構體,由各個參數結構體構成了參數鏈表。參數結構體的定義如下:

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;

} u;

};

參數結構體包括兩個部分,一個是 tag_header 結構體,一個是u 聯合體。

tag_header 結構體的定義如下:

struct tag_header {

u32 size;

u32 tag;

};

其中 size:表示整個tag 結構體的大小(用字的個數來表示,而不是字節的個數),等於tag_header 的大

小加上u 聯合體的大小,例如,參數結構體 ATAG_CORE 的

size=(sizeof(tag->tag_header)+sizeof(tag->u.core))>>2,一般通過函數 tag_size(struct * tag_xxx)來獲得

每個參數結構體的size。

其中 tag:表示整個tag 結構體的標記,如:ATAG_CORE 等。

聯合體 u 包括了所有可選擇的內核參數類型,包括:tag_core, tag_mem32,tag_ramdisk 等。參數結構

體之間的遍歷是通過函數 tag_next(struct * tag)來實現的。本系統參數鏈表包括的結構體有:

ATAG_CORE,ATAG_MEM,ATAG_RAMDISK,ATAG_INITRD32,ATAG_CMDLINE,ATAG_END。

在整個參數鏈表中除了參數結構體 ATAG_CORE 和ATAG_END 的位置固定以外,其他參數結構體的順

序是任意的。本 BootLoader 所傳遞的參數鏈表如下:第一個內核參數結構體,標記爲ATAG_CORE,參

數類型爲 tag_core。每個參數類型的定義請參考源代碼文件。

我們知道 u-boot 傳遞給內核的參數有很多個,如系統的根設備標誌,頁面大小,內存的起始地址和大小,

RAMDISK 的起始地址和大小,壓縮的RAMDISK 根文件系統的起始地址和大小等,而每個參數我們都是

單獨的採用一個struct tag 來標識的,之前提到的參數標記如ATAG_MEM32,ATAG_INTRD 等就是用來

標識該tag 結構是用來存放的哪種類型的參數。由於不同類型的參數傳遞的信息內容也不盡相同,爲了綜

合不同參數的tag 結構,所以在struct tag 結構中定義了一個聯合體union,根據不同的參數標記符來選擇

聯合體中不同的結構體來存儲參數的內容,如參數標記若爲ATAG_MEM32,則聯合體中採用struct

tag_mem32 來存儲內存參數的內容。

然而內核是如何從 gd->bd->bi_boot_params 指定的地址上知道參數從哪裏開始以及到哪裏結束呢?

所以我們在構建各種參數 tag 時,在開始時先要構建一個參數標記爲ATAG_CORE 的tag 結構標示從這個tag 結構開始接下來

就是參數

現來結合代碼分析在 u-boot 中是如何來構建這多個參數的tag 結構:

/common/cmd_bootm.c 文件中,bootm 命令對應的 do_bootm 函數,當分析uImage 中信息發現 OS 是 Linux 時 ,調

用./lib_arm/bootm.c 文件中的 do_bootm_linux 函數來啓動Linux kernel 。

#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); //通過bd 結構體中參數在內存中的存放地址gd->bd->bi_boot_params 來構建初始化的

tag 結構,表明參數結構的開始

#ifdef CONFIG_SERIAL_TAG

setup_serial_tag (&params); //構建串口參數的tag 結構

#endif

#ifdef CONFIG_REVISION_TAG

setup_revision_tag (&params);

#endif

#ifdef CONFIG_SETUP_MEMORY_TAGS

setup_memory_tags (bd); //構建內存參數的tag 結構

#endif

#ifdef CONFIG_CMDLINE_TAG

setup_commandline_tag (bd, commandline); //構建命令行參數的tag 結構

#endif

#ifdef CONFIG_INITRD_TAG

if (initrd_start && initrd_end)

setup_initrd_tag (bd, initrd_start, initrd_end); //構建ramdisk 參數的tag 結構

#endif

#if defined (CONFIG_VFD) || defined (CONFIG_LCD)

setup_videolfb_tag ((gd_t *) gd);

#endif

setup_end_tag (bd); //最後是構建參數tag 結構結束的tag 結構,標示參數已經結束,參數標記爲ATAG_NONE

#endif

注意上面參數的 tag 結構的構建是有宏的約束的,再來看看具體是怎樣構建每個tag 結構的:

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

static void setup_start_tag (bd_t *bd)

{

params = (struct tag *) bd->bi_boot_params;//將指定的內存中存放參數列表的地址強制轉化爲struct tag 的結構,這樣

便於內核存取各個參數

params->hdr.tag = ATAG_CORE; //標示這個tag 結構是用來標示參數結構的開始

params->hdr.size = tag_size (tag_core); //存放整個tag 結構的大小

params->u.core.flags = 0;

params->u.core.pagesize = 0;

params->u.core.rootdev = 0;

params = tag_next (params);

}

其中用到了一個重要的指針:params,這是一個指向 struct tag 的指針,在文件的開始處聲明,可以被這個文件中的所有函數

訪問:static struct tag *params;

tag 和tag_header 和內核中的結構一模一樣。tag_header 中的tag 字段表示的是這個tag 的類型,在內核

和Bootloader 中通過一些固定的整形常量來表示:

#define ATAG_CORE 0x54410001

#define ATAG_NONE 0x00000000

#define ATAG_CORE 0x54410001

#define ATAG_MEM 0x54410002

#define ATAG_VIDEOTEXT 0x54410003

#define ATAG_RAMDISK 0x54410004

#define ATAG_INITRD 0x54410005

#define ATAG_INITRD2 0x54420005

#define ATAG_SERIAL 0x54410006

#define ATAG_REVISION 0x54410007

#define ATAG_VIDEOLFB 0x54410008

#define ATAG_CMDLINE 0x54410009

#define ATAG_ACORN 0x41000101

#define ATAG_MEMCLK 0x41000402

上面是初始化tag 鏈表(在SDRAM 裏),最後一句是作爲鏈表的最關鍵部分,它的定義是:

#define tag_next(t) ((struct tag *)((u32 *)(t) + (t)->hdr.size)) 作用是指向下一個tag 結構體。一般在每個參

數的tag 結構體的最後都要調用這個宏,內核在遇到這個宏就可以直接跳轉到下一個參數tag 結構體的地

址上來存取。

再來看看其他參數種類的 tag 結構的構建

#ifdef CONFIG_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);

}

}

其中 defined (CONFIG_SETUP_MEMORY_TAGS) 和 defined

(CONFIG_CMDLINE_TAG) 是必不可少的。前者是標記內存的信息,而後者是

設置命令行標記(比如“root=/dev/mtdblock2 init=/linuxrc console=ttySAC0”)

到最後可以看到調用:theKernel (0, machid, bd->bi_boot_params);

當然,有很多的宏來選擇是否傳遞相應的tag 到linux kenel.實際是這些所以針對於bd->bi_boot_params

這個變量.這個變量是個整形變量,代表存放所有tag 的buffer 的地址.

例如,在 smdk2410.c 中的 board_init() 函數中,對於這個變量進行了如下賦值:

gd->bd->bi_boot_params = 0x30000100;

0x30000100 這個值可以隨意指定, 但是要保證和內核中相應的mach_type 一致.以smdk2410 爲例:

在內核中始終這個值的地方是: arch\arm\mach-s3c2410\mach-smdk2410.c 的最後

MACHINE_START(SMDK2410, "SMDK2410")

.phys_ram = S3C2410_SDRAM_PA,

.phys_io = S3C2410_PA_UART,

.io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,

.boot_params = S3C2410_SDRAM_PA + 0x100,

.map_io = smdk2410_map_io,

.init_irq = smdk2410_init_irq,

.timer = &s3c24xx_timer,

MACHINE_END

紅色部分的值, 必須等於0x30000100, 否者將會出現無法啓動的問題.

內核啓動後,會讀取0x300000100 位置的值, 當然,內核會把這個地址轉換成邏輯地址在操作. 因爲內核跑

起來後,MMU 已經工作, 必須要把0x300000100 這個物理地址轉成邏輯地址然後在操作.對於u- boot 傳給

內核的參數中(tag), 內核比較關係memory 的信息,比如memory 地址的起始,大小等.如果沒有得到,那麼內

核無法啓動,內核會進入BUG()函數,然後死在那裏.

而memory 的信息是由CONFIG_SETUP_MEMORY_TAGS 宏決定的. 因此當這個宏沒有被定義時,內

核跑不起來. 初始化meminfo 時會失敗. 現象就是:

Starting Kernel ...

死掉.

一般需要定義:

#define CONFIG_SETUP_MEMORY_TAGS

#define CONFIG_CMDLINE_TAG

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章