OrangePi 开发板支持从多种外部设备启动,包括SD卡、EMMC、NVMe固态硬盘等,本文则讲述如何在 Orangepi 开发板上修改从外部设备启动的优先级。
文章目录
- [1 失败的探索](#1 失败的探索)
-
- [1.1 错误的操作](#1.1 错误的操作)
- [1.2 出错的原因](#1.2 出错的原因)
- [2 修改并编译 u-boot 源码](#2 修改并编译 u-boot 源码)
-
- [2.1 u-boot 源码的编译方法](#2.1 u-boot 源码的编译方法)
- [2.2 u-boot 源码的修改](#2.2 u-boot 源码的修改)
- [2.3 u-boot 的更新](#2.3 u-boot 的更新)
- [3 后记](#3 后记)
- 参考文档
1 失败的探索
从Linux开发板启动的流程中我们可以找到问题的突破口。
当前每一款Linux开发板上都搭载了一个Flash 芯片,该Flash芯片用于存储系统启动相关的程序和参数,也就是BootLoader,Bootloader 负责硬件初始化、获取内存大小信息等,调整系统到适配状态,然后将操作系统镜像和嵌入式应用程序装载到内存中,最终启动操作系统。为了方便系统的调试与故障诊断,如今的 BootLoader增加了命令行交互功能、镜像烧录等功能,也能支持多种嵌入式 CPU 架构,这就形成了人们熟知的**u-boot **(Universal BootLoader)。
总的来说,u-boot的功能和Window PC的BIOS (Basic Input and Output System)类似,都能设置从哪一个存储设备中启动系统,所以,解决此问题的一个突破口就在于对u-boot中启动的优先级进行修改。
OrangePi 上所具有的 BootLoader 在板载的SPI Flash中。以OrangePi-5max 开发板为例,接下来,本人将讲述在其u-boot命令行中修改启动优先级的失败案例。
1.1 错误的操作
首先,我们需要建立调试PC和OrangePi开发板之间的连接。
由于在BootLoader启动系统阶段,无线网卡和有线网卡均没有使能和初始化,因此无法在u-boot 阶段实现网络连接。OrangePi 官方给出的5max 技术文档中提到板上具有调试串口,因此我们可以使用该串口连到调试PC上来实现与PC间的通信,查阅技术文档可以知道,该串口通信的波特率为1500000 bps :

在 SecureCRT 或 MobaXterm 软件中输入对应的串口号和波特率,再插上USB转TTL模块,此时就可以开始进行调试工作了。
在板子上电之后,终端会输出以下的内容:

这里的Hotkey 就是进入u-boot命令行的按键,在弹出Hotkey: ctrl+c 的字眼之后,应该立即按下ctl+c键,这样才能进入u-boot命令行(如果没有进入命令行也没有关系,等系统启动完成且输入账号密码进入终端之后,执行reboot命令然后重启可以再来)。
u-boot命令行如下图所示:

此时,在命令行输入printenv,则可以查看u-boot阶段所有的环境配置项:
shell
opi# printenv
arch=arm
autoload=no
baudrate=1500000
board=evb_rk3588
board_name=evb_rk3588
...
boot_prefixes=/ /boot/
boot_script_dhcp=boot.scr.uimg
boot_scripts=boot.scr.uimg boot.scr
boot_targets=mmc0 mmc1 nvme mtd2 mtd1 mtd0 usb0 pxe dhcp
bootargs=storagemedia=sd androidboot.storagemedia=sd androidboot.mode=normal
bootcmd=run distro_bootcmd;boot_android ${devtype} ${devnum};boot_fit;bootrkp;
bootcmd_dhcp=run boot_net_usb_start; run boot_net_pci_enum; if dhcp ${scriptaddr} ${boot_script_dhcp}; then source ${scriptaddr}; fi;
...
bootdelay=0
cpu=armv8
...
scriptaddr=0x00500000
soc=rockchip
stderr=serial,vidconsole
stdout=serial,vidconsole
usb_boot=usb start; if usb dev ${devnum}; then setenv devtype usb; run scan_dev_for_boot_part; fi
vendor=rockchip
Environment size: 4799/32764 bytes
上述结果给出了u-boot源码中响应的配置信息:
| 环境参数 | 值 | 含义 |
|---|---|---|
| arch | arm | 开发板为arm架构 |
| baudrate | 1500000 | 调试串口的通信波特率 |
| cpu | armv8 | CPU指令集为armv8 |
| board | evb_rk3588 | 板子的名称为evb_rk3588 |
| soc | rockchip | 处理器类型为瑞芯微 |
| boot_targets | mmc0 mmc1 nvme mtd2 mtd1 mtd0 usb0 ... | 从外部设备启动的优先级 |
| ... | ... | ... |
重点在于u-boot命令行打印的这一句话:
shell
boot_targets=mmc0 mmc1 nvme mtd2 mtd1 mtd0 usb0 pxe dhcp
为了弄清楚每一个代号的含义是什么,我们可以此时退出u-boot命令行,让系统正常启动,即此时在命令行执行reset:
shell
opi# reset
进入操作系统下,使用lsblk命令可以看到设备名称:
shell
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
mtdblock0 31:0 0 16M 0 disk
mmcblk1 179:0 0 233G 0 disk
├─mmcblk1p1 179:1 0 1G 0 part /boot
└─mmcblk1p2 179:2 0 229.6G 0 part /
mmcblk1boot0 179:32 0 4M 1 disk
mmcblk1boot1 179:64 0 4M 1 disk
zram0 254:0 0 7.8G 0 disk [SWAP]
zram1 254:1 0 200M 0 disk /var/log
nvme0n1 259:0 0 931.5G 0 disk
├─nvme0n1p1 259:1 0 1G 0 part
└─nvme0n1p2 259:2 0 2.5G 0 part
此时板子上连接的存储设备有固态硬盘和EMMC,其中EMMC的容量为256GB,固态硬盘的容量为1TB。显然,nvme0n1是固态硬盘的设备名,mmcblk1是EMMC的设备名,由此可知u-boot环境下boot_targets变量的值中,mmc1代指EMMC,nvme代指固态硬盘,mmc0代指SD卡。
弄清楚了boot_targets变量中每一个代号的含义,接下来我们就可以用u-boot命令来修改boot_targets的值。
此时,我们使用reboot命令将板子重启,随后使用Hotkey进入u-boot命令行。
进入命令行之后,依次执行下列语句,修改boot_targets的值:
shell
opi# setenv boot_targets=nvme mmc1 mmc0 mtd2 mtd1 mtd0 usb0 pxe dhcp
opi# saveenv
opi# print boot_targets
u-boot命令行的输出为:

似乎是修改成功了,那就reset之后再进入u-boot命令行看一下是否生效吧:

怎么回事?之前在命令行中分明已经修改了,为何还是原来的值呢?问题没有那么简单,恐怕要动u-boot底层的代码才能解决问题了!
1.2 出错的原因
先给出修改不成功的原因:厂家在设计原始u-boot程序的时候为了不让开发板被用户变成**"砖",做了最保险的处理,那就是 不允许用户直接在u-boot命令行使用setenv命令来修改该环境下的任何一个值**,这样就防止了因用户私自修改而造成的系统无法正常启动的问题。
至于说在源码中厂家是如何实现这一点的,也许与其中一个名叫CONFIG_ENV_IS_NOWHERE的配置项有关。
2 修改并编译 u-boot 源码
既然在u-boot命令行下不能直接修改boot_targets的值,那么最终可行的方法就只有在源码中手动调整启动优先级,重新编译源码之后再更新uboot。
在修改源码前先明确一件事,那就是官方给出的u-boot源码和内核源码在比ubuntu 22.04低版本的系统中也是能正常编译的。本人在ubuntu 20.04的系统中正常编译了u-boot源码并生成了.deb文件,其实官方提出的那句***"当前使用的 Ubuntu 版本不符合22.04的要求, 请更换系统后再进行下面的操作"*** 担心是多余的。
2.1 u-boot 源码的编译方法
为了少踩坑,本文就使用OrangePi官方文档给出的orangepi-build来进行编译,依照官方文档给出的方法,先从Github上克隆orangepi-build:
shell
git clone https://github.com/orangepi-xunlong/orangepi-build.git
如克隆失败,可直接去该链接下载源码zip压缩包。
orangepi-build文件夹中有一键源码下载的shell脚本,即build.sh,第一次执行该脚本时,程序首先会下载所有的交叉编译工具链,这一步如果保证网络通常,一般就能全部下载成功,下载成功的所有交叉编译工具链都存放在toolchains文件夹下。
由于代码量庞大且网络拥塞,build.sh直接从Github上克隆源码不太现实。此时我们还是直接去下面源码的链接中下载zip文件包。先使用mkdir命令创建一个名为u-boot的文件夹:
shell
cd orangepi-build & mkdir u-boot
然后从下面的代码地址中下载源码的zip文件包:
shell
https://github.com/orangepi-xunlong/u-boot-orangepi/tree/v2017.09-rk3588
文件包下载之后,使用unzip解压,并改名:
shell
unzip u-boot-orangepi-2017.09-rk3588.zip & mv ./u-boot-orangepi-2017.09-rk3588 v2017.09-rk3588
之后使用build.sh再编译,就跳过了下载源码的过程,直接进入源码编译选项。
源码编译选项中选择板子类型为OrangePi-5Max-->u-boot编译-->legacy版本,随后源码开始编译。
编译结束之后,会在orangepi-build目录下生成一个output文件夹,最终生成的是一个.deb文件,该文件的路径为:
shell
orangepi_build/output/debs/u-boot/linux-u-boot-legacy-orangepi5max_1.0.2_arm64.deb
该.deb文件就是用于u-boot更新的软件包。
2.2 u-boot 源码的修改
本人需要将外设启动的优先级调整为Nvme固态硬盘-->SD卡-->EMMC模块,则对应的boot_targets的值应该改为:
shell
boot_targets=nvme mmc0 mmc1 mtd2 mtd1 mtd0 usb0 pxe dhcp
查看u-boot源码,可以找到boot_targets在文件v2017.09-rk3588/include/config_distro_bootcmd.h中:
c
#define BOOTENV_BOOT_TARGETS \
"boot_targets=" BOOT_TARGET_DEVICES(BOOTENV_DEV_NAME) "\0"
可以找到BOOT_TARGET_DEVICES在文件v2017.09-rk3588/include/configs/rockchip-common.h中被定义:
c
#define BOOT_TARGET_DEVICES(func) \
BOOT_TARGET_MMC(func) \
BOOT_TARGET_NVME(func) \
BOOT_TARGET_SCSI(func) \
BOOT_TARGET_USB(func) \
BOOT_TARGET_MTD(func) \
BOOT_TARGET_RKNAND(func) \
BOOT_TARGET_PXE(func) \
BOOT_TARGET_DHCP(func)
很明显,此处启动的顺序和u-boot命令行中打印出来的顺序一模一样,说明此处的内容是需要修改的,修改的顺序应该为:
c
#define BOOT_TARGET_DEVICES(func) \
BOOT_TARGET_NVME(func) \
BOOT_TARGET_MMC(func) \
BOOT_TARGET_SCSI(func) \
BOOT_TARGET_USB(func) \
BOOT_TARGET_MTD(func) \
BOOT_TARGET_RKNAND(func) \
BOOT_TARGET_PXE(func) \
BOOT_TARGET_DHCP(func)
除此之外,我们还能从源码中看到另一段系统启动顺序的判断:
#define RKIMG_DET_BOOTDEV \
"rkimg_bootdev=" \
"if nvme dev 0; then " \
...
"fi; \0" \
"rkimg_bootdev_download=" \
"scsi scan;" \
"nvme scan;" \
"if mmc dev 1; then " \
...
"fi; \0"
上面这段代码的给出了系统正常启动时扫描外部设备的顺序和加载外部镜像时检测外部设备的顺序。为了达到我们希望的顺序,则应该改为:
c
#define RKIMG_DET_BOOTDEV \
"rkimg_bootdev=" \
"pci_init; nvme scan; " \
"if nvme dev 0; then " \
"setenv devtype nvme; setenv devnum 0; echo Boot from nvme;" \
"elif mmc dev 1; then " \
"setenv devtype mmc; setenv devnum 1; echo Boot from SDcard;" \
"elif mmc dev 0; then " \
"setenv devtype mmc; setenv devnum 0; echo Boot from eMMC;" \
"elif mtd_blk dev 0; then " \
"setenv devtype mtd; setenv devnum 0;" \
"elif mtd_blk dev 1; then " \
"setenv devtype mtd; setenv devnum 1;" \
"elif mtd_blk dev 2; then " \
"setenv devtype mtd; setenv devnum 2;" \
"elif rknand dev 0; then " \
"setenv devtype rknand; setenv devnum 0;" \
"elif rksfc dev 0; then " \
"setenv devtype spinand; setenv devnum 0;" \
"elif rksfc dev 1; then " \
"setenv devtype spinor; setenv devnum 1;" \
"else" \
"setenv devtype ramdisk; setenv devnum 0;" \
"fi; \0" \
"rkimg_bootdev_download=" \
"pci_init; scsi scan; nvme scan;" \
"if nvme dev 0; then " \
"setenv devtype nvme; setenv devnum 0;" \
"elif mmc dev 1; then " \
"setenv devtype mmc; setenv devnum 1;" \
"elif mmc dev 0; then " \
"setenv devtype mmc; setenv devnum 0;" \
"elif scsi dev 0; then " \
"setenv devtype scsi; setenv devnum 0;" \
"fi; \0"
开始执行RKIMG_DET_BOOTDEV时,先扫描PCIe/Nvme接口是否有固态硬盘接入,然后再进行判断。
将需要修改的代码整合到一个头文件里,并将该头文件命名为**orangepi_5_max_boot.h**,该文件的路径与rockchip-common.h一致:
c
#undef RKIMG_DET_BOOTDEV
#define RKIMG_DET_BOOTDEV \
"rkimg_bootdev=" \
"pci_init; nvme scan; " \
"if nvme dev 0; then " \
"setenv devtype nvme; setenv devnum 0; echo Boot from nvme;" \
"elif mmc dev 1; then " \
"setenv devtype mmc; setenv devnum 1; echo Boot from SDcard;" \
"elif mmc dev 0; then " \
"setenv devtype mmc; setenv devnum 0; echo Boot from eMMC;" \
"elif mtd_blk dev 0; then " \
"setenv devtype mtd; setenv devnum 0;" \
"elif mtd_blk dev 1; then " \
"setenv devtype mtd; setenv devnum 1;" \
"elif mtd_blk dev 2; then " \
"setenv devtype mtd; setenv devnum 2;" \
"elif rknand dev 0; then " \
"setenv devtype rknand; setenv devnum 0;" \
"elif rksfc dev 0; then " \
"setenv devtype spinand; setenv devnum 0;" \
"elif rksfc dev 1; then " \
"setenv devtype spinor; setenv devnum 1;" \
"else" \
"setenv devtype ramdisk; setenv devnum 0;" \
"fi; \0" \
"rkimg_bootdev_download=" \
"pci_init; scsi scan; nvme scan;" \
"if nvme dev 0; then " \
"setenv devtype nvme; setenv devnum 0;" \
"elif mmc dev 1; then " \
"setenv devtype mmc; setenv devnum 1;" \
"elif mmc dev 0; then " \
"setenv devtype mmc; setenv devnum 0;" \
"elif scsi dev 0; then " \
"setenv devtype scsi; setenv devnum 0;" \
"fi; \0"
#undef BOOT_TARGET_MMC
#define BOOT_TARGET_MMC(func) \
func(MMC, mmc, 1) \
func(MMC, mmc, 0)
#undef BOOT_TARGET_DEVICES
#define BOOT_TARGET_DEVICES(func) \
BOOT_TARGET_NVME(func) \
BOOT_TARGET_MMC(func) \
BOOT_TARGET_SCSI(func) \
BOOT_TARGET_USB(func) \
BOOT_TARGET_MTD(func) \
BOOT_TARGET_RKNAND(func) \
BOOT_TARGET_PXE(func) \
BOOT_TARGET_DHCP(func)
代码中使用了#undef,用于取消在其他头文件中的宏定义,然后再重新定义宏。
将新建的头文件orangepi_5_max_boot.h在v2017.09-rk3588/include/configs/rk3588_common.h中包含:
c
#include "rockchip-common.h"
#if defined(CONFIG_ORANGEPI_5_MAX)
#include "orangepi_5_max_boot.h"
#endif
将CONFIG_ORANGEPI_5_MAX加入板子对应的defconfig文件v2017.09-rk3588/configs/orangepi_5_max_defconfig中:
c
CONFIG_ORANGEPI_5_MAX=y
最后在v2017.09-rk3588/board/rockchip/evb_rk3588/Kconfig文件中添加以下内容:
c
config ORANGEPI_5_MAX
bool "Orange Pi 5 Max boot order (NVMe, SD, eMMC)"
depends on TARGET_EVB_RK3588
default n
help
Override Rockchip boot device scan: try NVMe first, then SD
(mmc 1), then eMMC (mmc 0). Enable for rk3588-orangepi-5-max
boards.
至此,u-boot源码修改完毕。
2.3 u-boot 的更新
编译生成的.deb文件直接按照官方给出的方法更新即可:
先删除原有的u-boot配置:
shell
sudo apt purge -y linux-u-boot-orangepi5max-legacy
然后使用lrzsz或者scp上传编译生成的.deb文件。
接着安装deb包:
shell
sudo dpkg -i linux-u-boot-legacy-orangepi5max_1.0.2_arm64.deb
然后使用nand-sata-install命令更新板载Flash中的u-boot,在图形化界面中选择选项7。
至此,系统启动优先级的修改工作完成。
3 后记
本人修改启动优先级的目的是想在不拆下EMMC模块的情况下来备份和烧写EMMC开发环境,虽然OrangePi-5Max使用的EMMC不是焊死在板子上的,但此工作的意义在于给其他不同型号的OrangePi开发板提供了一套备份其EMMC的方案,例如CM5核心板。
从读写速度上来看,Nvme固态硬盘的读写速度最快,EMMC次之,最慢是SD卡,因此当需要大规模复制EMMC开发环境时,使用固态硬盘作为外接设备来进行此工作是最佳选择。但OrangePi系列开发板的出厂u-boot均不允许用户在u-boot命令行下修改变量值,且将EMMC的系统启动优先级设在固态硬盘之前,所以此时就需要从u-boot源码入手进行修改。
复制EMMC开发环境的过程中,EMMC是外接设备,固态硬盘才是启动设备,因此我们可以使用烧写在固态硬盘上的操作系统来制作EMMC的镜像,在镜像制作完之后,我们将固态硬盘拆下来装到其他待烧写系统的板子上,然后此时就可以烧写新EMMC了,本文所作工作的直接运用就在于此。