rpi3: create a bootable image

At [1] we temporarily fixed the issue with the stripped header from the
kernel image. There we also suggested to make a future change making it
possible to just simply 'dd' an image to the SD-card.

This change introduces a bash script that generates a rootfs partition
and a boot partition. The rootfs partition will contain the root
filesystem as created by Buildroot, while the boot partition will
contain the firmware files placed in a temporary folder, that will be
used to created the boot partition. To create the boot partition, we
leverage the 'genimage' tool, hence we enable and build that as host
tool in Buildroot.

The change removes the need to manually create, mount partitions and
copy files to them. With this, it is sufficient to simply 'dd' the
generated image to the SD-card. The 'make img-help' target has been
kept, but now it's basically just a 'dd' line instead. People can also
use other tools to write the image to a SD-card, such as Balena Etcher
and similar tools.

Link: [1] https://github.com/OP-TEE/build/pull/643

Signed-off-by: Joakim Bech <joakim.bech@linaro.org>
Tested-by: Joakim Bech <joakim.bech@linaro.org>
diff --git a/rpi3.mk b/rpi3.mk
index 3b5e4ca..85bb232 100644
--- a/rpi3.mk
+++ b/rpi3.mk
@@ -22,14 +22,19 @@
 BR2_PACKAGE_OPENSSH_SERVER ?= y
 BR2_PACKAGE_OPENSSH_KEY_UTILS ?= y
 
-# We don't want buildroot to strip kernel8.img, since that will remove the
-# "magic" from the kernel header.
-BR2_STRIP_EXCLUDE_FILES ?= kernel8.img
-
 OPTEE_OS_PLATFORM = rpi3
 
 include common.mk
 
+# Required tools to create the SD image
+BR2_PACKAGE_HOST_GENIMAGE=y
+
+# We need the ext2/4 image to be generated, so we will be able to copy that
+# directly into a parition on the image.
+BR2_TARGET_ROOTFS_EXT2=y
+BR2_TARGET_ROOTFS_EXT2_4=y
+BR2_TARGET_ROOTFS_EXT2_SIZE="128M"
+
 ################################################################################
 # Paths to git projects and various binaries
 ################################################################################
@@ -50,6 +55,8 @@
 OPTEE_BIN		?= $(OPTEE_PATH)/out/arm/core/tee-header_v2.bin
 OPTEE_BIN_EXTRA1	?= $(OPTEE_PATH)/out/arm/core/tee-pager_v2.bin
 OPTEE_BIN_EXTRA2	?= $(OPTEE_PATH)/out/arm/core/tee-pageable_v2.bin
+BOOT_PARTITION_FILES	?= $(ROOT)/out/boot
+CREATE_IMAGE		?= $(BUILD_PATH)/rpi3/scripts/create-image.sh
 
 LINUX_IMAGE		?= $(LINUX_PATH)/arch/arm64/boot/Image
 LINUX_DTB_RPI3_B	?= $(LINUX_PATH)/arch/arm64/boot/dts/broadcom/bcm2710-rpi-3-b.dtb
@@ -59,7 +66,8 @@
 ################################################################################
 # Targets
 ################################################################################
-all: arm-tf buildroot optee-os u-boot linux update_rootfs
+all: arm-tf buildroot optee-os u-boot linux update_bootfs update_rootfs \
+	sdcard-image
 clean: arm-tf-clean buildroot-clean u-boot-clean \
 	optee-os-clean
 
@@ -162,67 +170,38 @@
 # Make sure this is built before the buildroot target which will create the
 # root file system based on what's in $(BUILDROOT_TARGET_ROOT)
 buildroot: update_rootfs
-update_rootfs: arm-tf linux u-boot
-	@mkdir -p --mode=755 $(BUILDROOT_TARGET_ROOT)/boot
+update_rootfs: linux
 	@mkdir -p --mode=755 $(BUILDROOT_TARGET_ROOT)/usr/bin
