天天看點

uboot移植——啟動第二階段

文章目錄

    • 1.start_armboot函數簡介
    • 2.uboot第二階段簡介
      • 2.1 uboot第二階段應該做什麼
      • 2.2 uboot第二階段完結于何處?
    • 3.變量定義分析
      • 3.1 init_fnc_t
      • 3.2 DECLARE_GLOBAL_DATA_PTR
    • 4.記憶體排布
      • 4.1為什麼要配置設定記憶體
      • 4.2記憶體排布
    • 5.for循環執行init_sequence
      • 5.1 cpu_init
      • 5.2 board_init
        • 5.2.1機器碼的設定
        • 5.2.2核心啟動時傳參的記憶體位址
        • 5.2.3 DDR配置的背景知識
      • 5.3 interrupt_init
      • 5.4 env_init
      • 5.5 init_baudrate
      • 5.6 serial_init
      • 5.7 console_init_f
      • 5.8 display_banner
      • 5.9 print_cpuinfo
      • 5.10 checkboard
      • 5.11 init_func_i2c
      • 5.12 dram_init
      • 5.13 display_dram_config
      • 5.14 init_sequence總結
    • 6. CFG_NO_FLASH
    • 7. mem_malloc_init
    • 8.開發闆獨有初始化:mmc初始化
    • 9. env_relocate
    • 10. IP位址、MAC位址的确定
    • 11. devices_init
    • 12. jumptable_init
    • 13. console_init_r
    • 14. enable_interrupts
    • 15. loadaddr、bootfile兩個環境變量
    • 16. board_late_init
    • 17. eth_initialize
    • 18. x210_preboot_init(LCD和logo顯示)
    • 19. check menukey to update from sd
    • 20. 死循環
    • 21. uboot啟動2階段總結
      • 21.1啟動流程回顧、重點函數标出
      • 21.2啟動過程特征總結
      • 21.3移植時的注意點

1.start_armboot函數簡介

(1)這個函數在uboot/lib_arm/board.c的第444行開始到908行結束。

(2)450行還不是全部,因為裡面還調用了别的函數。

(3)為什麼這麼長的函數,怎麼不分成兩三個函數?主要因為這個函數整個構成了uboot啟動的第二階段。

2.uboot第二階段簡介

2.1 uboot第二階段應該做什麼

(1)概括來講uboot第一階段主要就是初始化了SoC内部的一些部件(譬如看門狗、時鐘),然後初始化DDR并且完成重定位。

(2)由宏觀分析來講,uboot的第二階段就是要初始化剩下的還沒被初始化的硬體。主要是SoC外部硬體(譬如iNand、網卡晶片····)、uboot本身的一些東西(uboot的指令、環境變量等····)。然後最終初始化完必要的東西後進入uboot的指令行準備接受指令。

2.2 uboot第二階段完結于何處?

(1)uboot啟動後自動運作列印出很多資訊(這些資訊就是uboot在第一和第二階段不斷進行初始化時,列印出來的資訊)。然後uboot進入了倒數bootdelay秒然後執行bootcmd對應的啟動指令。

(2)如果使用者沒有幹涉則會執行bootcmd進入自動啟動核心流程(uboot就死掉了);此時使用者可以按下Enter鍵打斷uboot的自動啟動進入uboot的指令行下。然後uboot就一直工作在指令行下。

(3)uboot的指令行就是一個死循環,循環體内不斷重複:接收指令、解析指令、執行指令。這就是uboot最終的歸宿。

3.變量定義分析

3.1 init_fnc_t

typedef int (init_fnc_t) (void);
init_fnc_t **init_fnc_ptr;
           

(1)typedef int (init_fnc_t) (void); 這是一個函數類型。

(2)init_fnc_ptr是一個二重函數指針,二重指針的作用有2個(其中一個是用來指向一重指針),一個是用來指向指針數組。是以這裡的init_fuc_ptr可以用來指向一個函數指針數組。

3.2 DECLARE_GLOBAL_DATA_PTR

#define DECLARE_GLOBAL_DATA_PTR    
register volatile gd_t *gd asm ("r8")
           

(1)定義了一個全局變量名字叫gd,這個全局變量是一個指針類型,占4位元組。用volatile修飾表示可變的,用register修飾表示這個變量要盡量放到寄存器中,後面的asm(“r8”)是gcc支援的一種文法,意思就是要把gd放到寄存器r8中。

