启动开发指南
1. 概述
1.1 编写目的
主要介绍SpacemiT的刷机启动流程和自定义配置,以及uboot、opensbi相关的驱动开发,基本调试方法,方便开发者快速上手或者二次开发。
1.2 适用范围
本文档适用于SpacemiT的K1系列SOC。
1.3 相关人员
- 刷机启动开发工程师
- 内核开发工程师
1.4 文档结构
本文档先 介绍刷机启动相关的流程和配置方式,然后再介绍uboot/opensbi的编译配置和驱动开发调试,最后记录一些常见的问题处理
2. 刷机启动
本章节介绍刷机和启动相关的配置以及实现原理,并提供自定义刷机、启动的方式。 注意,刷机与启动是相互关联的,如果需要对刷机做自定义配置,启动可能也需要做相关的调整,反之亦然。
2.1 固件布局
K1系列SOC常见的固件布局有以下三种
-
emmc 1.如上图所示,emmc有boot0和user_data_area两个区域。这是它的存储特点决定的,不做多展开。 2.bootinfo_emmc.bin和FSBL.bin存储在boot0区域。user_data_area区域用gpt管理分区表,user区域的fsbl分区里面实际上没有内容。brom启动后会从boot0区域加载bootinfo_emmc.bin和FSBL.bin。
-
sdcard 1.类似emmc,但sdcard只有user_data_area区域。bootinfo_sd.bin占用前面80 byte,gpt表在1 blk位置。
-
nor+ssd
1.包含nor上固件(fsbl+opensbi+uboot),ssd上固件(bootfs+rootfs) 2.也支持nor+emmc等blk设备,默认已适配。假如没有插入ssd,只有nor+emmc,拨码开关拨向nor启动,则刷机启动为nor+emmc 3.仅支持pcie0接口插入的ssd作为刷机启动介质,如果ssd插入非pcie0接口,则仅作为存储介质。 4.如果由taitanflaser工具刷机,ssd上的分区 表实际会包含fsbl/ubot/opensbi,但分区内容没有产生作用。这个是由实际的分区表决定
2.2 刷机流程和配置
以下内容包含的刷机、烧写为同一个概念。卡量产本质上也是刷机,只是数据来源于sd卡。 卡量产和卡启动为两个不同的概念,卡启动是将镜像烧写到sdcard里面,从sd卡里面启动系统。
2.2.1 刷机流程
刷机流程实际上是通过fastboot协议,将PC端的镜像传输到机子,然后通过mmc/mtd/nvme等接口写入到对应的存储介质。 完整的刷机流程包括进入uboot fastboot模式,镜像传输,存储介质设备检测,创建gpt表,写入数据到存储介质等内容。
2.2.1.1 uboot fastboot模式
本平台刷机通讯协议是基于fastboot协议,并做了一些自定义扩展,所有实际的刷机行为都是基于uboot fastboot模式下开展。 进入uboot fastboot模式后,通过PC端执行fastboot devices能检测到设备(需要配置PC fastboot环境)
~$ fastboot devices
c3bc939586f0 Android Fastboot
以下介绍进入uboot fastboot模式的三种方法。 1.通过板子上按键组合fel键+reset键进入brom-fastboot,然后在上位机(PC)执行以下命令,板子进入uboot刷机交互界面。brom-fastboot仅用于加载启动uboot fastboot
fastboot stage factory/FSBL.bin
fastboot continue
#sleep wait for uboot ready
#linux环境下
sleep 1
#windows环境下
#timeout /t 1 >null
fastboot stage u-boot.itb
fastboot continue
2.对于设备已经启动到os,可通过上位机(PC)运行adb reboot bootloader,板子进入uboot刷机交互界面。(某些固件会去掉adb功能,该方式并不是通用的方式) 3.板子启动时通过串口长按s键进入uboot shell,然后串 口执行fastboot 0进入uboot刷机交互界面。
以下介绍各种组合介质的刷机命令,其中brom可根据boot pin切换不同的启动介质(nor/nand/emmc),这个依赖硬件设计,详情请查看硬件参考设计。
2.2.1.2 emmc刷机
- emmc刷机流程
对于emmc的刷机流程如下,前面部分是从brom-fastboot启动到uboot-fastboot模式,下面其他的介质也一样
fastboot stage factory/FSBL.bin
fastboot continue
#sleep to wait for uboot ready
#linux环境下
sleep 1
#windows环境下
#timeout /t 1 >null
fastboot stage u-boot.itb
fastboot continue
fastboot flash gpt partition_universal.json
#bootinfo_emmc.bin内容无实际作用,请参考FAQ 6.4章节。但刷写步骤还需执行
fastboot flash bootinfo factory/bootinfo_emmc.bin
fastboot flash fsbl factory/FSBL.bin
fastboot flash env env.bin
fastboot flash opensbi fw_dynamic.itb
fastboot flash uboot u-boot.itb
fastboot flash bootfs bootfs.img
fastboot flash rootfs rootfs.ext4
对于emmc,bootinfo_emmc.bin的内容固化在uboot代码里面,这是为了用同一份partition_universal.json分区表兼容卡启动的制作等。且bootinfo_emmc.bin和fsbl.bin实际上是写入到emmc的boot0分区
- emmc分区表配置
分区表保存在buildroot-ext/board/spacemit/k1/partition_universal.json。其中bootinfo不作为一个显式的分区,用于保存启动介质相关的信息。
{
"version": "1.0",
"format": "gpt",
"partitions": [
{
"name": "bootinfo",
"offset": "0",
"size": "80B",
"image": "factory/bootinfo_sd.bin"
},
{
"name": "fsbl",
"offset": "256K",
"size": "256K",
"image": "factory/FSBL.bin"
},
{
"name": "env",
"offset": "512K",
"size": "128K"
},
{
"name": "opensbi",
"offset": "1M",
"size": "1M",
"image": "opensbi.itb"
},
{
"name": "uboot",
"offset": "2M",
"size": "2M",
"image": "u-boot.itb"
},
{
"name": "bootfs",
"offset": "4M",
"size": "128M",
"image": "bootfs.img"
},
{
"name": "rootfs",
"size": "-"
}
]
}
2.2.1.3 nor+blk设备刷机
- nor+blk刷机流程
k1支持nor+ssd/emmc等blk设备的组合刷机启动,且为自适应的方式,如果同时存在ssd和emmc,默认烧写到ssd
fastboot stage factory/FSBL.bin
fastboot continue
#sleep wait for uboot ready
#linux环境下
sleep 1
#windows环境下
#timeout /t 1 >null
fastboot stage u-boot.itb
fastboot continue
#刷spi nor
fastboot flash mtd partition_2M.json
fastboot flash bootinfo factory/bootinfo_spinor.bin
fastboot flash fsbl factory/FSBL.bin
fastboot flash env env.bin
fastboot flash opensbi fw_dynamic.itb
fastboot flash uboot u-boot.itb
#刷block设备
#刷机工具会根据实际的分区表烧写镜像,所以对于nor+blk,blk设备的fsbl/uboot/等分区并没有实际作用
fastboot flash gpt partition_universal.json
fastboot flash bootfs bootfs.img
fastboot flash rootfs rootfs.ext4
- nor+blk分区表配置
nor分区表,如buildroot-ext/board/spacemit/k1/partition_2M.json。对于nand/nor设备,分区表都是以partiton_xM.json命名,且需要根据实际的flash容量重命名,否则会导致刷机时找不到对应的分区表。 nor/nand的分区表会向最小容量兼容,比如nor的介质容量为8MB,刷机包里面只有partition_2M.json,则会匹配到partition_2M.json的分区表。
对于分区表配置,mtd存储介质(nand/nor flash)会以partition_2M.json等容量的形式表示,blk设备(包括emmc/sd/ssd等)都以partition_universal.json命名。
Nor flash分区表修改:
1.分区起始地址和size默认以64KB对齐(对应erasesize 为64KB)。
2.如果起始地址和size需要更改成4KB对齐,则要开启uboot的编译配置CONFIG_SPI_FLASH_USE_4K_SECTORS
//buildroot-ext/board/spacemit/k1/partition_2M.json
{
"version": "1.0",
"format": "mtd",
"partitions": [
{
"name": "bootinfo",
"offset": "0",
"size": "128K",
"image": "factory/bootinfo_spinor.bin"
},
{
"name": "fsbl",
"offset": "128K",
"size": "256K",
"image": "factory/FSBL.bin"
},
{
"name": "env",
"offset": "384K",
"size": "64K"
},
{
"name": "opensbi",
"offset": "448K",
"size": "192K",
"image": "fw_dynamic.itb"
},
{
"name": "uboot",
"offset": "640K",
"size": "-",
"image": "u-boot.itb"
}
]
}
ssd分区表。对于blk设备的分区表,都是partition_universal.json。此时bootinfo、fsbl、env、opensbi、uboot等分区以及里面的数据不会影响到正常的启动。
//buildroot-ext/board/spacemit/k1/partition_universal.json
{
"version": "1.0",
"format": "gpt",
"partitions": [
{
"name": "bootinfo",
"offset": "0K",
"size": "512",
"image": "factory/bootinfo_sd.bin",
"holes": "{\"(80;512)\"}"
},
{
"name": "fsbl",
"offset": "128K",
"size": "256K",
"image": "factory/FSBL.bin"
},
{
"name": "env",
"offset":"384K",
"size": "64K",
"image": "env.bin"
},
{
"name": "opensbi",
"size": "384K",
"image": "fw_dynamic.itb"
},
{
"name": "uboot",
"size": "2M",
"image": "u-boot.itb"
},
{
"name": "bootfs",
"offset": "4M",
"size": "256M",
"image": "bootfs.img",
"compress": "gzip-5"
},
{
"name": "rootfs",
"offset": "260M",
"size": "-",
"image": "rootfs.ext4",
"compress": "gzip-5"
}
]
}
2.2.1.4 nand 刷机启动
- nand刷机流程
k1支持nand刷机启动。但由于nand与nor共用同一个spi接口,只能选择其中一个方案,默认支持nor启动。 如需支持nand刷机启动,请参考刷机启动中的nand启动配置章节,配置nand启动。
fastboot stage factory/FSBL.bin
fastboot continue
#sleep wait for uboot ready
#linux环境下
sleep 1
#windows环境下
#timeout /t 1 >null
fastboot stage u-boot.itb
fastboot continue
fastboot flash mtd partition_64M.json
fastboot flash bootinfo factory/bootinfo_spinand.bin
fastboot flash fsbl factory/FSBL.bin
fastboot flash env env.bin
fastboot flash opensbi fw_dynamic.itb
fastboot flash uboot u-boot.itb
fastboot flash user-bootfs bootfs.img
fastboot flash user-rootfs rootfs.img
nand上文件系统管理依赖ubifs,其中bootfs和rootfs分区制作方法如下,
#制作bootfs.img
mkfs.ubifs -F -m 2048 -e 124KiB -c 8124 -x zlib -o output/bootfs.img -d input_bootfs/
#制作rootfs.img
mkfs.ubifs -F -m 2048 -e 124KiB -c 8124 -x zlib -o output/rootfs.img -d input_rootfs/
#input_bootfs和input_rootfs应该存放bootfs和rootfs中的文件
#例如对于bootfs,应该放入如下文件Image.itb、env_k1-x.txt、k1-x.bmp
#不同的nand需要修改对应的参数,以下是一些参数说明,具体可查看mkfs.ubifs的用法
-m 2048:设置最小输入/输出单元大小为 2048 字节(2 KiB),要与NAND Flash 的页大小相匹配。
-e 124KiB:设置逻辑擦除块的大小为 124 KiB(千字节),这应小于实际的物理擦除块的大小,以留出空间用于 UBI 的管理结构。
-c 8124:设置最大逻辑擦除块的数量为 8124,这个数字限制了文件系统的最大大小,基于逻辑擦除块的大小和数量计算。
-x zlib:设置压缩类型为 zlib,这将在文件系统上对数据进行 zlib 压缩。
-o output/ubifs.img:指定输出文件名,将生成的 UBIFS 文件系统镜像保存为 output/ubifs.img。
-d input/:指定源目录为 input/,mkfs.ubifs 将会把这个目录下的文件和文件结构创建成 UBIFS 文件系统镜像。
同时,本平台也支持nand+blk设备启动方式,刷机命令可参考nor+blk设备刷机章节。
- nand分区表配置
如256MB容量的nand,分区表为partition_256M.json
//buildroot-ext/board/spacemit/k1/partition_64M.json
{
"version": "1.0",
"format": "mtd",
"partitions": [
{
"name": "bootinfo",
"comment": "private partition, should not overrided it",
"offset": "0",
"size": "128K",
"image": "factory/bootinfo_spinand.bin"
},
{
"name": "fsbl",
"offset": "128K",
"size": "256K",
"image": "factory/FSBL.bin"
},
{
"name": "env",
"offset": "384K",
"size": "128K"
},
{
"name": "opensbi",
"offset": "640K",
"size": "384K",
"image": "opensbi.itb"
},
{
"name": "uboot",
"offset": "1M",
"size": "2M",
"image": "u-boot.itb"
},
{
"name": "user",
"offset": "3M",
"size": "-",
"volume_images": {"bootfs": "bootfs.img", "rootfs": "rootfs.img"}
}
]
}
2.2.1.5 卡启动
卡启动是指将镜像写入到sd卡中,设备插入该sd卡后,上电启动将从sd卡加载启动系统。
K1平台支持卡启动,且默认先try sd卡,启动失败后再从pin select选中的存储介质加载启动系统。
本平台不支持通过fastboot协议把镜像烧写到sd卡上。需要通过TitanFlash工具或者dd命令制作卡启动。
以下介绍如何通过刷机工具制作卡启动镜像:
- 将tf卡插入读卡器并接入电脑usb口。
- 电脑端打开titanflash刷机工具(安装方式请参考刷机工具使用章节),点击研发工具->卡启动,选择SD卡,刷机包。点击执行。
- 烧写成功后,将tf卡插入设备,上电后设备从卡启动。
2.2.1.6 卡量产
卡量产是指sd卡中写入刷机相关的镜像文件,设备上电启动后先从卡启动,检测到时卡量产后,将sd卡中的镜像文件,按照分区表的位置写入到pin select选中的存储介质
K1支持卡量产烧录。通过titanflash工具把sd卡制作成量产卡,将sd卡插入设备,上电启动后, 会将镜像烧写到emmc/nor/nand等存储介质中。
卡 量产的制作步骤如下:
- 将tf卡插入读卡器并接入电脑usb口。
- 打开titanflash工具,选择量产工具->制作量产卡,选择烧录量产卡,点击执行。
- 完成量产卡制作后,将sd卡插入设备,上电启动后会自动进入卡烧录。
- 完成烧录后需要拔下sd卡,设备端上电启动后能正常启动。(如果不拔下sd卡,重复上电会重复开始卡烧录)
2.2.2 刷机工具
本章节简单介绍刷机工具相关的内容
2.2.2.1 刷机工具使用
对于镜像的烧写,可以选择安装titanflash工具,或者使用fastboot工具刷机。 固件生成方式可参考文档下载和编译。
-
titanflash刷机工具针对完整刷机包,适用于一般的开发者。 刷机工具安装及使用方式,可参考这篇文档
-
fastboot工具针对单个分区的镜像烧写,适用于具备一定能力的开发者,单分区的镜像烧写错误可能会导致系统启动异常,需要谨慎使用,烧写流程请参考刷机流程章节(fastboot环境安装可参考网上链接,或者这篇)。
2.2.2.2 刷机介质选择
- 硬件提供boot download sel switch拨盘切换,可参考如MUSE Pi用户使用指南文档中的Boot Download Sel&JTAG Sel章节。
- 其他方案,请参考该方案的硬件用户使用指南
- 对于不同的启动介质,刷机方式已做成自动适配。
2.3 启动配置
本章节介绍emmc, sd, nor, nand启动的配置,以及自定义启动配置相关的注意事项。 刷机启动,需要配合正确的分区表等配置以及正确的启动配置。
2.3.1 启动流程
本章节介绍平台芯片的启动流程,并且提供定制化启动的修改方。如下框图, 启动流程主要分成brom->fsbl->opensbi->uboot->kernel等阶段。bootinfo提供fsbl所在偏移、大小、签名等信息
根据硬件的boot pin select,软件上会从sd或者emmc或者nor等介质加载启动下一级镜像,对于不同的启动介质,启动流程都与上图的一样。具体的boot pin select在刷机配置中的刷机介质选择章节做介绍。
K1上电启动后,会先try boot from sd,启动失败后再根据boot pin select,从emmc或者nand或者nor等存储介质加载uboot和opensbi的镜像。
以下小节将根据启动顺序,分别介绍bootinfo/fsbl/opensbi/uboot/kernel等启动配置
2.3.1.1 brom
brom是一段预编译好程序固化在SoC中,出厂即无法改变。芯片上电或复位会运行brom,通过不同的boot pin会指导brom从对应的存储介质加载bootinfo和fsbl。 Boot pin请参考刷机介质选择章节,这里不做展开。
2.3.1.2 bootinfo
brom启动后会从对应存储介质的物理0地址偏移获取booinfo信息(对于emmc会从boot0区域的0地址加载)。bootinfo包含fsbl、fsbl1的位置以及大小,crc值等信息。 其中,bootinfo描述文件在如下目录的.json
uboot-2022.10$ ls board/spacemit/k1-x/configs/
bootinfo_emmc.json bootinfo_sd.json bootinfo_spinand.json bootinfo_spinor.json
json文件举例说明,如下记录了各种存储介质的默认的fsbl/fsbl1位置, fsbl1为备份数据。如需修改偏移地址,可修改bootinfo_*json文件再重新编译uboot。
fsbl的位置尽量保持原厂设置,如果需要自定义,需要考虑不同存储介质的特性,如nand需要按sector对齐等。emmc的fsbl只支持在boot分区加载。
emmc:boot0:0x200, boot1:0x0
sd:0x20000/0x80000
nor:0x20000/0x70000
nand:0x20000/0x80000
编译uboot时在其根目录下生成对应的*.bin文件等bootinfo镜像文件,生成方式可参考uboot-2022.10/board/spacemit/k1-x/config.mk
uboot-2022.10$ ls -l
bootinfo_emmc.bin
bootinfo_sd.bin
bootinfo_spinand.bin
bootinfo_spinor.bin
2.3.1.3 fsbl
brom获取bootinfo后,会从指定的offset加载fsbl。 fsbl文件是由4K头信息+uboot-spl.bin组合而成,头信息会包含uboot-spl的一些必要信息,如crc校验值等 fsbl启动后,会先初始化ddr,然后从分区表加载opensbi、uboot到内存的指定位置,再运行opensbi,接着opensbi会启动uboot。(fsbl如何初始化ddr的内容不在本文档的描述范围内)
2.3.1.4 opensbi和uboot加载启动
不同存储介质对opensbi和uboot的加载方式是多样化的,以下按存储介质分类去描述spl加载启动uboot/opensbi的配置。 k1平台支持加载独立分区的opensbi和uboot,也支持加载一个镜像(uboot-opensbi.itb)的方式。默认以分区名加载。 加载启动一个镜像(uboot-opensbi.itb)的方式请参考FAQ部分。
2.3.1.4.1 emmc和sdcard
emmc与sd都是用到mmc驱动,uboot的代码流程上会根据启动的boot pin select选择emmc或sd。两者的启动配置类似。 opensbi和uboot有不同的image格式,根据存放的位置,可以分为裸分区、文件、fit格式等。 K1平台默认都会先try boot from sd,失败后再try其他介质(emmc/nor/nand等其中一个)。
spl-dts需要开启emmc/sd的配置
//uboot-2022.10/arch/riscv/dts/k1-x_spl.dts
/* eMMC */
sdh@d4281000 {
bus-width = <8>;
non-removable;
mmc-hs400-1_8v;
mmc-hs400-enhanced-strobe;
sdh-phy-module = <1>;
status = "okay";
u-boot,dm-spl;
};
/* sd */
sdh@d4280000 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_mmc1 &gpio80_pmx_func0>;
bus-width = <4>;
cap-sd-highspeed;
sdh-phy-module = <0>;
status = "okay";
u-boot,dm-spl;
};
以下针对emmc/sd的不同启动方式做介绍,默认以分区名加载。
- 裸分区方式
1.fsbl加载启动uboot/opensbi流程(以emmc为例,对于sd/nor等介质类似,限于篇幅不做赘述)
执行make uboot_menuconfig,进入spl的编译配置(所有spl相关的配置的入口,下面的不做赘述)
选中以分区表的方式加载,这里默认支持uboot/opensbi独立加载。spl会先找名称为opensbi、uboot的分区,如果找不到该分区名,则尝试分别以分区号1和2加载raw数据。
spl加载好opensbi和uboot后,会启动opensbi,并传入uboot的内存地址和dtb信息,opensbi根据地址和dtb启动uboot
- 绝对偏移
通过绝对偏移地址加载opensbi/uboot 开启以下配置,指定emmc的偏移地址。这时候会按照emmc的绝对偏移地址加载uboot/opensbi镜像。 该种方式不支持opensbi和uboot分开,也就是opensbi和uboot要打包成一个文件,即fit格式。 若想要支持独立加载,可参考uboot-2022.10/common/spl/spl_mmc.c中的case MMCSD_MODE_RAW部分代码
- 文件系统方式
更改spl通过文件系统加载opensbi/uboot uboot-spl支持通过文件系统的方式加载opensbi和uboot,该方式支持独立加载uboot、opensbi文件 make menuconfig打开以下配置,如图选中fat文件系统,加载opensbi.itb、uboot.itb
2.3.1.4.2 nor
对于nor介质启动,k1会提供nor(u-boot-spl/uboot/opensbi)+ssd(bootfs/rootfs)或者nor(u-boot-spl/uboot/opensbi)+emmc(bootfs/rootfs)的启动方式,默认先try ssd,
spl的dts配置如下
//uboot-2022.10/arch/riscv/dts/k1-x_spl.dts
spi@d420c000 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_qspi>;
u-boot,dm-spl;
spi-max-frequency = <15140000>;
flash@0 {
compatible = "jedec,spi-nor";
reg = <0>;
spi-max-frequency = <26500000>;
m25p,fast-read;
broken-flash-reset;
u-boot,dm-spl;
status = "okay";
};
};
spl支持nor加载启动,需要先开启以下配置,默认已开启 1.执行make uboot_menuconfig,选择SPL configuration options
2.开启“Support MTD drivers”、“Support SPI DM drivers in SPL”、“Support SPI drivers”、“Support SPI flash drivers”、“Support for SPI flash MTD drivers in SPL”、“Support loading from mtd device”, “Partition name to use to load U-Boot from”与分区表中的分区名保持一致。
3.blk设备配置 执行make uboot_menuconfig,选择Device Drivers --->Fastboot support 选择Support blk device,这里支持是ssd/emmc等blk设备,如ssd对应的是nvme,emmc对应是mmc。
4.对于mtd设备,需要开启env,以确保spl启动后能从env获取mtd分区信息 执行make uboot_menuconfig,选择Environment,选择开启spi的env加载。这里的env偏移地址需要与分区表的env分区保持一致,如0x80000。
5.spi flash驱动需要适配硬件上的spi flash对应厂商的型号(下面的nand也同理) 在menuconfig上选择对应的厂商manufactureID 执行make uboot_menuconfig,选择Device Drivers--->MTD Support--->SPI Flash Support 根据硬件的spi flash厂商,选择对应的驱动程序。
如果驱动里面都没有,可以在代码上直接添加。flash_name可以自定义,一般为硬件flash名称,0x1f4501为该flash的jedecid,其他参数可以根据该硬件的flash添加。
//uboot-2022.10/drivers/mtd/spi/spi-nor-ids.c
const struct flash_info spi_nor_ids[] = {
{ INFO("flash_name", 0x1f4501, 0, 64 * 1024, 16, SECT_4K) },
加载方式主要有两种,如下
- 裸分区方式
对于nor设备,会根据mtd分区表获取uboot/opensbi。配置方式如下
- 绝对偏移
nor启动支持以存储介质的绝对偏移加载启动,但不支持独立加载uboot、opensbi的方式,需把uboot和opensbi打包到一起,即fit格式。 开启以下配置,输入存储介质的绝对偏移地址
2.3.1.4.3 nand
对于nand介质启动,K1会提供nand(u-boot-spl/uboot/opensbi)+ssd(bootfs/rootfs)或者纯nand(u-boot-spl/uboot/opensbi/kernel)启动, 默认不开启nand启动。
spl-dts配置如下
//uboot-2022.10/arch/riscv/dts/k1-x_spl.dts
spi@d420c000 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_qspi>;
u-boot,dm-spl;
spi-max-frequency = <15140000>;
spi-nand@0 {
compatible = "spi-nand";
reg = <0>;
spi-tx-bus-width = <1>;
spi-rx-bus-width = <1>;
spi-max-frequency = <6250000>;
u-boot,dm-spl;
status = "okay";
};
};
下面将介绍纯nand启动方案配置。 执行make uboot_menuconfig,选择SPL configuration options
开启 Support MTD drivers Support SPI DM drivers in SPL Support SPI drivers Use standard NAND driver Support simple NAND drivers in SPL Support loading from mtd device, Partition name to use to load U-Boot from与分区表中的分区名保持一致。 如果开启opensbi/uboot独立镜像。则需要开启Second partition to use to load U-Boot from,且必须要保持opensbi/uboot的先后顺序。
对于mtd设备,需要开启env,以确保spl启动后能从env获取mtd分区信息 执行make uboot_menuconfig,选择Environment,选择开启spi的env加载。这里的env偏移地址需要与分区表的env分区保持一致,如0x80000。
nand flash驱动需要适配硬件上的spi flash对应厂商的型号。目前已支持的nand flash如下驱动所示。如果没有对应的驱动,可以在other.c驱动中添加厂商的jedecid
~/uboot-2022.10$ ls drivers/mtd/nand/spi/*.c
uboot-2022.10/drivers/mtd/nand/spi/core.c
uboot-2022.10/drivers/mtd/nand/spi/micron.c
uboot-2022.10/drivers/mtd/nand/spi/winbond.c
uboot-2022.10/drivers/mtd/nand/spi/gigadevice.c
uboot-2022.10/drivers/mtd/nand/spi/other.c
uboot-2022.10/drivers/mtd/nand/spi/macronix.c
uboot-2022.10/drivers/mtd/nand/spi/toshiba.c
//uboot-2022.10/drivers/mtd/nand/spi/other.c
static int other_spinand_detect(struct spinand_device *spinand)
{
u8 *id = spinand->id.data;
int ret = 0;
/*
* dosilicon nand flash
*/
if (id[1] == 0xe5)
ret = spinand_match_and_init(spinand, dosilicon_spinand_table,
ARRAY_SIZE(dosilicon_spinand_table),
id[2]);
/*FORESEE nand flash*/
if (id[1] == 0xcd)
ret = spinand_match_and_init(spinand, foresee_spinand_table,
ARRAY_SIZE(foresee_spinand_table),
id[2]);
if (ret)
return ret;
return 1;
}
2.3.1.5 启动kernel
uboot的env定义了多种启动组合,开发者可以根据需要自行定制。其中方案自带的k1-x_env.txt优先级更高,会覆盖env.bin相同的变量。 fsbl加载启动opensbi->uboot后,uboot通过文件系统load命令从fat或ext4文件系统里面加载kernel、dtb等镜像文件到内存,然后执行bootm命令启动kernel。 具体加载启动kernel的流程可以参考uboot-2022.10/board/spacemit/k1-x/k1-x.env,里面已包含emmc/sd/nor+blk等启动方案,且能自动适配对应的启动介质。
- mmc启动
对于sd/emmc,都属于mmc_boot
//uboot-2022.10/board/spacemit/k1-x/k1-x.env
run mmc_boot;
mmc_boot=echo "Try to boot from ${bootfs_devname}${boot_devnum} ..."; \
run commonargs; \
run set_mmc_root; \
run set_mmc_args; \
run detect_dtb; \
run loadknl; \
run loaddtb; \
run loadramdisk; \
bootm ${kernel_addr_r} ${ramdisk_combo} ${dtb_addr};
rootfs由uboot传递的bootargs到kernel,由kernel或者init脚本解析并挂载rootfs 如下字段root=/dev/mmcblk2p6即为rootfs分区
bootargs=earlycon=sbi earlyprintk console=tty1 console=ttyS0,115200 loglevel=8 clk_ignore_unused swiotlb=65536 rdinit=/init root=/dev/mmcblk2p6 rootwait rootfstype=ext4
- nor+blk启动
//uboot-2022.10/board/spacemit/k1-x/k1-x.env
run nor_boot;
nor_boot=echo "Try to boot from ${bootfs_devname}${boot_devnum} ..."; \
run commonargs; \
run set_nor_root; \
run set_nor_args; \
run detect_dtb; \
run loadknl; \
run loaddtb; \
run loadramdisk; \
bootm ${kernel_addr_r} ${ramdisk_combo} ${dtb_addr};
- nand启动
//uboot-2022.10/board/spacemit/k1-x/k1-x.env
run nand_boot;
//待适配
2.4 安全启动
安全启动基于fit image格式实现:
- opensbi、uboot.itb、kernel打包成fit image
- 打开代码中安全启动配置;
- 使用私钥对fit image做签名,并导出公钥到dtb中;
2.4.1 验签流程
启动验签流程如下图所示:
签名过程:
- Boot ROM做为信任根,不可更改;
- Hash of ROTPK烧录到芯片内部Efuse,只能烧录一次;
- 哈希算法为SHA256;
- 签名算法使用SHA256+RSA2048;
2.4.2 配置
- uboot编译配置
CONFIG_FIT_SIGNATURE=y
CONFIG_SPL_FIT_SIGNATURE=y
CONFIG_SHA256=y
CONFIG_SPL_SHA256=y
CONFIG_RSA=y
CONFIG_SPL_RSA=y
CONFIG_SPL_RSA_VERIFY=y
CONFIG_RSA_VERIFY=y
- opensbi编译配置
CONFIG_FIT_SIGNATURE=y
- kernel编译配置
CONFIG_FIT_SIGNATURE=y
2.4.3 公私钥
使用openssl生成私钥与证书,保护好私钥,发布证书。
# build private key without password
openssl genrsa -out prv-rsa.key 2048
# private key parse:
openssl rsa -in prv-rsa.key -text -noout
# build certificate that expired after 365 days
openssl req -batch -new -x509 -days 365 -key prv-rsa.key -out rsa.crt
# certificate parse:
openssl x509 -in rsa.crt -text -noout
# build public key from private key:
openssl rsa -in prv-rsa.key -pubout -out pub-rsa.key
# public key parse:
openssl rsa -in pub-rsa.key -pubin -noout -text
2.4.4 镜像签名
- 修改its文件,打开hash与签名配置
/dts-v1/;
/ {
description = "Configuration to load OpenSBI before U-Boot";
#address-cells = <2>;
fit,fdt-list = "of-list";
images {
opensbi {
description = "OpenSBI fw_dynamic Firmware";
type = "firmware";
os = "opensbi";
arch = "riscv";
compression = "none";
load = <0x0 0x0>;
entry = <0x0 0x0>;
data = /incbin/("./fw_dynamic.bin");
hash-1 {
algo = "sha256";
};
};
};
configurations {
default = "config_1";
config_1 {
description = "opensbi FIT config";
firmware = "opensbi";
signature {
algo = "sha256,rsa2048";
key-name-hint = "uboot_key_prv";
sign-images = "firmware";
};
};
};
};
- 使用私钥和证书,对fit image文件签名
# build empty dtb file, for next stage public key file output
printf "/dts-v1/;\n/ {\n};" > pubkey.dts
dtc -I dts -O dtb -o pubkey.dtb pubkey.dts
# build fit image
# input: fit script, folder contain private key and certification
# output: fit image, dtb that contain public key info
mkimage -f uboot_fdt_sign.its -K pubkey.dtb -k key -r u-boot.itb
# parse dtb file that public key info
fdtdump -s pubkey.dtb
-
更新public key info到上一级引导代码
- 如更新uboot签名所用私钥对应的公钥信息到FSBL dts中去
/ {
signature {
key-uboot_key_prv {
required = "conf";
algo = "sha256,rsa2048";
rsa,r-squared = <0x5353bc86 0x7070d595 0xe2ea6280 0xb9887ae1 0xf69bb145 0x161e6675 0x6f9d37dc 0x29646b18 0x0ecc66d1 0x0ef7fa25 0xddc925cf 0xf068e5e4 0x78e5b40b 0x124095c6 0x1282d13c 0x1bdf09d0 0x7ddf7bf4 0xb4e61d0b 0x8d68f15d 0xb77282df 0xb0b371d8 0xd887288d 0x6c2ee06e 0x4124c030 0xbcdb8688 0x13a6ea0a 0xbb8dc9d1 0xd4b8a0fd 0x141c1e45 0x91c77190 0xf2685d1e 0xa44e33eb 0x38a90bdf 0x671b076b 0x0efb5223 0x72762fd2 0xcbf35219 0x833553c7 0x91382847 0xa3806134 0xb785d6f6 0x64ba98d7 0x4f01bc2e 0x78e320dc 0x9233332c 0x8be5ebec 0x60605d78 0xd5e5741c 0x2980546e 0x6332d458 0x73023036 0xb5e64449 0xc3f81911 0xc7d57cad 0xf17d98b1 0x139801a2 0x778632bd 0xfc15d9ca 0x4f5fc152 0xa49e2b4f 0x6f09a6b5 0xecd52030 0x19022428 0x5907c874>;
rsa,modulus = <0xaa282eab 0xc7d0a288 0x5eee2ea1 0xd7d11bc5 0xaf57d029 0x4ad6c85f 0xedc802b1 0x227775cc 0x0d57d3de 0xc8e6113c 0xd3c238fd 0x03eecd4c 0x6983e4e0 0xd71eba6b 0xcdcc3c7f 0x6f602163 0x71e25d7e 0xd3ade9b9 0x25c9b950 0x4bf4d0a5 0xa067ca9c 0x64397ed2 0xd07dfa01 0x29102b9c 0x6008c40e 0xc55cc431 0xf3422d16 0xb8ade9d2 0xa8e5d3d1 0x40aca443 0x91603617 0x4159c91f 0xa10e3ef9 0xa21c40c7 0x377dfcc6 0xd831b829 0xd645d1b1 0xb04c534e 0xfd3352ef 0xdfe19a7d 0xf90c4295 0x7e753266 0x398ade75 0x85427a33 0x79412712 0x5dcd236d 0x015d8fb6 0xdde963ad 0xb8730cf5 0x45fc281b 0x1e40a1de 0xcd1d2af6 0x45ce6740 0x42e1e705 0x274af16a 0x50a66381 0xbb815c44 0x5222fe56 0x826e4475 0xd2193598 0x967573fd 0xc814bed6 0x95db8fae 0xe519808f>;
rsa,exponent = <0x00000000 0x00010001>;
rsa,n0-inverse = <0xfba86191>;
rsa,num-bits = <0x00000800>;
key-name-hint = "uboot_key_prv";
};
};
};
3. uboot功能与配置
本章节主要介绍uboot的功能以及常用的配置方法。
3.1 功能介绍
uboot的主要功能有以下几点:
- 加载启动内核
uboot从存储介质(emmc/sd/nand/nor/ssd等),加载内核镜像到内存指定位置,并启动内核。
- fastboot刷机功能
通过fastboot工具,烧写镜像到指定的分区位置。
- 开机logo
uboot启动阶段显示启动logo以及boot menu。
- 驱动调试
基于uboot调试设备驱动,如mmc/spi/nand/nor/nvme等驱动。uboot提供shell命令行对各个驱动进行功能调试。 uboot驱动在drivers/目录下。
3.2 编译
本章节介绍基于uboot代码环境,编译生成uboot的镜像文件。
- 编译配置
首次编译,或者需要重新选择其他方案,则需要先选择编译配置,这里以k1为例:
cd ~/uboot-2022.10
make ARCH=riscv k1_defconfig -C ~/uboot-2022.10/
可视化更改编译配置:
make ARCH=riscv menuconfig
通过键盘"Y"/"N"以开启/关闭相关的功能配置。保存后会更新到 uboot 根目录的.config 文件。
- 编译 uboot
cd ~/uboot-2022.10
GCC_PREFIX=riscv64-unknown-linux-gnu-
make ARCH=riscv CROSS_COMPILE=${GCC_PREFIX} -C ~/uboot-2022.10 -j4
- 编译产物
~/uboot-2022.10$ ls u-boot* -l
u-boot
u-boot.bin # uboot镜像
u-boot.dtb # dtb文件
u-boot-dtb.bin # 带dtb的uboot镜像
u-boot.itb # 将u-boot-nodtb.bin和dtb打包成fit格式
u-boot-nodtb.bin
bootinfo_emmc.bin # 用于emmc启动时记录spl位置的信息
bootinfo_sd.bin
bootinfo_spinand.bin
bootinfo_spinor.bin
FSBL.bin # u-boot-spl.bin加上头信息。由brom加载启动
k1-x_deb1.dtb # 方案deb1的dtb文件
k1-x_spl.dtb # spl的dtb文件
3.3 dts配置
uboot dts 配置在目录 uboot-2022.10/arch/riscv/dts/,根据不同的方案修改该方案的 dts,如 deb1 方案。
~/uboot-2022.10$ ls arch/riscv/dts/k1*.dts -l
arch/riscv/dts/k1-x_deb1.dts
arch/riscv/dts/k1-x_deb2.dts
arch/riscv/dts/k1-x_evb.dts
arch/riscv/dts/k1-x_fpga_1x4.dts
arch/riscv/dts/k1-x_fpga_2x2.dts
arch/riscv/dts/k1-x_fpga.dts
arch/riscv/dts/k1-x_spl.dts
4. uboot驱动开发调试
本章节主要介绍 uboot 的驱动使用和调试方法,默认情况下所有的驱动都已经做好配置。
4.1 boot kernel
本小节介绍 uboot 启动 kernel,以及分区的自定义配置和启动。
- 开发板上电启动后,立即按下键盘上的"s"键,进入 uboot shell
- 可通过执行 fastboot 0 进入 fastboot mode,通过电脑端的 fastboot stage Image 发送镜像到开发板。(或者其他下载镜像的方式,如 fatload 等命令)
- 执行 booti 启动 kernel(或者 bootm 启动 fit 格式镜像)
#下载kernel镜像
=> fastboot -l 0x40000000 0
Starting download of 50687488 bytes
...
downloading/uploading of 50687488 bytes finished
#电脑端执行命令
C:\Users>fastboot stage Z:\k1\output\Image
Sending 'Z:\k1\output\Image' (49499 KB) OKAY [ 1.934s]
Finished. Total time: 3.358s
#下载完成后,在uboot shell中,通过键盘输入CTRL+C退出fastboot mode。
#下载dtb
=> fastboot -l 0x50000000 0
Starting download of 33261 bytes
downloading/uploading of 33261 bytes finished
#电脑端执行命令
C:\Users>fastboot stage Z:\k1\output\k1-x_deb1.dtb
Sending 'Z:\k1\output\k1-x_deb1.dtb' (32 KB) OKAY [ 0.004s]
Finished. Total time: 0.054s
执行启动命令
=> booti 0x40000000 - 0x50000000
Moving Image from 0x40000000 to 0x200000, end=3d4f000
## Flattened Device Tree blob at 50000000
Booting using the fdt blob at 0x50000000
Using Device Tree in place at 0000000050000000, end 0000000050014896
Starting kernel ...
[ 0.000000] Linux version 6.1.15+ ... ...
[ 0.000000] OF: fdt: Ignoring memory range 0x0 - 0x200000
[ 0.000000] Machine model: spacemit k1-x deb1 board
[ 0.000000] earlycon: sbi0 at I/O port 0x0 (options '')
[ 0.000000] printk: bootconsole [sbi0] enabled
- 通过 bootm 命令启动 fit 格式镜像
假设 emmc 中分区 5 为 fat32 文件系统。且里面保存 uImage.itb 文件,通过以下命令加载启动 kernel。
=> fatls mmc 2:5
sdh@d4281000: 74 clk wait timeout(100)
50896911 uImage.itb
4671 env_k1-x.txt
2 file(s), 0 dir(s)
=> fatload mmc 2:5 0x40000000 uImage.itb
50896911 bytes read in 339 ms (143.2 MiB/s)
=> bootm 0x40000000
## Loading kernel from FIT Image at 40000000 ...
Boot from fit configuration k1_deb1
Using 'conf_2' configuration
Trying 'kernel' kernel subimage
Description: Vanilla Linux kernel
Type: Kernel Image
Compression: uncompressed
Data Start: 0x400000e8
Data Size: 50687488 Bytes = 48.3 MiB
Architecture: RISC-V
OS: Linux
Load Address: 0x01400000
Entry Point: 0x01400000
Verifying Hash Integrity ... OK
## Loading fdt from FIT Image at 40000000 ...
Using 'conf_2' configuration
Trying 'fdt_2' fdt subimage
Description: Flattened Device Tree blob for k1_deb1
Type: Flat Device Tree
Compression: uncompressed
Data Start: 0x43067c90
Data Size: 68940 Bytes = 67.3 KiB
Architecture: RISC-V
Load Address: 0x28000000
Verifying Hash Integrity ... OK
Loading fdt from 0x43067c90 to 0x28000000
Booting using the fdt blob at 0x28000000
Loading Kernel Image
Using Device Tree in place at 0000000028000000, end 0000000028013d4b
Starting kernel ...
[ 0.000000] Linux version 6.1.15+ ... ...
[ 0.000000] OF: fdt: Ignoring memory range 0x0 - 0x1400000
[ 0.000000] Machine model: spacemit k1-x deb1 board
[ 0.000000] earlycon: sbi0 at I/O port 0x0 (options '')
[ 0.000000] printk: bootconsole [sbi0] enabled
4.2 env
本章节介绍如何配置在 uboot 启动阶段,从指定存储介质加载 env。
- 执行 make menuconfig,进入 Environment,
目前支持可选的介质为 mmc,mtd 设备(其中 mtd 设备包括 spinor,spinand)。
env 的偏移地址需要根据分区表的配置来确定,具体可以参考刷机启动设置章节的分区表配置,默认是 0x80000。
(0x80000) Environment address #spinor的env偏移地址
(0x80000) Environment offset #mmc设备的env偏移地址
4.3 mmc
emmc 和 sd 卡都是使用到 mmc 驱动,dev number 分别为 2、0。
- config 配置
执行 make menuconfig,进入 Device Drivers--->MMC Host controller Support,开启以下配置
- dts 配置
//uboot-2022.10/arch/riscv/dts/k1-x.dtsi
sdhci0: sdh@d4280000 {
compatible = "spacemit,k1-x-sdhci";
reg = <0x0 0xd4280000 0x0 0x200>;
interrupt-parent = <&intc>;
interrupts = <99>;
resets = <&reset RESET_SDH_AXI>,
<&reset RESET_SDH0>;
reset-names = "sdh_axi", "sdh0";
clocks = <&ccu CLK_SDH0>,
<&ccu CLK_SDH_AXI>;
clock-names = "sdh-io", "sdh-core";
status = "disabled";
};
sdhci2: sdh@d4281000 {
compatible = "spacemit,k1-x-sdhci";
reg = <0x0 0xd4281000 0x0 0x200>;
interrupt-parent = <&intc>;
interrupts = <101>;
resets = <&reset RESET_SDH_AXI>,
<&reset RESET_SDH2>;
reset-names = "sdh_axi", "sdh2";
clocks = <&ccu CLK_SDH2>,
<&ccu CLK_SDH_AXI>;
clock-names = "sdh-io", "sdh-core";
status = "disabled";
};
//uboot-2022.10/arch/riscv/dts/k1-x_deb1.dts
&sdhci0 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_mmc1 &gpio80_pmx_func0>;
bus-width = <4>;
cd-gpios = <&gpio 80 0>;
cd-inverted;
cap-sd-highspeed;
sdh-phy-module = <0>;
status = "okay";
};
/* eMMC */
&sdhci2 {
bus-width = <8>;
non-removable;
mmc-hs400-1_8v;
mmc-hs400-enhanced-strobe;
sdh-phy-module = <1>;
status = "okay";
};
- 调试验证
uboot shell 提供命令行调试 mmc 驱动,需要开启编译配置项 CONFIG_CMD_MMC
=> mmc list
sdh@d4280000: 0 (SD)
sdh@d4281000: 2 (eMMC)
=> mmc dev 2 #切换到emmc
switch to partitions #0, OK
mmc2(part 0) is current device
#read 0偏移的0x1000个blk_cnt到内存0x40000000
=> mmc read 0x40000000 0 0x1000
MMC read: dev # 2, block # 0, count 4096 ... 4096 blocks read: OK
#从内存地址0x40000000 写到0x1000个blk_cnt到0偏移
=> mmc write 0x40000000 0 0x1000
MMC write: dev # 2, block # 0, count 4096 ... 4096 blocks written: OK
#其他用法可参考mmc -h
- 常用接口
参考 cmd/mmc.c 中的接口
4.4 nvme
nvme 驱动主要用于调试 ssd 硬盘。
- config 配置
执行 make menuconfig,进入 Device Driver,开启以下配置,
- dts 配置
//uboot-2022.10/arch/riscv/dts/k1-x.dtsi
pcie1_rc: pcie@ca400000 {
compatible = "k1x,dwc-pcie";
reg = <0x0 0xca400000 0x0 0x00001000>, /* dbi */
<0x0 0xca700000 0x0 0x0001ff24>, /* atu registers */
<0x0 0x90000000 0x0 0x00100000>, /* config space */
<0x0 0xd4282bd4 0x0 0x00000008>, /*k1x soc config addr*/
<0x0 0xc0c20000 0x0 0x00001000>, /* phy ahb */
<0x0 0xc0c10000 0x0 0x00001000>, /* phy addr */
<0x0 0xd4282bcc 0x0 0x00000008>, /* conf0 addr */
<0x0 0xc0b10000 0x0 0x00001000>; /* phy0 addr */
reg-names = "dbi", "atu", "config",
"k1x_conf", "phy_ahb",
"phy_addr", "conf0_addr",
"phy0_addr";
k1x,pcie-port = <1>;
clocks = <&ccu CLK_PCIE1>;
clock-names = "pcie-clk";
resets = <&reset RESET_PCIE1>;
reset-names = "pcie-reset";
bus-range = <0x00 0xff>;
max-link-speed = <2>;
num-lanes = <2>;
num-viewport = <8>;
device_type = "pci";
#address-cells = <3>;
#size-cells = <2>;
ranges = <0x01000000 0x0 0x90100000
0 0x90100000 0x0 0x100000>,
<0x02000000 0x0 0x90200000
0 0x90200000 0x0 0x0fe00000>;
interrupts = <142>, <146>;
interrupt-parent = <&intc>;
#interrupt-cells = <1>;
interrupt-map-mask = <0 0 0 0x7>;
interrupt-map = <0000 0 0 1 &pcie1_intc 1>, /* int_a */
<0000 0 0 2 &pcie1_intc 2>, /* int_b */
<0000 0 0 3 &pcie1_intc 3>, /* int_c */
<0000 0 0 4 &pcie1_intc 4>; /* int_d */
linux,pci-domain = <1>;
status = "disabled";
pcie1_intc: interrupt-controller@0 {
interrupt-controller;
reg = <0 0 0 0 0>;
#address-cells = <0>;
#interrupt-cells = <1>;
};
};
//uboot-2022.10/arch/riscv/dts/k1-x_deb1.dts
&pcie1_rc {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_pcie1_3>;
status = "okay";
};