编译环境:Ubuntu9.10
交叉编译工具:arm-linux-gcc 4.4.3
u-boot版本号:2016.07
移植目标单板信息: JZ2440v2
CPU: S3C2440
NAND: K9F2G08U0C
NOR: MX29LV160DBTI
网卡:DM9000A
(1) 先创建临时目录tmp
mkdir tmp
(2) 将下载好的压缩包解压到tmp中
tar xzf arm-linux-gcc-4.4.3-20100728.tar.gz -C tmp/
解压完成后可以发现交叉编译工具在 tmp/opt/FriendlyARM/toolschain/4.4.3/bin 目录中
(3) 安装到/usr/local/中
cd /usr/local
mkdir arm
chmod 777 arm
sudo cp -a tmp/opt/FriendlyARM/toolschain/4.4.3/ /usr/local/arm/
(4) 修改环境变量
方法一:立即生效
echo $PATH
得到当前PATH值,如:
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/gcc_old_path
复制当前PATH环境变量,将老的交叉编译工具路径给删除,然后添加新的路径:
/usr/local/arm/4.4.3/bin
然后export
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/arm/4.4.3/bin
方法二:如果不想每次都手动修改PATH,可以编辑/etc/environment 这个方法需要重启后生效
sudo vi /etc/environment
将老路径去掉,加上新路径
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/arm/4.4.3/bin"
:wq //保存退出
(5) 查看版本信息
arm-linux-gcc -v
若输出版本号是4.4.3则表示安装成功
(1)下载
u-boot源码下载地址:ftp://ftp.denx.de/pub/u-boot/
选择u-boot-2016.07下载
(2)在虚拟机上解压
tar xjf u-boot-2016.07.tar.bz2
Top Makefile:
803: all: $(ALL-y) //<----------------------
732: ALL-y += u-boot.srec u-boot.bin u-boot.sym System.map u-boot.cfg binary_size_check
765: ALL-y += u-boot-tegra.bin u-boot-nodtb-tegra.bin
771: ALL-y += $(CONFIG_BUILD_TARGET:"%"=%)
可见执行make会生成u-boot.bin
Top Makefile:
821:ifeq ($(CONFIG_OF_SEPARATE),y)
822:u-boot-dtb.bin: u-boot-nodtb.bin dts/dt.dtb FORCE
823: $(call if_changed,cat)
824:
825:u-boot.bin: u-boot-dtb.bin FORCE
826: $(call if_changed,copy)
827:else //由于没有定义CONFIG_OF_SEPARATE选项,所以我们关心下面这条依赖关系
828:u-boot.bin: u-boot-nodtb.bin FORCE //<----------------------
829: $(call if_changed,copy) //if_changed函数检测依赖文件是否有更新或目标文件是否不存在,
//然后echo打印传入的参数,最后执行传入的命令copy,
//下面贴出 scripts/Kbuild.include文件中的部分内容,大家可以自行分析
830:endif
展开后: (若无目标或依赖文件更新)
u-boot.bin: u-boot-nodtb.bin FORCE
echo COPY $@; cp $< $@ //输出 COPY u-boot.bin, 然后执行cp u-boot-nodtb.bin u-boot.bin
Top Makefile
-> 862:u-boot-nodtb.bin: u-boot FORCE
863: $(call if_changed,objcopy) //objcopy
864: $(call DO_STATIC_RELA,$<,$@,$(CONFIG_SYS_TEXT_BASE))
865: $(BOARD_SIZE_CHECK)
1173:quiet_cmd_u-boot__ ?= LD $@
1174: cmd_u-boot__ ?= $(LD) $(LDFLAGS) $(LDFLAGS_u-boot) -o $@ \
1175: -T u-boot.lds $(u-boot-init) \
1176: --start-group $(u-boot-main) --end-group \
1177: $(PLATFORM_LIBS) -Map u-boot.map
-> 1186:u-boot: $(u-boot-init) $(u-boot-main) u-boot.lds FORCE
1187: $(call if_changed,u-boot__) //将u-boot-init、u-boot-main按照u-boot.lds进行链接
1188:ifeq ($(CONFIG_KALLSYMS),y)
1189: $(call cmd,smap)
1190: $(call cmd,u-boot__) common/system_map.o
1191:endif
-> 679:u-boot-init := $(head-y)
-> 680:u-boot-main := $(libs-y)
(6)head-y
在linux上搜索head-y这个目标:
grep "head-y" * -nR
得到下面这个结果:
arch/arm/Makefile:74:head-y := arch/arm/cpu/$(CPU)/start.o
所以head-y指的是start.S
(7)libs-y
在顶层目录Makefile中搜索libs-y可以发现其包含许多目录
比如:
631:libs-y += drivers/mtd/
另:
667:libs-y := $(patsubst %/, %/built-in.o, $(libs-y))
这条规则使libs-y中每个条目的最后一个斜杠替换成 /built-in.o,比如 drivers/mtd/ 会变为 drivers/mtd/built-in.o
可见libs-y最后指向各个文件夹的built-in.o
而这些built-in.o则由 Kbuild Makefile 将obj-y所包含的各个文件编译而成,
具体可研究 scripts/Kbuild.include 和 scripts/Makefile.build
(8)%config
一般我们在执行make之前都需要配置一下开发板参数,比如make smdk2410_config,在顶层目录Makefile有:
396:scripts_basic:
397: $(Q)$(MAKE) $(build)=scripts/basic //build 在 scripts/Kbuild.include:181:
//build := -f $(srctree)/scripts/Makefile.build obj,
//展开来就是:@make -f scripts/Makefile.build obj=scripts/basic
398: $(Q)rm -f .tmp_quiet_recordmcount
-> 476:%config: scripts_basic outputmakefile FORCE
477:$(Q)$(MAKE) $(build)=scripts/kconfig $@ //展开来就是:
//@make -f scripts/Makefile.build obj=scripts/kconfig %config
scripts/Makefile.build中:
12: prefix := spl
13: src := $(patsubst $(prefix)/%,%,$(obj)) //obj=scripts/kconfig, src := scripts/kconfig
57: kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src))
//kbuild-dir := $(srctree)/scripts/kconfig
58: kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile)
//由于没有scripts/kconfig/Kbuild这个文件,所以 kbuild-file := $(srctree)/scripts/kconfig/Makefile
59: include $(kbuild-file) //在这里将scripts/kconfig/Makefile 添加进来了
%config这个目标的生成规则在scripts/kconfig/Makefile中:
15: SRCARCH := ..
113:%_defconfig: $(obj)/conf
114: $(Q)$< $(silent) --defconfig=arch/$(SRCARCH)/configs/$@ $(Kconfig)
//执行:@scripts/kconfig/conf --defconfig=arch/../configs/%_defconfig Kconfig (没有进入arch目录)
//即: @scripts/kconfig/conf --defconfig=configs/%_defconfig Kconfig
115:
116:# Added for U-Boot (backward compatibility)
-> 117:%_config: %_defconfig
118: @:
分析到这里,我们可以看出配置单板相关的参数是在 scripts/kconfig/conf 中进行的,
传入的参数是 --defconfig=configs/%_defconfig Kconfig
可以推测conf会从configs目录下读取对应的defconfig文件进行解析然后保存参数,但我看了一下scripts/kconfig/conf.c 的代码,发现实在是过于复杂,恕博主不继续深入分析,如果以后有时间会再继续研究。
scripts/Kbuild.include:
7:squote := '
30:escsq = $(subst $(squote),'\$(squote)',$1)
217:echo-cmd = $(if $($(quiet)cmd_$(1)),\
218:echo ' $(call escsq,$($(quiet)cmd_$(1)))$(echo-why)';)
234:ifneq ($(KBUILD_NOCMDDEP),1)
235:# Check if both arguments has same arguments. Result is empty string if equal.
236:# User may override this check using make KBUILD_NOCMDDEP=1
237:arg-check = $(strip $(filter-out $(cmd_$(1)), $(cmd_$@)) \
238: $(filter-out $(cmd_$@), $(cmd_$(1))) )
239:else
240:arg-check = $(if $(strip $(cmd_$@)),,1)
241:endif
249:make-cmd = $(call escsq,$(subst \#,\\\#,$(subst $$,$$$$,$(cmd_$(1)))))
253:any-prereq = $(filter-out $(PHONY),$?) $(filter-out $(PHONY) $(wildcard $^),$^)
-> 257:if_changed = $(if $(strip $(any-prereq) $(arg-check)), \
258: @set -e; \
259: $(echo-cmd) $(cmd_$(1)); \
260: printf '%s\n' 'cmd_$@ := $(make-cmd)' > $(dot-target).cmd)
314:echo-why = $(call escsq, $(strip $(why)))
这里简单分析一下if_changed规则:
首先any-prereq, arg-check
目标是检测目标和依赖是否存在或有更新
set -e
发生错误后立即停止,此处是为了不让错误像滚雪球一样越来越多。
echo-cmd
由于是”安静模式”下进行编译的,所以这个参数会被展开成: echo ' quiet_cmd_cmd(1) ';
此处忽略why, 编译的时候也没出现why信息, 估计是被屏蔽了(KBUILD_VERBOSE不等于2)
所以259行可以展开成:
echo ' quiet_cmd_cmd(1) '; $(cmd_$(1));
即先打印信息,然后执行命令
大家分析命令的时候可以搜索 quiet_cmd_xxx, 还有cmd_xxx, “xxx”是调用call函数时传入的命令参数
依赖关系如下:
all -> u-boot.bin -> u-boot-nodtb.bin -> u-boot |-> u-boot-init -> head-y (start.o) -> start.S
|-> u-boot-main -> libs-y (built-in.o) -> obj-y ...
首先大家需要配置交叉编译选项,编辑顶层目录Makefile:
vi Makefile
查找一下CROSS_COMPILE
这个参数, 然后直接在下面添加:
ARCH = arm
CROSS_COMPILE = arm-linux-
博主使用的开发板是s3c2440,但是configs目录下没有smdk2440_defconfig这个文件,只有smdk2410_defconfig:
执行:make smdk2410_config
如果大家更新了交叉编译工具,应该能配置成功,当然现在u-boot也支持menuconfig了,所以大家可以直接执行:
make menuconfig
然后在menuconfig中进行配置
配置完之后就可以进行编译了,执行:
make
编译成功后会生成一个u-boot.bin,可以烧写到开发板上,不过一般是用不起来的,需要进一步修改
想要分析启动流程,第一步就是分析链接文件,弄清楚程序入口是哪里。在第三步分析Makefile中,u-boot的依赖中可以看到链接脚本文件是u-boot.lds,这个文件是通过编译生成的,为了方便,我们就不分析生成规则了,编译成功后在顶层目录就可以看到u-boot.lds
打开u-boot.lds:
6: . = 0x00000000;
7: . = ALIGN(4);
8: .text :
9: {
10: *(.__image_copy_start)
11: *(.vectors)
12: arch/arm/cpu/arm920t/start.o (.text*)
13: *(.text*)
14: }
很明显,程序的第一条指令在arch/arm/cpu/arm920t/start.S中,当然这是我配置smdk2410过后得到的u-boot.lds文件,不能一概而论
arch/arm/cpu/arm920t/start.S:
/*
* armboot - Startup Code for ARM920 CPU-core
*
* Copyright (c) 2001 Marius Gröger
* Copyright (c) 2002 Alex Züpke
* Copyright (c) 2002 Gary Jennejohn
*
* SPDX-License-Identifier: GPL-2.0+
*/
#include
#include
#include
/*
*************************************************************************
*
* Startup Code (called from the ARM reset exception vector)
*
* do important init only if we don't start from memory!
* relocate armboot to ram
* setup stack
* jump to second stage
*
*************************************************************************
*/
.globl reset
reset:
/*
* set the cpu to SVC32 mode 1. 设置为SVC模式
*/
mrs r0, cpsr
bic r0, r0, #0x1f
orr r0, r0, #0xd3
msr cpsr, r0
#if defined(CONFIG_AT91RM9200DK) || defined(CONFIG_AT91RM9200EK)
/*
* relocate exception table
*/
ldr r0, =_start
ldr r1, =0x0
mov r2, #16
copyex:
subs r2, r2, #1
ldr r3, [r0], #4
str r3, [r1], #4
bne copyex
#endif
#ifdef CONFIG_S3C24X0
/* turn off the watchdog */
# if defined(CONFIG_S3C2400)
# define pWTCON 0x15300000
# define INTMSK 0x14400008 /* Interrupt-Controller base addresses */
# define CLKDIVN 0x14800014 /* clock divisor register */
#else
# define pWTCON 0x53000000 /* 看门狗控制寄存器地址 */
# define INTMSK 0x4A000008 /* Interrupt-Controller base addresses */
# define INTSUBMSK 0x4A00001C
# define CLKDIVN 0x4C000014 /* clock divisor register */
# endif
ldr r0, =pWTCON //2. 关看门狗
mov r1, #0x0
str r1, [r0]
/*
* mask all IRQs by setting all bits in the INTMR - default
*/
mov r1, #0xffffffff //3. 屏蔽中断
ldr r0, =INTMSK
str r1, [r0]
# if defined(CONFIG_S3C2410)
ldr r1, =0x3ff
ldr r0, =INTSUBMSK
str r1, [r0]
# endif
/* FCLK:HCLK:PCLK = 1:2:4 */
/* default FCLK is 120 MHz ! */
ldr r0, =CLKDIVN //4. 设置分频系数,未设置时钟频率
mov r1, #3
str r1, [r0]
#endif /* CONFIG_S3C24X0 */
/*
* we do sys-critical inits only at reboot,
* not when booting from ram!
*/
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_crit //5. 跳到cpu_init_crit函数
#endif
bl _main //6. 进入arch/arm/lib/crt0.S的_main函数,进行其他初始化
/*------------------------------------------------------------------------------*/
.globl c_runtime_cpu_setup
c_runtime_cpu_setup:
mov pc, lr
/*
*************************************************************************
*
* CPU_init_critical registers
*
* setup important registers
* setup memory timing
*
*************************************************************************
*/
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
cpu_init_crit:
/*
* flush v4 I/D caches [1]. 清除cache
*/
mov r0, #0
mcr p15, 0, r0, c7, c7, 0 /* flush v3/v4 cache */
mcr p15, 0, r0, c8, c7, 0 /* flush v4 TLB */
/*
* disable MMU stuff and caches [2]. 禁止MMU
*/
mrc p15, 0, r0, c1, c0, 0
bic r0, r0, #0x00002300 @ clear bits 13, 9:8 (--V- --RS)
bic r0, r0, #0x00000087 @ clear bits 7, 2:0 (B--- -CAM)
orr r0, r0, #0x00000002 @ set bit 1 (A) Align
orr r0, r0, #0x00001000 @ set bit 12 (I) I-Cache
mcr p15, 0, r0, c1, c0, 0
#ifndef CONFIG_SKIP_LOWLEVEL_INIT_ONLY
/*
* before relocating, we have to setup RAM timing
* because memory timing is board-dependend, you will
* find a lowlevel_init.S in your board directory.
*/
mov ip, lr
bl lowlevel_init //[3]. 进入board/samsung/smdk2410/lowlevel_init.S执行,
// 设置内存控制寄存器时序参数
mov lr, ip
#endif
mov pc, lr
#endif /* CONFIG_SKIP_LOWLEVEL_INIT */
arch/arm/lib/crt0.S:(对部分注释进行了翻译)
/*
* crt0 - C-runtime startup Code for ARM U-Boot
*
* Copyright (c) 2012 Albert ARIBAUD
*
* SPDX-License-Identifier: GPL-2.0+
*/
#include
#include
#include
#ifdef CONFIG_CPU_V7M
#include
#endif
/*
* This file handles the target-independent stages of the U-Boot
* start-up where a C runtime environment is needed. Its entry point
* is _main and is branched into from the target's start.S file.
*
* _main execution sequence is:
*
* 1. Set up initial environment for calling board_init_f().
* This environment only provides a stack and a place to store
* the GD ('global data') structure, both located in some readily
* available RAM (SRAM, locked cache...). In this context, VARIABLE
* global data, initialized or not (BSS), are UNAVAILABLE; only
* CONSTANT initialized data are available. GD should be zeroed
* before board_init_f() is called.
*
* 1. _main函数首先设置栈,然后预留一定的内存空间给gd_t结构体并清零,用来存放全局参数,
* 然后调用board_init_f();
*
* 2. Call board_init_f(). This function prepares the hardware for
* execution from system RAM (DRAM, DDR...) As system RAM may not
* be available yet, , board_init_f() must use the current GD to
* store any data which must be passed on to later stages. These
* data include the relocation destination, the future stack, and
* the future GD location.
*
* 2. board_init_f()这个函数调用一系列函数来初始化硬件以使程序能够在SDRAM中执行,
* 且这个函数需要使用1.中设置好的gd_t结构体来存放初始化中的各个参数以备后用
* 比如:重定位地址,重定位后的栈地址、gd_t的地址
*
* 3. Set up intermediate environment where the stack and GD are the
* ones allocated by board_init_f() in system RAM, but BSS and
* initialized non-const data are still not available.
*
* 3. 由于BSS段还没初始化,board_init_f()设置gd_t时不能使用全局变量、静态变量等,
* 所以设置的环境参数不是最终的
*
* 4a.For U-Boot proper (not SPL), call relocate_code(). This function
* relocates U-Boot from its current location into the relocation
* destination computed by board_init_f().
*
* 4b.For SPL, board_init_f() just returns (to crt0). There is no
* code relocation in SPL.
*
* 4. smdk_2410没有定义SPL,所以会在_main()函数中根据board_init_f设置的重定位参数进行代码重定位。
*
* 5. Set up final environment for calling board_init_r(). This
* environment has BSS (initialized to 0), initialized non-const
* data (initialized to their intended value), and stack in system
* RAM (for SPL moving the stack and GD into RAM is optional - see
* CONFIG_SPL_STACK_R). GD has retained values set by board_init_f().
*
* 5. 重定位后,BSS和non-const data都被初始化了,在进入board_init_r函数之前应先设置最终的环境参数
*
* 6. For U-Boot proper (not SPL), some CPUs have some work left to do
* at this point regarding memory, so call c_runtime_cpu_setup.
*
* 7. Branch to board_init_r().
*
* For more information see 'Board Initialisation Flow in README.
*/
/*
* entry point of crt0 sequence
*/
ENTRY(_main)
/*
* Set up initial C runtime environment and call board_init_f(0).
*/
#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
ldr sp, =(CONFIG_SPL_STACK)
#else
ldr sp, =(CONFIG_SYS_INIT_SP_ADDR) //1. 设置栈
#endif
#if defined(CONFIG_CPU_V7M) /* v7M forbids using SP as BIC destination */
mov r3, sp
bic r3, r3, #7
mov sp, r3
#else
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
#endif
mov r0, sp
bl board_init_f_alloc_reserve //2. 为gd_t结构体保留空间
mov sp, r0
/* set up gd here, outside any C code */
mov r9, r0
bl board_init_f_init_reserve //3. 初始化gd_t(清零)
//gd_t的地址存在r9寄存器中,结构体中存放的是全局参数
mov r0, #0
bl board_init_f //4. 进入board_init_f进行各种初始化,分配SDRAM内存空间,填充进gd_t结构体中
#if ! defined(CONFIG_SPL_BUILD)
/*
* Set up intermediate environment (new sp and gd) and call
* relocate_code(addr_moni). Trick here is that we'll return
* 'here' but relocated.
*/
ldr sp, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp */
#if defined(CONFIG_CPU_V7M) /* v7M forbids using SP as BIC destination */
mov r3, sp
bic r3, r3, #7
mov sp, r3
#else
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
#endif
ldr r9, [r9, #GD_BD] /* r9 = gd->bd */
sub r9, r9, #GD_SIZE /* new GD is below bd */
//5. 将重定位后的GD地址放入r9中
adr lr, here
ldr r0, [r9, #GD_RELOC_OFF] /* r0 = gd->reloc_off */
add lr, lr, r0
#if defined(CONFIG_CPU_V7M)
orr lr, #1 /* As required by Thumb-only */
#endif
ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
b relocate_code //6. 重定位代码,地址是gd->relocaddr
here: //7. 在SDRAM中运行
/*
* now relocate vectors
*/
bl relocate_vectors //重定位中断向量表
/* Set up final (full) environment */
bl c_runtime_cpu_setup /* we still call old routine here */
#endif
#if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_FRAMEWORK)
# ifdef CONFIG_SPL_BUILD
/* Use a DRAM stack for the rest of SPL, if requested */
bl spl_relocate_stack_gd
cmp r0, #0
movne sp, r0
movne r9, r0
# endif
ldr r0, =__bss_start /* this is auto-relocated! */
//8. 清BSS
#ifdef CONFIG_USE_ARCH_MEMSET
ldr r3, =__bss_end /* this is auto-relocated! */
mov r1, #0x00000000 /* prepare zero to clear BSS */
subs r2, r3, r0 /* r2 = memset len */
bl memset
#else
ldr r1, =__bss_end /* this is auto-relocated! */
mov r2, #0x00000000 /* prepare zero to clear BSS */
clbss_l:cmp r0, r1 /* while not at end of BSS */
#if defined(CONFIG_CPU_V7M)
itt lo
#endif
strlo r2, [r0] /* clear 32-bit BSS word */
addlo r0, r0, #4 /* move to next */
blo clbss_l
#endif
#if ! defined(CONFIG_SPL_BUILD)
bl coloured_LED_init
bl red_led_on
#endif
/* call board_init_r(gd_t *id, ulong dest_addr) */
mov r0, r9 /* gd_t */
ldr r1, [r9, #GD_RELOCADDR] /* dest_addr */
/* call board_init_r */
#if defined(CONFIG_SYS_THUMB_BUILD)
ldr lr, =board_init_r /* this is auto-relocated! */
bx lr
#else
ldr pc, =board_init_r /* this is auto-relocated! */ //9. 调用board_init_r
#endif
/* we should not return here. */
#endif
ENDPROC(_main)
5.3.1 start.S中:
(1) 将CPU设为SVC模式
(2) 关看门狗
(3) 屏蔽中断
(4) 设置分频系数,未设置时钟频率
(5) bl cpu_init_crit
[1]清除caches
[2] 禁止 MMU cache
[3] bl lowlevel_init
在重定位之前,需要设置好内存管理器的各时序参数,这个函数的源文件在对应的单板目录中。对于smdk2410, 在board/samsung/smdk2410/lowlever_init.S 中定义了这个函数
(6) bl _main
在arch/arm/lib/crt0.S中定义
bl board_init_f_alloc_reserve
给gd_t预留空间,gd_t是global_data结构体,里面存放了各种硬件相关参数,一般将gd_t的地址存放在r9中 bl board_init_f_init_reserve
初始化gd_t(清零) bl board_init_f
在这里面进行各种初始化,分配内存空间,填充gd_t结构体 至此,我们的初步分析就到这里结束,下面将开始分析如何添加单板,以及如何修改启动代码使串口有输出