基于Qemu eMMC模拟设备的启动

从eMMC中加载镜像并启动是我们常见开发板的启动方式,通过Qemu模拟eMMC设备,这样我们可以在Qemu上直接验证我们很多相关的逻辑功能,比如eMMC的启动、文件系统的mount、文件读写等。


  • Qemu: v7.2.0
  • U-Boot: v2022.04
  • Linux Kernel: linux-6.1.12

U-Boot eMMC架构

framework

eMMC镜像制作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
dd if=/dev/zero of=emmc.img bs=1M count=2048

# GPT partition table
sudo parted emmc.img mktable gpt
# msic part 512KiB
sudo parted emmc.img mkpart misc 2048KiB 2559KiB
# ubootenv part 512KiB
sudo parted -s -a none emmc.img mkpart ubootenv 2560KiB 3071KiB
# vbmeta_a part 512KiB
sudo parted -s -a none emmc.img mkpart vbmeta_a 3072KiB 3583KiB
# vbmeta_b part 512KiB
sudo parted -s -a none emmc.img mkpart vbmeta_b 3584KiB 4095KiB
# boot_a partition 150MiB
sudo parted -s -a none emmc.img mkpart boot_a 4MiB 154MiB
# boot_b partition 150MiB
sudo parted -s -a none emmc.img mkpart boot_b 154MiB 304MiB
sudo parted emmc.img set 5 boot on
sudo parted emmc.img set 6 boot on
# buildroot partition 512MiB
sudo parted -s -a none emmc.img mkpart buildroot 304MiB 816MiB
# ubuntu partition 1024MiB
sudo parted -s -a none emmc.img mkpart buildroot 816MiB 2046MiB
sync

sudo kpartx -av emmc.img
sudo mkfs.vfat /dev/mapper/loopXp5
sudo mkfs.vfat /dev/mapper/loopXp6
sudo mkfs.ext4 /dev/mapper/loopXp7
sudo mkfs.ext4 /dev/mapper/loopXp8

sudo tune2fs -f -O ^metadata_csum /dev/mapper/loopXp7
sudo tune2fs -f -O ^metadata_csum /dev/mapper/loopXp8
sync

sudo kpartx -dv emmc.img

注意:loopX是kpartx自动生成的,X根据实际情况替换成数字

eMMC验证

U-Boot配置选项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
CONFIG_MMC=y
CONFIG_MMC_WRITE=y
CONFIG_DM_MMC=y
CONFIG_MMC_SDHCI_ADMA_HELPERS=y
CONFIG_MMC_HW_PARTITIONING=y
CONFIG_SUPPORT_EMMC_RPMB=y
CONFIG_SUPPORT_EMMC_BOOT=y
CONFIG_CMD_MMC=y
CONFIG_MMC_VERBOSE=y
CONFIG_MMC_SDHCI=y
CONFIG_MMC_SDHCI_SDMA=y
CONFIG_MMC_SDHCI_ADMA=y
# CONFIG_SPL_MMC_SDHCI_ADMA is not set
# CONFIG_MMC_SDHCI_BCMSTB is not set
CONFIG_MMC_SDHCI_CADENCE=y

运行验证

1
qemu-system-aarch64 -M hobot-sigi-virt,emmc=on,virt=on -m 4G -display none -device loader,addr=0x3000200000,file=/path/to/u-boot/u-boot-dtb.bin,cpu-num=0 -drive file=/path/to/emmc.img,format=raw,if=emmc -serial stdio

eMMC

U-Boot distro配置

U-Boot distro运行需要的文件复制到emmc.img的分区part 5或者part 6,这样U-Boot后面将自动地从eMMC媒介中加载内核等镜像,完成启动引导。

extlinux.cfg配置文件的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
## /boot/extlinux/extlinux.conf
##

menu title U-Boot menu
prompt 0
timeout 50

label sigi-kernel-buildroot
kernel /Image
fdt /hobot-sigi-virt.dtb
append earlycon=uart8250,mmio32,0x39050000,115200n8 console=ttyS0,115200n8 loglevel=10 root=/dev/mmcblk0p7 rootwait init=/init rw

label sigi-kernel-ubuntu
kernel /Image
fdt /hobot-sigi-virt.dtb
append earlycon=uart8250,mmio32,0x39050000,115200n8 console=ttyS0,115200n8 loglevel=10 root=/dev/mmcblk0p8 rootwait init=/init rw

label sigi-kernel-nvme
kernel /Image
fdt /hobot-sigi-virt.dtb
append earlycon=uart8250,mmio32,0x39050000,115200n8 console=ttyS0,115200n8 loglevel=10 root=/dev/nvme0n1p7 rootwait init=/init rw

下面脚本将Image、initramfs、dtb等文件复制到emmc.img的boot_a分区:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#!/usr/bin/bash

mkdir -p rootfs
set -x

part_map=$(sudo kpartx -av emmc.img)

sudo mount /dev/mapper/$(echo "$part_map" | sed -n '5p' | awk '{print $3}') rootfs/

# copy boot config
sudo mkdir -p rootfs/extlinux/
sudo cp extlinux.conf rootfs/extlinux/

# copy kernel images
sudo cp linux/arch/arm64/boot/Image rootfs/
sudo cp linux/arch/arm64/boot/Image.gz rootfs/
sudo cp linux/arch/arm64/boot/dts/hobot/hobot-sigi-virt.dtb rootfs/

# copy initramfs image
sudo cp buildroot/output/images/rootfs.cpio.lz4 rootfs/

