背景
公司的一款产品用了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.c和common/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成功启动!
总结
遇到别光顾着加打印,要多思考,从千头万绪中寻找线索。