#!/bin/sh -e # SPDX-License-Identifier: GPL-2.0-only # # Create eMMC block device image from boot, RPMB and user data images # # Copyright (c) Siemens, 2025 # # Authors: # Jan Kiszka # usage() { echo "$0 [OPTIONS] USER_IMG[:SIZE] OUTPUT_IMG" echo "" echo "SIZE must be a power of 2 up to 2G and multiples of 512 byte from there on." echo "If no SIZE is specified, the size of USER_ING will be used (rounded up)." echo "" echo "Supported options:" echo " -b BOOT1_IMG[:SIZE] Add boot partitions. SIZE must be multiples of 128K. If" echo " no SIZE is specified, the size of BOOT1_IMG will be" echo " used (rounded up). BOOT1_IMG will be stored in boot" echo " partition 1, and a boot partition 2 of the same size" echo " will be created as empty (all zeros) unless -B is" echo " specified as well." echo " -B BOOT2_IMG Fill boot partition 2 with BOOT2_IMG. Must be combined" echo " with -b which is also defining the partition size." echo " -r RPMB_IMG[:SIZE] Add RPMB partition. SIZE must be multiples of 128K. If" echo " no SIZE is specified, the size of RPMB_IMG will be" echo " used (rounded up)." echo " -h, --help This help" echo "" echo "All SIZE parameters support the units K, M, G. If SIZE is smaller than the" echo "associated image, it will be truncated in the output image." exit "$1" } process_size() { name=$1 image_file=$2 alignment=$3 image_arg=$4 if [ "${image_arg#*:}" = "$image_arg" ]; then if ! size=$(wc -c < "$image_file" 2>/dev/null); then echo "Missing $name image '$image_file'." >&2 exit 1 fi if [ "$alignment" = 128 ]; then size=$(( (size + 128 * 1024 - 1) & ~(128 * 1024 - 1) )) elif [ $size -gt $((2 * 1024 * 1024 * 1024)) ]; then size=$(( (size + 511) & ~511 )) elif [ $(( size & (size - 1) )) -gt 0 ]; then n=0 while [ "$size" -gt 0 ]; do size=$((size >> 1)) n=$((n + 1)) done size=$((1 << n)) fi else value="${image_arg#*:}" if [ "${value%K}" != "$value" ]; then size=${value%K} multiplier=1024 elif [ "${value%M}" != "$value" ]; then size=${value%M} multiplier=$((1024 * 1024)) elif [ "${value%G}" != "$value" ]; then size=${value%G} multiplier=$((1024 * 1024 * 1024)) else size=$value multiplier=1 fi # check if "$size" is a valid integer by doing a self-comparison if [ "$size" -eq "$size" ] 2>/dev/null; then size=$((size * multiplier)) else echo "Invalid value '$value' specified for $image_file image size." >&2 exit 1 fi if [ "$alignment" = 128 ]; then if [ $(( size & (128 * 1024 - 1) )) -ne 0 ]; then echo "The $name image size must be multiples of 128K." >&2 exit 1 fi elif [ $size -gt $((2 * 1024 * 1024 * 1024)) ]; then if [ $(( size & 511)) -ne 0 ]; then echo "The $name image size must be multiples of 512 (if >2G)." >&2 exit 1 fi elif [ $(( size & (size - 1) )) -gt 0 ]; then echo "The $name image size must be power of 2 (up to 2G)." >&2 exit 1 fi fi echo $size } check_truncation() { image_file=$1 output_size=$2 if [ "$image_file" = "/dev/zero" ]; then return fi if ! actual_size=$(wc -c < "$image_file" 2>/dev/null); then echo "Missing image '$image_file'." >&2 exit 1 fi if [ "$actual_size" -gt "$output_size" ]; then echo "Warning: image '$image_file' will be truncated on output." fi } userimg= outimg= bootimg1= bootimg2=/dev/zero bootsz=0 rpmbimg= rpmbsz=0 while [ $# -gt 0 ]; do case "$1" in -b) shift [ $# -ge 1 ] || usage 1 bootimg1=${1%%:*} bootsz=$(process_size boot "$bootimg1" 128 "$1") shift ;; -B) shift [ $# -ge 1 ] || usage 1 bootimg2=$1 shift ;; -r) shift [ $# -ge 1 ] || usage 1 rpmbimg=${1%%:*} rpmbsz=$(process_size RPMB "$rpmbimg" 128 "$1") shift ;; -h|--help) usage 0 ;; *) if [ -z "$userimg" ]; then userimg=${1%%:*} usersz=$(process_size user "$userimg" U "$1") elif [ -z "$outimg" ]; then outimg=$1 else usage 1 fi shift ;; esac done [ -n "$outimg" ] || usage 1 if [ "$bootsz" -gt $((32640 * 1024)) ]; then echo "Boot image size is larger than 32640K." >&2 exit 1 fi if [ "$rpmbsz" -gt $((16384 * 1024)) ]; then echo "RPMB image size is larger than 16384K." >&2 exit 1 fi echo "Creating eMMC image" truncate -s 0 "$outimg" pos=0 if [ "$bootsz" -gt 0 ]; then echo " Boot partition 1 and 2: $((bootsz / 1024))K each" blocks=$(( bootsz / (128 * 1024) )) check_truncation "$bootimg1" "$bootsz" dd if="$bootimg1" of="$outimg" conv=sparse bs=128K count=$blocks \ status=none check_truncation "$bootimg2" "$bootsz" dd if="$bootimg2" of="$outimg" conv=sparse bs=128K count=$blocks \ seek=$blocks status=none pos=$((2 * bootsz)) fi if [ "$rpmbsz" -gt 0 ]; then echo " RPMB partition: $((rpmbsz / 1024))K" blocks=$(( rpmbsz / (128 * 1024) )) check_truncation "$rpmbimg" "$rpmbsz" dd if="$rpmbimg" of="$outimg" conv=sparse bs=128K count=$blocks \ seek=$(( pos / (128 * 1024) )) status=none pos=$((pos + rpmbsz)) fi if [ "$usersz" -lt 1024 ]; then echo " User data: $usersz bytes" elif [ "$usersz" -lt $((1024 * 1024)) ]; then echo " User data: $(( (usersz + 1023) / 1024 ))K ($usersz)" elif [ "$usersz" -lt $((1024 * 1024 * 1024)) ]; then echo " User data: $(( (usersz + 1048575) / 1048576))M ($usersz)" else echo " User data: $(( (usersz + 1073741823) / 1073741824))G ($usersz)" fi check_truncation "$userimg" "$usersz" dd if="$userimg" of="$outimg" conv=sparse bs=128K seek=$(( pos / (128 * 1024) )) \ count=$(( (usersz + 128 * 1024 - 1) / (128 * 1024) )) status=none pos=$((pos + usersz)) truncate -s $pos "$outimg" echo "" echo "Instantiate by appending to the qemu command line:" echo " -drive file=$outimg,if=none,format=raw,id=emmc-img" echo " -device emmc,boot-partition-size=$bootsz,rpmb-partition-size=$rpmbsz,drive=emmc-img"