在新ARM板上移植U-Boot和Linux指南
序言
- 从支持一个定制板子在U-Boot和Linux中的过程中得到经验
- 以一个带有知名SoC(i.MX6)且IP已经得到支持的板子为例,这次讨论几乎不涉及编码技能,
- 更多地聚焦在U-Boot部分
一般原则
- 如果您有您的BSP(板级支持包)的源代码,请编译并运行BSP以:
- 验证您正在工作的IP(知识产权/硬件模块)能够与某些代码一起工作
- 拥有一个参考代码
- 拥有一个您可以用于调试的代码
- 专注于正确配置RAM和UART
- 提交
- 一次处理一个IP
- 提交
定制板介绍
- 基于i.MX6的模块,带有扩展板,
- 以太网,I2C,SPI,NAND,eMMC,SD卡读卡器,USB设备,EEPROM,
GPIO,UART,音频(I2S),HDMI,LVDS,PCIe,USB主机,RTC,电源管理集成电路(PMIC)
U-Boot移植
U-Boot 状态
- 持续从板头文件定义迁移到Kconfig选项,
- 持续从手动驱动探测迁移到驱动模型
U-Boot 目录结构
- arch/
与架构或平台相关的所有内容:设备树(DTS)、CPU初始化、引脚复用控制器、DRAM、时钟等… - board/
特定于板子的代码(初始化、引脚复用配置等),指定板头文件的Kconfig文件,板文件,路径,板文件的Makefile, - configs/
所有板子的默认配置(defconfigs) - drivers/
- include/
所有头文件 - include/configs/
所有板子的头文件
…
U-Boot新板支持工作流程
- 创建板文件
- 创建板Kconfig文件
- 创建板Makefile
- 创建板defconfig
- 创建板头文件
- 在架构的Kconfig中包含板的Kconfig
- 在其CPU的Kconfig中定义TARGET Kconfig选项
一些平台共享公共文件,因此只需要一个defconfig
创建板文件
board/my_vendor/my_board/my_board.c
- 声明全局数据指针
- 可以在代码中使用全局变量gd
- 在ARM架构中,对于ARM32等于硬件寄存器r9,对于ARM64等于x18
- 用于在启动后非常早期的“某些内存”中存储信息,这些内存在系统初始化期间可用(直到我们设置好内存控制器,可以使用RAM)
- 查看include/asm-generic/global_data.h可以了解它能存储哪些类型的信息。
创建板Kconfig文件
board/my_vendor/my_board/Kconfig
- SYS_VENDOR 和 SYS_BOARD 用于标识目录,这样 make 命令能找到编译所需的文件
- 如果两者都存在
board/SYS_VENDOR/SYS_BOARD/
- 如果省略了 SYS_VENDOR
board/SYS_BOARD/
- 如果省略了 SYS_BOARD
board/SYS_VENDOR/common/ - SYS_CONFIG_NAME 用于标识板头文件
include/configs/SYS_CONFIG_NAME.h
创建板Makefile文件
board/my_vendor/my_board/Makefile
创建板defconfig文件
configs/my_board_defconfig
- 在这里放置任何可以在Kconfig(菜单配置)中选择的内容
- 驱动程序、特性、U-Boot行为、库等
创建板header文件
include/configs/my_board.h
引入板子的Kconfig文件
arch/arm/Kconfig or
arch/arm/mach-imx/mx6/Kconfig
定义板子的TARGET Kconfig选项
arch/arm/mach-imx/mx6/Kconfig
U-Boot初始化序列
- U-Boot会运行两组函数列表,其目的是在用户访问控制台之前初始化或配置特定的IP
- 第一个列表定义在common/board_f.c文件中的static init_fnc_t init_sequence_f[]数组
- 第一个列表负责初始化DRAM,映射它,并在工作后重新定位引导程序代码
- 第二个列表定义在common/board_r.c文件中的static init_fnc_t init_sequence_r[]数组
- 只有当定义了常量时,才会运行某些函数(例如,定义CONFIG_BOARD_EARLY_INIT_F以运行board_early_init_f())
- 任何返回非零值的函数都会停止初始化序列,并使U-Boot无法启动
- 在初始化序列有问题时定义DEBUG
- 并非所有”特性”在所有函数中都可用(例如,board_early_init_f()中没有udelay)。
驱动选择
- 从具有相同IP的板子中获取灵感
- 检查相应子系统中的驱动程序
- 专注于驱动程序的行为
- 然后查看寄存器、位偏移、掩码等
- 检查未定义的宏或常量
- 查找被ifdef块包围的代码片段
- 在子系统的Makefile中查找这个驱动程序的对象文件
- 使用grep搜索CONFIG_MY_DRIVER
- 如果在某些Kconfig文件中是可见符号 => 添加到板子的defconfig中
- 如果在某些Kconfig文件中是非可见符号或未定义 => 添加到板头文件中 - 确保你的驱动程序被编译(查找my_driver.o)
- 驱动程序文件位于drivers/mtd/nand/nand_mxs.c
- 使用CONFIG_NAND_MXS来编译驱动程序
- 使用CONFIG_SYS_MAX_NAND_DEVICE和CONFIG_SYS_NAND_BASE常量来配置设备
驱动选择 - NAND示例
configs/my_board_defconfig
include/configs/my_board.h
board/my_vendor/my_board.c
关于设备树的说明
- 设备树的迁移始于2012年,代码正逐渐迁移,逐个驱动程序,逐个子系统
- 驱动模型需要使用设备树
- 大多数驱动程序有大量的CONFIG_DM条件编译块
- 你不能真正基于每个驱动程序选择启用DM支持 - 子系统核心代码也是如此
- 我没有深入研究,因为我们要么不需要,要么需要完全支持设备树,而NAND框架还没有迁移到DM。
Linux Kernel 移植
Linux内核新板支持工作流程
1. 创建板子的设备树
2. 将板子的DTB添加到架构DTS Makefile中
3. 为你的板子创建一个默认配置(defconfig)
Device Tree
- 一个特殊DTS(设备树源)格式的文件
- 纯粹描述了你的板子的硬件
- 通过兼容字符串将IP与驱动程序匹配
- 文档可以在Documentation/devicetree/bindings中找到
- 有关设备树是什么以及如何编写的更深入解释,请访问:设备树详细介绍视频。
创建板子的设备树
arch/arm/boot/dts/imx6s-my-board.dts
将DTB添加到架构DTS Makefile中
arch/arm/boot/dts/Makefile
为你的板子创建一个默认配置(defconfig)
- 从SoC系列的defconfig开始(例如imx_v6_v7_defconfig),如果没有,从架构的defconfig开始(例如multi_v7_defconfig)
- 从defconfig中删除无用的SoC系列、驱动程序和特性
- 添加你想要编译的驱动程序的CONFIG
- 使用grep搜索驱动程序的基本名称是正确的方法
- 大多数驱动程序依赖于子系统或其他选项,如果你的驱动程序没有选择它们,你也需要将它们添加到你的defconfig中 - PCIe驱动程序正在探测但未枚举设备
- 驱动程序在BSP中工作,发现缺少对电源管理器的支持是罪魁祸首
- 迅速编写了一个40行的补丁并提交给了上游 - 以太网驱动程序缺少在BSP中设置的PHY复位后延迟
- 迅速编写了一个20行的补丁并提交给了上游。