第一階段:http://blog.csdn.net/lizuobin2/article/details/52054293
上篇文章說到,再清 BSS 段之後,CPU 跳轉到 sdram 裡的 start_armboot() 函數,本文,分析 uboot 流程的第二階段、第三階段。
start_armboot函數在lib_arm/board.c中定義,是U-Boot第二階段代碼的入口。第二階段的主要工作是進行單闆級别的初始化,初始化 nandflash 、norflash 、初始化序列槽、設定環境變量、最終跳轉到 main_loop 裡,接收序列槽傳遞進來的各種指令。
第三階段主要的工作就是設定 uboot 将要傳遞給核心的 tag 以及解析 uboot 頭部裡包含的資訊,最終跳轉到核心起始位址去執行,将主要權交給核心。
一、gd 結構體
/* Pointer is writable since we allocated a register for it */
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));
_armboot_start 是 start.S 中的彙編語言的标号
.globl _armboot_start
_armboot_start:
.word _start
彙編中我們可以通過不同的指令獲得一個标号的位址或者标号位址處存放的資料,在 C 中,直接通路一個标号表示擷取标号位址處存放的資料,一個無符号整形變量
extern ulong _armboot_start; /* code start */
extern ulong _bss_start; /* code + data end == BSS start */
前邊第一篇文章我們分析過堆棧的劃分,為 gd 結構體 留出了128K 的空間,根據上邊的代碼我們可以知道這 128K 有高到低先放一個 gd_t 類型的結構體,緊挨着又放了一個它的成員 bd_t 類型的結構。
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;
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 機器ID*/
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;
前邊的 gd 是個全局變量:
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
二、初始化工作
前邊配置設定完了 gd 結構體之後,就開始一系列的初始化工作,初始化函數定義在 init_sequence 中
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
init_fnc_t *init_sequence[] = {
cpu_init, /* basic cpu dependent setup */
board_init, /* basic board dependent setup */
interrupt_init, /* set up exceptions */
env_init, /* initialize environment */
init_baudrate, /* initialze baudrate settings */
serial_init, /* serial communications setup */
console_init_f, /* stage 1 init of console */
display_banner, /* say that we are here */
#if defined(CONFIG_DISPLAY_CPUINFO)
print_cpuinfo, /* display cpu info (and speed) */
#endif
#if defined(CONFIG_DISPLAY_BOARDINFO)
checkboard, /* display board info */
#endif
dram_init, /* configure available RAM banks */
display_dram_config,
NULL,
};
2.1 cpu_init
int cpu_init (void)
{
/*
* setup up stacks if necessary
*/
#ifdef CONFIG_USE_IRQ
IRQ_STACK_START = _armboot_start - CFG_MALLOC_LEN - CFG_GBL_DATA_SIZE - 4;
FIQ_STACK_START = IRQ_STACK_START - CONFIG_STACKSIZE_IRQ;
#endif
return 0;
}
如果使用 irq 的話,将這兩個宏指向之前配置設定的棧空間
2.2 board_init
int board_init (void)
{
//獲得時鐘寄存器的位址,s3c24x0通用無需修改。
S3C24X0_CLOCK_POWER * const clk_power = S3C24X0_GetBase_CLOCK_POWER();
//獲得GPIO寄存器的位址,s3c24x0不通用需要增加2440GPIO寄存器。
S3C24X0_GPIO * const gpio = S3C24X0_GetBase_GPIO();
/* to reduce PLL lock time, adjust the LOCKTIME register */
clk_power->LOCKTIME = 0xFFFFFF;
/* configure MPLL */
/*MPLL初始化 參數位于./board/smdk2410/smdk2410.c中,2440應有自己的檔案以及定義*/
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;
//#define MACH_TYPE_SMDK2410 193 位于./include/asm-asm/Mach-types.h中定義
/* adress of boot parameters */
gd->bd->bi_boot_params = 0x30000100; //核心啟動參數存放位址
//開資料 指令cache
icache_enable();
dcache_enable();
return 0;
}
重點工作,向 gd 結構體中記錄了 機器ID 以及 tag 的存放位址,這倆都是要傳遞給核心的!!!
2.3 interrupt_init PWM的初始化,無關緊要
2.4 env_init
typedef struct environment_s {
unsigned long crc; /* CRC32 over data bytes */
unsigned char flags; /* active/obsolete flags */
unsigned char data[ENV_SIZE]; /* Environment data */
} env_t;
env_t *env_ptr = (env_t *)CFG_ENV_ADDR; // 0+0x070000
static env_t *flash_addr = (env_t *)CFG_ENV_ADDR; // 0+0x070000
環境變量用一個 environment_s 結構來描述,它被放置在 norflash 的 0x70000 起始的位址處,crc 是環境變量的校驗和,全部的環境變量都以字元串的形式存放在 data 數組中,兩個環境變量之間用 “\0” 隔開。
int env_init(void)
{
if (crc32(0, env_ptr->data, ENV_SIZE) == env_ptr->crc) {
gd->env_addr = (ulong)&(env_ptr->data);
gd->env_valid = 1;
return(0);
}
//如果校驗失敗,則使用預設的環境變量
gd->env_addr = (ulong)&default_environment[0]; //将預設環境變量的位址賦給全局指針gd->env_addr
gd->env_valid = 0;
return (0);
}
現在env_ptr指向 Norfalsh 中的0x070000,進行校驗,判斷環境變量是否可用。
1、uboot 第一次啟動,那麼 norflash 這個位址處并沒有任何東西,校驗失敗,則使用預設的環境變量,使全局指針 gd->env_addr 指向記憶體中的預設環境變量,并設定标志位 gd->env_valid 為 0 。
2、uboot 非第一次啟動,那麼校驗成功,将全局指針 gd->env_addr 指向環境變量,并使标志位 gd->env_valid 置一。
預設環境變量的 定義 CONFIG_BOOTARGS 等宏在 Smdk2410.h (include\configs)
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
#ifdef CONFIG_CLOCKS_IN_MHZ
"clocks_in_mhz=1\0"
#endif
#if defined(CONFIG_PCI_BOOTDELAY) && (CONFIG_PCI_BOOTDELAY > 0)
"pcidelay=" MK_STR(CONFIG_PCI_BOOTDELAY) "\0"
#endif
#ifdef CONFIG_EXTRA_ENV_SETTINGS
CONFIG_EXTRA_ENV_SETTINGS
#endif
"\0"
};
以 "bootargs=" CONFIG_BOOTARGS "\0" 為例:
#define CONFIG_BOOTARGS "root=ramfs devfs=mount console=ttySA0,9600"
确實如同前邊說所,data 裡邊放置的都是一個個字元串,兩個環境變量之間 通過 “\0”隔開。
2.5 init_baudrate 設定波特率 首先從環境變量中讀取,如果沒有則設定為 115200
2.6 serial_init 序列槽初始化
int serial_init (void)
{
serial_setbrg ();
return (0);
}
void serial_setbrg (void)
{
S3C24X0_UART * const uart = S3C24X0_GetBase_UART(UART_NR);
int i;
unsigned int reg = 0;
/* value is calculated so : (int)(PCLK/16./baudrate) -1 */
reg = get_PCLK() / (16 * gd->baudrate) - 1;
/* FIFO enable, Tx/Rx FIFO clear */
uart->UFCON = 0x07;
uart->UMCON = 0x0;
/* Normal,No parity,1 stop,8 bit */
uart->ULCON = 0x3;
/*
* tx=level,rx=edge,disable timeout int.,enable rx error int.,
* normal,interrupt or polling
*/
uart->UCON = 0x245;
uart->UBRDIV = reg;
#ifdef CONFIG_HWFLOW
uart->UMCON = 0x1; /* RTS up */
#endif
for (i = 0; i < 100; i++);
}
序列槽控制寄存器的設定.....以前移植的時候遇到一個問題,get_PCLK() 這個函數需要修改,2410 和 2440 不一樣。
2.7 console_init_f 控制台初始化,無關緊要
2.8 display_banner 列印代碼段 BSS段等位址資訊
2.9 dram_init
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;
}
在全局指針 gd 中标記記憶體範圍
2.A display_dram_config 列印 sdram 資訊
2.B norflash 初始化
#ifndef CFG_NO_FLASH
/* configure available FLASH banks */
size = flash_init ();
2.C nandflash 初始化
#if (CONFIG_COMMANDS & CFG_CMD_NAND)
puts ("NAND: ");
nand_init(); /* go init the NAND */
2.D 環境變量重定位
/* initialize environment */
env_relocate ();
void env_relocate (void)
{
DEBUGF ("%s[%d] offset = 0x%lx\n", __FUNCTION__,__LINE__,
gd->reloc_off);
//在sdram中開辟一開記憶體空間并使env_ptr指向它
env_ptr = (env_t *)malloc (CFG_ENV_SIZE);
DEBUGF ("%s[%d] malloced ENV at %p\n", __FUNCTION__,__LINE__,env_ptr);
/*
* After relocation to RAM, we can always use the "memory" functions
*/
env_get_char = env_get_char_memory;
if (gd->env_valid == 0) // 開始的時候使用的預設環境變量,前邊提過了
puts ("*** Warning - bad CRC, using default environment\n\n");
SHOW_BOOT_PROGRESS (-1);
if (sizeof(default_environment) > ENV_SIZE)
{
puts ("*** Error - default environment is too large\n\n");
return;
}
//向sdram中的env_ptr位址拷貝預設的預設環境變量。
memset (env_ptr, 0, sizeof(env_t));//寫之前 先初始化
memcpy (env_ptr->data,
default_environment,
sizeof(default_environment));
#ifdef CFG_REDUNDAND_ENVIRONMENT
env_ptr->flags = 0xFF;
#endif
env_crc_update (); //env_ptr->crc寫入校驗值
gd->env_valid = 1; //告訴全局gd env可用
}
else {
//如果,使用的非預設的env,也就是 nor中有
env_relocate_spec (); //将norflash中的env拷貝到sdram
}
gd->env_addr = (ulong)&(env_ptr->data); //将sdram中 env->data位址告訴gd
#ifdef CONFIG_AMIGAONEG3SE
disable_nvram();
#endif
}
#ifdef CONFIG_AUTO_COMPLETE
void env_relocate_spec (void)
{
//注意次函數從env_relocate()中調用時,env_ptr已經改變為新在記憶體中開辟的位址
//是以次函數功能:将Nor中的env,拷貝到sdram
memcpy (env_ptr, (void*)flash_addr, CFG_ENV_SIZE);
}
重定位之後,我們在uboot指令行讀取到的環境變量都是記憶體中的,修改後要寫回 norflash
#ifdef CMD_SAVEENV
int saveenv(void)
{
int len, rc;
ulong end_addr;
ulong flash_sect_addr;
uchar *env_buffer = (uchar *)env_ptr;//指向flash中的 0+0x070000
int rcode = 0;
flash_sect_addr = (ulong)flash_addr; //flash_sect_addr儲存的是env_ptr的位址,也就是0x070000
len = CFG_ENV_SIZE;
end_addr = flash_sect_addr + len - 1; //env的末位址
debug ("Protect off %08lX ... %08lX\n",
(ulong)flash_sect_addr, end_addr);
//後邊是一些 擦除 寫操作,可以看出nandflash的擦除寫都是按sect來的,那麼env在Nar中的偏移位址肯定得是sect對齊的。
if (flash_sect_protect (0, flash_sect_addr, end_addr))
return 1;
puts ("Erasing Flash...");
if (flash_sect_erase (flash_sect_addr, end_addr))
return 1;
puts ("Writing to Flash... ");
rc = flash_write((char *)env_buffer, flash_sect_addr, len);
if (rc != 0) {
flash_perror (rc);
rcode = 1;
} else {
puts ("done\n");
}
/* try to re-protect */
(void) flash_sect_protect (1, flash_sect_addr, end_addr);
return rcode;
}
2.E 從環境變量中擷取 ip mac 位址放入全局 gd 結構
/* IP Address */
gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");
/* MAC Address */
{
int i;
ulong reg;
char *s, *e;
char tmp[64];
i = getenv_r ("ethaddr", tmp, sizeof (tmp));
s = (i > 0) ? tmp : NULL;
for (reg = 0; reg < 6; ++reg) {
gd->bd->bi_enetaddr[reg] = s ? simple_strtoul (s, &e, 16) : 0;
if (s)
s = (*e) ? e + 1 : e;
}
#ifdef CONFIG_HAS_ETH1
i = getenv_r ("eth1addr", tmp, sizeof (tmp));
s = (i > 0) ? tmp : NULL;
for (reg = 0; reg < 6; ++reg) {
gd->bd->bi_enet1addr[reg] = s ? simple_strtoul (s, &e, 16) : 0;
if (s)
s = (*e) ? e + 1 : e;
}
有意思的是,這裡用了一個{ },大概是為了防止變量名字沖突?
2.F devices_init (); 沒研究過,不知道幹啥的,應該沒啥影響
2.G jumptable_init ()
void jumptable_init (void)
{
int i;
gd->jt = (void **) malloc (XF_MAX * sizeof (void *));
for (i = 0; i < XF_MAX; i++)
gd->jt[i] = (void *) dummy;
gd->jt[XF_get_version] = (void *) get_version;
gd->jt[XF_malloc] = (void *) malloc;
gd->jt[XF_free] = (void *) free;
gd->jt[XF_getenv] = (void *) getenv;
gd->jt[XF_setenv] = (void *) setenv;
gd->jt[XF_get_timer] = (void *) get_timer;
gd->jt[XF_simple_strtoul] = (void *) simple_strtoul;
gd->jt[XF_udelay] = (void *) udelay;
#if defined(CONFIG_I386) || defined(CONFIG_PPC)
gd->jt[XF_install_hdlr] = (void *) irq_install_handler;
gd->jt[XF_free_hdlr] = (void *) irq_free_handler;
#endif /* I386 || PPC */
#if (CONFIG_COMMANDS & CFG_CMD_I2C)
gd->jt[XF_i2c_write] = (void *) i2c_write;
gd->jt[XF_i2c_read] = (void *) i2c_read;
#endif /* CFG_CMD_I2C */
在全局結構 gd 中記錄 這些函數,不太懂。
2.H enable_interrupts 在 cpsr 中使能 irq fiq
void enable_interrupts (void)
{
unsigned long temp;
__asm__ __volatile__("mrs %0, cpsr\n"
"bic %0, %0, #0x80\n"
"msr cpsr_c, %0"
: "=r" (temp)
:
: "memory");
}
2.I 網卡初始化
#if (CONFIG_COMMANDS & CFG_CMD_NET)
#if defined(CONFIG_NET_MULTI)
puts ("Net: ");
#endif
eth_initialize(gd->bd);
2.J 跳轉到 uboot 菜單
for (;;) {
main_loop ();
}
三、第三階段 啟動核心
我們在 uboot 中輸入 bootm 指令,U-Boot接收輸入的字元串“bootm”,傳遞給 run_command 函數。run_command 函數調用 common/command.c 中實作的 find_cmd 函數在 __u_boot_cmd_start 與 __u_boot_cmd_end間查找指令,并傳回 bootm 指令的 cmd_tbl_t 結構。然後 run_command 函數使用傳回的 cmd_tbl_t 結構中的函數指針調用 bootm 指令的響應函數 do_bootm。
int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
......
image_header_t *hdr = &header; //uimage 是核心加了一個4K的頭部,這個頭部的内容是按照結構體image_header_t來放在,是image傳遞給Uboot的資訊。
......
if (argc < 2) {
addr = load_addr; //如果bootm的參數小于2 則使用預設的加載位址
} else {
addr = simple_strtoul(argv[1], NULL, 16);
}
......
switch (hdr->ih_comp) {
case IH_COMP_NONE:
if(ntohl(hdr->ih_load) == addr) {
/*
這裡判斷“uimage頭部裡指定的加載位址”與bootm指定的加載位址是否相等,不相等則需要移動
判斷的方式有兩種
1、判斷 uimage頭部裡指定的加載位址 == bootm指定的加載位址 (hdr->ih_load == addr)
此時:
實際存放的位址 == uimage加載位址 == uimage連接配接位址-64位元組
bootm == 實際存放的位址
例子:
實際存放在 0x30007fc0
bootm 0x30007fc0
加載位址 0x30007fc0
連接配接位址 0x30008000
1、uboot根據Bootm指定的0x30007fc0去找image,實際位址為0x30007fc0,找到頭部
2、讀出頭部裡的加載位址,判斷是否和Bootm相等,相等則不移動,啟動
2、判斷 uimage頭部裡指定的加載位址 == bootm指定的加載位址 + 64位元組 (hdr->ih_load == addr+64位元組)
此時:
實際存放位址+64位元組 == uimage加載位址 == uimage連接配接位址
bootm == 實際存放的位址
例子:
實際存放在 0x30007fc0
bootm 0x30007fc0
加載位址 0x30008000
連接配接位址 0x30008000
1、uboot根據Bootm指定的0x30007fc0去找image,實際位址為0x30007fc0,找到頭部
2、讀出頭部裡的加載位址,判斷是否和Bootm + 位元組相等,相等則不移動,啟動
首先bootm的位址要和我們 實際存放(不管它的加載位址是多少) 整個uimage的首位址吻合,這樣就可以找到頭部。
這裡存在兩種情況,我們可以看到 Uboot源碼裡
1、 hdr->ih_load == addr
也就是說判斷的是bootm_addr 與uimage裡的ih_load加載位址是否相等,這樣的話,我們在制作uimage的時候就應該讓ih_load=bootmaddr
那麼uimage裡的另一個參數,連接配接位址就應該等于bootm+4K
2、hdr->ih_load == addr+64位元組 友善以及韋東山老師的uboot裡判斷條件都被改成了這樣
那麼,uimage裡的Load位址和連接配接位址應該相等,等于bootm+64位元組
*/
printf (" XIP %s ... ", name);
} else {
......do_bootm_linux (cmdtp, flag, argc, argv,addr, len_ptr, verify);
}
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)
#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); // 标記必須以它起始
/* 在/include/config/smdk2410.h這些宏一個都沒定義,是以,uboot一個标記都沒給核心放,這樣是不對滴,最起碼的,記憶體标記以及cmdline應該告訴核心。不然跳到核心後調用序列槽驅動的初始化,會讓你的序列槽啥都列印不出來。關于什麼是 tag 以及原理,在自己寫bootloader一文中介紹過了。
*/
#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
/* 這句話很常見~~~也經常從此沒了下文~~~ */
printf ("\nStarting kernel ...\n\n");
cleanup_before_linux ();
/* 啟動核心 第1個參數 0 ,第二個參數 機器ID 第三個參數 tag 位址 */
theKernel (0, bd->bi_arch_number, bd->bi_boot_params);
}
至此,uboot 的生命周期結束,控制權交給核心~
四、添加自定義指令
下面以添加menu指令(啟動菜單)為例講解U-Boot添加指令的方法。
1、建立common/cmd_menu.c
習慣上通用指令源代碼放在common目錄下,與開發闆專有指令源代碼則放在board/<board_dir>目錄下,并且習慣以“cmd_<指令名>.c”為檔案名。
2、定義“menu”指令
在 cmd_menu.c中使用如下的代碼定義“menu”指令:參考 cmd_bootm.c
U_BOOT_CMD(
bootm, CFG_MAXARGS, 1, do_bootm,
"bootm - boot application image from memory\n",
"[addr [arg ...]]\n - boot application image stored in memory\n"
"\tpassing arguments 'arg ...'; when booting a Linux kernel,\n"
"\t'arg' can be the address of an initrd image\n"
#ifdef CONFIG_OF_FLAT_TREE
"\tWhen booting a Linux kernel which requires a flat device-tree\n"
"\ta third argument is required which is the address of the of the\n"
"\tdevice-tree blob. To boot that kernel without an initrd image,\n"
"\tuse a '-' for the second argument. If you do not pass a third\n"
"\ta bd_info struct will be passed instead\n"
#endif
);
U_BOOT_CMD(
menu, 3, 0, do_menu,
"menu - display a menu, to select the items to do something\n",
" - display a menu, to select the items to do something"
);
U_BOOT_CMD(name,maxargs,rep,cmd,usage,help)
各個參數的意義如下:
name:指令名,非字元串,但在U_BOOT_CMD中用“#”符号轉化為字元串
maxargs:指令的最大參數個數
rep:是否自動重複(按Enter鍵是否會重複執行)
cmd:該指令對應的響應函數
usage:簡短的使用說明(字元串)
help:較詳細的使用說明(字元串)
U_BOOT_CMD宏在include/command.h中定義,分為有無幫助資訊兩種:
#define Struct_Section __attribute__ ((unused,section (".u_boot_cmd")))
#ifdef CFG_LONGHELP
#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \
cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage, help}
#else /* no long help info */
#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \
cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage}
#endif /* CFG_LONGHELP */
定義一個 cmd_tbl_t 類型的結構體,比如 bootm 名字叫 __u_boot_cmd_bootm 放在 .u_boot_cmd 段。執行指令時就可以在“u_boot_cmd”段查找到對應的 cmd_tbl_t 類型結構體。
struct cmd_tbl_s {
char *name; /* 指令名 */
int maxargs; /* 最大參數個數 */
int repeatable; /* 是否自動重複 */
int (*cmd)(struct cmd_tbl_s *, int, int, char *[]); /* 響應函數 */
char *usage; /* 簡短的幫助資訊 */
#ifdef CONFIG_SYS_LONGHELP
char *help; /* 較詳細的幫助資訊 */
#endif
#ifdef CONFIG_AUTO_COMPLETE
/* 自動補全參數 */
int (*complete)(int argc, char *argv[], char last_char, int maxv, char *cmdv[]);
#endif
};
typedef struct cmd_tbl_s cmd_tbl_t;
一個cmd_tbl_t結構體變量包含了調用一條指令的所需要的資訊。
3、實作指令的函數
在cmd_menu.c中添加“menu”指令的響應函數的實作。具體的實作代碼略:
int do_menu (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
/* 實作代碼略 */
}
4、将common/cmd_menu.c編譯進u-boot.bin
在common/Makefile中加入如下代碼:
COBJS-$(CONFIG_BOOT_MENU) += cmd_menu.o
在include/configs/smdk2410.h加入如代碼:
#define CONFIG_BOOT_MENU 1
重新編譯下載下傳U-Boot就可以使用menu指令了