在U-Boot开发过程中,串口输出是最基础也是最重要的调试手段之一。特别是在早期启动阶段(early boot),串口可能是唯一的调试输出通道。
在U-Boot中启用串口输出通常需要以下步骤:
// 在板级配置文件中定义串口相关参数
#define CONFIG_BAUDRATE 115200
#define CONFIG_SYS_NS16550 // 使用NS16550兼容串口控制器
#define CONFIG_SYS_NS16550_REG_SIZE -4
#define CONFIG_SYS_NS16550_CLK 1843200
#define CONFIG_SYS_NS16550_COM1 0x01c28000 // 串口物理地址
// 启用控制台输出
#define CONFIG_CONSOLE_MUX
#define CONFIG_SYS_CONSOLE_IS_IN_ENV
在U-Boot的极早期阶段(如CPU初始化、内存控制器初始化之前),常规的串口输出可能还不可用。这时可以使用以下方法:
通过控制GPIO点亮/熄灭LED来指示执行流程:
void early_debug_led(int state) {
volatile unsigned int *gpio = (unsigned int *)0x10000000;
if(state)
*gpio |= (1 << 5); // 点亮LED
else
*gpio &= ~(1 << 5); // 熄灭LED
}
实现一个极简的串口输出函数,不依赖U-Boot框架:
void early_putc(const char c) {
volatile unsigned int *uart = (unsigned int *)0x01c28000;
while (!(*uart & 0x02)); // 等待发送缓冲区空
*uart = c;
}
void early_puts(const char *s) {
while (*s) early_putc(*s++);
}
注意: Early debug代码应该尽可能简单,避免使用未初始化的内存和复杂的数据结构。
U-Boot提供了一些有用的调试宏:
| 宏 | 描述 | 示例 |
|---|---|---|
| debug() | 条件调试输出,需要定义DEBUG | debug("Value: %x\n", val); |
| pr_debug() | 更灵活的调试输出 | pr_debug("Debug info\n"); |
| print_buffer() | 以十六进制格式打印内存区域 | print_buffer(0, buf, 1, 16, 0); |
gd (global data) 结构体是U-Boot中一个非常重要的全局数据结构,包含了系统运行时的各种关键信息。
gd结构体定义在include/asm-generic/global_data.h中,包含以下常用字段:
typedef struct global_data {
bd_t *bd; // 板级信息结构体
unsigned long flags; // 全局状态标志
unsigned int baudrate; // 串口波特率
unsigned long cpu_clk; // CPU时钟频率
unsigned long bus_clk; // 总线时钟频率
unsigned long ram_size; // RAM大小
unsigned long relocaddr; // 重定位地址
// ... 其他字段
} gd_t;
在U-Boot代码中,可以通过宏gd直接访问全局数据:
// 打印当前波特率
printf("Current baudrate: %d\n", gd->baudrate);
// 检查标志位
if (gd->flags & GD_FLG_RELOC) {
printf("U-Boot has been relocated\n");
}
// 打印内存信息
printf("RAM size: %lu MB\n", gd->ram_size / 1024 / 1024);
警告: 在极早期初始化阶段(如before_reloc),gd结构体可能还未初始化,直接访问会导致错误。
可以根据需要为特定板卡扩展gd结构体:
// 在板级头文件中定义扩展结构体
struct myboard_global_data {
int custom_flag;
void *custom_data;
};
// 在global_data.h中添加到gd结构体
#define GD_FLG_MYBOARD 0x10000000
struct global_data {
// ... 标准字段
#ifdef CONFIG_MYBOARD
struct myboard_global_data myboard;
#endif
};
// 使用示例
gd->myboard.custom_flag = 1;
void dump_gd_info(void) {
printf("GD at %p\n", gd);
printf("Flags: 0x%lx\n", gd->flags);
printf("Baudrate: %u\n", gd->baudrate);
printf("CPU clock: %lu Hz\n", gd->cpu_clk);
printf("RAM size: 0x%lx bytes\n", gd->ram_size);
printf("Reloc addr: 0x%lx\n", gd->relocaddr);
}
// 在关键函数中添加gd状态检查
int do_something(...) {
printf("[Before] GD flags: 0x%lx\n", gd->flags);
// 执行操作...
printf("[After] GD flags: 0x%lx\n", gd->flags);
return 0;
}
最佳实践: 在开发板级支持包时,可以将关键硬件状态信息存储在gd扩展结构中,便于调试和状态跟踪。
对于复杂问题,可以结合JTAG调试器进行:
对于时序敏感问题,可以使用示波器:
在无法实时调试时,可以将日志保存到: