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" 这个变量, 如果找到就用环境变量配置的值, 如果没有找到就用 default_environment[]里的默认值: 115200
/* * 使用给定的 波特率 初始化串口. 始终设置为 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函数