uboot與linux核心間的引數傳遞過程分析
阿新 • • 發佈:2019-01-03
轉載於:uboot與linux核心間的引數傳遞過程分析(ChinaUnix上的文章,不能收藏過來,只好轉載了)
U-boot會給Linux Kernel傳遞很多引數,如:串列埠,RAM,videofb、MAC地址等。而Linux kernel也會讀取和處理這些引數。兩者之間通過struct tag來傳遞引數。U-boot把要傳遞給kernel的東西儲存在struct tag資料結構中,啟動kernel時,把這個結構體的實體地址傳給kernel;Linux kernel通過這個地址,用parse_tags分析出傳遞過來的引數。
本文主要以U-boot(1.1.6)傳遞RAM和Linux kernel讀取RAM引數為例進行說明。
1、u-boot給kernel傳RAM引數
在介紹該之前,我們需要看一看幾個資料結構,這些是u-boot中幾個重要的資料結構:
(1)gd_t結構體
U-Boot使用了一個結構體gd_t來儲存全域性資料區的資料,這個結構體在U-Boot的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 //我們一般沒有配置這個,這個是frame buffer的首地址
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;
/*
* Global Data Flags
*/
#define GD_FLG_RELOC 0x00001 /* Code was relocated to RAM */
#define GD_FLG_DEVINIT 0x00002 /* Devices have been initialized */
#define GD_FLG_SILENT 0x00004 /* Silent mode */
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
在global_data.h中U-Boot使用了一個儲存在暫存器中的指標gd來記錄全域性資料區的地址:
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
DECLARE_GLOBAL_DATA_PTR定義一個gd_t全域性資料結構的指標,這個指標存放在指定的暫存器r8中。這個宣告也避免編譯器把r8分配給其它的變數。任何想要訪問全域性資料區的程式碼,只要程式碼開頭加入“DECLARE_GLOBAL_DATA_PTR”一行程式碼,然後就可以使用gd指標來訪問全域性資料區了。
根據U-Boot記憶體使用圖中可以計算gd的值:
gd = TEXT_BASE -CONFIG_SYS_MALLOC_LEN - sizeof(gd_t)
2)bd_t 儲存與板子相關的配置引數
bd_t在U-Boot的include/asm-arm/u-boot.h中定義如下:
typedef struct bd_info {
int bi_baudrate; /* 串列埠通訊波特率 */
unsigned long bi_ip_addr; /* IP地址 */
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配置資訊 */
{
ulong start;
ulong size;
} bi_dram[CONFIG_NR_DRAM_BANKS]; //在我的板子上DRAM配置是1個
#ifdef CONFIG_HAS_ETH1
/* second onboard ethernet port */
unsigned char bi_enet1addr[6];
#endif
} bd_t;
#define bi_env_data bi_env->data
#define bi_env_crc bi_env->crc
U-Boot啟動核心時要給核心傳遞引數,這時就要使用gd_t,bd_t結構體中的資訊來設定標記列表。
3)啟動引數的資料結構
向核心傳遞啟動引數可儲存在兩種資料結構中,param_struct和tag,前者是2.4核心用的,後者是2.6以後的核心更期望用的但是,到目前為止,2.6的核心也可以相容前一種結構,核心引數通過一個靜態的param_struct或tag連結串列在啟動的時候傳遞到核心。需要注意的是,這兩個資料結構在uboot中和linux中分別有定義,這個定義必須一致才能正常傳遞引數如果實際使用中不一致的話就不能正常傳遞,可以自行修改 兩種資料結構具體定義如下(這裡說的是核心原始碼中的定義):
struct param_struct {
union {
struct {
unsigned long page_size; /* 0 */
unsigned long nr_pages; /* 4 */
unsigned long ramdisk_size; /* 8 */
unsigned long flags; /* 12 */
#define FLAG_READONLY 1
#define FLAG_RDLOAD 4
#define FLAG_RDPROMPT 8
unsigned long rootdev; /* 16 */
unsigned long video_num_cols; /* 20 */
unsigned long video_num_rows; /* 24 */
unsigned long video_x; /* 28 */
unsigned long video_y; /* 32 */
unsigned long memc_control_reg; /* 36 */
unsigned char sounddefault; /* 40 */
unsigned char adfsdrives; /* 41 */
unsigned char bytes_per_char_h; /* 42 */
unsigned char bytes_per_char_v; /* 43 */
unsigned long pages_in_bank[4]; /* 44 */
unsigned long pages_in_vram; /* 60 */
unsigned long initrd_start; /* 64 */
unsigned long initrd_size; /* 68 */
unsigned long rd_start; /* 72 */
unsigned long system_rev; /* 76 */
unsigned long system_serial_low; /* 80 */
unsigned long system_serial_high; /* 84 */
unsigned long mem_fclk_21285; /* 88 */
} s;
char unused[256];
} u1;
union {
char paths[8][128];
struct {
unsigned long magic;
char n[1024 - sizeof(unsigned long)];
} s;
} u2;
char commandline[COMMAND_LINE_SIZE];
};
param_struct只需要設定cmmandline,u1.s.page_size,u1.s.nr_pages三個域,下面是使用param_struct例子通過param_struct讓uboot中的go命令可以傳遞引數
分析:go的程式碼在common/cmd_boot.c中,裡面並沒有拷貝啟動引數的程式碼,轉向核心的時候也沒有傳送
啟動引數所在的地址,因此新增如下程式碼用於拷貝引數,可以看到,對於param_struct只需要設定cmmandline
u1.s.page_size,u1.s.nr_pages三個域
char *commandline = getenv("bootargs");
struct param_struct *lxy_params=(struct param_struct *)0x80000100;
printf("setup linux parameters at 0x80000100\n");
memset(lxy_params,0,sizeof(struct param_struct));
lxy_params->u1.s.page_size=(0x1<<12); //4K 這個是必須有的,否則無法啟動
lxy_params->u1.s.nr_pages=(0x4000000)>>12; //64M 這個是必須有的,否則無法啟動
memcpy(lxy_params->commandline,commandline,strlen(commandline)+1);
printf("linux command line is: \"%s\"\n",lxy_params->commandline);
然後還要向核心傳遞引數地址,將下面一行程式碼修改:
rc = ((ulong (*)(int, char *[]))addr) (--argc, &argv[1]); //需要被修改的程式碼
rc = ((ulong(*)(int,int,uint))addr) (0, gd->bd->bi_arch_number,gd->bd->bi_boot_params);//修改之後的程式碼
關於param_struct不是這裡重點,下面主要分析tag
對於tag來說,在實際使用中是一個struct tag組成的列表,在tag->tag_header中,一項是u32 tag(重名,注意型別)其值用巨集ATAG_CORE,ATAG_MEM,ATAG_CMDLINE,ATAG_NONE等等來表示,此時下面union就會使用與之相關的資料結構同時,規定tag列表中第一項必須是ATAG_CORE,最後一項必須是ATAG_NONE,比如在linux程式碼中,找到啟動引數之後首先看tag列表中的第一項的tag->hdr.tag是否為ATAG_CORE,如果不是,就會認為啟動引數不是tag結構而是param_struct結構,然後呼叫函式來轉換.在tag->tag_header中,另一項是u32 size,表示tag的大小,tag組成列表的方式就是指標+size
tag資料結構在arch/arm/include/asm/setup.h(U-Boot的在include/asm-arm/setup.h定義,完全一樣)中定義如下:
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;
};
其中tag_header為tag頭,表明tag_xxx的型別和大小,之所以要標識tag_xxx的型別是因為不同的tag需要不同的處理函式
核心tag_header的結構(arch/arm/include/asm/setup.h)為
struct tag_header {
__u32 size;
__u32 tag;
};
U-Boot的在include/asm-arm/setup.h定義
struct tag_header {
u32 size;
u32 tag;
};
size表示tag的結構大小,tag為表示tag型別的常量。這個靜態的連結串列必須以tag_header.tag = ATAG_CORE開始,並以tag_header.tag = ATAG_NONE結束。由於不同的tag所使用的格式可能不盡相同,所以核心又定義了一個結構tagtable來把tag和相應的操作函式關聯起來
(arch/arm/include/asm/setup.h)
struct tagtable {
__u32 tag;
int (*parse)(const struct tag *);
};
其中tag為標識入ATAG_NONE,ATAG_CORE等。parse為處理函式。Linux核心將tagtable也組成了一個靜態的連結串列放入.taglist.init節中,這是通過__tagtable巨集來實現的
#define __tag __used __attribute__((__section__(".taglist.init")))
#define __tagtable(tag, fn) \
static struct tagtable __tagtable_##fn __tag = { tag, fn }
這個tagtable 列表 是怎麼形成的?
如arch/arm/kernel/setup.c
556 static int __init parse_tag_mem32(const struct tag *tag)
557 {
558 return arm_add_memory(tag->u.mem.start, tag->u.mem.size);
559 }
560
561 __tagtable(ATAG_MEM, parse_tag_mem32);
607 __tagtable(ATAG_SERIAL, parse_tag_serialnr);
608
609 static int __init parse_tag_revision(const struct tag *tag)
610 {
611 system_rev = tag->u.revision.rev;
612 return 0;
613 }
614
615 __tagtable(ATAG_REVISION, parse_tag_revision);
618 static int __init parse_tag_cmdline(const struct tag *tag)
619 {
620 strlcpy(default_command_line, tag->u.cmdline.cmdline, COMMAND_LINE_SIZE);
621 return 0;
622 }
623
624 __tagtable(ATAG_CMDLINE, parse_tag_cmdline);
根據前面相關巨集定義,__tagtable(ATAG_CMDLINE, parse_tag_cmdline)展開後為
static struct tagtable __tagtable_parse_tag_cmdline __used __attribute__((__section__(".taglist.init"))) = { ATAG_CMDLINE, parse_tag_cmdline }
__tagtable將ATAG_CMDLINE和parse_tag_cmdline掛鉤,
再參看arch/arm/kernel/vmlinux.lds.S檔案
34 __proc_info_begin = .;
35 *(.proc.info.init)
36 __proc_info_end = .;
37 __arch_info_begin = .;
38 *(.arch.info.init)
39 __arch_info_end = .;
40 __tagtable_begin = .;
41 *(.taglist.init)
42 __tagtable_end = .;
tagtable 列表編譯連線後被存放在.taglist.init中。
現在再來看一下U-boot給Linux Kernel傳遞啟動引數的傳遞過程
啟動引數是包裝在struct tag資料結構裡的,在linux kernel啟動的時候,bootloader把這個資料結構拷貝到某個地址,在改動PC跳向核心介面的同時,通過通用暫存器R2來傳遞這個地址的值,在bootm執行的流程中,會呼叫do_bootm_linux()在執行Linux核心,核心的起始地址如下:
void (*theKernel)(int zero, int arch, uint params);
image_header_t *hdr = &header;
theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);
header是uImage的頭部,通過頭部,得到核心映像起始的執行地址,標識為theKernel。從中也可以看到,核心接受三個引數,第一個為0,第二個為系統的ID號,第三個是傳入核心的引數。
在do_bootm_linux()的最後,會跳到核心去執行:
theKernel (0, bd->bi_arch_number, bd->bi_boot_params);
thekernel其實不是個函式,而是指向核心入口地址的指標,把它強行轉化為帶三個引數的函式指標,會把三個
引數儲存到通用暫存器中,實現了向kernel傳遞資訊的功能,在這個例子裡,把R0賦值為0,R1賦值為機器號bd->bi_arch_number, R2賦值為啟動引數資料結構的首地址bd->bi_boot_params。最後兩個引數在board/smdk2410/smdk2410.c的board_init()中被初始化。
因此,要向核心傳遞引數很簡單,只要把啟動引數封裝在linux預定好的資料結構裡,拷貝到某個地址(一般
約定俗成是記憶體首地址+100dex,後面會見到) p { margin-bottom: 0.21cm; }
U-boot向核心傳遞引數的具體實現過程
64 #define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
b、在start_armboot(lib_arm/board.c)主函式中計算全域性資料結構的地址並賦值給指標gd,並對struct tag資料結構裡引數賦值
下面是start_armboot函式部分程式碼
55 DECLARE_GLOBAL_DATA_PTR; //gd指標引用聲名
248 gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));
249 /* compiler optimization barrier needed for GCC >= 3.4 */
250 __asm__ __volatile__("": : :"memory");
251
252 memset ((void*)gd, 0, sizeof (gd_t));
253 gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));
254 memset (gd->bd, 0, sizeof (bd_t));
255
256 monitor_flash_len = _bss_start - _armboot_start;
257
258 for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
259 if ((*init_fnc_ptr)() != 0) {
260 hang ();
261 }
262 }
首先在55行對gd指標引用聲名,在248行計算全域性資料結構的地址並賦值給指標gd,具體計算請參看前面的說明,253行計算出結構體中bd指標的地址,然後在第258行逐個呼叫init_sequence初始化函式列表陣列中的初始化函式對平臺硬體進行初始化,這裡只分析後面用到的硬體初始化函式board_init、dram_init。這兩個函式都在board/smdk2410/smdk2410.c中實現
首先看board_init函式,以下是部分實現
31 DECLARE_GLOBAL_DATA_PTR;
105 /* arch number of SMDK2410-Board */
106 gd->bd->bi_arch_number = MACH_TYPE_SMDK2410;
107
108 /* adress of boot parameters */
109 gd->bd->bi_boot_params = 0x30000100;//一般約定俗成是記憶體首地址+100dex
可以看到,theKernel最後兩個引數在這裡的第106和109行被初始化,uboot傳給核心的引數表存被放在記憶體中起始偏移0x100的位置,這裡只是指定了“指標”的位置,但還沒初始化其中的值,後面傳遞到核心的引數列表的構建才初始化其中的值,這是在 do_bootm_linux()中跳到核心前去完成的。值得注意的是, 核心的預設執行地址的0x30008000,前面就是留給引數用的。所以一般不要將核心下載到該地址之前,以免沖掉了傳給核心的引數。這裡在55行同樣要對gd指標引用聲名,MACH_TYPE_SMDK2410在include/asm-arm/mach-types.h中定義,值為192
而dram_init函式是對struct tag資料結構裡記憶體引數賦值,後面會用到。
117 int dram_init (void)
118 {
119 gd->bd->bi_dram[0].start = PHYS_SDRAM_1;
120 gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE;
121
122 return 0;
123 }
PHYS_SDRAM_1與PHYS_SDRAM_1_SIZE巨集都在include/configs/smdk2410.h中定義。
#define CONFIG_NR_DRAM_BANKS 1 /* we have 1 bank of DRAM */
#define PHYS_SDRAM_1 0x30000000 /* SDRAM Bank #1 */
#define PHYS_SDRAM_1_SIZE 0x04000000 /* 64 MB */
c、傳遞到核心的引數列表的構建
./common/cmd_bootm.c檔案中,bootm命令對應的do_bootm函式,當分析uImage中資訊發現OS是Linux時,呼叫./lib_arm/armlinux.c檔案中的do_bootm_linux函式來啟動Linux kernel。在do_bootm_linux函式中(lib_arm/armlinux.c) ,以下是部分相關原始碼:
#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); /* 設定ATAG_CORE標誌 */
#ifdef CONFIG_SERIAL_TAG
setup_serial_tag (¶ms);
#endif
#ifdef CONFIG_REVISION_TAG
setup_revision_tag (¶ms);
#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
setup_end_tag (bd); /* 設定ATAG_NONE標誌 */
#endif
在uboot中,進行設定傳遞到核心的引數列表tag的函式都在lib_arm/armlinux.c中,在這些函式前面是有ifdef的因此,如果你的bootm命令不能傳遞核心引數,就應該是在你的board的config檔案裡沒有對上述的巨集進行設定,定義一下即可
這裡對於setup_start_tag、setup_memory_tags和setup_end_tag函式說明如下。它們都在lib_arm/armlinux.c檔案中定義,如下
static void setup_start_tag (bd_t *bd)
{
params = (struct tag *) bd->bi_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);
}
標記列表必須以ATAG_CORE開始,setup_start_tag函式在核心的引數的開始地址設定了一個ATAG_CORE標記。
#ifdef CONFIG_SETUP_MEMORY_TAGS
static void setup_memory_tags (bd_t *bd) //初始化記憶體相關tag
{
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; //0x30000000
params->u.mem.size = bd->bi_dram[i].size; //0x04000000(64M)
params = tag_next (params);
}
}
#endif /* CONFIG_SETUP_MEMORY_TAGS */
setup_memory_tags函式設定了一個ATAG_MEM標記,該標記包含記憶體起始地址,記憶體大小這兩個引數。RAM相關引數在前面的setup_memory_tags函式中已經初始化.
78 static struct tag *setup_commandline_tag(struct tag *params, char *cmdline)
{
if (!cmdline)
return params; /* eat leading white space */
while (*cmdline == ' ') cmdline++; /*
* Don't include tags for empty command lines; let the kernel
* use its default command line.
*/
if (*cmdline == '\0')
return params; params->hdr.tag = ATAG_CMDLINE;
params->hdr.size =
(sizeof (struct tag_header) + strlen(cmdline) + 1 + 3) >> 2;
strcpy(params->u.cmdline.cmdline, cmdline); return tag_next(params);
}
static void setup_end_tag (bd_t *bd)
{
params->hdr.tag = ATAG_NONE;
params->hdr.size = 0;
}
這個靜態的連結串列必須以標記ATAG_CORE開始,並以標記ATAG_NONE結束。setup_end_tag函式設定了一個ATAG_NONE標記,表示標記列表的結束。
d、最後do_bootm_linux函式呼叫theKernel (0, machid, bd->bi_boot_params)去啟動核心並傳遞引數,可以看見r0是machid,r2是bi_boot_params引數的地址。
2、Kernel讀取U-boot傳遞的相關引數
對於Linux Kernel,ARM平臺啟動時,先執行arch/arm/kernel/head.S,此時r2暫存器的值為引數的地址,此檔案會呼叫arch/arm/kernel/head-common.S中的函式,並最後呼叫start_kernel,看下面head-common.S的原始碼:
14 #define ATAG_CORE 0x54410001
15 #define ATAG_CORE_SIZE ((2*4 + 3*4) >> 2)
16 #define ATAG_CORE_SIZE_EMPTY ((2*4) >> 2)
17
18 .align 2
19 .type __switch_data, %object
20 __switch_data:
21 .long __mmap_switched
22 .long __data_loc @ r4
23 .long _data @ r5
24 .long __bss_start @ r6
25 .long _end @ r7
26 .long processor_id @ r4
27 .long __machine_arch_type @ r5
28 .long __atags_pointer @ r6
29 .long cr_alignment @ r7
30 .long init_thread_union + THREAD_START_SP @ sp
31
32 /*
33 * The following fragment of code is executed with the MMU on in MMU mode,
34 * and uses absolute addresses; this is not position independent.
35 *
36 * r0 = cp#15 control register
37 * r1 = machine ID
38 * r2 = atags pointer
39 * r9 = processor ID
40 */
41 __mmap_switched:
42 adr r3, __switch_data + 4
43
44 ldmia r3!, {r4, r5, r6, r7}
45 cmp r4, r5 @ Copy data segment if needed
46 1: cmpne r5, r6
47 ldrne fp, [r4], #4
48 strne fp, [r5], #4
49 bne 1b
50
51 mov fp, #0 @ Clear BSS (and zero fp)
52 1: cmp r6, r7
53 strcc fp, [r6],#4
54 bcc 1b
55
56 ARM( ldmia r3, {r4, r5, r6, r7, sp})
57 THUMB( ldmia r3, {r4, r5, r6, r7} )
58 THUMB( ldr sp, [r3, #16] )
59 str r9, [r4] @ Save processor ID
60 str r1, [r5] @ Save machine type
61 str r2, [r6] @ Save atags pointer
62 bic r4, r0, #CR_A @ Clear 'A' bit
63 stmia r7, {r0, r4} @ Save control register values
64 b start_kernel
str r2,[r6]:因為通用暫存器2 (r2) 必須是 kernel parameter list 的實體地址(parameter list 是由boot loader傳遞給kernel,用來描述裝置資訊屬性的列表),所以將uboot傳遞進來的tags實體地址數值存入__atags_pointer指標( [r6] )中,__atags_pointer在第28行定義並通過42、56行將其載入到r6中,在arch/arm/kernel/setup.c中的setup_arch中將引用__atags_pointer為指向引數的地址.
init/main.c中的start_kernel函式中會呼叫setup_arch函式來處理各種平臺相關的動作
start_kernel()
{
……
setup_arch(&command_line);
……
}
包括了u-boot傳遞過來引數的分析和儲存,對tag的處理程式碼也在setup_arch裡面。以下是一部分的關鍵程式碼(setup_arch函式在arch/arm/kernel/setup.c檔案中實現):
767 void __init setup_arch(char **cmdline_p)
768 {
769 struct tag *tags = (struct tag *)&init_tags;//tags指向預設的tag連結串列
770 struct machine_desc *mdesc;
771 char *from = default_command_line;
772
773 unwind_init();
774
775 setup_processor();
776 mdesc = setup_machine(machine_arch_type);// mdesc包含啟動引數在記憶體中的地址
.....................................................................................................
782 if (__atags_pointer) //檢查BootLoader是否傳入引數
783 tags = phys_to_virt(__atags_pointer);//bootloader有傳遞啟動引數到核心
784 else if (mdesc->boot_params)//如果BootLoader沒有傳入引數則使用核心machine descriptor中設定的啟動引數地址(arch/arm/mach-s3c2410/mach-smdk2410.c),這裡設定的地址與BootLoader是否傳入的一般是一致的。
785 tags = phys_to_virt(mdesc->boot_params);
786
787 #if defined(CONFIG_DEPRECATED_PARAM_STRUCT)
788 /*
789 * If we have the old style parameters, convert them to
790 * a tag list.
791 */
792 if (tags->hdr.tag != ATAG_CORE)//如果是舊的啟動引數結構,將其轉成新的tag連結串列的形式,新的tag連結串列的形式核心引數列表第一項必須是ATAG_CORE型別,如果不是,則需要轉換成新的核心引數型別。
793 convert_to_tag_list(tags);//此函式完成新舊引數結構轉換,將引數結構轉換為tag list結構
794 #endif
795 if (tags->hdr.tag != ATAG_CORE)//轉換失敗,使用內建的啟動引數
796 tags = (struct tag *)&init_tags;//則選用預設的核心引數,init_tags檔案中有定義。
797
798 if (mdesc->fixup) //用核心引數列表填充meminfo,fixup函數出現在註冊machine_desc中,即MACHINE_START、MACHINE_END定義中,這個函式,有些板子有,但在2410中沒有定義這個函式。
799 mdesc->fixup(mdesc, tags, &from, &meminfo);
800
801 if (tags->hdr.tag == ATAG_CORE) {
802 if (meminfo.nr_banks != 0) //說明記憶體被初始化過
803 squash_mem_tags(tags);//如果在meminfo中有配置記憶體tag則跳過對記憶體tag的處理,如果是tag list,那麼如果系統已經建立了預設的meminfo.nr_banks,清除tags中關於MEM的引數,以免再次被初始化
804 save_atags(tags);
805 parse_tags(tags);//做出一些針對各個tags的處理
806 }
.....................................................................................................
851 }
第769行tags指向預設的tag連結串列,核心中定義了一些預設的tags
init_tags在arch/arm/kernel/setup.c檔案下定義如下
662 static struct init_tags {
663 struct tag_header hdr1;
664 struct tag_core core;
665 struct tag_header hdr2;
666 struct tag_mem32 mem;
667 struct tag_header hdr3;
668 } init_tags __initdata = {
669 { tag_size(tag_core), ATAG_CORE },
670 { 1, PAGE_SIZE, 0xff },
671 { tag_size(tag_mem32), ATAG_MEM },
672 { MEM_SIZE, PHYS_OFFSET },
673 { 0, ATAG_NONE }
674 };
上述結構中一個tag_header和tag_xxx形成了tag的完整描述,tag_size返回tag_head和tag_xxx的總大小,在tag_size中我們要注意的是u32*指標加1地址值實際上地址加了4
#define tag_next(t) ((struct tag*)((u32*)(t)+(t)->hdr.size))
#define tag_size(type) ((sizeof(struct tag_header)+sizeof(struct type)) >> 2
tag_size實際上計算的是(tag_head+tag_xxx)/4。經過進一步的分析還發現每個tag在記憶體中的大小並不是相同的,這一點可以從tag_next看出,tag_next只是將指標移到了下一個tag的tag_header處,這種記憶體佈局更加緊湊。
注:2.6.18核心smdk2410的meminfo沒有設定nr_banks,所以必須在核心的啟動引數裡面傳遞mem=”memory size”@”memory base address”,否則系統識別記憶體錯誤,這點從系統的啟動資訊就可以看出來,而且在載入initrd的時候也會遇到記憶體溢位的錯誤
if (__atags_pointer)
tags = phys_to_virt(__atags_pointer);
指向各種tag起始位置的指標,定義如下:
unsigned int __atags_pointer __initdata;
此指標指向__initdata段,各種tag的資訊儲存在這個段中。
mdesc->fixup(mdesc, tags, &from, &meminfo):fixup函式是板級相關的,通常就是一些ram起址大小bank之類的設定函式,如果執行過了,nrbank就不為0了,那麼繼續執行後面的語句時:
if (tags->hdr.tag == ATAG_CORE) {
if (meminfo.nr_banks != 0)
squash_mem_tags(tags);
parse_tags(tags);
}
就會呼叫squash_mem_tags把你u-boot傳入的值給幹掉,使parse_tags函式呼叫時不會處理ATAG_MEM。
然後執行到parse_tags
parse_tags定義如下(arch/arm/kernel/setup.c)
static void __init parse_tags(const struct tag *t)
{
for (; t->hdr.size; t = tag_next(t))
if (!parse_tag(t)) //針對每個tag 呼叫parse_tag 函式
printk(KERN_WARNING
"Ignoring unrecognised tag 0x%08x\n",
t->hdr.tag);
}
parse_tags遍歷tag連結串列呼叫parse_tag對tag進行處理。parse_tags在tabtable中尋找tag的處理函式(通過tag_header結構中的tag)
static int __init parse_tag(const struct tag *tag)
{
extern struct tagtable __tagtable_begin, __tagtable_end;
struct tagtable *t;
for (t = &__tagtable_begin; t < &__tagtable_end; t++) //遍歷tagtable列表,並呼叫處理函式,
if (tag->hdr.tag == t->tag) {
t->parse(tag); //呼叫處理函式
break;
}
return t < &__tagtable_end;
}
處理各種tags,其中包括了RAM引數的處理。這個函式處理如下tags:
561 __tagtable(ATAG_MEM, parse_tag_mem32);
554 __tagtable(ATAG_CORE, parse_tag_core);
555
對於處理RAM的tag,呼叫了parse_tag_mem32函式:
556 static int __init parse_tag_mem32(const struct tag *tag)
557 {
558 return arm_add_memory(tag->u.mem.start, tag->u.mem.size);
559 }
560
561 __tagtable(ATAG_MEM, parse_tag_mem32);
如上可見,parse_tag_mem32函式呼叫arm_add_memory函式把RAM的start和size等引數儲存到了meminfo結構的meminfo結構體中。對照uboot部分記憶體初始化函式,我們知道uboot傳遞過來的tag->u.mem.start, tag->u.mem.size分別為0x30000000,0x4000000,現在再來分析arm_add_memory
arm_add_memory定義如下(arch/arm/kernel/setup.c)
static int __init arm_add_memory(unsigned long start, unsigned long size)
{
struct membank *bank = &meminfo.bank[meminfo.nr_banks];
if (meminfo.nr_banks >= NR_BANKS) {
printk(KERN_CRIT "NR_BANKS too low, "
"ignoring memory at %#lx\n", start);
return -EINVAL;
}
/*
* Ensure that start/size are aligned to a page boundary.
* Size is appropriately rounded down, start is rounded up.
*/
size -= start & ~PAGE_MASK;
bank->start = PAGE_ALIGN(start);
bank->size = size & PAGE_MASK;
/*
* Check whether this memory region has non-zero size or
* invalid node number.
*/
if (bank->size == 0)
return -EINVAL;
meminfo.nr_banks++;
return 0;
}
經過這樣的處理,setup.c檔案中的meminfo可就不再是
struct meminfo meminfo = { 0, };
而是
struct meminfo meminfo = { 1,{0x30000000,0x4000000,0},{}, };
表示當前有一個記憶體區域,實體地址是從0x30000000開始,大小是64M
最後,在setup_arch中執行下面語句
paging_init(&meminfo, mdesc)
再來看看另一個引數處理函式
618 static int __init parse_tag_cmdline(const struct tag *tag)
619 {
620 strlcpy(default_command_line, tag->u.cmdline.cmdline, COMMAND_LINE_SIZE);
621 return 0;
622 }
623
624 __tagtable(ATAG_CMDLINE, parse_tag_cmdline);
767 void __init setup_arch(char **cmdline_p)
768 {
769 struct tag *tags = (struct tag *)&init_tags;
770 struct machine_desc *mdesc;
771 char *from = default_command_line;
771行default_command_line在setup.c檔案129行中定義如下:
static char default_command_line[COMMAND_LINE_SIZE] __initdata = CONFIG_CMDLINE;
其中CONFIG_CMDLINE在“.config”配置檔案中定義的。定義如下:
CONFIG_CMDLINE="root=/dev/mtdblock3 init=/linuxrc console=ttySAC0,115200 mem=64M"
default_command_line 原來的內容是我們配置檔案中確定的,但是現在,他被tag->u.cmdline.cmdline覆蓋了。可見,從uboot傳遞過來的命令列引數的優先順序要高於配置檔案的預設命令列.
我們接著setup_arch中的parse_tags(tags)往下看:
808 init_mm.start_code = (unsigned long) _text;
809 init_mm.end_code = (unsigned long) _etext;
810 init_mm.end_data = (unsigned long) _edata;
811 init_mm.brk = (unsigned long) _end;
812
813 /* parse_early_param needs a boot_command_line */
814 strlcpy(boot_command_line, from, COMMAND_LINE_SIZE);
815
816 /* populate cmd_line too for later use, preserving boot_command_line */
817 strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);
818 *cmdline_p = cmd_line;
819
820 parse_early_param();
821
822 arm_memblock_init(&meminfo, mdesc);
823
824 paging_init(mdesc);
825 request_standard_resources(&meminfo, mdesc);
init_mm.brk = (unsigned long) _end:從這兒之後的記憶體可以動態的分配了。填充 init_mm 的成員,這些數值在lds裡面。分別是程式碼段,資料段和bss段。
strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);
上面的程式碼先把uboot傳遞過來的命令列引數儲存起來,以備後用。
linux核心commandline引數解析過程
前面詳細分析了u-boot與linux核心間的tag引數傳遞及解析過程,但對命令列引數沒做詳細的分析,在setup_arch函式的第817行我們看到把uboot傳遞過來的命令列引數儲存起來,以備後用。這裡的後用就是linux核心commandline引數解析,也就是給第820行程式碼備用的,對2.6.36以前版本沒用 parse_early_param而是用parse_cmdline函式,在分析這兩個函式前,我們先來看一下從u-boot到核心命令列引數設定及傳遞過程,以便更好的理解後面的分析。
在u-boot的include/configs/smdk2410.h配置檔案中我們可以找到CONFIG_BOOTARGS配置項,在這裡我們可以設定要傳遞的到核心的命令列引數,如:
*#define CONFIG_BOOTARGS "root=/dev/mtdblock3 init=/linuxrc console=ttySAC0,115200 mem=64M"
再看u-boot的common/env_common.c檔案
static uchar env_get_char_init (int index);
uchar (*env_get_char)(int) = env_get_char_init; /************************************************************************
* Default settings to be used when no valid environment is found
*/
#define XMK_STR(x) #x
#define MK_STR(x) XMK_STR(x) uchar default_environment[] = {
#ifdef CONFIG_BOOTARGS
"bootargs=" CONFIG_BOOTARGS "\0"
#endif
#ifdef CONFIG_BOOTCOMMAND
"bootcmd=" CONFIG_BOOTCOMMAND "\0"
#endif
#ifdef CONFIG_RAMBOOTCOMMAND
"ramboot=" CONFIG_RAMBOOTCOMMAND "\0"
#endif
#ifdef CONFIG_NFSBOOTCOMMAND
"nfsboot=" CONFIG_NFSBOOTCOMMAND "\0"
#endif
#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
"bootdelay=" MK_STR(CONFIG_BOOTDELAY) "\0"
#endif
#if defined(CONFIG_BAUDRATE) && (CONFIG_BAUDRATE >= 0)
"baudrate=" MK_STR(CONFIG_BAUDRATE) "\0"
#endif
#ifdef CONFIG_LOADS_ECHO
"loads_echo=" MK_STR(CONFIG_LOADS_ECHO) "\0"
#endif
#ifdef CONFIG_ETHADDR
"ethaddr=" MK_STR(CONFIG_ETHADDR) "\0"
#endif
#ifdef CONFIG_ETH1ADDR
"eth1addr=" MK_STR(CONFIG_ETH1ADDR) "\0"
#endif
#ifdef CONFIG_ETH2ADDR
"eth2addr=" MK_STR(CONFIG_ETH2ADDR) "\0"
#endif
#ifdef CONFIG_ETH3ADDR
"eth3addr=" MK_STR(CONFIG_ETH3ADDR) "\0"
#endif
#ifdef CONFIG_IPADDR
"ipaddr=" MK_STR(CONFIG_IPADDR) "\0"
#endif
#ifdef CONFIG_SERVERIP
"serverip=" MK_STR(CONFIG_SERVERIP) "\0"
#endif
#ifdef CFG_AUTOLOAD
"autoload=" CFG_AUTOLOAD "\0"
#endif
#ifdef CONFIG_PREBOOT
"preboot=" CONFIG_PREBOOT "\0"
#endif
#ifdef CONFIG_ROOTPATH
"rootpath=" MK_STR(CONFIG_ROOTPATH) "\0"
#endif
#ifdef CONFIG_GATEWAYIP
"gatewayip=" MK_STR(CONFIG_GATEWAYIP) "\0"
#endif
#ifdef CONFIG_NETMASK
"netmask=" MK_STR(CONFIG_NETMASK) "\0"
#endif
#ifdef CONFIG_HOSTNAME
"hostname=" MK_STR(CONFIG_HOSTNAME) "\0"
#endif
#ifdef CONFIG_BOOTFILE
"bootfile=" MK_STR(CONFIG_BOOTFILE) "\0"
#endif
#ifdef CONFIG_LOADADDR
"loadaddr=" MK_STR(CONFIG_LOADADDR) "\0"
#endif
。。。。。。。。。。。。。。。
可以知道CONFIG_BOOTARGS被轉化為
"bootargs=""root=/dev/mtdblock3 init=/linuxrc console=ttySAC0,115200 mem=64M"
u-boot引導核心為呼叫u-boot的lib_arm/armlinux.c檔案的do_bootm_linux函式 void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],
ulong addr, ulong *len_ptr, int verify)
{
ulong len = 0, checksum;
ulong initrd_start, initrd_end;
ulong data;
void (*theKernel)(int zero, int arch, uint params);
image_header_t *hdr = &header;
bd_t *bd = gd->bd;
#ifdef CONFIG_CMDLINE_TAG
char *commandline = getenv ("bootargs");
#endif 245 #ifdef CONFIG_CMDLINE_TAG
setup_commandline_tag (bd, commandline);
#endif .......................... }
在這裡它首先呼叫getenv ("bootargs")函式獲得命令列引數並讓commandline指向它,然後呼叫setup_commandline_tag函式將命令列引數放到tag引數例表,
static struct tag *setup_commandline_tag(struct tag *params, char *cmdline)
{
if (!cmdline)
return params; /* eat leading white space */
while (*cmdline == ' ') cmdline++; /*
* Don't include tags for empty command lines; let the kernel
* use its default command line.
*/
if (*cmdline == '\0')
return params; params->hdr.tag = ATAG_CMDLINE;
params->hdr.size =
(sizeof (struct tag_header) + strlen(cmdline) + 1 + 3) >> 2;
strcpy(params->u.cmdline.cmdline, cmdline); return tag_next(params);
} 關於tag引數例表前面己有詳細分析,這裡我只對u-boot取命令列環境引數函式getenv進行分析,它定義在common/cmd_nvedit.c檔案中 char *getenv (char *name)
{
int i, nxt; WATCHDOG_RESET(); for (i=0; env_get_char(i) != '\0'; i=nxt+1) {
int val; for (nxt=i; env_get_char(nxt) != '\0'; ++nxt) {
if (nxt >= CFG_ENV_SIZE) {
return (NULL);
}
}
if ((val=envmatch((uchar *)name, i)) < 0)
continue;
return ((char *)env_get_addr(val));
} return (NULL);
}
這裡重點理解env_get_char函式,它定義在common/env_common.c中: static uchar env_get_char_init (int index);
uchar (*env_get_char)(int) = env_get_char_init; /************************************************************************
* Default settings to be used when no valid environment is found
*/
#define XMK_STR(x) #x
#define MK_STR(x) XMK_STR(x) uchar default_environment[] = {
#ifdef CONFIG_BOOTARGS
"bootargs=" CONFIG_BOOTARGS "\0"
#endif
................. static uchar env_get_char_init (int index)
{
uchar c; /* if crc was bad, use the default environment */
if (gd->env_valid)
{
c = env_get_char_spec(index);
} else {
c = default_environment[index];
} return (c);
}
這裡gd->env_valid引數在start_armboot函式中的初始化函式例表中的env_init函式中設定,如果配置引數儲存在flash中,gd->env_valid被設定為1,這裡就通過env_get_char_spec函式從flash中取引數,否則gd->env_valid設定為0,使用預設環境變數引數,預設環境變數引數定義在u-boot的common/env_common.c檔案uchar default_environment[] ,也就是include/configs/smdk2410.h配置檔案中配置的引數。這裡針對不同的flash儲存晶片有不同的env_get_char_spec定義 common/env_flash.c uchar env_get_char_spec (int index)
{
return ( *((uchar *)(gd->env_addr + index)) );
} common/env_nand.c DECLARE_GLOBAL_DATA_PTR; uchar env_get_char_spec (int index)
{
return ( *((uchar *)(gd->env_addr + index)) );
} common/env_nvram.c #ifdef CONFIG_AMIGAONEG3SE
uchar env_get_char_spec (int index)
{
#ifdef CFG_NVRAM_ACCESS_ROUTINE
uchar c; nvram_read(&c, CFG_ENV_ADDR+index, 1); return c;
#else
uchar retval;
enable_nvram();
retval = *((uchar *)(gd->env_addr + index));
disable_nvram();
return retval;
#endif
}
#else
uchar env_get_char_spec (int index)
{
#ifdef CFG_NVRAM_ACCESS_ROUTINE
uchar c; nvram_read(&c, CFG_ENV_ADDR+index, 1); return c;
#else
return *((uchar *)(gd->env_addr + index));
#endif
}
#endif
為確定gd->env_addr,我們來看一下env_init函式,這裡以flash為例,它在common/env_flash.c中
int env_init(void)
{
#ifdef CONFIG_OMAP2420H4
int flash_probe(void);
if(flash_probe() == 0)
goto bad_flash;
#endif
if (crc32(0, env_ptr->data, ENV_SIZE) == env_ptr->crc) {
gd->env_addr = (ulong)&(env_ptr->data);
gd->env_valid = 1;
return(0);
}
#ifdef CONFIG_OMAP2420H4
bad_flash:
#endif
gd->env_addr = (ulong)&default_environment[0];
gd->env_valid = 0; 使用預設環境變數引數,gd->env_valid設定為0
return (0);
}
而在include/configs/s