(2)綜合分析,DECLARE_GLOBAL_DATA_PTR就是定義了一個要放在寄存器r8中的全局變量,名字叫gd,類型是一個指向gd_t類型變量的指針。

(3)為什麼要定義為register?因為這個全局變量gd(global data的簡稱)是uboot中很重要的一個全局變量(準确的說這個全局變量是一個結構體,裡面有很多内容,這些内容加起來構成的結構體就是uboot中常用的所有的全局變量),這個gd在程式中經常被通路,是以放在register中提升效率。是以純粹是運作效率方面考慮,和功能要求無關。并不是必須的。

typedef	struct	global_data {
	bd_t		*bd;
	unsigned long	flags;
	unsigned long	baudrate;
	unsigned long	cpu_clk;	/* CPU clock in Hz! */
	unsigned long	bus_clk;
	......
} gd_t;
           

(4)gd_t定義在include/asm-arm/global_data.h中。

gd_t中定義了很多全局變量,都是整個uboot使用的;其中有一個bd_t類型的指針,指向一個bd_t類型的變量,這個bd是開發闆的闆級資訊的結構體,裡面有不少硬體相關的參數,譬如波特率、IP位址、機器碼、DDR記憶體分布。

4.記憶體排布

4.1為什麼要配置設定記憶體

(1)DECLARE_GLOBAL_DATA_PTR隻能定義了一個指針,也就是說gd裡的這些全局變量并沒有被配置設定記憶體,我們在使用gd之前要給他配置設定記憶體,否則gd也隻是一個野指針而已。

(2)gd和bd需要記憶體,記憶體目前沒有被人管理(因為沒有作業系統統一管理記憶體),大片的DDR記憶體散放着可以随意使用(隻要使用記憶體位址直接去通路記憶體即可)。但是因為uboot中後續很多操作還需要大片的連着記憶體塊,是以這裡使用記憶體要本着夠用就好,緊湊排布的原則。是以我們在uboot中需要有一個整體規劃。

4.2記憶體排布

#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
           

(1)uboot區 CFG_UBOOT_BASE-xx(長度為uboot的實際長度)

(2)堆區 長度為CFG_MALLOC_LEN,實際為912KB

(3)棧區 長度為CFG_STACK_SIZE,實際為512KB

(4)gd 長度為sizeof(gd_t),實際36位元組

(5)bd 長度為sizeof(bd_t),實際為44位元組左右

(6)記憶體間隔 為了防止高版本的gcc的優化造成錯誤。

5.for循環執行init_sequence

for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
		if ((*init_fnc_ptr)() != 0) {
			hang ();
		}
	}
           

(1)init_sequence是一個函數指針數組,數組中存儲了很多個函數指針,這些指向指向的函數都是init_fnc_t類型(特征是接收參數是void類型,傳回值是int)。

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,
};
           

(2)init_sequence在定義時就同時給了初始化,初始化的函數指針都是一些函數名

(3)init_fnc_ptr是一個二重函數指針,可以指向init_sequence這個函數指針數組。

(4)用for循環肯定是想要去周遊這個函數指針數組(周遊的目的也是去依次執行這個函數指針數組中的所有函數)。思考:如何周遊一個函數指針數組?有2種方法:第一種也是最常用的一種,用下标去周遊,用數組元素個數來截至。第二種不常用,但是也可以。就是在數組的有效元素末尾放一個标志,依次周遊到标志處即可截至(有點類似字元串的思路)。我們這裡使用了第二種思路。因為數組中存的全是函數指針,是以我們選用了NULL來作為标志。我們周遊時從開頭依次進行,直到看到NULL标志截至。這種方法的優勢是不用事先統計數組有多少個元素。

(5)init_fnc_t的這些函數的傳回值定義方式一樣的,都是:函數執行正确時傳回0,不正确時傳回-1.是以我們在周遊時去檢查函數傳回值,如果周遊中有一個函數傳回值不等于0則hang()挂起。從分析hang函數可知:uboot啟動過程中初始化闆級硬體時不能出任何錯誤,隻要有一個錯誤整個啟動就終止,除了重新開機開發闆沒有任何辦法。

(6)init_sequence中的這些函數,都是board級别的各種硬體初始化。

