linux-uboot基础

uboot移植

文章目录

  • uboot移植
  • uboot/bootloader概述
    • uboot作用
    • uboot启动的过程
  • uboot命令
    • 常用命令
  • uboot 环境变量
    • bootargs
    • bootcmd
    • sf命令
  • uboot编译
    • 打补丁
    • 编译
      • 确定平台的配置文件
      • 执行编译
  • 修改uboot环境变量
    • 修改内核偏移量
  • 烧录命令汇总
    • 烧录tf卡中的文件
  • 添加自己的代码
    • uboot的入口函数
    • 编译相关
      • 链接库
      • 添加编译自己的文件
  • 源码分析配置过程
    • 配置过程总结
  • 源码分析编译过程

uboot/bootloader概述

uboot作用

uboot的最终目的是用来启动内核的,它读取bootenv,获取bootcmd并执行它,bootcmd执行将kernel拷贝到ram中,并将bootargs传递给kernel,然后启动内核。

实际工作中uboot开关,主要涉及:修改启动方式、修改分区表(bootargs)、验签、tf卡烧录等。

uboot启动的过程

uboot启动主要分为两个阶段,主要在cpu目录下的start.S文件中:
第一阶段:硬件的初始化,包括:
1、设置处理器模式为SVC模式(管理员)
2、关闭看门狗:避免reset MCU,此外uboot阶段也用不到watchdog
3、屏蔽中断:此时用不到中断,同时避免因意外中断导致uboot启动失败
4、设置时钟:为了之后配置外设
5、初始化sdram:为了将内核拷贝之sdram中运行
6、设置栈
7、清除bss段:bss段是用来存储静态变量,全局变量的。
然后程序跳转到start_arm_boot函数,宣告第一阶段的结束。


第二阶段:
1.从flash中读出内核
2.启动内核:执行start_arm_boot,主要流程为:
    
进入main_loop,执行s=getenv(“bootcmd”)和run_command(s,0),它会根据bootcmd环境变量的内容来启动内核,bootcmd环境变量中指示了从某个flash地址读取内核到启动的内存地址,然后启动它,再执行bootm。
    
uboot启动的内核为uImage,这种格式的内核是由两部分组成:真正的内核和内核头部组成,头部中包括内核中的一些信息,比如内核的加载地址,入口地址。
    
uboot在接受到启动命令后,要做的主要是,1,读取内核头部,2,移动内核到合适的加载地址,3,启动内核,执行do_bootm_linux

uboot命令

常用命令

reset/re: 重启系统
printenv: 打印当前系统环境变量
setenv: 设置环境变量, 格式: setenv name value, 表示将 name 变量设置成 value 值;如果没有value, 表示删除该变量。
saveenv: 保存环境变量到 flash 中。
fatls: 显示 SD 卡的文件, 格式 fatls mmc 0 。

uboot 环境变量

示例:

isvp_t40# printenv
bootargs=console=ttyS1,115200n8 32M@0x0 rmem=32M@0x2000000
init=/linuxrc rootfstype=squashfs root=/dev/mtdblock2 rw
mtdparts=jz_sfc:256k(boot),2560k(kernel),2048k(root),-(appfs)
bootcmd=sf probe;sf read 0x80600000 0x40000 0x280000; bootm 0x80600000
bootdelay=1
ipaddr=193.169.4.151
serverip=193.169.4.2

bootargs

bootargs 内核通过 bootargs 找到文件系统位置,并挂载它。
● console=ttyS1,115200n8 设置串口号为 ttyS1, 波特率为 115200
● mem=32M@0x0 rmem=32M@0x2000000 分配内存,@为从0x2000000开始分配32M给rmem。
● init=/linuxrc 系统首先运行/linuxrc 文件
● rootfstype=squashfs 指定文件系统类型,例如jffs2、ext4等
● root=/dev/mtdblock2 rw 告诉内核根文件从 mtd 分区 3 挂载, 可读写
● mtdparts=jz_sfc:256k(boot),2560k(kernel),2048k(root),-(appfs) mtd :指定分区大小,有些bootargs中没有声明该字段。