# copy xen image
# sudo cp -r xen-tools/4.17/install/boot/xen* rootfs/
# sudo cp xen/xen/xen rootfs/

sudo umount rootfs
sudo kpartx -dv emmc.img

sync

Ubuntu启动

Ubuntu镜像制作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
wget http://cdimage.ubuntu.com/ubuntu-base/releases/20.04.4/release/ubuntu-base-20.04.4-base-arm64.tar.gz
dd if=/dev/zero of=ubuntu.img bs=1M count=1024 oflag=direct
mkfs.ext4 ubuntu.img

mkdir -p rootfs
sudo mount -t ext4 ubuntu.img rootfs/
sudo tar -xzf ubuntu-base-20.04.4-base-arm64.tar.gz -C rootfs/

sudo cp /usr/bin/qemu-aarch64-static rootfs/usr/bin/
sudo cp /etc/resolv.conf rootfs/etc/resolv.conf

sudo mount -t proc /proc rootfs/proc
sudo mount -t sysfs /sys rootfs/sys
sudo mount -o bind /dev rootfs/dev
sudo mount -o bind /dev/pts rootfs/dev/pts

sudo chroot rootfs/
apt-get update
apt-get install sudo vim-tiny -y
apt-get install net-tools iputils-ping -y
apt-get install network-manager netplan.io systemd -y
apt-get install kmod net-tools ifupdown -y

useradd -m -d /home/charley -s /bin/bash -p ubuntu "charley"
usermod -a -G sudo charley
/bin/bash -c "echo \"virt\" >/etc/hostname"
/bin/bash -c "echo \"127.0.0.1 localhost\" >/etc/hosts"
/bin/bash -c "echo \"127.0.0.1 virt\" >>/etc/hosts"

exit

sudo umount rootfs/proc
sudo umount rootfs/sys
sudo umount rootfs/dev/pts
sudo umount rootfs/dev
sudo umount rootfs/

将生成的ubuntu.img里面的内容都复制到emmc.img的分区part 8里面,这样U-Boot启动时才能挂载Ubuntu文件系统。

运行验证

1
qemu-system-aarch64 -M hobot-sigi-virt,emmc=on,virt=on -m 4G -display none -device loader,addr=0x3000200000,file=/path/to/u-boot/u-boot-dtb.bin,cpu-num=0 -drive file=/path/to/emmc.img,format=raw,if=emmc -serial stdio

ubuntu
启动日志附件:U-Boot -> Kernel -> Ubuntu Booting

基于Buildroot文件系统启动

将buildroot生成的rootfs.cpio.lz4里面的内容展开到emmc.img的分区part 7里面,这样U-Boot启动时才能挂载buildroot根文件系统。

1
qemu-system-aarch64 -M hobot-sigi-virt,emmc=on,virt=on -m 4G -display none -device loader,addr=0x3000200000,file=/path/to/u-boot/u-boot-dtb.bin,cpu-num=0 -drive file=/path/to/emmc.img,format=raw,if=emmc -serial stdio

buildroot
启动日志附件:U-Boot -> Kernel -> Buildroot Booting

eMMC RPMB访问

RPMB介绍

RPMB(Replay Protected Memory Block) Partition是eMMC中的一个具有安全特性的分区。
eMMC在写入数据到RPMB时,会校验数据的合法性,只有指定的Host才能够写入,同时在读数据时,也提供了签名机制,保证Host读取到的数据是RPMB内部数据,而不是攻击者伪造的数据。

RPMB在实际应用中,通常用于存储一些有防止非法篡改需求的数据,例如手机上指纹支付相关的公钥、序列号等。RPMB可以对写入操作进行鉴权,但是读取并不需要鉴权,任何人都可以进行读取的操作,因此存储到RPMB的数据通常会进行加密后再存储。

运行验证

上面我们制作的emmc.img镜像里面都没有包含rpmb.img镜像,因此我们要制作一个rpmb.img的空间,和emmc.img拼在一起,这样U-Boot才能够访问RPMB的空间。

1
2
3
4
5
dd if=/dev/zero of=rpmb.img bs=1M count=2
dd if=/dev/zero of=emmc.img bs=1M count=2046
#emmc.img按照上面制作方法进行分区和复制内容

cat rpmb.img emmc.img >emmc-with-rpmb.img

emmc.img大小必须是1G、2G、4G等(要满足2的N次方形式)
否者,就会报如下的错误信息:
emmc-size

1
qemu-system-aarch64 -M hobot-sigi-virt,emmc=on,part-config=0x20,virt=on -m 4G -display none -device loader,addr=0x3000200000,file=/path/to/u-boot/u-boot-dtb.bin,cpu-num=0 -drive file=/path/to/emmc-with-rpmb.img,format=raw,if=emmc -serial stdio

rpmb

1
2
3
4
5
6
7
8
9
10
11
12
# 烧写rpmb key,每个eMMC只能烧写一次
load mmc 0:5 0x3090000000 /rpmb-key.txt;mmc rpmb key 0x3090000000

# 使用这个key,把data写入到RPMB分区内
fatload mmc 0:5 0x3091000000 Image;mmc rpmb write 0x3091000000 0x0 0x10 0x3090000000

md 0x3092000000 0x10
# 读取RPMB分区里面的data,可以不用key校验
mmc rpmb read 0x3092000000 0x0 0x10

# 显示两次,做对比,看看数据有没有正确读出来
md 0x3092000000 0x10