5.1 cpu_init

(1)看名字這個函數應該是cpu内部的初始化,是以這裡是空的。

5.2 board_init

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;
}
           

(1)board_init在uboot/board/samsung/x210/x210.c中,這個看名字就知道是x210開發闆相關的初始化。

(2)DECLARE_GLOBAL_DATA_PTR在這裡聲明是為了後面使用gd友善。可以看出把gd的聲明定義成一個宏的原因就是我們要到處去使用gd,是以就要到處聲明,定義成宏比較友善。

(3)網卡初始化。CONFIG_DRIVER_DM9000這個宏是x210_sd.h中定義的,這個宏用來配置開發闆的網卡的。dm9000_pre_init函數就是對應的DM9000網卡的初始化函數。開發闆移植uboot時,如果要移植網卡,主要的工作就在這裡。

(4)這個函數中主要是網卡的GPIO和端口的配置,而不是驅動。因為網卡的驅動都是現成的正确的,移植的時候驅動是不需要改動的,關鍵是這裡的基本初始化。因為這些基本初始化是硬體相關的。

5.2.1機器碼的設定

(1)bi_arch_number是board_info中的一個元素,含義是:開發闆的機器碼。所謂機器碼就是uboot給這個開發闆定義的一個唯一編号。

(2)機器碼的主要作用就是在uboot和linux核心之間進行比對和适配。

(3)嵌入式裝置中每一個裝置的硬體都是定制化的,不能通用。嵌入式裝置的高度定制化導緻硬體和軟體不能随便适配使用。這就告訴我們:這個開發闆移植的核心鏡像絕對不能下載下傳到另一個開發闆去,否則也不能啟動,就算啟動也不能正常工作,有很多隐患。是以linux做了個設定:給每個開發闆做個唯一編号(機器碼),然後在uboot、linux核心中都有一個軟體維護的機器碼編号。然後開發闆、uboot、linux三者去比對機器碼,如果機器碼對上了就啟動,否則就不啟動(因為軟體認為我和這個硬體不适配)。

(4)MACH_TYPE在x210_sd.h中定義,值是2456,并沒有特殊含義,隻是目前開發闆對應的編号。這個編号就代表了x210這個開發闆的機器碼,将來這個開發闆上面移植的linux核心中的機器碼也必須是2456,否則就啟動不起來。

(5)uboot中配置的這個機器碼,會作為uboot給linux核心的傳參的一部分傳給linux核心,核心啟動過程中會比對這個接收到的機器碼,和自己本身的機器碼相對比,如果相等就啟動,如果不想等就不啟動。

(6)理論上來說,一個開發闆的機器碼不能自己随便定。理論來說有權利去發放這個機器碼的隻有uboot官方,是以我們做好一個開發闆并且移植了uboot之後,理論上應該送出給uboot官方稽核并發放機器碼(好像是免費的)。但是國内的開發闆基本都沒有申請(主要是因為國内開發者英文都不行,和國外開源社群接觸比較少),都是自己随便編号的。随便編号的問題就是有可能和别人的編号沖突,但是隻要保證uboot和kernel中的編号是一緻的,就不影響自己的開發闆啟動。

5.2.2核心啟動時傳參的記憶體位址

(1)bd_info中另一個主要元素,bi_boot_params表示uboot給linux kernel啟動時的傳參的記憶體位址。也就是說uboot給linux核心傳參的時候是這麼傳的:uboot事先将準備好的傳參(字元串,就是bootargs)放在記憶體的一個位址處(就是bi_boot_params),然後uboot就啟動了核心(uboot在啟動核心時真正是通過寄存器r0 r1 r2來直接傳遞參數的,其中有一個寄存器中就是bi_boot_params)。核心啟動後從寄存器中讀取bi_boot_params就知道了uboot給我傳遞的參數到底在記憶體的哪裡。然後自己去記憶體的那個地方去找bootargs。

(2)經過計算得知:X210中bi_boot_params的值為0x30000100,這個記憶體位址就被配置設定用來做核心傳參了。是以在uboot的其他地方使用記憶體時要注意,千萬不敢把這裡給淹沒了。

5.2.3 DDR配置的背景知識

(1)board_init中除了網卡的初始化之外,剩下的2行用來初始化DDR。

