第1次升级和移植uboot纪实(2017.09->2022.04)

目录

0. 前言

1. SPL

2. imximage.cfg

3. CONFIG_XX 与 条件编译

4. 板级文件的核心内容

5. 总结:学会收敛问题


0. 前言

这次的工作主要是把某项目设备上的uboot版本从2017.09升级到2022.04,是作为该项目整个BSP升级计划的一部分。2017.09版本上的设备板级文件也要适当移植到2022.04版本上。

原来按照指导同事的估计,顶多需要三天,作者却花了将近一个月!当中弯路很多,记下以鉴后人!


1. SPL

SPL指的是 Second Program Loader,原本是为了简化uboot启动过程的工具。SPL加载的文件叫做u-boot.img...... 但这次首要的任务就是要去掉SPL,编译出u-boot.imx。我们的项目不需要多余的东西。但是纵观uboot的源代码, CONFIG_SPL_BUILD之类的条件编译比比皆是。选不选SPL,同一个函数就会进入另外的实现分支,可能会出现不能正确运作的问题。

去掉SPL,首先就要在 buildroot-menuconfig --> bootloader里面去掉 install SPL...(如果使用buildroot来构建uboot的话),再在uboot-menuconfig --> SPL/TPL 确保不选择 Enable SPL。其他有关SPL的选项也要注意配置。


2. imximage.cfg

在板级文件目录下,比如说./board/freecale/mx6slevk/,除了源文件、Makefile、Kconfig等,作者还第一次看到 imximage.cfg 文件。这种cfg文件其实是为了高效地批量地配置CPU寄存器而设置的。其中最关键的是里面的DCD(Device Configuration Data)部分:

/*

* Device Configuration Data (DCD)

*

* Each entry must have the format:

* Addr-type Address Value

*

* where:

* Addr-type register length (1,2 or 4 bytes)

* Address absolute address of the register

* value value to be stored in the register

*/

DATA 4 0x020c4018 0x00260324

DATA 4 0x020c4068 0xffffffff

DATA 4 0x020c406c 0xffffffff

DATA 4 0x020c4070 0xffffffff

DATA 4 0x020c4074 0xffffffff

DATA 4 0x020c4078 0xffffffff

DATA 4 0x020c407c 0xffffffff

DATA 4 0x020c4080 0xffffffff

DATA 4 0x020e0344 0x00003030

DATA 4 0x020e0348 0x00003030

DATA 4 0x020e034c 0x00003030

......

言简意赅的说明,什么寄存器要设什么值。本项目设备有个隐患是DDR不太稳定,这就需要设置DDR寄存器。

NXP提供了DDR压力测试工具,来测试DDR的表现,这个工具会给出DDR的几个寄存器应该的值,将这些值同步到cfg文件即可。作者还没试过此类DDR压力测试,采用的是老法师已经配好的cfg文件,名字改成 imximage.cfg,放在设备的板级文件夹下即可。

PS: 在 uboot-menuconfig里面要显式的声明DCD配置的地方,也就是放 imximage.cfg的地方。一般是 uboot-menuconfig --> ARM architecture --> DCD script, 或者是 uboot-menuconfig --> Boot images --> Extra Options


3. CONFIG_XX 与 条件编译

纵观这次升级移植工作,卡住最久的地方就是上电后,uboot没有一点打印。

尝试了N多思路,最后才想起是不是串口初始化本身有问题。

举例说,在 ./board/freescale/mx6sabresd/mx6sabresd.c

.......
static void setup_iomux_uart(void)
{
	SETUP_IOMUX_PADS(uart1_pads);
}
......
int board_early_init_f(void)
{
	setup_iomux_uart();

	return 0;
}
......

这个 SETUP_IOMUX_PADS(x) 宏是用来设置imx6的管脚复用的相关寄存器组。我们这个宏的具体实现:

./arch/arm/include/asm/mach-imx/iomux-v3.h

/* macros for declaring and using pinmux array */
#if defined(CONFIG_MX6QDL)
......
#define SETUP_IOMUX_PADS(x)					\
	imx_iomux_v3_setup_multiple_pads(x, ARRAY_SIZE(x)/2)
#elif defined(CONFIG_MX6Q) || defined(CONFIG_MX6D)
......
#define SETUP_IOMUX_PADS(x)					\
	imx_iomux_v3_setup_multiple_pads(x, ARRAY_SIZE(x))

#elif defined(CONFIG_MX6UL) || defined(CONFIG_MX6ULL)
......
#define SETUP_IOMUX_PADS(x)					\
	imx_iomux_v3_setup_multiple_pads(x, ARRAY_SIZE(x))
