最近在看U-boot的源码,看得是昏天暗地,只为一心想知道命令行输入nand write.uboot 这命令后,程序内部的走向,下面是我所了解的,还有许多不明白的地方,希望看官们指点!
1、顶层Makefile文件
为了看懂Makefile,自己找了一些基础教程,这个(http://wiki.ubuntu.org.cn/%E8%B7%9F%E6%88%91%E4%B8%80%E8%B5%B7%E5%86%99Makefile:%E4%B9%A6%E5%86%99%E8%A7%84%E5%88%99)介绍的比较多,但也不是特别全面。
贴上Makefile 里的内容:
forlinx_nand_ram256_config : unconfig
@$(MKCONFIG) smdk6410 arm s3c64xx smdk6410 samsung s3c6410 NAND ram256
从最开始的配置,便是配置哪一块开发板,我用的是6410这个,在linux的Uboot目录下,敲入命令行"make forlinx_nand_ram256_config "
此时,make就会以forlinx_nand_ram256_config 做为目标,然后寻找依赖,我们看到依赖是unconfig,在Makefile里找到unconfig的内容:
unconfig:
@rm -f $(obj)include/config.h $(obj)include/config.mk \
$(obj)board/*/config.tmp $(obj)board/*/*/config.tmp
可以看到unconfig里只是做了删除操作——rm ,变量$(obj)此时是空的。通过这个unconfig,我们也可以猜想到
@$(MKCONFIG) smdk6410 arm s3c64xx smdk6410 samsung s3c6410 NAND ram256
这条命令多半是跟删除的文件有莫大的联系。@$(MKCONFIG) 是执行MKCONFIG这个shell脚本,而后面的smdk6410 arm s3c64xx smdk6410 samsung s3c6410 NAND ram256将作为脚本参数传入!
找到uboot目录下面的MKCONFIG,它大概做了①Create link to architecture specific headers 创建指定头文件的结构链接,大概是整改了uboot/include/asm 里的文件
if [ "$SRCTREE" != "$OBJTREE" ] ; then
mkdir -p ${OBJTREE}/include
mkdir -p ${OBJTREE}/include2
cd ${OBJTREE}/include2
rm -f asm
ln -s ${SRCTREE}/include/asm-$2 asm
LNPREFIX="../../include2/asm/"
cd ../include
rm -rf asm-$2
rm -f asm
mkdir asm-$2
ln -s asm-$2 asm
else
cd ./include
rm -f asm #the rm is the past
ln -s asm-$2 asm
fi
rm -f asm-$2/arch
if [ -z "$6" -o "$6" = "NULL" ] ; then
ln -s ${LNPREFIX}arch-$3 asm-$2/arch
else
ln -s ${LNPREFIX}arch-$6 asm-$2/arch
fi
后面的代码也类似,都是根据传入的参数进行创建板级相关的文件,
echo "ARCH = $2" > config.mk #cd ./include
echo "CPU = $3" >> config.mk
echo "BOARD = $4" >> config.mk
#
# Create board specific header file
#
if [ "$APPEND" = "yes" ] # Append to existing config file
then
echo >> config.h
else
> config.h # Create new config file #这里>表示追加,>>表示清空原文件内容,然后写数据。
fi
echo "TEXT_BASE = 0xC7E00000" >> ../board/samsung/smdk6410/config.mk#这里生成了config.mk的文件,里面放置了TEXT_BASE的地址,即Uboot代码段的基地址。
2、启动Start.S(参考:http://www.crifan.com/files/doc/docbook/uboot_starts_analysis/release/htmls/index.html)
启动代码主要进行了映射地址的设置
bl lowlevel_init/* go setup pll,mux,memory */
跳转到 lowlevel_init 执行时钟、uart、nand、ddr等初始化,这些初始化的所需参数都是有数据手册查阅得来的。
接着Start.S要开始搬运程序到内存。注意,第一条指令的执行是硬件自动将程序从nand里拷贝到SDRAM中去的,这个叫做steppingstone,拷贝的程序大小为8K,而uboot显然大于8K,故程序需要在前面初始化完ddr后,将代码从nandflash里复制到ddr中,这里便是复制到ddr中的0xce700000去,即text_base。另外,在u_boot前面8K的程序,所用都是相对地址的代码,用的是BL之类的指令,是在当前地址处加上偏移值而得到的,若用mov pc ,#立即数 这个代码,这是绝对地址代码,此时要跳转的那个位置还没有复制代码过去,故程序就飞了!
接下来设置栈指针,为后面的C运行搭建环境
stack_setup:
#ifdef CONFIG_MEMORY_UPPER_CODE
ldr sp, =(CFG_UBOOT_BASE + CFG_UBOOT_SIZE - 0xc)
#else
ldr r0, _TEXT_BASE/* upper 128 KiB: relocated uboot */
sub r0, r0, #CFG_MALLOC_LEN/* malloc area */
sub r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo */
#ifdef CONFIG_USE_IRQ
sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
#endif
subsp, r0, #12/* leave 3 words for abort-stack */
#endif
初始化_bss段,这个是一个全局为初始化的变量段,在程序中是没有占用空间的,但是在复制到内存的时候则要进行初始化为零。
clear_bss:
ldr r0, _bss_start/* find start of bss segment */
ldr r1, _bss_end/* stop here */
mov r2, #0x00000000/* clear */
clbss_l:
str r2, [r0]/* clear loop... */
add r0, r0, #4
cmp r0, r1
ble clbss_l
ldr pc, _start_armboot
其中_bss_start 与__bss_end 是从链接脚本里导入的
extern ulong _bss_start; /* code + data end == BSS start */
extern ulong _bss_end; /* BSS end */
用一全局变量声明即可用。
最后跳转到_start_armboot执行C程序
3、环境变量名与值
这个是纠结我最久的一个问题,如果你愿意一层一层一层一层的剥开它的心,你会发现,它还有一层。。。直接泪奔。还是总结下所了解到的吧,虽然还不是全面了解了:
char *getenv (char *name)
首先是char *getenv (char *name)这个函数,输入环境变量名,返回相应环境变量的值。该函数里调用了用外层for 循环一次次调用env_get_char()期望获得环境变量列表每一行的首地址,(static uchar default_environment[] 是默认环境变量列表,里面全是字符串,用‘\0’显示的隔开,但每一个环境变量的长度不一致) 又调用了static int envmatch (uchar *s1, int i2) 进行环境变量的match。若匹配到了,则返回变量名“=”号后一个字符串的地址,调用env_get_addr()最终得到返回的值。一层一层找下去,发现关键在于下面这个函数
uchar env_get_char_spec (int index)
{
return ( *((uchar *)(gd->env_addr + index)) );
}
gd是个全局变量,有这个宏定义定义的DECLARE_GLOBAL_DATA_PTR;
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
而gd->env_addr 经过层层指向,最终还是指到了default_environment[] 这个命令列表。全局变量的指定可能会在某些初始化函数里面,注意!
贴上两个default_environment的内容。
static uchar default_environment[] = {
#if defined(CONFIG_BOOTARGS)
"bootargs=" CONFIG_BOOTARGS "\0"
#endif
#if defined(CONFIG_BOOTCOMMAND)
"bootcmd=" CONFIG_BOOTCOMMAND "\0"
#endif}
4、命令的搜索与执行
main_loop()是主循环,人机交互都在这里了。
s = getenv ("bootcmd");这里获取了bootcmd的值#define CONFIG_BOOTCOMMAND"nand read 0xc0008000 0x200000 0x500000;bootm 0xc0008000"
parse_string_outer(s, FLAG_PARSE_SEMICOLON | FLAG_EXIT_FROM_LOOP);
由static int parse_string_outer(const char *s, int flag)进行分析执行。调用setup_string_in_str(&input, s); 将s 放进input里,调用parse_stream_outer(&input, flag);进行下一步的分析。过程很复杂,还没看得很透彻,但是大概的过程是将命令行解析出参数及命令。(cmdtp = find_cmd(child->argv[i]))查找命令, 然后调用rcode = (cmdtp->cmd)来执行。这里再讲下查找的方式。
命令是放在common文件夹下的,每一个设备的命令对应一个cmd.c文件例如cmd_nand.c之类,这就是nand里的命令
U_BOOT_CMD(
nand, 5,1,do_nand,
"nand - legacy NAND sub-system\n",
"info - show available NAND devices\n"
"nand device [dev] - show or set current device\n"
"nand read[.jffs2[s]] addr off size\n"
"nand write[.jffs2] addr off size - read/write `size' bytes starting\n"
" at offset `off' to/from memory address `addr'\n"
"nand erase [clean] [off size] - erase `size' bytes from\n"
" offset `off' (entire device if not specified)\n"
"nand bad - show bad blocks\n"
"nand read.oob addr off size - read out-of-band data\n"
"nand write.oob addr off size - read out-of-band data\n"
"nand beep--burn ok to beep\n"
"nand led---burn ok to led light on and off \n"
);
由common.h头文件中的宏定义来声明这些命令表:
#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \
cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage, help}
这里的宏定义 "##"是链接字符串, "#"是将变量名变成字符串,nand中的U_BOOT_CMD()经如处理后会变成
cmd_tbl_t __u_boot_cmd_nand(“nand”,5, 1,do_nand
"nand - legacy NAND sub-system\n",
"info - show available NAND devices\n"
"nand device [dev] - show or set current device\n"
"nand read[.jffs2[s]] addr off size\n"
"nand write[.jffs2] addr off size - read/write `size' bytes starting\n"
" at offset `off' to/from memory address `addr'\n"
"nand erase [clean] [off size] - erase `size' bytes from\n"
" offset `off' (entire device if not specified)\n"
"nand bad - show bad blocks\n"
"nand read.oob addr off size - read out-of-band data\n"
"nand write.oob addr off size - read out-of-band data\n"
"nand beep--burn ok to beep\n"
"nand led---burn ok to led light on and off \n")
其中do_nand 就是对应的处理的函数。在那个宏定义里还有一个 Struct_Section ,这里是一个宏定义,原型是
#define Struct_Section __attribute__ ((unused,section (".u_boot_cmd")))
这里用了attribute的机制(attribute在预编译是作用,用来提醒编译器的,这是我的理解),unused 这里不是很懂,重要的是setction(".u_boot_cmd")将这些cmd_tbl_t u_boot_cmd_##name 的变量放在.u_boot_cmd段中,只是为了方便管理,然后链接脚本了指定了.u_boot_cmd段的地址,于是就可以像之前_bss段那样导入,然后进行寻址了。于是find_cmd 就是这么干的!
贴下find_cmd的部分代码:
cmdtp = &__u_boot_cmd_start;
cmdtp != &__u_boot_cmd_end;
cmdtp++) {
if (strncmp (cmd, cmdtp->name, len) == 0) {
if (len == strlen (cmdtp->name))
return cmdtp; /* full match */
}
不懂的地方:
1、nand write 与 nand write.uboot 的不同。就是这个问题导致我想看u-boot源码的,之前用tftp下载uboot到内存,用nand write 命令然后烧写到nandflash里,启动不了,最后找到原因就是我应该用nand write.uboot来烧写uboot,nand write 与 nand write.uboot的区别
nand write.uboot 执行的是
if(!read && s != NULL && (!strcmp(s, ".uboot")) && nand->writesize == 8192)
size=8192;
ret=nand_write(nand, off, &size, (u_char *)addr);
off+=8192;
addr+=2048;
ret=nand_write(nand, off, &size, (u_char *)addr);
off+=8192;
addr+=2048;
ret=nand_write(nand, off, &size, (u_char *)addr);
off+=8192;
addr+=2048;
ret=nand_write(nand, off, &size, (u_char *)addr);
off+=8192;
addr+=2048;
size=1024*1024-4*8192;
ret = nand_write(nand, off, &size, (u_char *)addr);
而nand write仅仅是
nand_write(nand, off, &size, (u_char *)addr);
其中 nand为 struct mtd_info类型(MTD(memory technology device内存技术设备)) 由前面的 ret = nand_dump(nand, off);赋值。这里不太理解,nand write.uboot 写成好多行的原因我可以理解为uboot大小大过nand的一个页,但是这里偏移off加了8192 而addr 只加了2048 这是为什么呢?