定位Petalinux 2022.1的u-boot在zynq-7000芯片上启动失败

背景

公司的一款产品用了zynq-7000系列芯片,PL端的开发工具是vivado 2022.1,但PS端的开发工具却不是petalinux 2022.1,而是petalinux 2020.1,据驱动工程师说,petalinux 2022.1在zynq-7000上跑不起来,但具体什么故障没说。

从今年2月份开始,利用周六加班的时间定位该问题,昨天终于解决了,记录一下。

搭建petalinux 2022.1的PS工程

为了快速复现问题,我只下载了petalinux 2022.1的安装包,没下载SState、downloads两个大包,因为我猜测大概率在内核启动阶段甚至在u-boot启动阶段就会复现。

根据官方文档ug1144一步一步搭建并编译出BOOT.BIN,烧录到eMMC卡,用跳线帽将boot pin设置好,启动,发现uboot打印以下几行内容就再没输出打印了

复制代码
U-Boot 2022.01 (May 22 2026 - 10:05:33 +0000)

CPU:   Zynq 7z020
Silicon: v3.1
Model: Alientek Navigator Zynq Development Board
DRAM:  ECC disabled 512 MiB

定位u-boot不打印的原因

jtag调试?既不会也没资源

jtag调试需要vitis环境,自己没装;还需要xilinx的仿真器,公司有一个,FPGA同事在用,而且他在外地😂

给u-boot加打印

一开始尝试在u-boot的配置界面勾选那些似是而非的选项,后来发现卵用没有。

后续尝试直接修改u-boot源码,却发现找不到源码,后来在官方文档看到添加补丁的方法时,才知道要运行下面的命令将源码解压到工程目录下

复制代码
$ petalinux-devtool modify u-boot-xlnx

解压后的目录位于工程根目录/components/yocto/workspace/sources/u-boot-xlnx

我不知道uboot目前是死在f阶段(重定位前)还是r阶段(重定位后),于是想在common/board_f.ccommon/board_r.c开启打印,因为二者分别包含f阶段和r阶段的初始化步骤列表,执行这个列表的函数叫initcall_run_list(),来自include/initcall.h,它里面有行打印,可以显示当前初始化步骤的函数地址,结合符号表,就能知道对应的函数名

c 复制代码
            debug("initcall: %p\n", (char *)*init_fnc_ptr - reloc_ofs);

但是debug函数是个宏函数

c 复制代码
#ifdef DEBUG
#define _DEBUG  1
#else
#define _DEBUG  0
#endif

