uboot学习之四-----uboot启动第二阶段--start_armboot函数
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)命令补全