bootcmd

uboot启动kernel所执行的命令
● sf probe;sf read 0x80600000 0x40000 0x280000; bootm 0x80600000 从nor flash启动
● ipaddr 193.169.4.151 板子的 IP
● serverip=193.169.4.2 服务器 IP 地址
在uboot命令行中,执行help,查看具体命令的说明
这里拷贝nor flash使用sf命令,有时也用sfcnor命令,具体平台具体分析

sf命令

sf probe: 使能spi flash.
sf read 0x80600000 0x40000 0x280000: 从flash的0x40000开始,读取0x280000长度的数据,拷贝到内存0x80600000这个位置。
其中,0x280000就是kernel(2560k)的长度,0x40000是boot(4*64 = 256k)的长度。

uboot编译

一般对外发布补丁文件,通过打补丁的方式将自己的代码写入对应版本的uboot中

打补丁

有时厂家只提供补丁文件,和uboot发行版本号,需要自己打patch,进行编译。
1、patch -px < 补丁文件
命令说明:patch -px < 补丁文件
x:根据补丁文件中的描述,来确定的数字
例如:如果补丁文件中内容如下:
— u-boot-1.1.6/board/100ask24x0/100ask24x0.c 1970-01-01 08:00:00.000000000 +0800
如果我们在u-boot-1.1.6目录下打补丁,则需要忽略补丁文件中的第一个“u-boot-1.1.6”目录,因此这里为“-p1”

编译

确定平台的配置文件

1、查看顶层目录下boards.cfg
内容如下:

编译命令                 架构        芯片名称    开发板名字        厂商     soc名称     功能定义
halley2_uImage_sfc_nand  mips        xburst      halley2		 ingenic	x1000       halley2:SPL_SFC_NAND,MTD_SFCNAND

首先根据soc和开发板名字确定大体范围,然后根据功能定义确定自己需要的编译命令。

编译命令: make halley2_uImage_sfc_nand ==> 生成image
功能定义:
开头的字符串:例如上述的halley2,说明,halley2_uImage_sdc_nand对应的配置文件名是:halley2.h,可以在include/configs下找到
包含nor =》nor flash启动,一般是soc内的nor flash,速度快,但是flash容量小
包含nand => nand flash 启动,看芯片规格,是否已经有nand?是内置nand、还是外置nand?
包含emmc => mmc设备启动,一般是sd卡启动、tf卡启动等。

执行编译

修改工具链:顶层目录Makefile中,修改CROSS_COMPILE变量
make clean; make halley2_uImage_sfc_nand
在顶层目录下生成.bin文件

修改uboot环境变量

修改内核偏移量

bootcmd中指定了,从flash中的哪个位置拷贝多长数据,至内存,例如:

bootcmd=sfcnor read 0x40000 0x340000 0x80800000 ;bootm 0x80800000
从nor flash的0x40000(4*64 = 256k)位置,拷贝0x340000(3M + 4 *64 k = 3M + 256kb)数据,至内存0x8080000处

当,内核增加一些功能配置,导致内核数据变大了,大于bootcmd中指定的内核大小时,就需要改bootcmd
法1:在boot cmd中setenv修改,不推荐
法2:修改配置文件
通过boards.cfg找到编译命令对应的功能定义,其中第一个字段就是配置文件名。
vi include/configs/配置文件名.h
修改CONFIG_BOOTCOMMAND即可。

烧录命令汇总

烧录tf卡中的文件

1、查看tf卡文件名:fatls mmc 0
2、单烧分区:  
单烧rootfs,其他分区的烧类似
fatload mmc 0 0x80650000 rootfs.squashfs ;sf probe;sf erase 0x50000 0x230000;sf write 0x80650000 0x50000 0x230000
把tf卡的rootfs.squashfs拷贝到mmc(内存)的0x80650000中,该地址是从uboot的起始地址偏移到rootfs分区的起始地址,得到的。  

添加自己的代码

uboot的入口函数

board_r.c
-》
common/main.c中

编译相关

链接库

/Makefile:
LIBS-y

添加编译自己的文件