(2)注意:這裡的初始化DDR和彙編階段lowlevel_init中初始化DDR是不同的。當時是硬體的初始化,目的是讓DDR可以開始工作。現在是軟體結構中一些DDR相關的屬性配置、位址設定的初始化,是純軟體層面的。

(3)軟體層次初始化DDR的原因:對于uboot來說,他怎麼知道開發闆上到底有幾片DDR記憶體,每一片的起始位址、長度這些資訊呢?在uboot的設計中采用了一種簡單直接有效的方式:程式員在移植uboot到一個開發闆時,程式員自己在x210_sd.h中使用宏定義去配置出來闆子上DDR記憶體的資訊,然後uboot隻要讀取這些資訊即可(實際上還有另外一條思路:就是uboot通過代碼讀取硬體資訊來知道DDR配置,但是uboot沒有這樣。實際上PC的BIOS采用的是這種)。

(4)x210_sd.h的496行到501行中使用了标準的宏定義來配置DDR相關的參數。主要配置了這麼幾個資訊:有幾片DDR記憶體、每一片DDR的起始位址、長度。這裡的配置資訊我們在uboot代碼中使用到記憶體時就可以從這裡提取使用(想象uboot中使用到記憶體的地方都不是直接用位址數字的,都是用宏定義的)。

5.3 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);
}
           

(1)看名字函數是和中斷初始化有關的,但是實際上不是

,實際上這個函數是用來初始化定時器的(實際使用的是Timer4)。

(2)裸機中講過:210共有5個PWM定時器。其中Timer0-timer3都有一個對應的PWM信号輸出的引腳。而Timer4沒有引腳,無法輸出PWM波形。Timer4在設計的時候就不是用來輸出PWM波形的(沒有引腳,沒有TCMPB寄存器),這個定時器被設計用來做計時。

(3)Timer4用來做計時時要使用到2個寄存器:TCNTB4、TCNTO4。TCNTB中存了一個數,這個數就是定時次數(每一次時間是由時鐘決定的,其實就是由2級時鐘分頻器決定的)。我們定時時隻需要把定時時間/基準時間=數,将這個數放入TCNTB中即可;我們通過TCNTO寄存器即可讀取時間有沒有減到0,讀取到0後就知道定的時間已經到了。

(4)使用Timer4來定時,因為沒有中斷支援,是以CPU不能做其他事情同時定時,CPU隻能使用輪詢方式來不斷檢視TCNTO寄存器才能知道定時時間到了沒。因為Timer4的定時是不能實作微觀上的并行。uboot中定時就是通過Timer4來實作定時的。是以uboot中定時時不能做其他事(考慮下,典型的就是bootdelay,bootdelay中實作定時并且檢查使用者輸入是用輪詢方式實作的,原理參考裸機中按鍵章節中的輪詢方式處理按鍵)

(5)interrupt_init函數将timer4設定為定時10ms。關鍵部位就是get_PCLK函數擷取系統設定的PCLK_PSYS時鐘頻率,然後設定TCFG0和TCFG1進行分頻,然後計算出設定為10ms時需要向TCNTB中寫入的值,将其寫入TCNTB,然後設定為auto reload模式,然後開定時器開始計時就沒了。

總結:在學習這個函數時,注意标準代碼和之前裸機代碼中的差別,重點學會:通過定義結構體的方式來通路寄存器,通過函數來自動計算設定值以設定定時器。

5.4 env_init

(1)env_init,看名字就知道是和環境變量有關的初始化。

(2)為什麼有很多env_init函數,主要原因是uboot支援各種不同的啟動媒體(譬如norflash、nandflash、inand、sd卡·····),我們一般從哪裡啟動就會把環境變量env放到哪裡。而各種媒體存取操作env的方法都是不一樣的。是以uboot支援了各種不同媒體中env的操作方法。是以有好多個env_xx開頭的c檔案。實際使用的是哪一個要根據自己開發闆使用的存儲媒體來定(這些env_xx.c同時隻有1個會起作用,其他是不能進去的,通過x210_sd.h中配置的宏來決定誰被包含的),對于x210來說,我們應該看env_movi.c中的函數。

(3)經過基本分析,這個函數隻是對記憶體裡維護的那一份uboot的env做了基本的初始化或者說是判定(判定裡面有沒有能用的環境變量)。目前因為我們還沒進行環境變量從SD卡到DDR中的relocate,是以目前環境變量是不能用的。