#else
......
#define SETUP_IOMUX_PADS(x)					\
	imx_iomux_v3_setup_multiple_pads(x, ARRAY_SIZE(x))
#endif

问题1就出在这里,作者在工程里一直使能的是CONFIG_MX6DL,而2017.09的工程里使能的是CONFIG_MX6QDL。所以 SETUP_IOMUX_PADS(x) 实现方式不一样。串口相关寄存器的设置也不同!

解决了这个问题后(使能CONFIG_MX6QDL),发现串口还是没有打印,继续由后往前推。

在./arch/arm/lib/crt0.S

/*
 * entry point of crt0 sequence
 */

ENTRY(_main)

/*
 * Set up initial C runtime environment and call board_init_f(0).
 */

#if defined(CONFIG_TPL_BUILD) && defined(CONFIG_TPL_NEEDS_SEPARATE_STACK)
......
#endif
	bic	r0, r0, #7	/* 8-byte alignment for ABI compliance */
	mov	sp, r0
	bl	board_init_f_alloc_reserve
	mov	sp, r0
	/* set up gd here, outside any C code */
	mov	r9, r0
	bl	board_init_f_init_reserve

#if defined(CONFIG_DEBUG_UART) && CONFIG_IS_ENABLED(SERIAL)
	bl	debug_uart_init
#endif

#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_EARLY_BSS)
	CLEAR_BSS
#endif

	mov	r0, #0
	bl	board_init_f

#if ! defined(CONFIG_SPL_BUILD)

......

ENDPROC(_main)

在执行 board_init_f() 之前,串口的初始化其实还会有变数。通过对比发现问题2,2017.09的工程里没有使能CONFIG_DEBUG_UART,而新工程却使能了,导致串口先被执行debug_uart_init。

当作者消掉CONFIG_DEBUG_UART,再编译。串口printf终于可以打印了,uboot调试至此走上了快车道。

这个重大的教训让作者明白:uboot-menuconfig 里多如牛毛的 CONFIG_XXX 宏,它们与uboot源代码的桥梁就是大量的条件编译语句!我们在uboot-menuconfig里对各种宏的使能与否,直接导致了各种函数在源代码里的实现路径,甚至是会不会被具体实现(不然可能只是weak函数)!


4. 板级文件的核心内容

乍一看板级文件夹的主源文件,如 ./board/freescale/mx6sabresd/mx6sabresd.c,洋洋洒洒900多行。其实因为有大量条件编译,可能就执行里面三分之一的代码。

而且板级文件的核心内容其实就是配合 board_f.c及其他启动文件,把 串口SD/MMC 两个外设初始化。SD/MMC 是硬盘,是必需品。串口 是uboot唯一的人机交互手段。其他诸如I2C,SPI,显示,网口等非核心功能都可以注释掉(甚至最终状态下它们都不是必须的)。注意串口SD/MMC 两个外设的设备号或者总线号,容易搞错。


5. 总结:学会收敛问题

uboot作为一个大型的可交互的应用程序,要进行调试,必须先有串口打印功能。如果一上来,没有打印,必须首先把串口初始化和打印功能给弄出来。

 ./common/board_f.c 可以说是,uboot启动后第一个执行的C语言源文件。如下图,切入口是 board_init_f(),它执行的是 init_sequence_f[ ] 函数指针数组里的所有满足条件编译的函数。