例如在common下加自己的文件:
修改common/Makefile,加COBJS-XXX += *.c

源码分析配置过程

以atmel的sama5d3x为例:
配置命令:make CROSS_COMPILE=arm-vfpv4-linux-gnueabi- sama5d3xek_nandflash_defconfig
由make xxx_config可知:
顶层makefile中一定有其对应的规则,例如config或%config,在Makfile中查找可得:
./Makefile:

ifeq ($(KBUILD_EXTMOD),)
	ifneq ($(filter config %config,$(MAKECMDGOALS)),)
        config-targets := 1
		ifneq ($(filter-out config %config,$(MAKECMDGOALS)),)
		mixed-targets := 1
		endif
    endif
endif

···

ifeq ($(config-targets),1)
KBUILD_DEFCONFIG := sandbox_defconfig
export KBUILD_DEFCONFIG KBUILD_KCONFIG

config: scripts_basic outputmakefile FORCE
	$(Q)$(MAKE) $(build)=scripts/kconfig $@
%config: scripts_basic outputmakefile FORCE
	$(Q)$(MAKE) $(build)=scripts/kconfig $@

分析:
( K B U I L D E X T M O D ) 没 有 被 定 义 , 则 i f e q ( (KBUILD_EXTMOD)没有被定义,则ifeq ( (KBUILDEXTMOD)ifeq((KBUILD_EXTMOD),)成立
( f i l t e r c o n f i g (filter config %config, (filterconfig(MAKECMDGOALS)):
$(MAKECMDGOALS):保存执行make时所带的命令行参数
    
filter: $(filter PATTERN …, TEXT),取出TEXT中符合PATTREN模式的内容
因此,得到config-targets := 1


由上述可知config目标的三个前置条件为:
scripts_basic
1、scripts_basic
./Makefile:

$(srctree)/scripts/Kbuild.include: ;
include $(srctree)/scripts/Kbuild.include
···
scripts_basic:
	$(Q)$(MAKE) $(build)=scripts/basic
	$(Q)rm -f .tmp_quiet_recordmcount

分析:
其中 ( b u i l d ) 在 (build)在 (build)(srctree)/scripts/Kbuild.include文件中定义
Kbuild.include:

build := -f $(srctree)/scripts/Makefile.build obj

则,scripts_basic目标执行了:make -f ./scripts/Makefile.build obj=scripts/basic
即-f执行特定的makefile(执行不规则的makefile文件前需要添加-f),执行./scripts/Makefile.build,并传入参数obj=scripts/basic
2、./scripts/Makefile.build

prefix := tpl
src := $(patsubst $(prefix)/%,%,$(obj))
ifeq ($(obj),$(src))
prefix := spl
src := $(patsubst $(prefix)/%,%,$(obj))
ifeq ($(obj),$(src))
prefix := .
endif
endif

PHONY := __build
__build:

分析:
$(patsubst ( p r e f i x ) / (prefix)/%,%, (prefix)/(obj)), 将 ( o b j ) 中 的 (obj)中的 (obj)(prefix)/%替换为%。
因为obj = scripts/basic,其中没有tpl或spl,因此,不作替换。即src = scripts/basic
./scripts/Makefile.build中没有obj目标,因此选择默认目标作为目标
./scripts/Makefile.build:

	kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src))
	kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile)
	include $(kbuild-file)

如果scripts/basic目录下有Kbuild,就包含该Kbuild,否则包含scripts/basic目录下的Makefile,得到include scripts/basic/Makefile。
scripts/basic/Makefile:

	hostprogs-y     := fixdep
	always          := $(hostprogs-y)

./scripts/Makefile.build:

	ifneq ($(hostprogs-y)$(hostprogs-m),)
	include scripts/Makefile.host
	endif
		...
	__build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \
			$(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \
			$(subdir-ym) $(always)
			@:

include scripts/Makefile.host
__build:依赖于$(always),即scripts/basic/Makefile中定义的always:=fixdep
则需要先编译fixdep,它的编译规则在scripts/Makefile.host中指定。
scripts/Makefile.host:

	quiet_cmd_host-csingle 	= HOSTCC  $@
      cmd_host-csingle	= $(HOSTCC) $(hostc_flags) -o $@ $< \
	  	$(HOST_LOADLIBES) $(HOSTLOADLIBES_$(@F))
	$(host-csingle): $(obj)/%: $(src)/%.c FORCE
		$(call if_changed_dep,host-csingle)

最后在scripts/basic/下生成fixdep文件,fixdep工具用于更新编译文件
3、%config的执行命令:
( Q ) (Q) (Q)(MAKE) $(build)=scripts/kconfig $@
同上,首先进入./scripts/Makefile.build,并传入参数obj=scripts/kconfig xxx_config
即:make -f ./scripts/Makefile.build obj=scripts/kconfig sama5d3xek_nandflash_defconfig
包括以下几步:
a.在./scripts/Makefile.build里面包含scripts/kconfig/Makefile
b.include scripts/Makefile.host,在Makefile.host制定编译规则
c.生成scripts/kconfig/conf二进制文件
d.执行如下命令:
scripts/kconfig/conf --defconfig=arch/…/configs/sama5d3xek_nandflash_defconfig Kconfig
即scripts/kconfig/conf --defconfig=configs/sama5d3xek_nandflash_defconfig Kconfig
该命令引入了所有Kconfig和sama5d3xek_nandflash_defconfig中定义的CONFIG_XXX变量,最后输出到.config中。

至此,make sama5d3xek_nandflash_defconfig完成。

配置过程总结

make xxx_config:
a.执行script/makefile.build,生成fixdep工具,用于处理编译中间文件
b.在scripts/kconfig/目录下生成conf工具,用于处理配置过程中的参数
c.将Kconfig文件中的定义和xxx_config文件中定义的CONFIG_XXX变量,输出到.config中。

源码分析编译过程

1、构建include/config/auto.conf

	-include include/config/auto.conf
	-include include/config/auto.conf.cmd
	$(KCONFIG_CONFIG) include/config/auto.conf.cmd: ;
	include/config/%.conf: $(KCONFIG_CONFIG) include/config/auto.conf.cmd
		$(Q)$(MAKE) -f $(srctree)/Makefile silentoldconfig
		@# If the following part fails, include/config/auto.conf should be
		@# deleted so "make silentoldconfig" will be re-run on the next build.
		$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.autoconf || \
			{ rm -f include/config/auto.conf; false; }
		@# include/config.h has been updated after "make silentoldconfig".
		@# We need to touch include/config/auto.conf so it gets newer
		@# than include/config.h.
		@# Otherwise, 'make silentoldconfig' would be invoked twice.
		$(Q)touch include/config/auto.conf

该include/config/auto.conf文件并不存在,因此include/config/auto.conf成为目标
为了执行-include include/config/auto.conf,需要构建include/config/auto.conf目标
即include/config/%.conf。
Makefile:

	.config include/config/auto.conf.cmd: ;
	include/config/%.conf: .config include/config/auto.conf.cmd
		$(Q)$(MAKE) -f $(srctree)/Makefile silentoldconfig
		@# If the following part fails, include/config/auto.conf should be
		@# deleted so "make silentoldconfig" will be re-run on the next build.
		$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.autoconf || \
			{ rm -f include/config/auto.conf; false; }
		@# include/config.h has been updated after "make silentoldconfig".
		@# We need to touch include/config/auto.conf so it gets newer
		@# than include/config.h.
		@# Otherwise, 'make silentoldconfig' would be invoked twice.
		$(Q)touch include/config/auto.conf

将include/configs/xxx.h、.config、include的Makefile,将其中所有的CONFIG_XXX变量包含进来
2、链接CONFIG_XXX
Makefile:

	libs-y += lib/
	libs-$(HAVE_VENDOR_COMMON_LIB) += board/$(VENDOR)/common/
	libs-$(CONFIG_OF_EMBED) += dts/
	libs-y += fs/
	libs-y += net/
	libs-y += disk/
	libs-y += drivers/
	libs-y += drivers/dma/
	libs-y += drivers/gpio/
	libs-y += drivers/i2c/
	libs-y += drivers/mmc/
	libs-y += drivers/mtd/
	libs-$(CONFIG_CMD_NAND) += drivers/mtd/nand/
	libs-y += drivers/mtd/onenand/
	libs-$(CONFIG_CMD_UBI) += drivers/mtd/ubi/
	libs-y += drivers/mtd/spi/
	libs-y += drivers/net/
	libs-y += drivers/net/phy/
	libs-y += drivers/pci/
	libs-y += drivers/power/ \
		drivers/power/fuel_gauge/ \
		drivers/power/mfd/ \
		drivers/power/pmic/ \
		drivers/power/battery/
	libs-y += drivers/spi/
	libs-$(CONFIG_FMAN_ENET) += drivers/net/fm/
	libs-$(CONFIG_SYS_FSL_DDR) += drivers/ddr/fsl/
	libs-y += drivers/serial/
	libs-y += drivers/usb/eth/
	libs-y += drivers/usb/gadget/
	libs-y += drivers/usb/host/
	libs-y += drivers/usb/musb/
	libs-y += drivers/usb/musb-new/
	libs-y += drivers/usb/phy/
	libs-y += drivers/usb/ulpi/
	libs-y += common/
	libs-$(CONFIG_API) += api/
	libs-$(CONFIG_HAS_POST) += post/
	libs-y += test/
	libs-y += test/dm/

	libs-y += $(if $(BOARDDIR),board/$(BOARDDIR)/)

	libs-y := $(sort $(libs-y))

	u-boot-dirs	:= $(patsubst %/,%,$(filter %/, $(libs-y))) tools examples

	u-boot-alldirs	:= $(sort $(u-boot-dirs) $(patsubst %/,%,$(filter %/, $(libs-))))

	libs-y		:= $(patsubst %/, %/built-in.o, $(libs-y))

	u-boot-init := $(head-y)
	u-boot-main := $(libs-y)

根据CONFIG_XXX决定编译哪些lib,并最终链接成uboot
3、编译终极目标
make没有指定目标,默认构建终极目标_all。_all依赖all,all又依赖$(ALL-y)
$(ALL-y)展开后为:checkarmreloc RKLoader-uboot.bin u-boot.srec u-boot.bin System.map binary_size_check.
4、include/config/uboot.release

	define filechk_uboot.release
		echo "$(UBOOTVERSION)$$($(CONFIG_SHELL) $(srctree)/scripts/setlocalversion $(srctree))"
	endef

	# Store (new) UBOOTRELEASE string in include/config/uboot.release
	include/config/uboot.release: include/config/auto.conf FORCE
		$(call filechk,uboot.release)

调用filechk_uboot.release
5、构建$(u-boot-dirs)

	$(u-boot-dirs): prepare scripts
		$(Q)$(MAKE) $(build)=$@

其中, ( u − b o o t − d i r s ) 由 x x x d e f c o n f i g 中 的 : C O N F I G S Y S A R C H = " a r m " C O N F I G S Y S C P U = " a r m v 7 " 决 定 , 即 (u-boot-dirs)由xxx_defconfig中的: CONFIG_SYS_ARCH="arm" CONFIG_SYS_CPU="armv7" 决定,即 (ubootdirs)xxxdefconfigCONFIGSYSARCH="arm"CONFIGSYSCPU="armv7"(u-boot-dirs):= arch/arm/cpu/armv7
最终,包含arch/arm/cpu/armv7/Makefile,该Makefile中定义obj-y
上述obj-y,就是需要编译的文件,将它们编译生成的.o文件链接为arch/arm/cpu/armv7/build-in.o文件
6、uboot.lds
uboot.lds:arch/arm/cpu/u-boot.lds
uboot编译出来的第一个链接脚本就是执行u-boot.lds链接脚本,u-boot.lds中指定了uboot起始函数为:
ENTRY(_start)
7、_start
_start:在arch/arm/lib/vectors.S中定义,它主要定义了异常向量表,及其操作函数。
_start会跳去执行reset
reset:在arch/arm/cpu/armv7/start.S中定义
8、board.c
board.c:arch/arm/lib
uboot的移植工作主要在此处,包括网卡初始化,串口初始化,中断初始化,中断使能,环境变量的初始化等工作。
board_init_r():

	/* main_loop() can return to retry autoboot, if so just run it again. */
	for (;;) {
		main_loop();
	}

进入死循环,main_loop()定义在common/main.c下
8、main.c

/* We come here after U-Boot is initialised and ready to process commands */
void main_loop(void)
{
	const char *s;

	bootstage_mark_name(BOOTSTAGE_ID_MAIN_LOOP, "main_loop");

#ifndef CONFIG_SYS_GENERIC_BOARD
	puts("Warning: Your board does not use generic board. Please read\n");
	puts("doc/README.generic-board and take action. Boards not\n");
	puts("upgraded by the late 2014 may break or be removed.\n");
#endif

	modem_init();
#ifdef CONFIG_VERSION_VARIABLE
	setenv("ver", version_string);  /* set version variable */
#endif /* CONFIG_VERSION_VARIABLE */

	cli_init();

	run_preboot_environment_command();

#if defined(CONFIG_UPDATE_TFTP)
	update_tftp(0UL);
#endif /* CONFIG_UPDATE_TFTP */

	s = bootdelay_process();
	if (cli_process_fdt(&s))
		cli_secure_boot_cmd(s);

	autoboot_command(s);

	cli_loop();
}

9、添加自己的命令
在common/下创建文件cmd_hello.c
参考cmd_help.c拷贝头文件,写入如下内容:

#include 
#include 

static int do_hello(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
	int i;
	printf("hello world!\n");

	for(i = 0; i < argc; i++)
	{
		printf("argv[%d]:%s\n", i, argv[i]);
	}
	return 0;
}

U_BOOT_CMD(
	hello,	CONFIG_SYS_MAXARGS,	1,	do_hello,
	"hello uboot,-just for test",
	"\n"
	"	- hello long help ....\n"
);

修改Makefile,添加对cmd_hello.c的编译。
分析:
U_BOOT_CMD是一个宏定义,在command.h中定义如下:

#define U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd,		\
				_usage, _help, _comp)			\
		{ #_name, _maxargs, _rep, _cmd, _usage,			\
			_CMD_HELP(_help) _CMD_COMPLETE(_comp) }

#define U_BOOT_CMD_MKENT(_name, _maxargs, _rep, _cmd, _usage, _help)	\
	U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd,		\
					_usage, _help, NULL)