(4)在start_armboot函數中(776行)調用env_relocate才進行環境變量從SD卡中到DDR中的重定位。重定位之後需要環境變量時才可以從DDR中去取,重定位之前如果要使用環境變量隻能從SD卡中去讀取。

5.5 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);
}
           

(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的值作為波特率。從這可以看出:環境變量的優先級是很高的。

5.6 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函數其實什麼都沒做。因為在彙編階段序列槽已經被初始化過了,是以這裡就不再進行硬體寄存器的初始化了。

5.7 console_init_f

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);
}
           

(1)console_init_f是console(控制台)的第一階段初始化。_f表示是第一階段初始化,_r表示第二階段初始化。有時候初始化函數不能一次一起完成,中間必須要夾雜一些代碼,是以将完整的一個子產品的初始化分成了2個階段。(我們的uboot中start_armboot的826行進行了console_init_r的初始化)

(2)console_init_f在uboot/common/console.c中,僅僅是對gd->have_console設定為1而已,其他事情都沒做。

5.8 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

	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中用一個宏定義來實作的。

5.9 print_cpuinfo

int print_cpuinfo(void)
{
	uint set_speed;
	uint tmp;
	uchar result_set;

#if defined(CONFIG_CLK_533_133_100_100)
	set_speed = 53300;
#elif defined(CONFIG_CLK_667_166_166_133)
	set_speed = 66700;
#elif defined(CONFIG_CLK_800_200_166_133)
	set_speed = 80000;
#elif defined(CONFIG_CLK_1000_200_166_133)
	set_speed = 100000;
#elif defined(CONFIG_CLK_1200_200_166_133)
	set_speed = 120000;
#else
	set_speed = 100000;
	printf("Any CONFIG_CLK_XXX is not enabled\n");
#endif
	tmp = (set_speed / (get_ARMCLK()/1000000));

	if((tmp < 105) && (tmp > 95)){
		result_set = 1;
	} else {
		result_set = 0;
	}

#ifdef CONFIG_MCP_SINGLE
	printf("\nCPU:  S5PV210@%ldMHz(%s)\n", get_ARMCLK()/1000000, ((result_set == 1) ? "OK" : "FAIL"));
#else
	printf("\nCPU:  S5PC110@%ldMHz(%s)\n", get_ARMCLK()/1000000, ((result_set == 1) ? "OK" : "FAIL"));
#endif
	printf("        APLL = %ldMHz, HclkMsys = %ldMHz, PclkMsys = %ldMHz\n",
			get_FCLK()/1000000, get_HCLK()/1000000, get_PCLK()/1000000);
#if 1
	printf("	MPLL = %ldMHz, EPLL = %ldMHz\n",
			get_MPLL_CLK()/1000000, get_PLLCLK(EPLL)/1000000);
	printf("		       HclkDsys = %ldMHz, PclkDsys = %ldMHz\n",
			get_HCLKD()/1000000, get_PCLKD()/1000000);
	printf("		       HclkPsys = %ldMHz, PclkPsys = %ldMHz\n",
			get_HCLKP()/1000000, get_PCLKP()/1000000);
	printf("		       SCLKA2M  = %ldMHz\n", get_SCLKA2M()/1000000);
#endif
	puts("Serial = CLKUART ");

	return 0;
}
           

(1)uboot啟動過程中:

CPU: [email protected](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列印出來的。

5.10 checkboard

(1)checkboard看名字是檢查、确認開發闆的意思。這個函數的作用就是檢查目前開發闆是哪個開發闆并且列印出開發闆的名字。

5.11 init_func_i2c

(1)這個函數實際沒有被執行,X210的uboot中并沒有使用I2C。如果将來我們的開發闆要擴充I2C來接外接硬體,則在x210_sd.h中配置相應的宏即可開啟。

5.12 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);
}
           

(1)dram_init看名字是關于DDR的初始化。疑問:在彙編階段已經初始化過DDR了否則也無法relocate到第二部分運作,怎麼在這裡又初始化DDR?

(2)dram_init都是在給gd->bd裡面關于DDR配置部分的全局變量指派,讓gd->bd資料記錄下目前開發闆的DDR的配置資訊,以便uboot中使用記憶體。

