U-Boot 系统基础入门 - 调试技巧

1. 串口输出与early debug

在U-Boot开发过程中,串口输出是最基础也是最重要的调试手段之一。特别是在早期启动阶段(early boot),串口可能是唯一的调试输出通道。

1.1 配置串口输出

在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
            

1.2 Early Debug技巧

在U-Boot的极早期阶段(如CPU初始化、内存控制器初始化之前),常规的串口输出可能还不可用。这时可以使用以下方法:

LED指示灯

通过控制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代码应该尽可能简单,避免使用未初始化的内存和复杂的数据结构。

1.3 调试宏定义

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);

2. 使用gd结构体打印调试信息

gd (global data) 结构体是U-Boot中一个非常重要的全局数据结构,包含了系统运行时的各种关键信息。

2.1 gd结构体简介

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;
            

2.2 访问gd结构体

在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结构体可能还未初始化,直接访问会导致错误。

2.3 扩展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;
            

2.4 调试技巧

打印完整gd信息

                        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结构体追踪

                        // 在关键函数中添加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扩展结构中,便于调试和状态跟踪。

3. 其他调试技巧

3.1 使用JTAG调试

对于复杂问题,可以结合JTAG调试器进行:

3.2 使用示波器

对于时序敏感问题,可以使用示波器:

3.3 日志记录

在无法实时调试时,可以将日志保存到: