一、啓動參數傳遞過程
the kernel其實不是個函數,而是指向內核入口地址的指針,把它強行轉化爲帶三個參數的函數指針,會把三個參數保存到通用寄存器中,實現了向kernel傳遞信息的功能,在這個例子裏,會把R0賦值爲0,R1賦值爲機器號 R2賦值爲啓動參數數據結構的首地址。
因此,要向內核傳遞參數很簡單,只要把啓動參數封裝在linux預定好的數據結構裏,拷貝到某個地址(一般約定俗成是內存首地址+0x100) 。在tx2440開發板,該地址爲 0x3000 0000+0x100。
二、啓動參數數據結構
2.1 param_struct數據結構(適用2.4內核用的)
struct param_struct {
union
{
struct
{
unsigned long page_size; /* 0 */
unsigned long nr_pages; /* 4 */
unsigned long ramdisk_size; /* 8 */
。。。。。。。。。。。。。。。。。。。。。
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三個域,具體使用可參見下面的例子。2.2 tag數據結構(適用2.6內核用的)
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; //Acorn specific
struct tag_omap omap; //OMAP specific
struct tag_memclk memclk; //DC21285 specific
} u;
};
對於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_next (params).tag的具體使用的例子
需要注意的是,這兩個數據結構在uboot中和linux中分別有定義,這個定義必須一致才能正常傳遞參數如果實際使用中不一致的話就不能正常傳遞,可以自行修改。
三、兩種數據結構傳遞啓動參數例子
1:例子一:通過param_struct讓uboot中的go命令可以傳遞參數
分析:go的代碼在common/cmd_boot.c中,裏面並沒有拷貝啓動參數的代碼,轉向內核的時候也沒有傳送啓動參數所在的地址,因此添加如下代碼用於拷貝參數,可以看到,對於param_struct只需要設置cmmandline、u1.s.page_size、u1.s.nr_pages三個域
char *commandline =getenv("bootargs");通過環境變量bootargs, 獲取傳遞給內核啓動參數
struct param_struct *lxy_params=(struct param_struct *)0x30000100;設置地址
memset(lxy_params,0,sizeof(struct param_struct)); lxy_params區域清零
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);
然後還要向內核傳遞參數地址,
2:例子二:bootm命令中通過拷貝tag傳遞參數
爲方便閱讀,進行了少許修改,但功能不變,該函數參數爲存放啓動參數的地址
static void setup_linux_tag(ulong param_base)
{
struct tag *params = (struct tag *)param_base;
char *linux_cmd;
char *p;
memset(params, 0, sizeof(struct tag));
/* step1: setup start tag */
params->hdr.tag = ATAG_CORE;
params->hdr.size = tag_size(tag_core);
params->u.core.flags = 0;
params->u.core.pagesize = LINUX_PAGE_SIZE;
params->u.core.rootdev = 0;
params = tag_next(params);
/* step2: setup cmdline tag */
params->hdr.tag = ATAG_CMDLINE;
linux_cmd = getenv("bootargs");
/* eat leading white space */
for (p=linux_cmd; *p==' '; p++) {/* do nothing */;}
params->hdr.size = (sizeof(structtag_header)+strlen(linux_cmd)+1+4) >> 2;
memcpy(params->u.cmdline.cmdline, linux_cmd,strlen(linux_cmd)+1);
params = tag_next(params);
/* step3: setup end tag */
params->hdr.tag = ATAG_NONE;
params->hdr.size = 0;
}
四、調用內核前狀態
U-Boot設置好標記列表後就要調用內核了。但調用內核前,CPU必須滿足下面的條件:
(1) CPU寄存器的設置
Ø r0=0
Ø r1=機器碼
Ø r2=內核參數標記列表在RAM中的起始地址
(2) CPU工作模式
Ø 禁止IRQ與FIQ中斷
Ø CPU爲SVC模式
(3) 使數據Cache與指令Cache失效
do_bootm_linux中調用的cleanup_before_linux函數完成了禁止中斷和使Cache失效的功能。cleanup_before_linux函數在cpu/arm920t/cpu.中定義:
intcleanup_before_linux (void)
{ disable_interrupts (); /* 禁止FIQ/IRQ中斷 */
/* turn off I/D-cache */
icache_disable(); /* 使指令Cache失效 */
dcache_disable(); /* 使數據Cache失效 */
/* flush I/D-cache */
cache_flush(); /* 刷新Cache */
return 0;
}
由於U-Boot啓動以來就一直工作在SVC模式,因此CPU的工作模式就無需設置了。
do_bootm_linux中:
64 void (*theKernel)(intzero, int arch, uint params);
……
73 theKernel = (void (*)(int, int, uint))images->ep;
……
128 theKernel (0, machid, bd->bi_boot_params);
第73行代碼將內核的入口地址“images->ep”強制類型轉換爲函數指針。根據ATPCS規則,函數的參數個數不超過4個時,使用r0~r3這4個寄存器來傳遞參數。因此第128行的函數調用則會將0放入r0,機器碼machid放入r1,內核參數地址bd->bi_boot_params放入r2,從而完成了寄存器的設置,最後轉到內核的入口地址。
到這裏,U-Boot的工作就結束了,系統跳轉到Linux內核代碼執行。