您好,登錄后才能下訂單哦!
這篇文章主要介紹“u-boot第二階段啟動流程是什么”,在日常操作中,相信很多人在u-boot第二階段啟動流程是什么問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”u-boot第二階段啟動流程是什么”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
tq2440的uboot目錄有點繁瑣. 用nor中的uboot下載uboot到nand中可以使用tftp方式. 1.連接開發板與pc 2.選擇tftp下載
3. 設置pc與開發板同一個網段
之后在pc上安裝tftp server軟件. 然后在uboot的tftp下載模式下輸入 1. 因為tq2440的uboot源碼寫死了通過tftp下載的uboot文件名稱為 u-boot.bin, 所以將天嵌提供的uboot改名為 u-boot.bin 放到 tftp工具的同級目錄里即可自動完成下載, 下載前最好先格式化nand.
個人感覺uboot第一階段就是為了芯片能夠運行起來保證最最低的運行要求, 第二階段才是真正干活的代碼.
uboot啟動的第二階段流程大致如圖所示:
進入 start_armboot 之后首先為 gd , gd->bd 安排存儲空間, 然后將該空間清0.
int board_init (void) { S3C24X0_GPIO * const gpio = S3C24X0_GetBase_GPIO();//獲取S3C24X0_GPIO_BASE, GPIO寄存器的基地址 /* set up the I/O ports */ gpio->GPACON = 0x007FFFFF; ... ... gpio->GPJUP = 0x00001fff; /* arch number of TQ2440-Board */ gd->bd->bi_arch_number = MACH_TYPE_S3C2440; /* adress of boot parameters */ gd->bd->bi_boot_params = 0x30000100; //機器ID和ram中taglist的基地址在do_boot_linux中作為參數傳遞給kernel, [theKernel(0, machid, bd->bi_boot_params)] /* 開啟 指令/數據 cache*/ icache_enable(); dcache_enable(); return 0; }
int interrupt_init (void) { S3C24X0_TIMERS * const timers = S3C24X0_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 15625 @ 50 MHz */ timer_load_val = get_PCLK()/(2 * 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 & ~0x0700000) | 0x600000; /* auto load, start Timer 4 , 自動重裝*/ timers->TCON = (timers->TCON & ~0x0700000) | 0x500000; timestamp = 0; return (0); }
這里使用沒有輸出引腳的定時器 TIM4, 定時 10ms, 自動重裝.
TCFG0 Bit Description Prescaler 1 [15:8] These 8 bits determine prescaler value for Timer 2, 3 and 4. Prescaler 0 [7:0] These 8 bits determine prescaler value for Timer 0 and 1. 0x00
TCFG0=0x0F00, 預分頻值Prescaler 1 = 1/16;
TCFG1=0x0000, 分頻值 1/2 (默認值0000為2分頻)
timer_load_val = get_PCLK()/(2 * 16 * 100);
get_PCLK()位于cpu/arm920t/s3c24x0/speed.c 中,取得pclk的過程如下
FCLK => HCLK = FCLK / 2 => PCLK = HCLK / 2, FCLK : HCLK : PCLK = 4 : 2 : 1
即先通過MPLLCON的值計算得出FCLK,然后根據CLKDIVN的分頻率比得出HCLK 和 PCLK. 要做一個10ms的定時器,所以這里計算初值。上面prescaler1 = 16, 且此處沒有設置TCFG1,所以TCFG1為默認值0, 則divider選擇了1/2; 1s = 10ms * 100;
于是,需要的三個參數全都出來了, 分頻后頻率為 (clk /(2 * 16)),即每秒要做(clk/(2 * 16))次減計數,那么10ms就要做 clk / (2 * 16 * 100)次減計數。
lastdec = timers->TCNTB4 = timer_load_val;
將定時器計數初值保存在 TCNTB4 中, 在減計數完成時自動將 TCNTB4 賦值給 TCNT4
timers->TCON = (timers->TCON & ~0x0700000) | 0x600000; // ==> bit[22:20] = 110 auto reload mode, update TCNTB4 timers->TCON = (timers->TCON & ~0x0700000) | 0x500000; // ==> bit[22:20] = 101 auto reload mode, start for timer4 此時,真正開始定時器4
后期版本的uboot中的interrupt_init貌似更新為timer_init了
環境變量應該是如果在flash里有環境變量那就使用flash里的, 如果沒有就使用默認的環境變量.
這個函數在多個文件中定義, 但是每個文件開頭都有一個宏定義來將整個文件包含起來, 如在env_nand.c中就有
#if defined(CFG_ENV_IS_IN_NAND) /* Environment is in Nand Flash */
在 include/configs/smdk2440.h(EmbedSky.h)中如果定義了 CFG_ENV_IS_IN_NAND 就表示參數保存在nand中.
int env_init(){ gd->env_addr = (ulong)&default_environment[0]; gd->env_valid = 1; }
將默認的環境變量數組地址賦值給gd->env_addr;
default_environment[]定義在 common/env_common.c
getenv_r ("baudrate", tmp, sizeof (tmp)); 這里是對應上一步的 env_init, 如果設置了環境變量就依據 env_ptr 去尋找 "baudrate">
/* * 使用給定的 波特率 初始化串口. 始終設置為 8個數據位 無校驗 1個停止位 無開始位 */ 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 , 這里是為了計算波特率寄存器的值: RBRDIV*/ 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++); }
使用上一步獲取到的波特率來初始化串口, 串口默認設置為 8 N 1 無.
gd->have_console = 1; 等于設置了一個標志位, 表示uboot使用命令終端.
這兩個函數都是調試使用的, 他們打印出調試信息, 這里要注意的是打印函數, printf ; 在/common/console.c中它調用puts來實現輸出
void puts (const char *s) { //GD_FLG_DEVINIT表示了是向自己還是串口輸出 if (gd->flags & GD_FLG_DEVINIT) { /* Send to the standard output */ fputs (stdout, s); } else { /* Send directly to the handler */ serial_puts (s); } }
首先要在 /include/configs/smdk2440.h(embedsky.h)中定義: #define CONFIG_NR_DRAM_BANKS 1 /* 只有一塊sdram */ #define PHYS_SDRAM_1 0x30000000 /* SDRAM Bank #1 起始地址*/ #define PHYS_SDRAM_1_SIZE 0x08000000 /* 128 MB */ int dram_init (void) { gd->bd->bi_dram[0].start = PHYS_SDRAM_1; //bi_dram[0] 表示了 dram 起始地址和大小 gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE; return 0; }
最后總結一下最重要的兩個結構體:
(1) /include/asm-arm/global_data.h 中的 gd_t , 其中保存了 u-boot 的配置信息
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 */ void **jt; /* jump table */ } gd_t;
(2) /include/asm-arm/u-boot.h 中的 bd_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; //下面兩個參數在進入內核時候會作為參數 r1 r2 傳遞給內核啟動函數 ulong bi_arch_number; /* unique id for this board , 目標板id */ ulong bi_boot_params; /* where this board expects params , taglist的地址*/ struct /* RAM configuration */ { ulong start; ulong size; } bi_dram[CONFIG_NR_DRAM_BANKS]; } bd_t;
在/include /configs/smdk2440.h(embedsky.h) 中定義的 CFG_GBL_DATA_SIZE 為 128 , gd_t 和 bd_t 都保存在該區域中.
由內部的代碼可知這里是norflash的初始化
#define PHYS_FLASH_1 0x00000000 flashbase = PHYS_FLASH_1; //因為cpu從0地址處開始執行代碼, 2440在0地址處只可能有nor-flash和內部4k的sram, 所以這里是nor-flash的初始化無疑
該函數中初始化了nor-flash的各個sector起始地址和大小, 在display_flash_config中打印出了flash的配置信息
addr = (_bss_end + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1); //計算 framebuffer內存地址 size = lcd_setmem (addr); //設置 framebuffer尺寸大小 gd->fb_base = addr; //設置 framebuffer起始地址 mem_malloc_init (_armboot_start - CFG_MALLOC_LEN); //將 堆區(malloc) 全部清零
注意要在 /include/configs/smdk2440.h(embedsky.h) 中打開 CONFIG_COMMANDS 和 CFG_CMD_NAND 對 nand 的支持
nand_init();
/* initialize environment */ env_relocate (); //初始化環境變量
gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");
/* MAC Address */ //從網卡寄存器讀出mac地址 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;//這里問號表達式的優先級高于 = 即:先判斷s是否為真, 根據結果再賦值 if (s) s = (*e) ? e + 1 : e; }
devices_init(); //初始化 iic/lcd/video/logbuff/system/serial等設備
jumptable_init(); //初始化跳轉表
console_init_r();
enable_interrupt();
/* Initialize from environment */ if ((s = getenv ("loadaddr")) != NULL) { load_addr = simple_strtoul (s, NULL, 16); }
查找環境變量里是否含有 load_addr , 有的話將他的值寫入到 load_addr 中, 他是加載地址, 在bootm中還會見到他. 他表示uboot將kernel加載到內存的地址.
if ((s = getenv ("bootfile")) != NULL) { copy_filename (BootFile, s, sizeof (BootFile)); //保存framebuffer }
board_late_init (); //因為每個板子都不一樣, 板子特殊的初始化可以寫在這個函數里
eth_initialize
/* main_loop() can return to retry autoboot, if so just run it again. */ for (;;) { main_loop (); //進入主循環 }
截止目前還沒有講到uboot如何啟動linux, 而這最關鍵的一部分代碼就在main_loop中.
main_loop中主要是完成了 1. 設置啟動次數, 有些項目需要檢測設備的啟動次數, 如果大于某值則不予啟動 2. modem功能 3. uboot版本號 4. 命令自動補全功能(類似shell的自動補全命令) 5. 啟用倒計時啟動功能 6. 進入死循環 執行 read_line(), 讀取控制臺(一般都是串口)輸入的命令. 執行命令 run_command() 如果想要知道如何解析命令可以去跟蹤一下 run_command() 函數 7. 啟動linux
可見main_loop函數的重要性, 我們在下一篇中繼續分析main_loop函數
到此,關于“u-boot第二階段啟動流程是什么”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。