#define debug(fmt, args...)         \
    debug_cond(_DEBUG, fmt, ##args)

需要在包含它的.c文件开头添加DEBUG宏

c 复制代码
#define DEBUG

给board_f.c和board_f.c添加DEBUG宏后,打印内容一下子多了很多

复制代码
initcall: 0409d23c


U-Boot 2022.01 (May 22 2026 - 10:05:33 +0000)

initcall: 04022678
U-Boot code: 04000000 -> 040DA528  BSS: -> 040ED4E4
initcall: 040224c0
initcall: 0400265c
CPU:   Zynq 7z020
Silicon: v3.1
initcall: 04022d64
Model: Alientek Navigator Zynq Development Board
initcall: 040226a4
DRAM:  initcall: 04003568
ECC disabled initcall: 040228e0
Monitor len: 000ED4E4
Ram size: 20000000
Ram top: 20000000
initcall: 040224a8
initcall: 0400227c
initcall: 04022700
initcall: 04022708
initcall: 04022610
Reserving 949k for U-Boot at: 1ff02000
initcall: 040227a0
Reserving 20608k for malloc() at: 1eae2000
initcall: 04022748
Reserving 96 Bytes for Board Info at: 1eae1fa0
initcall: 040227d4
Reserving 216 Bytes for Global Data at: 1eae1ec0
initcall: 040225a4
Reserving 20000 Bytes for FDT at: 1eadd0a0
initcall: 04022710
initcall: 04022718
initcall: 04022738
initcall: 04022944
initcall: 04003564
initcall: 04022818

RAM Configuration:
Bank #0: 0 512 MiB
Bank #1: 0 0 Bytes
Bank #2: 0 0 Bytes
Bank #3: 0 0 Bytes

DRAM:  512 MiB
initcall: 04022960
initcall: 04022588
New Stack Pointer is: 1eadd080
initcall: 040224c8
initcall: 04022720
initcall: 04022728
initcall: 04022514
Relocation Offset is: 1bf02000
Relocating to 1ff02000, new gd at 1eae1ec0, sp at 1eadd080
initcall: 040226f0
initcall: 1ff24a18
initcall: 1ff24a20
initcall: 04022be8 (relocated to 1ff24be8)
initcall: 04022b9c (relocated to 1ff24b9c)
initcall: 04022c00 (relocated to 1ff24c00)
initcall: 04022b60 (relocated to 1ff24b60)
Pre-reloc malloc() used 0x7c4 bytes (1 KB)
initcall: 04022a08 (relocated to 1ff24a08)
initcall: 04022c18 (relocated to 1ff24c18)
initcall: 04022c08 (relocated to 1ff24c08)
initcall: 04022b4c (relocated to 1ff24b4c)
initcall: 04003404 (relocated to 1ff05404)
initcall: 040028bc (relocated to 1ff048bc)
initcall: 0408c570 (relocated to 1ff8e570)
initcall: 04022c28 (relocated to 1ff24c28)
initcall: 04022c10 (relocated to 1ff24c10)
initcall: 0402a3b8 (relocated to 1ff2c3b8)
initcall: 0405b768 (relocated to 1ff5d768)

可以看到,f阶段已经结束了,r阶段也跑过了一部分,卡在0405b768这个函数里了,对比map文件,可以确定该函数是serial_initialize

定位函数serial_initialize为什么失败

2个版本的serial_initialize

看样子是串口初始化失败,于是寻找该函数定义,发现有2个版本,一个是uclass版本,一个是legacy版本,都位于drivers/serial/目录,查看该目录下的Makefile,发现是用宏来条件编译的,其中一个宏还是嵌套宏😓

c 复制代码
ifdef CONFIG_SPL_BUILD

ifeq ($(CONFIG_$(SPL_TPL_)BUILD)$(CONFIG_$(SPL_TPL_)DM_SERIAL),yy)
obj-y += serial-uclass.o
else
obj-y += serial.o
endif

else

ifdef CONFIG_DM_SERIAL
obj-y += serial-uclass.o
else
obj-y += serial.o
endif

endif

不确定到底链接哪个版本,还是用笨办法,加打印,然后确定了是uclass版本,该版本源码位于drivers/serial/serial-uclass.c:

c 复制代码
int serial_initialize(void)
{
    /* Scanning uclass to probe devices */
    if (IS_ENABLED(CONFIG_SERIAL_PROBE_ALL)) {
        int ret;

        ret  = uclass_probe_all(UCLASS_SERIAL);
        if (ret)
            return ret;
    }

    return serial_init();
}

根据build目录下生成的u-boot配置选项,发现并没有启用CONFIG_SERIAL_PROBE_ALL,那控制流一定是走到serial_init()函数去了。

serial_init()死在哪儿了?

我用打点的方法给serial_init()的调用栈的每一层加打印,类似于宽度优先遍历,范围缩小到drivers/serial/serial-uclass.c的

serial_find_console_or_panic()的这一行

c 复制代码
                gd->cur_serial_dev = dev;
                return;

上面的操作相当于切换串口设备驱动,难怪突然不打印了,可能新加载的串口驱动反而不如老驱动兼容性好吧

想定位新加载的驱动是怎么来的,于是开启drivers/core/uclass.c和drivers/core/device.c的打印,并给后者也加入多层级打点,结果虽然多了很多其他硬件的打印,但串口的仍卡在相同地方

serial_init()说它自己仅运行在f阶段!

上周六结合打印走查代码时,突然看到serial_init()开头一行注释

c 复制代码
/* Called prior to relocation */
int serial_init(void)
{
#if CONFIG_IS_ENABLED(SERIAL_PRESENT)
    serial_find_console_or_panic();
    gd->flags |= GD_FLG_SERIAL_READY;
    serial_setbrg();
#endif

    return 0;
}

Called prior to relocation------在重定位前调用------而我卡死的位置在重定位后啊,所以serial_initialize调serial_init本身就是个错误!

那么怎么不让serial_initialize调serial_init呢?唯一的办法就是启用CONFIG_SERIAL_PROBE_ALL选项。

启用CONFIG_SERIAL_PROBE_ALL

对比petalinux2020和petalinux2022的u-boot的串口驱动配置界面,发现后者恰好就比前者多一个选项,那就是CONFIG_SERIAL_PROBE_ALL,而且默认还是关闭的。

尝试开启该选项,重编镜像并测试,u-boot成功启动!

总结

遇到别光顾着加打印,要多思考,从千头万绪中寻找线索。