uboot第二階段應該做什么?
uboot的第二階段就是要初始化剩下的還沒被初始化的硬件,主要是SOC外部硬件(譬如inand、網卡芯片)、uboot本身的一些東西(uboot的命令、環境變量等),然后最終初始化完必要的東西后進入uboot的命令行准備接受命令。
uboot第二階段完結於何處?
uboot啟動后自動運行打印出很多信息,這些信息就是uboot第一和第二階段不斷進行初始化時,打印出來的信息,然后uboot進入了bootdelay然后執行bootcmd對應的啟動命令,如果這時候用戶不干涉,會執行bootcmd進入自動啟動內核的流程了。(uboot的生命周期就結束了);所以uboot完結於命令行下,讀取命令,解析命令,執行命令。命令行死循環是uboot的最終歸宿
void start_armboot (void) { init_fnc_t **init_fnc_ptr; char *s; int mmc_exist = 0; #if !defined(CFG_NO_FLASH) || defined (CONFIG_VFD) || defined(CONFIG_LCD) ulong size; #endif #if defined(CONFIG_VFD) || defined(CONFIG_LCD) unsigned long addr; #endif #if defined(CONFIG_BOOT_MOVINAND) uint *magic = (uint *) (PHYS_SDRAM_1); #endif /* Pointer is writable since we allocated a register for it */ #ifdef CONFIG_MEMORY_UPPER_CODE /* by scsuh */ ulong gd_base; gd_base = CFG_UBOOT_BASE + CFG_UBOOT_SIZE - CFG_MALLOC_LEN - CFG_STACK_SIZE - sizeof(gd_t); #ifdef CONFIG_USE_IRQ gd_base -= (CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ); #endif gd = (gd_t*)gd_base; #else gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t)); #endif /* compiler optimization barrier needed for GCC >= 3.4 */ __asm__ __volatile__("": : :"memory"); memset ((void*)gd, 0, sizeof (gd_t)); gd->bd = (bd_t*)((char*)gd - sizeof(bd_t)); memset (gd->bd, 0, sizeof (bd_t)); monitor_flash_len = _bss_start - _armboot_start; for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) { if ((*init_fnc_ptr)() != 0) { hang (); } } #ifndef CFG_NO_FLASH /* configure available FLASH banks */ size = flash_init (); display_flash_config (size); #endif /* CFG_NO_FLASH */ #ifdef CONFIG_VFD # ifndef PAGE_SIZE # define PAGE_SIZE 4096 # endif /* * reserve memory for VFD display (always full pages) */ /* bss_end is defined in the board-specific linker script */ addr = (_bss_end + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1); size = vfd_setmem (addr); gd->fb_base = addr; #endif /* CONFIG_VFD */ #ifdef CONFIG_LCD /* board init may have inited fb_base */ if (!gd->fb_base) { # ifndef PAGE_SIZE # define PAGE_SIZE 4096 # endif /* * reserve memory for LCD display (always full pages) */ /* bss_end is defined in the board-specific linker script */ addr = (_bss_end + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1); size = lcd_setmem (addr); gd->fb_base = addr; } #endif /* CONFIG_LCD */ /* armboot_start is defined in the board-specific linker script */ #ifdef CONFIG_MEMORY_UPPER_CODE /* by scsuh */ mem_malloc_init (CFG_UBOOT_BASE + CFG_UBOOT_SIZE - CFG_MALLOC_LEN - CFG_STACK_SIZE); #else mem_malloc_init (_armboot_start - CFG_MALLOC_LEN); #endif //******************************// // Board Specific // #if defined(CONFIG_SMDKXXXX) //******************************// #if defined(CONFIG_SMDK6410) #if defined(CONFIG_GENERIC_MMC) puts ("SD/MMC: "); mmc_exist = mmc_initialize(gd->bd); if (mmc_exist != 0) { puts ("0 MB\n"); } #else #if defined(CONFIG_MMC) puts("SD/MMC: "); if (INF_REG3_REG == 0) movi_ch = 0; else movi_ch = 1; movi_set_capacity(); movi_init(); movi_set_ofs(MOVI_TOTAL_BLKCNT); #endif #endif if (INF_REG3_REG == BOOT_ONENAND) { #if defined(CONFIG_CMD_ONENAND) puts("OneNAND: "); onenand_init(); #endif /*setenv("bootcmd", "onenand read c0008000 80000 380000;bootm c0008000");*/ } else { puts("NAND: "); nand_init(); if (INF_REG3_REG == 0 || INF_REG3_REG == 7) setenv("bootcmd", "movi read kernel c0008000;movi read rootfs c0800000;bootm c0008000"); else setenv("bootcmd", "nand read c0008000 80000 380000;bootm c0008000"); } #endif /* CONFIG_SMDK6410 */ #if defined(CONFIG_SMDKC100) #if defined(CONFIG_GENERIC_MMC) puts ("SD/MMC: "); mmc_exist = mmc_initialize(gd->bd); if (mmc_exist != 0) { puts ("0 MB\n"); } #endif #if defined(CONFIG_CMD_ONENAND) puts("OneNAND: "); onenand_init(); #endif #if defined(CONFIG_CMD_NAND) puts("NAND: "); nand_init(); #endif #endif /* CONFIG_SMDKC100 */ #if defined(CONFIG_X210) #if defined(CONFIG_GENERIC_MMC) puts ("SD/MMC: "); mmc_exist = mmc_initialize(gd->bd); if (mmc_exist != 0) { puts ("0 MB\n"); #ifdef CONFIG_CHECK_X210CV3 check_flash_flag=0;//check inand error! #endif } #ifdef CONFIG_CHECK_X210CV3 else { check_flash_flag=1;//check inand ok! } #endif #endif #if defined(CONFIG_MTD_ONENAND) puts("OneNAND: "); onenand_init(); /*setenv("bootcmd", "onenand read c0008000 80000 380000;bootm c0008000");*/ #else //puts("OneNAND: (FSR layer enabled)\n"); #endif #if defined(CONFIG_CMD_NAND) puts("NAND: "); nand_init(); #endif #endif /* CONFIG_X210 */ #if defined(CONFIG_SMDK6440) #if defined(CONFIG_GENERIC_MMC) puts ("SD/MMC: "); mmc_exist = mmc_initialize(gd->bd); if (mmc_exist != 0) { puts ("0 MB\n"); } #else #if defined(CONFIG_MMC) if (INF_REG3_REG == 1) { /* eMMC_4.3 */ puts("eMMC: "); movi_ch = 1; movi_emmc = 1; movi_init(); movi_set_ofs(0); } else if (INF_REG3_REG == 7 || INF_REG3_REG == 0) { /* SD/MMC */ if (INF_REG3_REG & 0x1) movi_ch = 1; else movi_ch = 0; puts("SD/MMC: "); movi_set_capacity(); movi_init(); movi_set_ofs(MOVI_TOTAL_BLKCNT); } else { } #endif #endif if (INF_REG3_REG == 2) { /* N/A */ } else { puts("NAND: "); nand_init(); //setenv("bootcmd", "nand read c0008000 80000 380000;bootm c0008000"); } #endif /* CONFIG_SMDK6440 */ #if defined(CONFIG_SMDK6430) #if defined(CONFIG_GENERIC_MMC) puts ("SD/MMC: "); mmc_exist = mmc_initialize(gd->bd); if (mmc_exist != 0) { puts ("0 MB\n"); } #else #if defined(CONFIG_MMC) puts("SD/MMC: "); if (INF_REG3_REG == 0) movi_ch = 0; else movi_ch = 1; movi_set_capacity(); movi_init(); movi_set_ofs(MOVI_TOTAL_BLKCNT); #endif #endif if (INF_REG3_REG == BOOT_ONENAND) { #if defined(CONFIG_CMD_ONENAND) puts("OneNAND: "); onenand_init(); #endif /*setenv("bootcmd", "onenand read c0008000 80000 380000;bootm c0008000");*/ } else if (INF_REG3_REG == BOOT_NAND) { puts("NAND: "); nand_init(); } else { } if (INF_REG3_REG == 0 || INF_REG3_REG == 7) setenv("bootcmd", "movi read kernel c0008000;movi read rootfs c0800000;bootm c0008000"); else setenv("bootcmd", "nand read c0008000 80000 380000;bootm c0008000"); #endif /* CONFIG_SMDK6430 */ #if defined(CONFIG_SMDK6442) #if defined(CONFIG_GENERIC_MMC) puts ("SD/MMC: "); mmc_exist = mmc_initialize(gd->bd); if (mmc_exist != 0) { puts ("0 MB\n"); } #else #if defined(CONFIG_MMC) puts("SD/MMC: "); movi_set_capacity(); movi_init(); movi_set_ofs(MOVI_TOTAL_BLKCNT); #endif #endif #if defined(CONFIG_CMD_ONENAND) if (INF_REG3_REG == BOOT_ONENAND) { puts("OneNAND: "); onenand_init(); } #endif #endif /* CONFIG_SMDK6442 */ #if defined(CONFIG_SMDK2416) || defined(CONFIG_SMDK2450) #if defined(CONFIG_NAND) puts("NAND: "); nand_init(); #endif #if defined(CONFIG_ONENAND) puts("OneNAND: "); onenand_init(); #endif #if defined(CONFIG_BOOT_MOVINAND) puts("SD/MMC: "); if ((0x24564236 == magic[0]) && (0x20764316 == magic[1])) { printf("Boot up for burning\n"); } else { movi_init(); movi_set_ofs(MOVI_TOTAL_BLKCNT); } #endif #endif /* CONFIG_SMDK2416 CONFIG_SMDK2450 */ #ifdef CONFIG_HAS_DATAFLASH AT91F_DataflashInit(); dataflash_print_info(); #endif /* initialize environment */ env_relocate (); #ifdef CONFIG_VFD /* must do this after the framebuffer is allocated */ drv_vfd_init(); #endif /* CONFIG_VFD */ #ifdef CONFIG_SERIAL_MULTI serial_initialize(); #endif /* 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; } #endif } devices_init (); /* get the devices list going. */ #ifdef CONFIG_CMC_PU2 load_sernum_ethaddr (); #endif /* CONFIG_CMC_PU2 */ jumptable_init (); #if !defined(CONFIG_SMDK6442) console_init_r (); /* fully init console as a device */ #endif #if defined(CONFIG_MISC_INIT_R) /* miscellaneous platform dependent initialisations */ misc_init_r (); #endif /* enable exceptions */ enable_interrupts (); /* Perform network card initialisation if necessary */ #ifdef CONFIG_DRIVER_TI_EMAC extern void dm644x_eth_set_mac_addr (const u_int8_t *addr); if (getenv ("ethaddr")) { dm644x_eth_set_mac_addr(gd->bd->bi_enetaddr); } #endif #ifdef CONFIG_DRIVER_CS8900 cs8900_get_enetaddr (gd->bd->bi_enetaddr); #endif #if defined(CONFIG_DRIVER_SMC91111) || defined (CONFIG_DRIVER_LAN91C96) if (getenv ("ethaddr")) { smc_set_mac_addr(gd->bd->bi_enetaddr); } #endif /* CONFIG_DRIVER_SMC91111 || CONFIG_DRIVER_LAN91C96 */ /* Initialize from environment */ if ((s = getenv ("loadaddr")) != NULL) { load_addr = simple_strtoul (s, NULL, 16); } #if defined(CONFIG_CMD_NET) if ((s = getenv ("bootfile")) != NULL) { copy_filename (BootFile, s, sizeof (BootFile)); } #endif #ifdef BOARD_LATE_INIT board_late_init (); #endif #if defined(CONFIG_CMD_NET) #if defined(CONFIG_NET_MULTI) puts ("Net: "); #endif eth_initialize(gd->bd); #if defined(CONFIG_RESET_PHY_R) debug ("Reset Ethernet PHY\n"); reset_phy(); #endif #endif #if defined(CONFIG_CMD_IDE) puts("IDE: "); ide_init(); #endif /****************lxg added**************/ #ifdef CONFIG_MPAD extern int x210_preboot_init(void); x210_preboot_init(); #endif /****************end**********************/ /* check menukey to update from sd */ extern void update_all(void); if(check_menu_update_from_sd()==0)//update mode { puts ("[LEFT DOWN] update mode\n"); run_command("fdisk -c 0",0); update_all(); } else puts ("[LEFT UP] boot mode\n"); /* main_loop() can return to retry autoboot, if so just run it again. */ for (;;) { main_loop (); } /* NOTREACHED - no way out of command loop except booting */ }
start_armboot分析(uboot\uboot_jiuding\uboot\lib_arm 444行):
① init_fnc_t **init_fnc_ptr;
解析:
typedef int (init_fnc_t) (void);
這是一個函數類型,init_fnc_ptr就是一個二重函數指針,這里是用來指向一個函數指針數組。
②
這個文件開頭有一個宏: DECLARE_GLOBAL_DATA_PTR;
跳到定義處:
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
定義了一個全局變量gd,這個變量是一個指針類型,占四個字節,用volatile和register修飾,后面asm ("r8"),是gcc支持的;把gd放到r8中;
將這些全局變量定義成結構體gd,然后放到寄存器中,定義為register變量完全是為了訪問效率的提高。
gd_t定義在include/asm-arm
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 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 GD_FLG_POSTFAIL 0x00008 /* Critical POST test failed */ #define GD_FLG_POSTSTOP 0x00010 /* POST seqeunce aborted */ #define GD_FLG_LOGINIT 0x00020 /* Log Buffer has been initialized */ #define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
#ifndef _U_BOOT_H_ #define _U_BOOT_H_ 1 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; #define bi_env_data bi_env->data #define bi_env_crc bi_env->crc #endif /* _U_BOOT_H_ */
總結:gd_t定義了很多全局變量,都是整個uboot使用的,其中一個bd_t類型指針,指向了一個bd_t類型的變量,這個bd是開發板的板級信息的結構體,里面有不少硬件相關的參數,譬如波特率、IP地址、機器碼、DDR內存的分布等
③gd和bd變量的內存排布
gd的定義本身只是一個指針而已;
為什么分布內存?
(1)DECLARE_GLOBAL_DATA_PTR;只是定義了一個指針,只是分配在寄存器里面了,也就是說gd里的這些全局變量並沒有被分配內存,我們在使用gd之前要給它分配內存,否則gd也只是一個野指針而已。
(2)gd和bd需要內存,內存當前沒人管理(因為沒有操作系統統一管理內存)大片的DDR內存可以散放着可以隨意使用(只要使用內存地址去訪問內存即可);但是因為uboot中后續很多操作還需要大片的連着的內存塊,所以這里使用內存要本着夠用就好,緊湊排布的原則,我們就需要有一個規划了。這里就要研究一個內存排布的問題
內存排布:
gd_base = CFG_UBOOT_BASE + CFG_UBOOT_SIZE - CFG_MALLOC_LEN - CFG_STACK_SIZE - sizeof(gd_t);
(1)uboot區 長度為CFG_UBOOT_BASE + CFG_UBOOT_SIZE
(2)堆區 長度為CFG_MALLOC_LEN 實際912KB
(3)棧區 長度為CFG_STACK_SIZE 實際512KB
(4)gd 長度為sizeof(gd_t),實際36個字節,
(5)bd 長度為sizeof(bd_t) 實際為44個字節左右
(6)內存間隔 /* compiler optimization barrier needed for GCC >= 3.4 */
__asm__ __volatile__("": : :"memory");
避免 gcc版本高於3.4的優化造成的錯誤
實例化:
gd = (gd_t*)gd_base;
memset ((void*)gd, 0, sizeof (gd_t));
gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));
memset (gd->bd, 0, sizeof (bd_t));
拿到一片內存后實例化。同時使用memset進行清零內存。
④init_sequence
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) { if ((*init_fnc_ptr)() != 0) { hang (); } }
(1)它是一個函數指針數組,這個數組存放了很多函數指針,這些指針指向的函數都是init_fnc_t類型,這個類型是接收的參數是void類型,返回int類型,
(2)init_sequence定義時就給了初始化,初始化的函數指針都是一些函數名。函數名就是一個函數指針,這是基礎知識。
(3)init_fnc_ptr是一個二重函數指針,可以指向init_sequence這個函數指針數組。
(4)用一個for循環,是想要遍歷這個函數指針數組,我們遍歷他的目的也是去依次執行這個函數指針數組中的所有函數;思考:如何遍歷一個函數指針數組?
兩種方法:第一種:用下標去遍歷,用數組的元素個數來截止。
第二種:不常用,但是也可以,在數組有效元素的末尾放一個標志,依次遍歷到標志處,這個時候來截止。這種思路有點像字符串的思路。
這里使用的是第二種,因為數組里存放的全是函數指針,因此我們選用了NULL來作為標志,知道看到NULL來截止,這樣做的好處是不用事先統計數組元素個數。可以靈活的添加或者刪除。
(5)init_fnc_t這些函數的特點是正確時返回0,不正確時返回-1,所以我們在遍歷時去檢查函數返回值,如果遍歷中,有一個函數返回值不為0,那么就調用hang();掛起函數,
void hang (void) { puts ("### ERROR ### Please RESET the board ###\n"); for (;;); }
所以uboot啟動時,初始化板級硬件不能出任何錯誤,只要有一個錯誤就終止整個啟動,只能重啟。
(6)init_sequence中的這些函數,都是board級別的初始化
init_fnc_t *init_sequence[] = { cpu_init, /* basic cpu dependent setup */ #if defined(CONFIG_SKIP_RELOCATE_UBOOT) reloc_init, /* Set the relocation done flag, must do this AFTER cpu_init(), but as soon as possible */ #endif 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 #if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C) init_func_i2c, #endif dram_init, /* configure available RAM banks */ display_dram_config, NULL, };
一:cpu_init
真正的CPU初始化已經在之前結束了,所以這里是空的
board_init
它在/uboot/board/samsung/x210/x210.c中,
int board_init(void) { DECLARE_GLOBAL_DATA_PTR; #ifdef CONFIG_DRIVER_SMC911X smc9115_pre_init(); #endif #ifdef CONFIG_DRIVER_DM9000 dm9000_pre_init(); #endif gd->bd->bi_arch_number = MACH_TYPE; gd->bd->bi_boot_params = (PHYS_SDRAM_1+0x100); return 0; }
DECLARE_GLOBAL_DATA_PTR;在這里聲明是為了在后面訪問gd,因此這個gd定義成一個宏,這樣比較方便,這樣的方式跟頭文件包含一樣,但是沒有使用頭文件包含。
這里面有兩個初始化
網卡初始化
dm9000_pre_init();這個函數就是對應的DM9000網卡初始化的函數,這個函數是網卡gpio和端口的配置,而不是驅動,因為驅動都是現成的正確的,移植的時候驅動是不需要更改的。
DDR配置的背景知識:
初始化DDR,這里的初始化DDR和之前在匯編階段的lowlevel_inti中初始化ddr是不同的,當時時,硬件初始化,讓DDR可以工作,現在是軟件結構中DDR相關的屬性配置和一些地址設置的初始化,是純軟件層面的,不是硬件的初始化,為什么要在軟件層次初始化DDR,因為對uboot來說,它怎么知道我們的開發板上到底有幾片DDR內存,每一片的起始地址和長度這些信息呢?在uboot的設計中,采用了一種簡單直接有效的方式,那就是程序員移植uboot到一個開發板中,程序員自己在x210_sd.h中使用宏定義去配置出來板子上DDR的信息,uboot只需要讀取這些信息即可,實際上我們還有另外一條思路,uboot通過代碼讀取硬件的一些信息來知道DDR的配置,但是uboot沒有采用這樣的方式,實際上PC的BIOS采用的是這種。
在x210_sd.h的496行到501行中使用了標准的宏定義來配置DDR相關的參數,主要這么幾個信息:有幾片DDR內存,每一片DDR內存的起始地址、長度。這里的配置信息,在整個uboot代碼中使用到內存時,可以從這里提取使用。uboot中使用到內存的地方都不是用地址數字的,都是用宏定義的。
#define PHYS_SDRAM_1 MEMORY_BASE_ADDRESS /* SDRAM Bank #1 */ #define PHYS_SDRAM_1_SIZE SDRAM_BANK_SIZE #define PHYS_SDRAM_2 MEMORY_BASE_ADDRESS2 /* SDRAM Bank #2 */ #define PHYS_SDRAM_2_SIZE SDRAM_BANK_SIZE #define CFG_FLASH_BASE 0x80000000
gd->bd->bi_arch_number = MACH_TYPE;
開發板的 機器碼;就是uboot給開發板定義的唯一編號,
機器碼的主要作用就是在uboot和Linux內核之間進行比對和適配,主要原因是嵌入式設備中,每一個設備的硬件都是定制化的,不能通用。這就告訴我們,這個開發板移植的內核鏡像絕對不能下載到另一個開發板去,否則也不能啟動,就算啟動也不能正常工作,有很多隱患,因此Linux做了個設置:給每個開發板做唯一編號(機器碼),然后在uboot中、Linux內核中,都有一個軟件維護的機器碼編號,然后開發板 uboot Linux三者之間去比對機器碼,若果機器碼對上了,就啟動,否則就不啟動。
MACH_TYPE在x210_sd.h中定義 值為2456,這個編號代表了,x210開發板的機器碼,將來這個開發版的機器碼上面移植的機器碼也是2456,不然就啟動不起來
uboot配置的機器碼,會作為uboot給Linux內核傳參的一部分傳給Linux內核,內核啟動過程中,會對這個接收到的機器碼和自身的機器碼相比對,如果相等就啟動,如果不想等就不啟動,
理論上來說一個開發板的機器碼不能自己隨便定。有權利去發放機器碼的只有uboot官方,所以我們做好一個開發板並且移植了uboot之后,理論上應該提交給uboot官方審核並發放機器碼(好像是免費的),但是國內的開發板基本都沒有申請,主要原因是因為國內開發者英文都不行,和國外開源社區的接觸比較少,都是自己隨便編號的,隨便編號的問題就是有可能和別人的編號沖突,但是只要保證自己的uboot和kernel中保持一致,就不影響自己的開發板沖突。
gd->bd->bi_boot_params = (PHYS_SDRAM_1+0x100);
(1)bd_info中另一個主要的元素bi_boot_params表示uboot給Linux內核啟動時傳參的內存地址,uboot事先將准備好的傳參bootargs放置到內存的一個地址處,然后uboot就啟動了內核(uboot在啟動內核時,真正是通過寄存器r0 r1 r2來直接傳遞參數的,其中有一個寄存器就是bi_boot_params,內核啟動后從寄存器中讀取bi_boot_params就知道了uboot給我傳遞的參數到底放在內存哪里,然后自己去內存那個地方找bootargs)
經過計算得知x210中,(PHYS_SDRAM_1+0x100)為0x30000100這個地址將來就被分配來做內核傳參了,所以在uboot中其他地方使用內存時要注意,千萬不要把這里淹沒了。
二:interrupt_init
int interrupt_init(void) { S5PC11X_TIMERS *const timers = S5PC11X_GetBase_TIMERS(); /* use PWM Timer 4 because it has no output */ /* prescaler for Timer 4 is 16 */ timers->TCFG0 = 0x0f00; if (timer_load_val == 0) { /* * for 10 ms clock period @ PCLK with 4 bit divider = 1/2 * (default) and prescaler = 16. Should be 10390 * @33.25MHz and @ 66 MHz */ timer_load_val = get_PCLK() / (16 * 100); } /* load value for 10 ms timeout */ lastdec = timers->TCNTB4 = timer_load_val; /* auto load, manual update of Timer 4 */ timers->TCON = (timers->TCON & ~0x00700000) | TCON_4_AUTO | TCON_4_UPDATE; /* auto load, start Timer 4 */ timers->TCON = (timers->TCON & ~0x00700000) | TCON_4_AUTO | COUNT_4_ON; timestamp = 0; return (0); }
typedef struct { S5PC11X_REG32 TCFG0; S5PC11X_REG32 TCFG1; S5PC11X_REG32 TCON; S5PC11X_TIMER ch[4]; S5PC11X_REG32 TCNTB4; S5PC11X_REG32 TCNTO4; } /*__attribute__((__packed__))*/ S5PC11X_TIMERS;
/* return PCLK frequency */ ulong get_PCLK(void) { ulong hclk; uint div = CLK_DIV0_REG; uint pclk_msys_ratio = ((div>>12) & 0x7); hclk = get_HCLK(); return hclk/(pclk_msys_ratio+1); }
S5PV210共有五個寄存器,timer0-timer4。timer0-timer3都有輸出引腳,timer4沒有輸出引腳,沒法輸出pwm波形。這個timer4設計的時候就不是用來設計輸出pwm波形的,這個定時器被設計用來做計時。它用來做計時時,要使用兩個寄存器TCNTB4,TCNTO4,一個是用來定時長,一個是用來觀察,TCNTB4存了一個數,就是定時的次數,每一次的時間就是由兩級時鍾分頻器決定的,我們定時時,只需要把定時時間/基准時間=數,再將這個數放到TCNTB4中,我們通過讀取TCNTO4可以看到計數有沒有減到0,讀取到0后,就知道計數器的時間已經到了。
使用timer4時,沒有中斷,所以CPU只能通過輪詢的方式來不斷的查看TCNTO4寄存器,才能知道時間到了沒。沒有實現微觀上的並行,在操作系統中就不可以通過timer4來進行定時了。(bootdelay就是輪詢的方式實現的)
總結:這里需要學習的是通過定義結構體的方式來訪問寄存器,通過函數來自動計算設置值以設置定時器
三:env_init
有很多env_init函數,主要是因為uboot支持很多啟動方式,我們一般從哪種啟動介質就會把環境變量放在哪種,各種介質操作env_init不一樣,實際使用的是哪一個要根據自己開發板來定(這些env_xx.c同時只有一個會起作用,其他是不能被鏈接的,通過x210_sd.h中配置的宏來決定是誰被包含的,)對於inand版本的x210來說,我們應該看env_movi.c中的,
int env_init(void) { #if defined(ENV_IS_EMBEDDED) ulong total; int crc1_ok = 0, crc2_ok = 0; env_t *tmp_env1, *tmp_env2; total = CFG_ENV_SIZE; tmp_env1 = env_ptr; tmp_env2 = (env_t *)((ulong)env_ptr + CFG_ENV_SIZE); crc1_ok = (crc32(0, tmp_env1->data, ENV_SIZE) == tmp_env1->crc); crc2_ok = (crc32(0, tmp_env2->data, ENV_SIZE) == tmp_env2->crc); if (!crc1_ok && !crc2_ok) gd->env_valid = 0; else if(crc1_ok && !crc2_ok) gd->env_valid = 1; else if(!crc1_ok && crc2_ok) gd->env_valid = 2; else { /* both ok - check serial */ if(tmp_env1->flags == 255 && tmp_env2->flags == 0) gd->env_valid = 2; else if(tmp_env2->flags == 255 && tmp_env1->flags == 0) gd->env_valid = 1; else if(tmp_env1->flags > tmp_env2->flags) gd->env_valid = 1; else if(tmp_env2->flags > tmp_env1->flags) gd->env_valid = 2; else /* flags are equal - almost impossible */ gd->env_valid = 1; } if (gd->env_valid == 1) env_ptr = tmp_env1; else if (gd->env_valid == 2) env_ptr = tmp_env2; #else /* ENV_IS_EMBEDDED */ gd->env_addr = (ulong)&default_environment[0]; gd->env_valid = 1; #endif /* ENV_IS_EMBEDDED */ return (0); }
這個函數只對內存里維護的那一份env做了基本的判定(判定里面有沒有能用的環境變量),當前因為還沒進行環境變量從SD卡到DDR的重定位,因此當前的環境變量是不可以用的,在start_armboot的函數中,776行才調用了env_relocate函數才進行環境變量從SD卡中到DDR中的重定位,重定位之后需要環境變量時,才可以從DDR中去取,重定位之前如果需要環境變量,需要從SD卡中去讀取。
四:init_baudrate
static int init_baudrate (void) { char tmp[64]; /* long enough for environment variables */ int i = getenv_r ("baudrate", tmp, sizeof (tmp)); gd->bd->bi_baudrate = gd->baudrate = (i > 0) ? (int) simple_strtoul (tmp, NULL, 10) : CONFIG_BAUDRATE; return (0); }
int getenv_r (char *name, char *buf, unsigned len) { int i, nxt; for (i=0; env_get_char(i) != '\0'; i=nxt+1) { int val, n; for (nxt=i; env_get_char(nxt) != '\0'; ++nxt) { if (nxt >= CFG_ENV_SIZE) { return (-1); } } if ((val=envmatch((uchar *)name, i)) < 0) continue; /* found; copy out */ n = 0; while ((len > n++) && (*buf++ = env_get_char(val++)) != '\0') ; if (len == n) *buf = '\0'; return (n); } return (-1); }
串口通信的波特率初始化;
(1)init_baudrate看名字就是初始化串口通信的波特率的。
(2)getenv_r函數用來讀取環境變量的值。用getenv函數讀取環境變量中“baudrate”的值(注意讀取到的不是int型而是字符串類型),然后用simple_strtoul函數將字符串轉成數字格式的波特率。
(3)baudrate初始化時的規則是:先去環境變量中讀取"baudrate"這個環境變量的值。如果讀取成功則使用這個值作為環境變量,記錄在gd->baudrate和gd->bd->bi_baudrate中;如果讀取不成功則使用x210_sd.h中的的CONFIG_BAUDRATE的值作為波特率。從這可以看出:環境變量的優先級是很高的。
五:serial_init
(1)serial_init看名字是初始化串口的。(疑問:start.S中調用的lowlevel_init.S中已經使用匯編初始化過串口了,這里怎么又初始化?這兩個初始化是重復的還是各自有不同?)
(2)SI中可以看出uboot中有很多個serial_init函數,我們使用的是uboot/cpu/s5pc11x/serial.c中的serial_init函數。
(3)進來后發現serial_init函數其實什么都沒做。因為在匯編階段串口已經被初始化過了,因此這里就不再進行硬件寄存器的初始化了。
六:console_init_f
在/uboot/common/console.c中
int console_init_f (void) { gd->have_console = 1; #ifdef CONFIG_SILENT_CONSOLE if (getenv("silent") != NULL) gd->flags |= GD_FLG_SILENT; #endif return (0); }
_f表示第一階段 r表示第二階段,有時候初始化函數不能一次一起完成,中間必須要夾雜一些代碼,因此將完整的一個模塊的初始化分成了兩個階段,(我們的start_armboot中826行進行了console_init_r的初始化)
僅僅是把have_console設置為1;
七:display_banner
static int display_banner (void) { printf ("\n\n%s\n\n", version_string); debug ("U-Boot code: %08lX -> %08lX BSS: -> %08lX\n", _armboot_start, _bss_start, _bss_end); #ifdef CONFIG_MEMORY_UPPER_CODE /* by scsuh */ debug("\t\bMalloc and Stack is above the U-Boot Code.\n"); #else debug("\t\bMalloc and Stack is below the U-Boot Code.\n"); #endif #ifdef CONFIG_MODEM_SUPPORT debug ("Modem Support enabled\n"); #endif #ifdef CONFIG_USE_IRQ debug ("IRQ Stack: %08lx\n", IRQ_STACK_START); debug ("FIQ Stack: %08lx\n", FIQ_STACK_START); #endif open_backlight();//lqm. //open_gprs(); return (0); }
(1)display_banner用來串口輸出顯示uboot的logo
(2)display_banner中使用printf函數向串口輸出了version_string這個字符串。那么上面的分析表示console_init_f並沒有初始化好console怎么就可以printf了呢?
(3)通過追蹤printf的實現,發現printf->puts,而puts函數中會判斷當前uboot中console有沒有被初始化好。如果console初始化好了則調用fputs完成串口發送(這條線才是控制台);如果console尚未初始化好則會調用serial_puts(再調用serial_putc直接操作串口寄存器進行內容發送)。
(4)控制台也是通過串口輸出,非控制台也是通過串口輸出。究竟什么是控制台?和不用控制台的區別?實際上分析代碼會發現,控制台就是一個用軟件虛擬出來的設備,這個設備有一套專用的通信函數(發送、接收···),控制台的通信函數最終會映射到硬件的通信函數中來實現。uboot中實際上控制台的通信函數是直接映射到硬件串口的通信函數中的,也就是說uboot中用沒用控制器其實並沒有本質差別。
(5)但是在別的體系中,控制台的通信函數映射到硬件通信函數時可以用軟件來做一些中間優化,譬如說緩沖機制。(操作系統中的控制台都使用了緩沖機制,所以有時候我們printf了內容但是屏幕上並沒有看到輸出信息,就是因為被緩沖了。我們輸出的信息只是到了console的buffer中,buffer還沒有被刷新到硬件輸出設備上,尤其是在輸出設備是LCD屏幕時)
(6)U_BOOT_VERSION在uboot源代碼中找不到定義,這個變量實際上是在makefile中定義的,然后在編譯時生成的include/version_autogenerated.h中用一個宏定義來實現的。
八:print_cpuinfo
(1)uboot啟動過程中:
CPU: S5PV210@1000MHz(OK)
APLL = 1000MHz, HclkMsys = 200MHz, PclkMsys = 100MHz
MPLL = 667MHz, EPLL = 96MHz
HclkDsys = 166MHz, PclkDsys = 83MHz
HclkPsys = 133MHz, PclkPsys = 66MHz
SCLKA2M = 200MHz
Serial = CLKUART
這些信息都是print_cpuinfo打印出來的。
九:checkboard
(1)checkboard看名字是檢查、確認開發板的意思。這個函數的作用就是檢查當前開發板是哪個開發板並且打印出開發板的名字。
init_func_i2c
(1)這個函數實際沒有被執行,X210的uboot中並沒有使用I2C。如果將來我們的開發板要擴展I2C來接外接硬件,則在x210_sd.h中配置相應的宏即可開啟。
uboot學習實踐
(1)對uboot源代碼進行完修改(修改內容根據自己的理解和分析來修改)
(2)make distclean然后make x210_sd_config然后make
(3)編譯完成得到u-boot.bin,然后去燒錄。燒錄方法按照裸機第三部分講的linux下使用dd命令來燒寫的方法來燒寫。
(4)燒寫過程:
第一步:進入sd_fusing目錄下
第二步:make clean
第三步:make
第四步:插入sd卡,ls /dev/sd*得到SD卡在ubuntu中的設備號(一般是/dev/sdb,注意SD卡要連接到虛擬機ubuntu中,不要接到windows中)
第五步:./sd_fusing.sh /dev/sdb完成燒錄(注意不是sd_fusing2.sh)
(5)總結:uboot就是個龐大點復雜點的裸機程序而已,我們完全可以對他進行調試。調試的方法就是按照上面步驟,根據自己對代碼的分析和理解對代碼進行更改,然后重新編譯燒錄運行,根據運行結果來學習。
十:dram_init
(1)dram_init看名字是關於DDR的初始化。疑問:在匯編階段已經初始化過DDR了否則也無法relocate到第二部分運行,怎么在這里又初始化DDR?
(2)dram_init都是在給gd->bd里面關於DDR配置部分的全局變量賦值,讓gd->bd數據記錄下當前開發板的DDR的配置信息,以便uboot中使用內存。
(3)從代碼來看,其實就是初始化gd->bd->bi_dram這個結構體數組。
十一:display_dram_config
(1)看名字意思就是打印顯示dram的配置信息。
(2)啟動信息中的:(DRAM: 512 MB)就是在這個函數中打印出來的。
(3)思考:如何在uboot運行中得知uboot的DDR配置信息?uboot中有一個命令叫bdinfo,這個命令可以打印出gd->bd中記錄的所有硬件相關的全局變量的值,因此可以得知DDR的配置信息。
DRAM bank = 0x00000000
-> start = 0x30000000
-> size = 0x10000000
DRAM bank = 0x00000001
-> start = 0x40000000
-> size = 0x10000000
init_sequence總結
(1)都是板級硬件的初始化以及gd、gd->bd中的數據結構的初始化。譬如:
網卡初始化、機器碼(gd->bd->bi_arch_number)、內核傳參DDR地址(gd->bd->bi_boot_params)、Timer4初始化為10ms一次、波特率設置(gd->bd->bi_baudrate和gd->baudrate)、console第一階段初始化(gd->have_console設置為1)、打印uboot的啟動信息、打印cpu相關設置信息、檢查並打印當前開發板名字、DDR配置信息初始化(gd->bd->bi_dram)、打印DDR總容量。
總結回顧:本節課結束后已經到了start_armboot的第487行。
⑤CFG_NO_FLASH
1)雖然NandFlash和NorFlash都是Flash,但是一般NandFlash會簡稱為Nand而不是Flash,一般講Flash都是指的Norflash。這里2行代碼是Norflash相關的。
(2)flash_init執行的是開發板中對應的NorFlash的初始化、display_flash_config打印的也是NorFlash的配置信息(Flash: 8 MB就是這里打印出來的)。但是實際上X210中是沒有Norflash的。所以着兩行代碼是可以去掉的(我也不知道為什么沒去掉?猜測原因有可能是去掉着兩行代碼會導致別的地方工作不正常,需要花時間去移植調試,然后移植的人就懶得弄。實際上不去掉除了顯示有8MB Flash實際沒用之外也沒有別的影響)
代碼實踐,去掉Flash看會不會出錯。
結論:加上CONFIG_NOFLASH宏之后編譯出錯,說明代碼移植的不好,那個文件的包含沒有被這個宏控制。於是乎移植的人就直接放這沒管。
CONFIG_VFD和CONFIG_LCD是顯示相關的,這個是uboot中自帶的LCD顯示的軟件架構。但是實際上我們用LCD而沒有使用uboot中設置的這套軟件架構,我們自己在后面自己添加了一個LCD顯示的部分。
⑥mem_malloc_init
(1)mem_malloc_init函數用來初始化uboot的堆管理器。
(2)uboot中自己維護了一段堆內存,肯定自己就有一套代碼來管理這個堆內存。有了這些東西uboot中你也可以malloc、free這套機制來申請內存和釋放內存。我們在DDR內存中給堆預留了896KB的內存。
⑦開發板獨有初始化:mmc初始化
(1)從536到768行為開發板獨有的初始化。意思是三星用一套uboot同時滿足了好多個系列型號的開發板,然后在這里把不同開發板自己獨有的一些初始化寫到了這里。用#if條件編譯配合CONFIG_xxx宏來選定特定的開發板。
(2)X210相關的配置在599行到632行。
(3)mmc_initialize看名字就應該是MMC相關的一些基礎的初始化,其實就是用來初始化SoC內部的SD/MMC控制器的。函數在uboot/drivers/mmc/mmc.c里。
(4)uboot中對硬件的操作(譬如網卡、SD卡···)都是借用的linux內核中的驅動來實現的,uboot根目錄底下有個drivers文件夾,這里面放的全都是從linux內核中移植過來的各種驅動源文件。
(5)mmc_initialize是具體硬件架構無關的一個MMC初始化函數,所有的使用了這套架構的代碼都掉用這個函數來完成MMC的初始化。mmc_initialize中再調用board_mmc_init和cpu_mmc_init來完成具體的硬件的MMC控制器初始化工作。
(6)cpu_mmc_init在uboot/cpu/s5pc11x/cpu.c中,這里面又間接的調用了drivers/mmc/s3c_mmcxxx.c中的驅動代碼來初始化硬件MMC控制器。這里面分層很多,分層的思想一定要有,否則完全就糊塗了。
printf("%ldMB\n", (mmc->capacity/(1024*1024/(1<<9))));
capacity/(1024*1024/(1<<9))這里為什么不這樣寫capacity*(1<<9)/(1024*1024),因為怕capacity*(1<<9)超過了int的范圍;
⑧env_relocate
(1)env_relocate是環境變量的重定位,完成從SD卡中將環境變量讀取到DDR中的任務。
(2)環境變量到底從哪里來?SD卡中有一些(8個)獨立的扇區作為環境變量存儲區域的。但是我們燒錄/部署系統時,我們只是燒錄了uboot分區、kernel分區和rootfs分區,根本不曾燒錄env分區。所以當我們燒錄完系統第一次啟動時ENV分區是空的,本次啟動uboot嘗試去SD卡的ENV分區讀取環境變量時失敗(讀取回來后進行CRC校驗時失敗),我們uboot選擇從uboot內部代碼中設置的一套默認的環境變量出發來使用(這就是默認環境變量);這套默認的環境變量在本次運行時會被讀取到DDR中的環境變量中,然后被寫入(也可能是你saveenv時寫入,也可能是uboot設計了第一次讀取默認環境變量后就寫入)SD卡的ENV分區。然后下次再次開機時uboot就會從SD卡的ENV分區讀取環境變量到DDR中,這次讀取就不會失敗了。
(3)真正的從SD卡到DDR中重定位ENV的代碼是在env_relocate_spec內部的movi_read_env完成的。
⑨IP地址、MAC地址的確定
(1)開發板的IP地址是在gd->bd中維護的,來源於環境變量ipaddr。getenv函數用來獲取字符串格式的IP地址,然后用string_to_ip將字符串格式的IP地址轉成字符串格式的點分十進制格式。
(2)IP地址由4個0-255之間的數字組成,因此一個IP地址在程序中最簡單的存儲方法就是一個unsigend int。但是人類容易看懂的並不是這種類型,而是點分十進制類型(192.168.1.2)。這兩種類型可以互相轉換。
⑩devices_init
(1)devices_init看名字就是設備的初始化。這里的設備指的就是開發板上的硬件設備。放在這里初始化的設備都是驅動設備,這個函數本來就是從驅動框架中衍生出來的。uboot中很多設備的驅動是直接移植linux內核的(譬如網卡、SD卡),linux內核中的驅動都有相應的設備初始化函數。linux內核在啟動過程中就有一個devices_init(名字不一定完全對,但是差不多),作用就是集中執行各種硬件驅動的init函數。
(2)uboot的這個函數其實就是從linux內核中移植過來的,它的作用也是去執行所有的從linux內核中繼承來的那些硬件驅動的初始化函數。
⑪jumptable_init
(1)jumptable跳轉表,本身是一個函數指針數組,里面記錄了很多函數的函數名。看這陣勢是要實現一個函數指針到具體函數的映射關系,將來通過跳轉表中的函數指針就可以執行具體的函數。這個其實就是在用C語言實現面向對象編程。在linux內核中有很多這種技巧。
(2)通過分析發現跳轉表只是被賦值從未被引用,因此跳轉表在uboot中根本就沒使用。
⑫console_init_r
(1)console_init_f是控制台的第一階段初始化,console_init_r是第二階段初始化。實際上第一階段初始化並沒有實質性工作,第二階段初始化才進行了實質性工作。
(2)uboot中有很多同名函數,使用SI工具去索引時經常索引到不對的函數處(回憶下當時start.S中找lowlevel_init.S時,自動索引找到的是錯誤的,真正的反而根本沒找到。)
(3)console_init_r就是console的純軟件架構方面的初始化(說白了就是去給console相關的數據結構中填充相應的值),所以屬於純軟件配置類型的初始化。
(4)uboot的console實際上並沒有干有意義的轉化,它就是直接調用的串口通信的函數。所以用不用console實際並沒有什么分別。(在linux內console就可以提供緩沖機制等不用console不能實現的東西)。
⑬enable_interrupts
(1)看名字應該是中斷初始化代碼。這里指的是CPSR中總中斷標志位的使能。
(2)因為我們uboot中沒有使用中斷,因此沒有定義CONFIG_USE_IRQ宏,因此我們這里這個函數是個空殼子。
(3)uboot中經常出現一種情況就是根據一個宏是否定義了來條件編譯決定是否調用一個函數內部的代碼。uboot中有2種解決方案來處理這種情況:方案一:在調用函數處使用條件編譯,然后函數體實際完全提供代碼。方案二:在調用函數處直接調用,然后在函數體處提供2個函數體,一個是有實體的一個是空殼子,用宏定義條件編譯來決定實際編譯時編譯哪個函數進去。
⑭loadaddr、bootfile兩個環境變量
(1)這兩個環境變量都是內核啟動有關的,在啟動linux內核時會參考這兩個環境變量的值。
⑮board_late_init
(1)看名字這個函數就是開發板級別的一些初始化里比較晚的了,就是晚期初始化。所以晚期就是前面該初始化的都初始化過了,剩下的一些必須放在后面初始化的就在這里了。側面說明了開發板級別的硬件軟件初始化告一段落了。
(2)對於X210來說,這個函數是空的。
⑯eth_initialize
(1)看名字應該是網卡相關的初始化。這里不是SoC與網卡芯片連接時SoC這邊的初始化,而是網卡芯片本身的一些初始化。
(2)對於X210(DM9000)來說,這個函數是空的。X210的網卡初始化在board_init函數中,網卡芯片的初始化在驅動中。
⑰x210_preboot_init(LCD和logo顯示)
(1)x210開發板在啟動起來之前的一些初始化,以及LCD屏幕上的logo顯示。
⑱check menukey to update from sd
(1)uboot啟動的最后階段設計了一個自動更新的功能。就是:我們可以將要升級的鏡像放到SD卡的固定目錄中,然后開機時在uboot啟動的最后階段檢查升級標志(是一個按鍵。按鍵中標志為"LEFT"的那個按鍵,這個按鍵如果按下則表示update mode,如果啟動時未按下則表示boot mode)。如果進入update mode則uboot會自動從SD卡中讀取鏡像文件然后燒錄到iNand中;如果進入boot mode則uboot不執行update,直接啟動正常運行。
(2)這種機制能夠幫助我們快速燒錄系統,常用於量產時用SD卡進行系統燒錄部署。
⑲死循環
(1)解析器
(2)開機倒數自動執行
(3)命令補全