#define U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, _comp) \
	ll_entry_declare(cmd_tbl_t, _name, cmd) =			\
		U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd,	\
						_usage, _help, _comp);

#define U_BOOT_CMD(_name, _maxargs, _rep, _cmd, _usage, _help)		\
	U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, NULL)

展开就是:

cmd_tbl_t _u_boot_list_2_do_bootm_2_bootm __aligned(4) __attribute__((unused,section(".u_boot_list_2_"#_list"_2_"#_name)))
={ bootm, CONFIG_SYS_MAXARGS, 1, do_bootm,"boot application image from memory", bootm_help_text, NULL}

即:U_BOOT_CMD的作用就是定义一个结构体变量(struct cmd_tbl_s),并将其存放在uboot的没有被占用的section段中。
在uboot中输入的内容,uboot会查找uboot相应的section段,执行相应的命令。
10、uboot分区
a.include/configs/sama5d3xek.h
该文件中包含了:
#include “at91-sama5_common.h”

b.include/at91-sama5_common.h

#define CONFIG_BOOTARGS							\
	"console=ttyS0,115200 earlyprintk "				\
	"mtdparts=atmel_nand:256k(bootstrap)ro,512k(uboot)ro,"		\
	"256K(env),256k(env_redundent),256k(spare),"			\
	"512k(dtb),6M(kernel)ro,-(rootfs) "				\
	"rootfstype=ubifs ubi.mtd=7 root=ubi0:rootfs"

你可能感兴趣的:(linux系统移植,linux,内核,uboot)