U-Boot與Linux內核的交互
說明:本文所使用的U-Boot的版本是1.1.6,平台是S3C2440。
目錄
1.1標記列表
二、設置標記存放的地址
2.1相關的結構體定義
2.2標記存放地址的設定
三、標記的設置
3.1設置標記ATAG_CORE
3.2設置內存標記ATAG_MEM
3.3設置命令行標記ATAG_CMDLINE
3.4設置ATAG_NONE
一、簡介
U-Boot與Linux內核的交互是單向的,U-Boot將各類參數傳遞給內核。由於他們不能同時運行,傳遞辦法只能有一個個:U-Boot將參數放在某個約定的地方之后,在啟動內核,內核啟動后從這個地方獲得參數。
1.1標記列表
除了約定好參數存放的地方外,還要規定參數的結構。Linux2.4.x以后的內核都以標記列表(tagged list)的形式來傳遞參數。標記就是一種數據結構;標記列表就是挨着存放的多個標記。標記列表以標記ATAG_CORE開始,以ATAGE_NONE結束。
標記的數據結構為tag,它是偶一個tag_header結構和一個聯合體(union)組成。tag_header結構體表示標記的類型及長度,比如是表示內存還是表示命令行參數等。對於不同類型的標記使用不同的聯合體,比如表示內存=時使用tag_men32,表示命令行時使用tag_cmdline。其定定義在include/asm-arm/setup.c文件中。
/* * The new way of passing information: a list of tagged entries */ /* The list ends with an ATAG_NONE node. */ #define ATAG_NONE 0x00000000 struct tag_header { u32 size; u32 tag; }; /* The list must start with an ATAG_CORE node */ #define ATAG_CORE 0x54410001 struct tag_core { u32 flags; /* bit 0 = read-only */ u32 pagesize; u32 rootdev; }; /* it is allowed to have multiple ATAG_MEM nodes */ #define ATAG_MEM 0x54410002 struct tag_mem32 { u32 size; u32 start; /* physical start address */ }; /* VGA text type displays */ #define ATAG_VIDEOTEXT 0x54410003 struct tag_videotext { u8 x; u8 y; u16 video_page; u8 video_mode; u8 video_cols; u16 video_ega_bx; u8 video_lines; u8 video_isvga; u16 video_points; }; /* describes how the ramdisk will be used in kernel */ #define ATAG_RAMDISK 0x54410004 struct tag_ramdisk { u32 flags; /* bit 0 = load, bit 1 = prompt */ u32 size; /* decompressed ramdisk size in _kilo_ bytes */ u32 start; /* starting block of floppy-based RAM disk image */ }; /* describes where the compressed ramdisk image lives (virtual address) */ /* * this one accidentally used virtual addresses - as such, * its depreciated. */ #define ATAG_INITRD 0x54410005 /* describes where the compressed ramdisk image lives (physical address) */ #define ATAG_INITRD2 0x54420005 struct tag_initrd { u32 start; /* physical start address */ u32 size; /* size of compressed ramdisk image in bytes */ }; /* board serial number. "64 bits should be enough for everybody" */ #define ATAG_SERIAL 0x54410006 struct tag_serialnr { u32 low; u32 high; }; /* board revision */ #define ATAG_REVISION 0x54410007 struct tag_revision { u32 rev; }; /* initial values for vesafb-type framebuffers. see struct screen_info * in include/linux/tty.h */ #define ATAG_VIDEOLFB 0x54410008 struct tag_videolfb { u16 lfb_width; u16 lfb_height; u16 lfb_depth; u16 lfb_linelength; u32 lfb_base; u32 lfb_size; u8 red_size; u8 red_pos; u8 green_size; u8 green_pos; u8 blue_size; u8 blue_pos; u8 rsvd_size; u8 rsvd_pos; }; /* command line: \0 terminated string */ #define ATAG_CMDLINE 0x54410009 struct tag_cmdline { char cmdline[1]; /* this is the minimum size */ }; /* acorn RiscPC specific information */ #define ATAG_ACORN 0x41000101 struct tag_acorn { u32 memc_control_reg; u32 vram_pages; u8 sounddefault; u8 adfsdrives; }; /* footbridge memory clock, see arch/arm/mach-footbridge/arch.c */ #define ATAG_MEMCLK 0x41000402 struct tag_memclk { u32 fmemclk; }; 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; }; #define tag_next(t)<span style="white-space:pre"> </span>((struct tag *)((u32 *)(t) + (t)->hdr.size)) #define tag_size(type)<span style="white-space:pre"> </span>((sizeof(struct tag_header) + sizeof(struct type)) >> 2) //???
二、設置標記存放的地址
2.1相關的結構體定義
結構體bd中保存了標記存放的地址。bd結構體是gd結構體的一項,我們先看gd結構體,其定義在include/asm-arm/global_data.h文件中:
typedef struct global_data { bd_t *bd;//開發板相關參數 ,結構體變量,參考u-boot.h unsigned long flags;//指示標志,如設備已經初始化標志等 unsigned long baudrate;//串行口通訊速率 unsigned long have_console; /* serial_init() was called 如果執行了該函數,則設置為1 */ unsigned long reloc_off; /* *Relocation Offset 重定位偏移,就是實際定向的位置與編譯連接時指定的位置之差,一般為0 */ unsigned long env_addr; /* 環境參數地址*/ unsigned long env_valid; /* 環境參數CRC檢驗有效標志*/ unsigned long fb_base; /*幀緩沖區基地址*/ #ifdef CONFIG_VFD unsigned char vfd_type; /* 顯示類型*/ #endif #if 0 unsigned long cpu_clk; /*cpu時鍾*/ 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;
接來下我們來看一下bd結構體,這個結構體定義在include/asm-arm/u-boot.h文件中:
typedef struct bd_info { int bi_baudrate; /* 串口波特率*/ unsigned long bi_ip_addr; /* IP 地址*/ unsigned char bi_enetaddr[6]; /* MAC地址*/ struct environment_s *bi_env; ulong bi_arch_number; /* 板子的id*/ ulong bi_boot_params; /* 啟動參數*/ struct /* RAM 配置*/ { 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;
2.2標記存放地址的設定
在board/smdk2410/smdk2410.c的board_init 函數設置了bi_boot_params 參數:
int board_init (void) { S3C24X0_CLOCK_POWER * const clk_power = S3C24X0_GetBase_CLOCK_POWER();//獲取時鍾和電源配置寄存器的第一個寄存器的地址,寄存器的地上是連續的 S3C24X0_GPIO * const gpio = S3C24X0_GetBase_GPIO();//獲取GPIO配置寄存器的第一個寄存器的地址 /* to reduce PLL lock time, adjust the LOCKTIME register */ clk_power->LOCKTIME = 0xFFFFFF; /* configure MPLL */ clk_power->MPLLCON = ((M_MDIV << 12) + (M_PDIV << 4) + M_SDIV); /* some delay between MPLL and UPLL */ delay (4000); /* configure UPLL */ clk_power->UPLLCON = ((U_M_MDIV << 12) + (U_M_PDIV << 4) + U_M_SDIV); /* some delay between MPLL and UPLL */ delay (8000); /* set up the I/O ports */ gpio->GPACON = 0x007FFFFF; gpio->GPBCON = 0x00044555; gpio->GPBUP = 0x000007FF; gpio->GPCCON = 0xAAAAAAAA; gpio->GPCUP = 0x0000FFFF; gpio->GPDCON = 0xAAAAAAAA; gpio->GPDUP = 0x0000FFFF; gpio->GPECON = 0xAAAAAAAA; gpio->GPEUP = 0x0000FFFF; gpio->GPFCON = 0x000055AA; gpio->GPFUP = 0x000000FF; gpio->GPGCON = 0xFF95FFBA; gpio->GPGUP = 0x0000FFFF; gpio->GPHCON = 0x002AFAAA; gpio->GPHUP = 0x000007FF; /* arch number of SMDK2410-Board */ gd->bd->bi_arch_number = MACH_TYPE_SMDK2410; /* adress of boot parameters */ gd->bd->bi_boot_params = 0x30000100; icache_enable(); //調用cpu/arm920t/cpu.c中的函數 dcache_enable(); return 0; }
三、標記的設置
U-Boot通過bootm命令引導Linux內核,bootm命令對吼調用do_bootm_linux函數來引導內核。在do_bootm_linux函數就設置了標記,該函數的定義在lib_arm/armlinux.c中:
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 theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep); 設置kernal加載地址 /* * Check if there is an initrd image */ 用戶自定義了initrd之后需要加載進來,整個過程需要進行頭部以及整個數據內部校,類似於內核的加載校驗,這里省略了。 initial RAM disk Linux初始 RAM磁盤(initrd)是在系統引導過程中掛載的一個臨時根文件系統,用來支持兩階段的引導過程。initrd文件中包含了各種可執行程序和驅動程序,它們可以用來掛載實際的根文件系統,然后再將這個 initrd RAM 磁盤卸載,並釋放內存。在很多嵌入式Linux 系統中,initrd 就是最終的根文件系統。 if (argc >= 3) { SHOW_BOOT_PROGRESS (9); addr = simple_strtoul (argv[2], NULL, 16); printf ("## Loading Ramdisk Image at %08lx ...\n", addr); /* Copy header so we can blank CRC field for re-calculation */ #ifdef CONFIG_HAS_DATAFLASH if (addr_dataflash (addr)) { read_dataflash (addr, sizeof (image_header_t), (char *) &header); } else #endif memcpy (&header, (char *) addr, sizeof (image_header_t)); if (ntohl (hdr->ih_magic) != IH_MAGIC) { printf ("Bad Magic Number\n"); SHOW_BOOT_PROGRESS (-10); do_reset (cmdtp, flag, argc, argv); } data = (ulong) & header; len = sizeof (image_header_t); checksum = ntohl (hdr->ih_hcrc); hdr->ih_hcrc = 0; if (crc32 (0, (unsigned char *) data, len) != checksum) { printf ("Bad Header Checksum\n"); SHOW_BOOT_PROGRESS (-11); do_reset (cmdtp, flag, argc, argv); } SHOW_BOOT_PROGRESS (10); print_image_hdr (hdr); data = addr + sizeof (image_header_t); len = ntohl (hdr->ih_size); #ifdef CONFIG_HAS_DATAFLASH if (addr_dataflash (addr)) { read_dataflash (data, len, (char *) CFG_LOAD_ADDR); data = CFG_LOAD_ADDR; } #endif if (verify) { ulong csum = 0; printf (" Verifying Checksum ... "); csum = crc32 (0, (unsigned char *) data, len); if (csum != ntohl (hdr->ih_dcrc)) { printf ("Bad Data CRC\n"); SHOW_BOOT_PROGRESS (-12); do_reset (cmdtp, flag, argc, argv); } printf ("OK\n"); } SHOW_BOOT_PROGRESS (11); if ((hdr->ih_os != IH_OS_LINUX) || (hdr->ih_arch != IH_CPU_ARM) || (hdr->ih_type != IH_TYPE_RAMDISK)) { printf ("No Linux ARM Ramdisk Image\n"); SHOW_BOOT_PROGRESS (-13); do_reset (cmdtp, flag, argc, argv); } #if defined(CONFIG_B2) || defined(CONFIG_EVB4510) || defined(CONFIG_ARMADILLO) /* *we need to copy the ramdisk to SRAM to let Linux boot */ memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len); data = ntohl(hdr->ih_load); #endif /* CONFIG_B2 || CONFIG_EVB4510 */ /* * Now check if we have a multifile image */ } else if ((hdr->ih_type == IH_TYPE_MULTI) && (len_ptr[1])) { ulong tail = ntohl (len_ptr[0]) % 4; int i; SHOW_BOOT_PROGRESS (13); /* skip kernel length and terminator */ data = (ulong) (&len_ptr[2]); /* skip any additional image length fields */ for (i = 1; len_ptr[i]; ++i) data += 4; /* add kernel length, and align */ data += ntohl (len_ptr[0]); if (tail) { data += 4 - tail; } len = ntohl (len_ptr[1]); } else { /* * no initrd image */ SHOW_BOOT_PROGRESS (14); len = data = 0; } #ifdef DEBUG if (!data) { printf ("No initrd\n"); } #endif if (data) { initrd_start = data; initrd_end = initrd_start + len; } else { initrd_start = 0; initrd_end = 0; } 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) setup_start_tag (bd);設置各種tag,用於傳遞參數給Linux #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); #endif /* we assume that the kernel is in place */ printf ("\nStarting kernel ...\n\n");打印信息 #ifdef CONFIG_USB_DEVICE { extern void udc_disconnect (void); udc_disconnect (); } #endif cleanup_before_linux ();啟動之前先做一些清理工作cpu/arm920t/cpu.c 調用內核需要傳遞的參數如下: R0:必須為0 R1:機器類型ID,本機為ARM(bd->bi_arch_number) R2:啟動參數列表在內存中的位置(bd->bi_boot_params) theKernel (0, bd->bi_arch_number, bd->bi_boot_params); }
3.1設置標記ATAG_CORE
標記列表以標記ATAG_CORE開始
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);//指向當前標記的末尾 }
3.2設置內存標記ATAG_MEM
在board/smdk2410/smdk2410.c的dram_init函數設置了bd的bi_dram結構體:
int dram_init (void) { gd->bd->bi_dram[0].start = PHYS_SDRAM_1; gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE; return 0; }
下面是這邊內存標記的結構體:
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); } }
3.3設置命令行標記ATAG_CMDLINE
命令行就是一個字符串,用來控制內核的一些行為。比如“root=/dev/mtdblock2 init=/linuxrc console=ttySAC0 ”表示根文件系統在MTD2分區上系統啟動后執行的第一個程序為/linuxrc,控制台是ttySAC0 。
static void setup_commandline_tag (bd_t *bd, char *commandline) { char *p; if (!commandline) return; /* 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 == '\0') 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); }
3.4設置ATAG_NONE
標記列表以標記ATAG_NONE介紹。