UBoot详解
一、固件是什么固件Firmware是固化在硬件设备内部、负责控制和操作该硬件的底层软件。它介于纯硬件和上层软件如操作系统、应用程序之间是连接硬件与软件的“桥梁”。可以这样理解硬件 人的身体骨骼、肌肉固件 人的神经系统和本能心跳、呼吸、条件反射软件 人的后天知识和技能数学、语言、编程没有固件硬件就是一堆废铁固件错了硬件就会“抽风”或直接“死掉”。二、固件的核心特征特征说明固化通常存储在 ROM、Flash 等非易失性存储器中断电不丢失底层直接操作硬件寄存器初始化 CPU、内存、外设启动最早芯片上电后第一个执行的代码BootROM就是固件可升级现代固件可以通过“刷机”升级如 UEFI 更新、手机 OTA不可随意修改一旦刷错或刷坏设备可能“变砖”三、固件的作用分阶段看阶段一系统启动前硬件初始化固件的首要任务是在芯片上电后把硬件“扶上马”初始化 CPU 核心设置寄存器、时钟、缓存初始化内存DDR进行“内存训练”让 DDR 能正常工作初始化基础外设串口、时钟、中断控制器GIC、存储控制器加载下一级引导程序从 eMMC/NAND/SPI Flash 中读取 U-Boot 或 UEFI并跳转执行你之前看到的AS610_HBOOT2_UEFI.fd就是 UEFI 固件它在这一阶段工作负责初始化硬件并加载 U-Boot 或直接加载 Linux 内核。阶段二系统运行中提供底层服务固件不仅在启动时工作在操作系统运行时也会提供一些基础服务ACPI高级配置与电源管理接口固件提供 ACPI 表内核据此管理电源、休眠、风扇转速UEFI Runtime Services操作系统可以通过固件接口获取时间、重启、关机安全启动Secure Boot固件校验内核签名防止恶意软件篡改硬件错误上报固件捕获硬件错误如 ECC 内存错误上报给操作系统你之前看到的 eMMC RAS 补丁就是与此相关四、你之前接触过的“固件”具体是哪些你见过的固件具体作用BootROM芯片内部芯片上电后第一个执行的代码由芯片原厂固化在硅片中用户无法修改。它负责加载 U-Boot 或 UEFI。UEFI 固件AS610_HBOOT2_UEFI.fd你通过builduefiAS310P.sh编译生成的固件。它运行在 BootROM 之后、U-Boot 之前初始化 DDR、PCIe、ACPI然后加载 U-Boot。U-BootBootloader虽然 U-Boot 本身也是一个 Bootloader但它也可以被看作固件的一部分。它负责加载 Linux 内核和设备树。网卡/SSD 固件设备内部的微型固件控制具体硬件行为。比如你之前想修改的 PXE 缓冲区大小就涉及网卡的 UNDI 固件或 UEFI 网卡驱动。eMMC 固件eMMC 芯片内部也有固件负责坏块管理、磨损均衡、垃圾回收。这部分固件通常由 eMMC 原厂提供用户不可见。五、固件和内核驱动的区别对比项固件Firmware内核驱动Kernel Driver运行阶段系统启动早期甚至在内核加载之前操作系统运行时内核初始化后运行环境裸机没有操作系统支持依赖 Linux 内核 API内存分配、中断注册、锁存储位置固化在 Flash/ROM 中如 SPI Flash、芯片内部 ROM存储在硬盘/SSD/eMMC 的文件系统中/lib/modules/升级方式需要“刷机”烧录固件镜像通过insmod/rmmod或modprobe动态加载/卸载作用范围初始化硬件、引导系统、提供底层服务让操作系统能够使用该硬件设备总结固件是硬件出厂时就“刻”在芯片里的底层软件是系统启动的第一步负责初始化硬件、引导操作系统并在系统运行中提供底层服务。它是“硬件的灵魂”没有它硬件无法工作。开发板启动三阶段阶段一上电后内核运行前初始化硬件当芯片上电内部的一级固件BootROM跑完之后就会把控制权交给U-Boot。此时U-Boot需要做以下工作1. 初始化核心外设初始化DDR内存此时内存还是“一片空白”U-Boot必须通过复杂的时序算法把DDR内存“训练”好使其能被正常读写。如果这一步失败emmc.bin镜像里的一切都无法运行。初始化串口UART让你能看到打印信息即 BootIsp.c 里提到的PRINT_INFO。初始化存储设备让U-Boot能够识别到eMMC/dev/mmcblk0、NAND或SD卡才能读取里面的内核。初始化网络网卡用于从网络NFS/TFTP直接加载内核也就是你之前提到的PXE启动的基础。2. 加载内核和根文件系统核心使命U-Boot会根据其内部或你传入的启动命令bootcmd和环境变量bootargs从指定的存储介质如eMMC的某个分区中读取Linux内核镜像Image/zImage和设备树文件dtb到内存中然后跳转执行。阶段二启动过程中构建内核启动环境U-Boot不仅仅是在搬运数据它还会准备“启动菜单”向内核传递启动参数。传递内核启动参数cmdline这就是你之前用cat /proc/cmdline看到的那些参数。U-Boot会通过启动命令将root/dev/mmcblk0p2 consolettyS0,115200等参数传给内核告诉内核“根文件系统在哪”、“用什么串口打印日志”。提供设备树dtbU-Boot会把编译好的.dtb设备树二进制文件的内存地址告诉内核内核据此获知硬件的具体布局如寄存器地址、中断号。阶段三启动完成后提供运维功能即便内核启动完成后U-Boot也并非“消失”了。它通常会在启动过程中短暂停留提供以下核心价值提供急救/调试模式如果在启动过程中按下任意键通常是回车或空格U-Boot会中断启动过程进入命令行模式。这就像是系统的“保险箱密码”。你可以在这里输入命令修改启动参数比如临时换个根文件系统试试。烧写固件用mmc write命令把新的emmc.bin写进去完成系统升级。网络启动如果板载eMMC坏了可以直接用tftpboot从网络加载内核启动。驱动的流程一览第一步编译时 —— 决定“存在与否”当CONFIG_WANGZAI_D1S_SERIAL y驱动被编译wangzai_serial_putc等函数的机器码存在于最终的 U-Boot 二进制文件中。此时它具备了被执行的“可能性”。当CONFIG_WANGZAI_D1S_SERIAL n驱动不被编译这些函数的代码根本不在 U-Boot里。那么你问题中的注册和执行都无从谈起。这就是 obj-$(CONFIG_…) 的根本作用。所以要回答“不编译怎么执行”答案是不编译就无法执行。第二步链接时 —— 构建“静态注册表”U-Boot 使用了一个链接器技巧。U_BOOT_DRIVER()这个宏会在编译链接时把struct driver结构体即填写的 {.name ...} 那部分内容放到一个特殊的、连续的内存段中比如叫_u_boot_list_2_driver_2可以理解为U-Boot 在编译完成后就在内存中为自己建立好了一张“驱动程序注册表”所有被编译进来的驱动都在这张表里第三步运行时 —— DM 框架的“扫描与匹配”当 U-Boot 启动执行到 DM 框架初始化时通常在 board_init_f 或 board_init_r 阶段会发生以下过程扫描注册表DM 框架会去内存中扫描那个特殊的 _u_boot_list_2_driver_2 段找到所有被编译进来的 struct driver 对象其中就包括你的 serial_wangzai_d1s。解析设备树DM 框架会解析硬件设备树(.dtb)为每一个设备节点创建一个 struct udevice 对象。匹配驱动和设备这是最关键的一步。对于每一个设备树节点DM会取出它的 compatible 属性比如 “wangzai,d1s-uart”然后去那张“驱动注册表”里寻找 compatible相匹配的驱动。实例化与绑定当驱动 (.compatible “wangzai,d1s-uart”) 与设备树节点(compatible “wangzai,d1s-uart”) 匹配成功后DM会认为这个硬件有对应的驱动程序了。于是它会调用驱动中指定的 .probe 函数即wangzai_serial_probe来初始化硬件。注册操作接口在 probe 函数中驱动会把wangzai_serial_ops 这个操作函数表赋值给设备的 dev-driver-ops 指针。自此DM框架就知道了“对于这个 UART 设备你想发字符去找它的 putc 函数。”第四步调用时 —— “回调”而非“直接调用”之后当上层代码比如printf()需要输出字符时调用链如下printf()-puts()-serial_putc()这是 DM 框架提供的标准函数把U-Boot当成一家餐厅•Kconfig 菜单•menuconfig 你在点菜•xxx_configwangzai_d1s_config 你提前写好的固定菜单•.config 厨房最终收到的订单写在wangzai_d1s_config里的 你手动指定的没写的 自动使用Kconfig里的【默认值】UBoot架构相关/板级相关配置1、获取UBoot源码2、在根目录下的configs文件夹下创建板级config文件uboot的不同板级配置通过config文件进行管理的该文件能帮助开发者快速编译出适合某个板子的镜像3、在根目录下的board文件夹下创建对应品牌的文件夹在该文件夹下创建目标板的文件夹存放必要文件**board/wangzai/wangzai-d1s/├── d1s.c # 板级初始化代码核心逻辑 ├── Kconfig # 配置菜单定义编译配置 ├── MAINTAINERS # 维护者信息联系人和状态 └── Makefile # 编译规则构建指令4、在uboot的arch/riscv/dts/创建目标板的设备树文件 xxx.dts5、在uboot的arch/riscv/dts/Makfile文件中添加dtb-$(CONFIG_TARGET_WANGZAI_D1S)wangzai-d1s-uboot.dtb#用于指定某个板卡对应的设备树文件DTB6、在uboot的根目录的include/configs/文件夹下创建wangzai-d1s.h文件wangzai-d1s.h是板级配置头文件它定义了该板卡特有的内存布局基地址、大小外设地址定时器、串口等环境变量内核加载地址、启动参数等编译选项启用/禁用哪些功能至此uboot 的关于架构相关、板级相关的移植就完成了Uboot添加驱动串口驱动1、添加Kconfig文件打开drivers/serial/Kconfig文件并在该文件中添加如下内容//1、 创建可配置的编译选项config WANGZAI_D1S_SERIAL//定义了一个配置宏最终会在include/config.h中生成CONFIG_WANGZAI_D1S_SERIAL宏boolWangzai D1s UART support//声明这是一个布尔类型的选项在make menuconfig等配置界面中会显示为一个复选框选中后驱动代码会被编译进U-Boot不选中则被排除//2、管理依赖关系depends on DM_SERIAL//表示该选项依赖于DM_SERIALDriver Model SerialU-Boot的驱动模型串口框架只有当CONFIG_DM_SERIALy时CONFIG_WANGZAI_D1S_SERIAL选项才会可见/可选。这确保了驱动只能在支持驱动模型的环境下启用避免配置错误//3、提供帮助信息help Wangzai D1s uart support//提供了选项的说明告诉开发者这个选项是用于什么目的Wangzai D1s的UART支持当用户在配置界面按?键或使用make help时会显示这段文本。2、添加Makefile打开drivers/serial/Makefile文件并在该文件中添加如下内容obj-$(CONFIG_WANGZAI_D1S_SERIAL)serial_wangzai_d1s.oa、变量展开在Makefile中obj-$ (CONFIG_WANGZAI_D1S_SERIAL)中的$ (CONFIG_WANGZAI_D1S_SERIAL)会展开为$ (CONFIG_WANGZAI_D1S_SERIAL)宏的值。当在Kconfig中选中该选项时CONFIG_WANGZAI_D1S_SERIAL的值被设置为y这一行就变成了obj-y serial_wangzai_d1s.o。当你不选中该选项时CONFIG_WANGZAI_D1S_SERIAL的值被设置为n这一行就变成了obj-n serial_wangzai_d1s.o。b、Makefile的判定obj-y是Makefile中一个特殊的变量它明确地告诉Kbuild构建系统“将serial_wangzai_d1s.o这个目标文件编译并链接到最终的镜像中”。obj-n则表示忽略该文件不进行编译3、添加串口驱动文件在drivers/serial/目录下创建serial_wangzai_d1s.c文件并添加驱动代码内容驱动框架通过统一的ops接口回调驱动的具体实现卡驱动1、添加Kconfig文件打开drivers/mmc/Kconfig文件并在该文件中添加如下内容config MMC_WANGZAI2.boolWangzai D1s SD/MMC Host Controller supporthelp This selects supportforthe SD/MMC Host Controller on D1s SoC3.2、添加Makefile打开drivers/mmc/Makefile文件并在该文件中添加如下内容obj-$(CONFIG_MMC_WANGZAI)wangzai_d1s_mmc.o3、添加卡驱动文件:在drivers/mmc/目录下创建wangzai_d1s_mmc.c文件并添加内容