(3)從代碼來看,其實就是初始化gd->bd->bi_dram這個結構體數組。

5.13 display_dram_config

static int display_dram_config (void)
{
	int i;

#ifdef DEBUG
	puts ("RAM Configuration:\n");

	for(i=0; i<CONFIG_NR_DRAM_BANKS; i++) {
		printf ("Bank #%d: %08lx ", i, gd->bd->bi_dram[i].start);
		print_size (gd->bd->bi_dram[i].size, "\n");
	}
#else
	ulong size = 0;

	for (i=0; i<CONFIG_NR_DRAM_BANKS; i++) {
		size += gd->bd->bi_dram[i].size;
	}

	puts("DRAM:    ");
	print_size(size, "\n");
#endif

	return (0);
}
           

(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

5.14 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總容量。

6. CFG_NO_FLASH

#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 */
           

(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實際沒用之外也沒有别的影響)

CONFIG_VFD和CONFIG_LCD是顯示相關的,這個是uboot中自帶的LCD顯示的軟體架構。但是實際上我們用LCD而沒有使用uboot中設定的這套軟體架構,我們自己在後面自己添加了一個LCD顯示的部分。

7. mem_malloc_init

static void mem_malloc_init (ulong dest_addr)
{
	mem_malloc_start = dest_addr;
	mem_malloc_end = dest_addr + CFG_MALLOC_LEN;
	mem_malloc_brk = mem_malloc_start;

	memset ((void *) mem_malloc_start, 0,
			mem_malloc_end - mem_malloc_start);
}
           

(1)mem_malloc_init函數用來初始化uboot的堆管理器。

(2)uboot中自己維護了一段堆記憶體,肯定自己就有一套代碼來管理這個堆記憶體。有了這些東西uboot中你也可以malloc、free這套機制來申請記憶體和釋放記憶體。我們在DDR記憶體中給堆預留了896KB的記憶體。

8.開發闆獨有初始化:mmc初始化

#if defined(CONFIG_SMDKC110)

	#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_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_SMDKC110 */
           

(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控制器。這裡面分層很多,分層的思想一定要有,否則完全就糊塗了。

9. env_relocate

void env_relocate (void)
{
	DEBUGF ("%s[%d] offset = 0x%lx\n", __FUNCTION__,__LINE__,
		gd->reloc_off);

#ifdef CONFIG_AMIGAONEG3SE
	enable_nvram();
#endif

#ifdef ENV_IS_EMBEDDED
	/*
	 * The environment buffer is embedded with the text segment,
	 * just relocate the environment pointer
	 */
	env_ptr = (env_t *)((ulong)env_ptr + gd->reloc_off);
	DEBUGF ("%s[%d] embedded ENV at %p\n", __FUNCTION__,__LINE__,env_ptr);
#else
	/*
	 * We must allocate a buffer for the environment
	 */
	env_ptr = (env_t *)malloc (CFG_ENV_SIZE);
	DEBUGF ("%s[%d] malloced ENV at %p\n", __FUNCTION__,__LINE__,env_ptr);
#endif

	if (gd->env_valid == 0) {
#if defined(CONFIG_GTH)	|| defined(CFG_ENV_IS_NOWHERE)	/* Environment not changable */
		puts ("Using default environment\n\n");
#else
		puts ("*** Warning - bad CRC, using default environment\n\n");
		show_boot_progress (-60);
#endif
		set_default_env();
	}
	else {
		env_relocate_spec ();
	}
	gd->env_addr = (ulong)&(env_ptr->data);

#ifdef CONFIG_AMIGAONEG3SE
	disable_nvram();
#endif
}
           

(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完成的。

10. 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)。這兩種類型可以互相轉換。

11. devices_init

(1)devices_init看名字就是裝置的初始化。這裡的裝置指的就是開發闆上的硬體裝置。放在這裡初始化的裝置都是驅動裝置,這個函數本來就是從驅動架構中衍生出來的。uboot中很多裝置的驅動是直接移植linux核心的(譬如網卡、SD卡),linux核心中的驅動都有相應的裝置初始化函數。linux核心在啟動過程中就有一個devices_init(名字不一定完全對,但是差不多),作用就是集中執行各種硬體驅動的init函數。

(2)uboot的這個函數其實就是從linux核心中移植過來的,它的作用也是去執行所有的從linux核心中繼承來的那些硬體驅動的初始化函數。

12. jumptable_init

(1)jumptable跳轉表,本身是一個函數指針數組,裡面記錄了很多函數的函數名。看這陣勢是要實作一個函數指針到具體函數的映射關系,将來通過跳轉表中的函數指針就可以執行具體的函數。這個其實就是在用C語言實作面向對象程式設計。在linux核心中有很多這種技巧。

(2)通過分析發現跳轉表隻是被指派從未被引用,是以跳轉表在uboot中根本就沒使用。

13. 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不能實作的東西)。

14. enable_interrupts

(1)看名字應該是中斷初始化代碼。這裡指的是CPSR中總中斷标志位的使能。

(2)因為我們uboot中沒有使用中斷,是以沒有定義CONFIG_USE_IRQ宏,是以我們這裡這個函數是個空殼子。

(3)uboot中經常出現一種情況就是根據一個宏是否定義了來條件編譯決定是否調用一個函數内部的代碼。uboot中有2種解決方案來處理這種情況:方案一:在調用函數處使用條件編譯,然後函數體實際完全提供代碼。方案二:在調用函數處直接調用,然後在函數體處提供2個函數體,一個是有實體的一個是空殼子,用宏定義條件編譯來決定實際編譯時編譯哪個函數進去。

15. loadaddr、bootfile兩個環境變量

(1)這兩個環境變量都是核心啟動有關的,在啟動linux核心時會參考這兩個環境變量的值。

16. board_late_init

(1)看名字這個函數就是開發闆級别的一些初始化裡比較晚的了,就是晚期初始化。是以晚期就是前面該初始化的都初始化過了,剩下的一些必須放在後面初始化的就在這裡了。側面說明了開發闆級别的硬體軟體初始化告一段落了。

(2)對于X210來說,這個函數是空的。

17. eth_initialize

(1)看名字應該是網卡相關的初始化。這裡不是SoC與網卡晶片連接配接時SoC這邊的初始化,而是網卡晶片本身的一些初始化。

(2)對于X210(DM9000)來說,這個函數是空的。X210的網卡初始化在board_init函數中,網卡晶片的初始化在驅動中。

18. x210_preboot_init(LCD和logo顯示)

(1)x210開發闆在啟動起來之前的一些初始化,以及LCD螢幕上的logo顯示。

19. 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卡進行系統燒錄部署。

20. 死循環

(1)解析器

(2)開機倒數自動執行

(3)指令補全

21. uboot啟動2階段總結

21.1啟動流程回顧、重點函數标出

(1)第二階段主要是對開發闆級别的硬體、軟體資料結構進行初始化。

(2)

init_sequence

cpu_init 空的

board_init 網卡、機器碼、記憶體傳參位址

dm9000_pre_init 網卡

gd->bd->bi_arch_number 機器碼

gd->bd->bi_boot_params 記憶體傳參位址

interrupt_init 定時器

env_init

init_baudrate gd資料結構中波特率

serial_init 空的

console_init_f 空的

display_banner 列印啟動資訊

print_cpuinfo 列印CPU時鐘設定資訊

checkboard 檢驗開發闆名字

dram_init gd資料結構中DDR資訊

display_dram_config 列印DDR配置資訊表

mem_malloc_init 初始化uboot自己維護的堆管理器的記憶體

mmc_initialize inand/SD卡的SoC控制器和卡的初始化

env_relocate 環境變量重定位

gd->bd->bi_ip_addr gd資料結構指派

gd->bd->bi_enetaddr gd資料結構指派

devices_init 空的

jumptable_init 不用關注的

console_init_r 真正的控制台初始化

enable_interrupts 空的

loadaddr、bootfile 環境變量讀出初始化全局變量

board_late_init 空的

eth_initialize 空的

x210_preboot_init LCD初始化和顯示logo

check_menu_update_from_sd 檢查自動更新

main_loop 主循環

21.2啟動過程特征總結

(1)第一階段為彙編階段、第二階段為C階段

(2)第一階段在SRAM中、第二階段在DRAM中

(3)第一階段注重SoC内部、第二階段注重SoC外部Board内部

21.3移植時的注意點

(1)x210_sd.h頭檔案中的宏定義

(2)特定硬體的初始化函數位置(譬如網卡)

繼續閱讀