-	@install -v -p --mode=755 $(LINUX_DTB_RPI3_B) $(BUILDROOT_TARGET_ROOT)/boot/bcm2710-rpi-3-b.dtb
-	@install -v -p --mode=755 $(LINUX_DTB_RPI3_BPLUS) $(BUILDROOT_TARGET_ROOT)/boot/bcm2710-rpi-3-b-plus.dtb
-	@install -v -p --mode=755 $(RPI3_BOOT_CONFIG) $(BUILDROOT_TARGET_ROOT)/boot/config.txt
-	@install -v -p --mode=755 $(LINUX_IMAGE) $(BUILDROOT_TARGET_ROOT)/boot/kernel8.img
-	@install -v -p --mode=755 $(TF_A_BOOT) $(BUILDROOT_TARGET_ROOT)/boot/armstub8.bin
-	@install -v -p --mode=755 $(RPI3_UBOOT_ENV) $(BUILDROOT_TARGET_ROOT)/boot/uboot.env
-	@install -v -p --mode=755 $(RPI3_STOCK_FW_PATH)/boot/bootcode.bin $(BUILDROOT_TARGET_ROOT)/boot/bootcode.bin
-	@install -v -p --mode=755 $(RPI3_STOCK_FW_PATH)/boot/COPYING.linux $(BUILDROOT_TARGET_ROOT)/boot/COPYING.linux
-	@install -v -p --mode=755 $(RPI3_STOCK_FW_PATH)/boot/fixup_cd.dat $(BUILDROOT_TARGET_ROOT)/boot/fixup_cd.dat
-	@install -v -p --mode=755 $(RPI3_STOCK_FW_PATH)/boot/fixup.dat $(BUILDROOT_TARGET_ROOT)/boot/fixup.dat
-	@install -v -p --mode=755 $(RPI3_STOCK_FW_PATH)/boot/fixup_db.dat $(BUILDROOT_TARGET_ROOT)/boot/fixup_db.dat
-	@install -v -p --mode=755 $(RPI3_STOCK_FW_PATH)/boot/fixup_x.dat $(BUILDROOT_TARGET_ROOT)/boot/fixup_x.dat
-	@install -v -p --mode=755 $(RPI3_STOCK_FW_PATH)/boot/LICENCE.broadcom $(BUILDROOT_TARGET_ROOT)/boot/LICENCE.broadcom
-	@install -v -p --mode=755 $(RPI3_STOCK_FW_PATH)/boot/start_cd.elf $(BUILDROOT_TARGET_ROOT)/boot/start_cd.elf
-	@install -v -p --mode=755 $(RPI3_STOCK_FW_PATH)/boot/start_db.elf $(BUILDROOT_TARGET_ROOT)/boot/start_db.elf
-	@install -v -p --mode=755 $(RPI3_STOCK_FW_PATH)/boot/start.elf $(BUILDROOT_TARGET_ROOT)/boot/start.elf
-	@install -v -p --mode=755 $(RPI3_STOCK_FW_PATH)/boot/start_x.elf $(BUILDROOT_TARGET_ROOT)/boot/start_x.elf
 	@cd $(MODULE_OUTPUT) && find . | cpio -pudm $(BUILDROOT_TARGET_ROOT)
 