static const init_fnc_t init_sequence_f[] = {
	setup_mon_len,
#ifdef CONFIG_OF_CONTROL
	fdtdec_setup,
#endif
#ifdef CONFIG_TRACE_EARLY
	trace_early_init,
#endif
	initf_malloc,
	log_init,
	initf_bootstage,	/* uses its own timer, so does not need DM */
#ifdef CONFIG_BLOBLIST
	bloblist_init,
#endif
	setup_spl_handoff,
#if defined(CONFIG_CONSOLE_RECORD_INIT_F)
	console_record_init,
#endif
#if defined(CONFIG_HAVE_FSP)
	arch_fsp_init,
#endif
	arch_cpu_init,		/* basic arch cpu dependent setup */
	mach_cpu_init,		/* SoC/machine dependent CPU setup */
	initf_dm,
	arch_cpu_init_dm,
#if defined(CONFIG_BOARD_EARLY_INIT_F)
	board_early_init_f,
#endif
#if defined(CONFIG_PPC) || defined(CONFIG_SYS_FSL_CLK) || defined(CONFIG_M68K)
	/* get CPU and bus clocks according to the environment variable */
	get_clocks,		/* get CPU and bus clocks (etc.) */
#endif
#if !defined(CONFIG_M68K)
	timer_init,		/* initialize timer */
#endif
#if defined(CONFIG_BOARD_POSTCLK_INIT)
	board_postclk_init,
#endif
	env_init,		/* initialize environment */
	init_baud_rate,		/* initialze baudrate settings */
	serial_init,		/* serial communications setup */
	console_init_f,		/* stage 1 init of console */
	display_options,	/* say that we are here */
	display_text_info,	/* show debugging info if required */
	checkcpu,
#if defined(CONFIG_SYSRESET)
	print_resetinfo,
#endif
#if defined(CONFIG_DISPLAY_CPUINFO)
	print_cpuinfo,		/* display cpu info (and speed) */
#endif
#if defined(CONFIG_DTB_RESELECT)
	embedded_dtb_select,
#endif
#if defined(CONFIG_DISPLAY_BOARDINFO)
	show_board_info,
#endif
	INIT_FUNC_WATCHDOG_INIT
#if defined(CONFIG_MISC_INIT_F)
	misc_init_f,
#endif
	INIT_FUNC_WATCHDOG_RESET
#if CONFIG_IS_ENABLED(SYS_I2C_LEGACY)
	init_func_i2c,
#endif
#if defined(CONFIG_VID) && !defined(CONFIG_SPL)
	init_func_vid,
#endif
	announce_dram_init,
	dram_init,		/* configure available RAM banks */
#ifdef CONFIG_POST
	post_init_f,
#endif
	INIT_FUNC_WATCHDOG_RESET
#if defined(CONFIG_SYS_DRAM_TEST)
	testdram,
#endif /* CONFIG_SYS_DRAM_TEST */
	INIT_FUNC_WATCHDOG_RESET

#ifdef CONFIG_POST
	init_post,
#endif
	INIT_FUNC_WATCHDOG_RESET
	/*
	 * Now that we have DRAM mapped and working, we can
	 * relocate the code and continue running from DRAM.
	 *
	 * Reserve memory at end of RAM for (top down in that order):
	 *  - area that won't get touched by U-Boot and Linux (optional)
	 *  - kernel log buffer
	 *  - protected RAM
	 *  - LCD framebuffer
	 *  - monitor code
	 *  - board info struct
	 */
	setup_dest_addr,
#ifdef CONFIG_OF_BOARD_FIXUP
	fix_fdt,
#endif
#ifdef CONFIG_PRAM
	reserve_pram,
#endif
	reserve_round_4k,
	arch_reserve_mmu,
	reserve_video,
	reserve_trace,
	reserve_uboot,
	reserve_malloc,
	reserve_board,
	reserve_global_data,
	reserve_fdt,
	reserve_bootstage,
	reserve_bloblist,
	reserve_arch,
	reserve_stacks,
	dram_init_banksize,
	show_dram_config,
	INIT_FUNC_WATCHDOG_RESET
	setup_bdinfo,
	display_new_sp,
	INIT_FUNC_WATCHDOG_RESET
	reloc_fdt,
	reloc_bootstage,
	reloc_bloblist,
	setup_reloc,
#if defined(CONFIG_X86) || defined(CONFIG_ARC)
	copy_uboot_to_ram,
	do_elf_reloc_fixups,
#endif
	clear_bss,
#if !defined(CONFIG_ARM) && !defined(CONFIG_SANDBOX) && \
		!CONFIG_IS_ENABLED(X86_64)
	jump_to_copy,
#endif
	NULL,
};

void board_init_f(ulong boot_flags)
{
	gd->flags = boot_flags;
	gd->have_console = 0;

	if (initcall_run_list(init_sequence_f))
		hang();

#if !defined(CONFIG_ARM) && !defined(CONFIG_SANDBOX) && \
		!defined(CONFIG_EFI_APP) && !CONFIG_IS_ENABLED(X86_64) && \
		!defined(CONFIG_ARC)
	/* NOTREACHED - jump_to_copy() does not return */
	hang();
#endif
}

init_sequence_f[ ]中,真正有关串口初始化和printf功能准备的是这三句:

static const init_fnc_t init_sequence_f[] = {
......
#if defined(CONFIG_BOARD_EARLY_INIT_F)
	board_early_init_f,
#endif
......
	init_baud_rate,		/* initialze baudrate settings */
	serial_init,		/* serial communications setup */
......
};

作者甚至尝试把这三句放在init_sequence_f[ ]的前三排,结果后面列表里的所有函数都可以用printf打印出信息。(作者首先是在能用的 2017.09老版本uboot上做这个实验)。

所以把 printf 尽早靠前的使能好,就可尽量提早的发现程序在C语言源代码里执行到哪一步出问题了;如果在串口功能没问题的情况下,还是没打印,也可以把问题收敛到最开始汇编文件的范围里。

所以这次的一大思路: 首先确保串口初始化printf()正确可用 ---> 再用printf() 来定位问题。
 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值