本想直接学习驱动的,结果发现对于驱动的启动有很多困惑,包括,uboot怎么编译生成,怎么启动,怎么加载内核,内核是怎么把驱动放进去的。因此有了下面的uboot系列笔记,用于记录自己的解惑过程
这篇笔记包括如下内容:
第一部分是,u-boot.bin是怎么生成的。
第二部分是,u-boot.bin需要修改哪些关键代码才能支撑一个新平台。
第三部分是,解析源码看看,整个启动过程
本文档里面使用的源码,来自于友善之臂对mini2440的移植,u-boot-2010-03。
本节内容:
要想查看一个工程的所有Makefile文件,对于初次接触者来说,似乎太过困难。一般的方法是通过阅读工程提供的说明性文档,或者,前人的分析文档,获得。
此处使用Makefile的调试手段,查看u-boot.bin的生成过程。
进入工程目录,使用如下的命令:
make -n --debug=basic
上面的命令,会将目标的依赖以及执行的命令,打印出来。但是并不会执行真正的命令。
这些make选项,请参考前面,关于makefile的系列文章。
在未来将会有Android 系统的分析,其中对makefile的分析会更加详细,也会引入make的调试工具,用来确定,具体的make流程。
不过对于u-boot的make,使用上面的命令即可。
当运行完上面的命令,会在终端得到想要的结果。查看时倒着来。举例如下:
Prerequisite 'u-boot' is newer than target 'u-boot.srec'.
Must remake target 'u-boot.srec'.
arm-linux-objcopy -O srec u-boot u-boot.srec
Successfully remade target file 'u-boot.srec'.
Prerequisite 'u-boot' is newer than target 'u-boot.bin'.
Must remake target 'u-boot.bin'.
arm-linux-objcopy --gap-fill=0xff -O binary u-boot u-boot.bin
Successfully remade target file 'u-boot.bin'.
Prerequisite 'u-boot' is newer than target 'System.map'.
Must remake target 'System.map'.
arm-linux-nm u-boot | grep -v '\(compiled\)\|\(\.o$\)\|\( [aUw] \)\|\(\.\.ng$\)\|\(LASH[RL]DI\)' | LC_ALL=C sort > System.map
Successfully remade target file 'System.map'.
上面打印了依赖u-boot的目标,有:u-boot.srec,u-boot.bin,System.map.
可以得出结论1:u-boot.bin由u-boot生成,生成的命令为:arm-linux-objcopy --gap-fill=0xff -O binary u-boot u-boot.bin
接下来查找u-boot的生成,继续倒着看。
原文如下:
Must remake target 'u-boot'.
UNDEF_SYM=
...因为太长此处省略
...
... -lgcc -Map u-boot.map -o u-boot
Successfully remade target file 'u-boot'.
可以得出如下结论:
结论2:u-boot由很多的.a文件以及u-boot.lds文件生成,生成的命令使用arm-linux-ld,具体见终端输出。(因为太长,省略不写)
接下来查看u-boot.lds文件和各个.a文件的生成。
u-boot.lds是直接由源码提供的,编译连接脚本,定义u-boot.bin的最终内存布局。lds脚本的写法,可以参考前面armlink系列文章。
对于各个.a文件的生成,方法同上,使用
make -n选项
终端输出中,会发现如下原文:
for dir in tools examples/standalone examples/api cpu/arm920t /media/wanbiao/disk1t/androidstudioproject/u-boot-2010.03/cpu/arm920t/ ; do \
make -C $dir _depend ; done
原来会在上面的目录中,遍历其下的makefile。对于目录遍历的标准写法,以及注意事项。可以参考,前面的Makefile系列文章第二篇里面的:合理使用伪目标,提高编译效率。
至此,可以知道u-boot.bin的生成过程为:
对于上面每个步骤使用的命令,如arm-linux-gcc,arm-linux-ld,arm-linux-objcopy.不作介绍,因为它的用法跟,gcc,ld,objcopy几乎类似。而这些命令的使用,可直接google到。
至于编译过程中,生成的其他文件,此处不是我需要关心的地方。我的目标在于u-boot.bin如何生成的。现在知道了u-boot.bin的生成过程了。接下来是定位,各个.a文件,由那些源文件(.c,.S)生成。
对于uboot源代码来说,里面包含了各种硬件平台的源码,那么,怎么才能知道,哪些源文件被编译使用了呢?正常情况下,直接在上面列出的目录下,找到各自的Makefile文件,Readme文件,以及一些其他说明文件。但是这样往往需要花费大量的时间和精力。
为了节省时间和精力,这就需要根据目的来判断了,依据目的来找到为这些目的服务的源文件,然后进行分析。剩下的源文件,几乎可以使用同样的方法来进行分析。最后在将各个目的结合起来,几乎就是整个工程的源码文件了。
此处我的目的包括,u-boot是怎么启动?那么就以此为目的,找到对应的源文件有哪些。而uboot的启动却跟硬件平台相关,全部浏览完一个平台所有硬件,几乎也是耗时耗力的。同样的我再次引入一个目的为切入点,来查找对应的源代码。这个目的就是——将开发板提供的uboot源码,自己尝试移植一下。
移植之前先分析别人移植好的代码,然后尝试自己移植。
常见的方法是:
这里,我假定,没有任何文档可供参考,此时,该如何,知道这些源文件在哪里?
我将要使用的方法:
现在,从u-boot的生成来看看有没有什么引起注意的关键字。u-boot的生成如下。
arm-linux-ld -Bstatic -T u-boot.lds -Ttext 0x33F80000 $UNDEF_SYM cpu/arm920t/start.o --start-group \
lib_generic/libgeneric.a \
lib_generic/lzma/liblzma.a \
lib_generic/lzo/liblzo.a \
cpu/arm920t/libarm920t.a \
cpu/arm920t/s3c24x0/libs3c24x0.a \
lib_arm/libarm.a \
fs/cramfs/libcramfs.a \
fs/fat/libfat.a \
fs/fdos/libfdos.a \
fs/jffs2/libjffs2.a \
fs/reiserfs/libreiserfs.a \
fs/ext2/libext2fs.a \
fs/yaffs2/libyaffs2.a \
fs/ubifs/libubifs.a \
net/libnet.a \
disk/libdisk.a \
drivers/bios_emulator/libatibiosemu.a \
drivers/block/libblock.a \
drivers/dma/libdma.a \
drivers/fpga/libfpga.a \
drivers/gpio/libgpio.a \
drivers/hwmon/libhwmon.a \
drivers/i2c/libi2c.a \
drivers/input/libinput.a \
drivers/misc/libmisc.a \
drivers/mmc/libmmc.a \
drivers/mtd/libmtd.a \
drivers/mtd/nand/libnand.a \
drivers/mtd/onenand/libonenand.a \
drivers/mtd/ubi/libubi.a \
drivers/mtd/spi/libspi_flash.a \
drivers/net/libnet.a \
drivers/net/phy/libphy.a \
drivers/pci/libpci.a \
drivers/pcmcia/libpcmcia.a \
drivers/power/libpower.a \
drivers/spi/libspi.a \
drivers/rtc/librtc.a \
drivers/serial/libserial.a \
drivers/twserial/libtws.a \
drivers/usb/gadget/libusb_gadget.a \
drivers/usb/host/libusb_host.a \
drivers/usb/musb/libusb_musb.a \
drivers/usb/slave/libusb_slave.a \
drivers/usb/phy/libusb_phy.a \
drivers/video/libvideo.a \
drivers/watchdog/libwatchdog.a \
common/libcommon.a \
libfdt/libfdt.a \
api/libapi.a \
post/libpost.a \
board/embedclub/smdk2440a/libsmdk2440a.a \
--end-group -L /home/wanbiao/arm-tools/gcc-3.4.5-glibc-2.3.6/arm-linux/bin/../lib/gcc/arm-linux/3.4.5 -lgcc -Map u-boot.map -o u-boot
从上面的关键字,几乎可以肯定的是:start.o,libarm920t.a,libs3c24x0.a,libsmdk2440a.a这四个文件有关。看到此处,真是太好了,原来移植一个u-boot只需要生成四个不同的文件即可。
这个只是从关键字来推测关键代码,那么具体是不是这四个文件,需要进一步确认。接下来使用方法二中提到u-boot.map文件,进行推测
u-boot.map文件的简单说明:
我们要关注的是内存映射部分,它包括.text,.rodata,.data,.bss节。
u-boot是soc加载的第一个代码。那么芯片从哪个地址开始加载呢?
我选用的平台是友善之臂mini2440。cpu为三星s3c2440,根据前面的arm汇编系列文章可知。它从0x00000000开始运行。
在开始地址处的前面的一部分文件都应属于关键文件。到底这个“一部分”该如何界定,也没有明文规定,此处再次查看有意义的名字。只要我们认为是有意义的,就可以认定为关键文件。
那么查看内存映射时,就直接从0x00000000开始查看即可。原文如下:
Linker script and memory map
0x00000000 . = 0x0
0x00000000 . = ALIGN (0x4)
.text 0x33f80000 0x304c8
cpu/arm920t/start.o(.text)
.text 0x33f80000 0x500 cpu/arm920t/start.o
0x33f80050 IRQ_STACK_START
0x33f80048 _bss_start
0x33f8004c _bss_end
0x33f80044 _armboot_start
0x33f80000 _start
0x33f80054 FIQ_STACK_START
board/embedclub/smdk2440a/lowlevel_init.o(.text)
.text 0x33f80500 0x64 board/embedclub/smdk2440a/lowlevel_init.o
0x33f80504 lowlevel_init
board/embedclub/smdk2440a/nand_read.o(.text)
.text 0x33f80564 0x360 board/embedclub/smdk2440a/nand_read.o
0x33f80658 nand_read_ll
从上面可知,关键文件有三:
接下来确定,上面三个关键文件被打包成哪一个归档文件。
进入board/embedclub/smdk2440a/文件夹,查看底下的Makefile文件。部分原文如下:
LIB = $(obj)lib$(BOARD).a
#COBJS := sbc2410x.o flash.o
COBJS := nand_read.o smdk2440a.o flash.o
SOBJS := lowlevel_init.o
SRCS := $(SOBJS:.o=.S) $(COBJS:.o=.c)
OBJS := $(addprefix $(obj),$(COBJS))
SOBJS := $(addprefix $(obj),$(SOBJS))
$(LIB): $(obj).depend $(OBJS) $(SOBJS)
$(AR) $(ARFLAGS) $@ $(OBJS) $(SOBJS)
上面可知,COBJS代表c语言写的,SOBJS代表汇编语言写的。将其加上前缀,然后赋值给OBJS和SOBJS。最后依OBJS,SOBJS代表的源文件编译生成$(LIB).而此处,$(LIB)的值就为:libsmdk2440a.a
到此,再次,确认了libsmdk2440a.a为关键文件。
那么,到此时可以这样认为,关键文件为四个,分别为:
注意,此处的start.o是直接参与编译的,并没有生成任何包之后,再参与编译。
接下来开始定位,这些文件分别由什么源文件生成。
依然使用makefile的debug选项。查看具体的依赖,选项如下:
make -n --debug=basic
对于start.o,debug信息如下:
Must remake target 'start.o'.
arm-linux-gcc .... -o start.o start.S -c
Successfully remade target file 'start.o'.
哦原来是,start.S文件。
接下来确定libarm920t.a由谁生成。
debug信息如下
Must remake target 'cpu.o'.
arm-linux-gcc ... -o cpu.o cpu.c -c
Successfully remade target file 'cpu.o'.
...
Must remake target 'interrupts.o'.
arm-linux-gcc ... -o interrupts.o interrupts.c -c
Successfully remade target file 'interrupts.o'.
Must remake target 'libarm920t.a'.
arm-linux-ar crv libarm920t.a cpu.o interrupts.o
Successfully remade target file 'libarm920t.a'.
原来由:cpu.o interrupts.o生成,而他们分别由cpu.c和interrupts.c生成。
接下来确定libs3c24x0.a,debug信息如下:
...
Must remake target 'libs3c24x0.a'.
arm-linux-ar crv libs3c24x0.a interrupts.o speed.o timer.o usb.o usb_ohci.o mmc.o
Successfully remade target file 'libs3c24x0.a'.
libs3c24x0.a由,interrupts.o speed.o timer.o usb.o usb_ohci.o mmc.o生成,而这些.o文件分别由interrupts.c speed.c timer.c usb.c usb_ohci.c mmc.c文件生成。
接下来确定libsmdk2440a.a,根据debug信息,可以得出由下面的源文件生成:
lowlevel_init.S,flash.c,smdk2440a.c,nand_read.c。
总结如下:
现在,我们可以大胆假设,如果需要做移植,只需要修改上面列出的文件即可。总共需要13个文件。至于是不是这13个文件,那么改改试一下,就知道了。
接下来就是从官网上面下载一份uboot源码,然后进行移植了。为了降低难度,(主要是为了弄懂uboot怎么编译生成,怎么启动,怎么加载内核,并不是为了移植u-boot),选择u-boot-2010-03源码,进行移植,让其能够在mini2440开发板上面跑起来。