+update_bootfs: arm-tf u-boot
+	@mkdir -p --mode=755 $(BOOT_PARTITION_FILES)
+	@install -v -p --mode=755 $(LINUX_DTB_RPI3_B) $(BOOT_PARTITION_FILES)/bcm2710-rpi-3-b.dtb
+	@install -v -p --mode=755 $(LINUX_DTB_RPI3_BPLUS) $(BOOT_PARTITION_FILES)/bcm2710-rpi-3-b-plus.dtb
+	@install -v -p --mode=755 $(RPI3_BOOT_CONFIG) $(BOOT_PARTITION_FILES)/config.txt
+	@install -v -p --mode=755 $(LINUX_IMAGE) $(BOOT_PARTITION_FILES)/kernel8.img
+	@install -v -p --mode=755 $(TF_A_BOOT) $(BOOT_PARTITION_FILES)/armstub8.bin
+	@install -v -p --mode=755 $(RPI3_UBOOT_ENV) $(BOOT_PARTITION_FILES)/uboot.env
+	@install -v -p --mode=755 $(RPI3_STOCK_FW_PATH)/boot/bootcode.bin $(BOOT_PARTITION_FILES)/bootcode.bin
+	@install -v -p --mode=755 $(RPI3_STOCK_FW_PATH)/boot/COPYING.linux $(BOOT_PARTITION_FILES)/COPYING.linux
+	@install -v -p --mode=755 $(RPI3_STOCK_FW_PATH)/boot/fixup_cd.dat $(BOOT_PARTITION_FILES)/fixup_cd.dat
+	@install -v -p --mode=755 $(RPI3_STOCK_FW_PATH)/boot/fixup.dat $(BOOT_PARTITION_FILES)/fixup.dat
+	@install -v -p --mode=755 $(RPI3_STOCK_FW_PATH)/boot/fixup_db.dat $(BOOT_PARTITION_FILES)/fixup_db.dat
+	@install -v -p --mode=755 $(RPI3_STOCK_FW_PATH)/boot/fixup_x.dat $(BOOT_PARTITION_FILES)/fixup_x.dat
+	@install -v -p --mode=755 $(RPI3_STOCK_FW_PATH)/boot/LICENCE.broadcom $(BOOT_PARTITION_FILES)/LICENCE.broadcom
+	@install -v -p --mode=755 $(RPI3_STOCK_FW_PATH)/boot/start_cd.elf $(BOOT_PARTITION_FILES)/start_cd.elf
+	@install -v -p --mode=755 $(RPI3_STOCK_FW_PATH)/boot/start_db.elf $(BOOT_PARTITION_FILES)/start_db.elf
+	@install -v -p --mode=755 $(RPI3_STOCK_FW_PATH)/boot/start.elf $(BOOT_PARTITION_FILES)/start.elf
+	@install -v -p --mode=755 $(RPI3_STOCK_FW_PATH)/boot/start_x.elf $(BOOT_PARTITION_FILES)/start_x.elf
+
+.PHONY: sdcard-image
+sdcard-image: update_bootfs update_rootfs buildroot
+	$(CREATE_IMAGE) -w $(ROOT)
+
 # Creating images etc, could wipe out a drive on the system, therefore we don't
 # want to automate that in script or make target. Instead we just simply provide
 # the steps here.
 .PHONY: img-help
 img-help:
-	@echo "$$ fdisk /dev/sdx   # where sdx is the name of your sd-card"
-	@echo "   > p             # prints partition table"
-	@echo "   > d             # repeat until all partitions are deleted"
-	@echo "   > n             # create a new partition"
-	@echo "   > p             # create primary"
-	@echo "   > 1             # make it the first partition"
-	@echo "   > <enter>       # use the default sector"
-	@echo "   > +64M          # create a boot partition with 64MB of space"
-	@echo "   > n             # create rootfs partition"
-	@echo "   > p"
-	@echo "   > 2"
-	@echo "   > <enter>"
-	@echo "   > <enter>       # fill the remaining disk, adjust size to fit your needs"
-	@echo "   > t             # change partition type"
-	@echo "   > 1             # select first partition"
-	@echo "   > e             # use type 'e' (FAT16)"
-	@echo "   > a             # make partition bootable"
-	@echo "   > 1             # select first partition"
-	@echo "   > p             # double check everything looks right"
-	@echo "   > w             # write partition table to disk."
-	@echo ""
-	@echo "run the following as root"
-	@echo "   $$ mkfs.vfat -F16 -n BOOT /dev/sdx1"
-	@echo "   $$ mkdir -p /media/boot"
-	@echo "   $$ mount /dev/sdx1 /media/boot"
-	@echo "   $$ cd /media"
-	@echo "   $$ gunzip -cd $(ROOT)/out-br/images/rootfs.cpio.gz | sudo cpio -idmv \"boot/*\""
-	@echo "   $$ umount boot"
-	@echo ""
-	@echo "run the following as root"
-	@echo "   $$ mkfs.ext4 -L rootfs /dev/sdx2"
-	@echo "   $$ mkdir -p /media/rootfs"
-	@echo "   $$ mount /dev/sdx2 /media/rootfs"
-	@echo "   $$ cd rootfs"
-	@echo "   $$ gunzip -cd $(ROOT)/out-br/images/rootfs.cpio.gz | sudo cpio -idmv"
-	@echo "   $$ rm -rf /media/rootfs/boot/*"
-	@echo "   $$ cd .. && umount rootfs"
+	@echo "Use 'dmesg' to find your device/SD-card name, then run the following as root:"
+	@echo "   $$ sudo dd if=$(ROOT)/out/rpi3-sdcard.img of=/dev/<name-of-my-sd-card> bs=1024k conv=fsync status=progress"
diff --git a/rpi3/scripts/create-image.sh b/rpi3/scripts/create-image.sh
new file mode 100755
index 0000000..e79ca82
--- /dev/null
+++ b/rpi3/scripts/create-image.sh
@@ -0,0 +1,137 @@
+#!/usr/bin/env bash
+# Helper script to make a SD-card image for the official OP-TEE Raspberry Pi 3
+# build.
+
+set -e
+WORKDIR=""
+
+display_usage() {
+	echo "Usage: $0 [options]"
+	echo "Options:"
+	echo "  -w|--workdir <dir> : The OP-TEE RPI3 working directory"
+	echo "  -h|--help          : Display this help message"
+}
+
+while [[ $# -gt 0 ]]; do
+	key="$1"
+	case $key in
+	-w | --workdir)
+		WORKDIR="$2"
+		shift 2
+		;;
+	-h | --help)
+		display_usage
+		exit 0
+		;;
+	*)
+		echo "Invalid option: $1"
+		display_usage
+		exit 1
+		;;
+	esac
+done
+
+# No point to continue if files/folders we depend on are not existing.
+check_exist() {
+	local dir="$1"
+	if [ ! -e "${dir}" ]; then
+		echo "Folder ${dir} does not exist. Exiting..."
+		exit 1
+	fi
+}
+
+check_exist ${WORKDIR}
+
+IMAGE_FILE=${WORKDIR}/out/rpi3-sdcard.img
+rm -f ${IMAGE_FILE}
+
+################################################################################
+# Configuration
+################################################################################
+OFFSET_KIB=1024
+BOOT_PARTITION_SIZE_MB=64
+# ROOT FS size is read out from the rootfs file created by Buildroot, therefore
+# we don't need to configure that here.
+
+################################################################################
+# Image and partition generation
+################################################################################
+# In bytes, always 512, when creating msdos/MBR images.
+SECTOR_SIZE=512
+
+# 1. Compute the sector size for the offset to the first partition.
+OFFSET_SECTOR_SIZE=$((OFFSET_KIB * 1024 / SECTOR_SIZE))
+echo "OFFSET_SECTOR_SIZE: $((OFFSET_KIB * 1024)) bytes ($((OFFSET_KIB / 1024))MB in ${OFFSET_SECTOR_SIZE} sectors)"
+
+# 2. Calculate the boot partition sizes.
+BOOT_PARTITION_SIZE_KIB=$((BOOT_PARTITION_SIZE_MB * 1024))
+BOOT_PARTITION_SIZE_BYTES=$((BOOT_PARTITION_SIZE_KIB * 1024))
+BOOT_PARTITION_SECTOR_SIZE=$((BOOT_PARTITION_SIZE_BYTES / SECTOR_SIZE))
+echo "BOOT PARTITION SIZE: ${BOOT_PARTITION_SIZE_BYTES} bytes ($(($BOOT_PARTITION_SIZE_BYTES / 1024 / 1024))MB in ${BOOT_PARTITION_SECTOR_SIZE} sectors)"
+
+# 3. Find out the size of the rootfs produced by Buildroot and calculate the
+#    rootfs partition sizes.
+ROOTFS_IMG=${WORKDIR}/out-br/images/rootfs.ext2
+check_exist ${ROOTFS_IMG}
+ROOTFS_SIZE_BYTES=$(stat -c %s ${ROOTFS_IMG})
+ROOTFS_SECTOR_SIZE=$((ROOTFS_SIZE_BYTES / SECTOR_SIZE))
+echo "ROOTFS PARTITION SIZE: ${ROOTFS_SIZE_BYTES} bytes ($((ROOTFS_SIZE_BYTES / 1024 / 1024))MB in ${ROOTFS_SECTOR_SIZE} sectors)"
+
+# 4. Compute the images size, based on the rootfs size, the
+#    desired boot image size and the offset to the first partition.
+IMG_SECTOR_SIZE=$((OFFSET_SECTOR_SIZE + BOOT_PARTITION_SECTOR_SIZE + ROOTFS_SECTOR_SIZE))
+IMG_SIZE_KIB=$((IMG_SECTOR_SIZE * SECTOR_SIZE / 1024))
+echo "TOTAL IMAGE SIZE: ${IMG_SIZE_KIB} bytes ($((IMG_SIZE_KIB / 1024))MB in ${IMG_SECTOR_SIZE} sectors)"
+
+# Create empty disk image
+truncate -s ${IMG_SIZE_KIB}KiB ${IMAGE_FILE}
+
+parted -s ${IMAGE_FILE} unit kiB mklabel msdos
+parted -a optimal ${IMAGE_FILE} unit kiB mkpart primary fat16 ${OFFSET_KIB} ${BOOT_PARTITION_SIZE_KIB}
+parted -a optimal ${IMAGE_FILE} set 1 boot on
+parted -a optimal ${IMAGE_FILE} unit kiB mkpart primary ext4 $((OFFSET_KIB + BOOT_PARTITION_SIZE_KIB)) 100%
+
+# Show some useful information about the image we just created.
+echo ""
+fdisk -l ${IMAGE_FILE}
+
+################################################################################
+# Copy the ext2 partition from Buildroot to the second partition.
+################################################################################
+dd if=${ROOTFS_IMG} of=${IMAGE_FILE} bs=${SECTOR_SIZE} seek=$((OFFSET_SECTOR_SIZE + BOOT_PARTITION_SECTOR_SIZE)) conv=notrunc,fsync
+
+################################################################################
+# Create the boot image and copy it to the boot partition
+################################################################################
+GENIMAGE=${WORKDIR}/out-br/host/bin/genimage
+check_exist ${GENIMAGE}
+
+# Used for a temporary genimage config file.
+RPI3_GENIMAGE_CFG=$(mktemp)
+
+# Used for temporarily storing files when creating the boot vfat image.
+RPI3_GENIMAGE_TMPPATH=$(mktemp -d)
+echo "RPI3_GENIMAGE_CFG: ${RPI3_GENIMAGE_CFG}"
+echo "RPI3_GENIMAGE_TMPPATH: ${RPI3_GENIMAGE_TMPPATH}"
+
+trap "rm -rf ${RPI3_GENIMAGE_CFG} ${RPI3_GENIMAGE_TMPPATH}" EXIT
+
+BOOT_PARTITION_FILES=${WORKDIR}/out/boot
+check_exist ${BOOT_PARTITION_FILES}
+
+# Create the config file for genimage on the fly.
+cat <<EOF >${RPI3_GENIMAGE_CFG}
+image boot.vfat {
+        vfat {
+        }
+
+        size = ${BOOT_PARTITION_SIZE_MB}M
+        mountpoint = "/"
+}
+EOF
+
+# Create the boot.vfat image.
+${GENIMAGE} --rootpath ${BOOT_PARTITION_FILES} --config ${RPI3_GENIMAGE_CFG} --tmppath=${RPI3_GENIMAGE_TMPPATH} --outputpath=${WORKDIR}/out
+
+# Copy the contents of boot.vfat to the boot partition in the image we prepared.
+dd if=${WORKDIR}/out/boot.vfat of=${IMAGE_FILE} bs=${SECTOR_SIZE} seek=${OFFSET_SECTOR_SIZE} conv=notrunc,fsync