| 1 | #!/bin/sh -e |
| 2 | # SPDX-License-Identifier: GPL-2.0-only |
| 3 | # |
| 4 | # Create eMMC block device image from boot, RPMB and user data images |
| 5 | # |
| 6 | # Copyright (c) Siemens, 2025 |
| 7 | # |
| 8 | # Authors: |
| 9 | # Jan Kiszka <jan.kiszka@siemens.com> |
| 10 | # |
| 11 | |
| 12 | usage() { |
| 13 | echo "$0 [OPTIONS] USER_IMG[:SIZE] OUTPUT_IMG" |
| 14 | echo "" |
| 15 | echo "SIZE must be a power of 2 up to 2G and multiples of 512 byte from there on." |
| 16 | echo "If no SIZE is specified, the size of USER_ING will be used (rounded up)." |
| 17 | echo "" |
| 18 | echo "Supported options:" |
| 19 | echo " -b BOOT1_IMG[:SIZE] Add boot partitions. SIZE must be multiples of 128K. If" |
| 20 | echo " no SIZE is specified, the size of BOOT1_IMG will be" |
| 21 | echo " used (rounded up). BOOT1_IMG will be stored in boot" |
| 22 | echo " partition 1, and a boot partition 2 of the same size" |
| 23 | echo " will be created as empty (all zeros) unless -B is" |
| 24 | echo " specified as well." |
| 25 | echo " -B BOOT2_IMG Fill boot partition 2 with BOOT2_IMG. Must be combined" |
| 26 | echo " with -b which is also defining the partition size." |
| 27 | echo " -r RPMB_IMG[:SIZE] Add RPMB partition. SIZE must be multiples of 128K. If" |
| 28 | echo " no SIZE is specified, the size of RPMB_IMG will be" |
| 29 | echo " used (rounded up)." |
| 30 | echo " -h, --help This help" |
| 31 | echo "" |
| 32 | echo "All SIZE parameters support the units K, M, G. If SIZE is smaller than the" |
| 33 | echo "associated image, it will be truncated in the output image." |
| 34 | exit "$1" |
| 35 | } |
| 36 | |
| 37 | process_size() { |
| 38 | name=$1 |
| 39 | image_file=$2 |
| 40 | alignment=$3 |
| 41 | image_arg=$4 |
| 42 | if [ "${image_arg#*:}" = "$image_arg" ]; then |
| 43 | if ! size=$(wc -c 2>/dev/null < "$image_file"); then |
| 44 | echo "Missing $name image '$image_file'." >&2 |
| 45 | exit 1 |
| 46 | fi |
| 47 | if [ "$alignment" = 128 ]; then |
| 48 | size=$(( (size + 128 * 1024 - 1) & ~(128 * 1024 - 1) )) |
| 49 | elif [ $size -gt $((2 * 1024 * 1024 * 1024)) ]; then |
| 50 | size=$(( (size + 511) & ~511 )) |
| 51 | elif [ $(( size & (size - 1) )) -gt 0 ]; then |
| 52 | n=0 |
| 53 | while [ "$size" -gt 0 ]; do |
| 54 | size=$((size >> 1)) |
| 55 | n=$((n + 1)) |
| 56 | done |
| 57 | size=$((1 << n)) |
| 58 | fi |
| 59 | else |
| 60 | value="${image_arg#*:}" |
| 61 | if [ "${value%K}" != "$value" ]; then |
| 62 | size=${value%K} |
| 63 | multiplier=1024 |
| 64 | elif [ "${value%M}" != "$value" ]; then |
| 65 | size=${value%M} |
| 66 | multiplier=$((1024 * 1024)) |
| 67 | elif [ "${value%G}" != "$value" ]; then |
| 68 | size=${value%G} |
| 69 | multiplier=$((1024 * 1024 * 1024)) |
| 70 | else |
| 71 | size=$value |
| 72 | multiplier=1 |
| 73 | fi |
| 74 | # check if "$size" is a valid integer by doing a self-comparison |
| 75 | if [ "$size" -eq "$size" ] 2>/dev/null; then |
| 76 | size=$((size * multiplier)) |
| 77 | else |
| 78 | echo "Invalid value '$value' specified for $image_file image size." >&2 |
| 79 | exit 1 |
| 80 | fi |
| 81 | if [ "$alignment" = 128 ]; then |
| 82 | if [ $(( size & (128 * 1024 - 1) )) -ne 0 ]; then |
| 83 | echo "The $name image size must be multiples of 128K." >&2 |
| 84 | exit 1 |
| 85 | fi |
| 86 | elif [ $size -gt $((2 * 1024 * 1024 * 1024)) ]; then |
| 87 | if [ $(( size & 511)) -ne 0 ]; then |
| 88 | echo "The $name image size must be multiples of 512 (if >2G)." >&2 |
| 89 | exit 1 |
| 90 | fi |
| 91 | elif [ $(( size & (size - 1) )) -gt 0 ]; then |
| 92 | echo "The $name image size must be power of 2 (up to 2G)." >&2 |
| 93 | exit 1 |
| 94 | fi |
| 95 | fi |
| 96 | echo $size |
| 97 | } |
| 98 | |
| 99 | check_truncation() { |
| 100 | image_file=$1 |
| 101 | output_size=$2 |
| 102 | if [ "$image_file" = "/dev/zero" ]; then |
| 103 | return |
| 104 | fi |
| 105 | if ! actual_size=$(wc -c 2>/dev/null < "$image_file"); then |
| 106 | echo "Missing image '$image_file'." >&2 |
| 107 | exit 1 |
| 108 | fi |
| 109 | if [ "$actual_size" -gt "$output_size" ]; then |
| 110 | echo "Warning: image '$image_file' will be truncated on output." |
| 111 | fi |
| 112 | } |
| 113 | |
| 114 | userimg= |
| 115 | outimg= |
| 116 | bootimg1= |
| 117 | bootimg2=/dev/zero |
| 118 | bootsz=0 |
| 119 | rpmbimg= |
| 120 | rpmbsz=0 |
| 121 | |
| 122 | while [ $# -gt 0 ]; do |
| 123 | case "$1" in |
| 124 | -b) |
| 125 | shift |
| 126 | [ $# -ge 1 ] || usage 1 |
| 127 | bootimg1=${1%%:*} |
| 128 | bootsz=$(process_size boot "$bootimg1" 128 "$1") |
| 129 | shift |
| 130 | ;; |
| 131 | -B) |
| 132 | shift |
| 133 | [ $# -ge 1 ] || usage 1 |
| 134 | bootimg2=$1 |
| 135 | shift |
| 136 | ;; |
| 137 | -r) |
| 138 | shift |
| 139 | [ $# -ge 1 ] || usage 1 |
| 140 | rpmbimg=${1%%:*} |
| 141 | rpmbsz=$(process_size RPMB "$rpmbimg" 128 "$1") |
| 142 | shift |
| 143 | ;; |
| 144 | -h|--help) |
| 145 | usage 0 |
| 146 | ;; |
| 147 | *) |
| 148 | if [ -z "$userimg" ]; then |
| 149 | userimg=${1%%:*} |
| 150 | usersz=$(process_size user "$userimg" U "$1") |
| 151 | elif [ -z "$outimg" ]; then |
| 152 | outimg=$1 |
| 153 | else |
| 154 | usage 1 |
| 155 | fi |
| 156 | shift |
| 157 | ;; |
| 158 | esac |
| 159 | done |
| 160 | |
| 161 | [ -n "$outimg" ] || usage 1 |
| 162 | |
| 163 | if [ "$bootsz" -gt $((32640 * 1024)) ]; then |
| 164 | echo "Boot image size is larger than 32640K." >&2 |
| 165 | exit 1 |
| 166 | fi |
| 167 | if [ "$rpmbsz" -gt $((16384 * 1024)) ]; then |
| 168 | echo "RPMB image size is larger than 16384K." >&2 |
| 169 | exit 1 |
| 170 | fi |
| 171 | |
| 172 | echo "Creating eMMC image" |
| 173 | |
| 174 | truncate -s 0 "$outimg" |
| 175 | pos=0 |
| 176 | |
| 177 | if [ "$bootsz" -gt 0 ]; then |
| 178 | echo " Boot partition 1 and 2: $((bootsz / 1024))K each" |
| 179 | blocks=$(( bootsz / (128 * 1024) )) |
| 180 | check_truncation "$bootimg1" "$bootsz" |
| 181 | dd if="$bootimg1" of="$outimg" conv=sparse bs=128K count=$blocks \ |
| 182 | status=none |
| 183 | check_truncation "$bootimg2" "$bootsz" |
| 184 | dd if="$bootimg2" of="$outimg" conv=sparse bs=128K count=$blocks \ |
| 185 | seek=$blocks status=none |
| 186 | pos=$((2 * bootsz)) |
| 187 | fi |
| 188 | |
| 189 | if [ "$rpmbsz" -gt 0 ]; then |
| 190 | echo " RPMB partition: $((rpmbsz / 1024))K" |
| 191 | blocks=$(( rpmbsz / (128 * 1024) )) |
| 192 | check_truncation "$rpmbimg" "$rpmbsz" |
| 193 | dd if="$rpmbimg" of="$outimg" conv=sparse bs=128K count=$blocks \ |
| 194 | seek=$(( pos / (128 * 1024) )) status=none |
| 195 | pos=$((pos + rpmbsz)) |
| 196 | fi |
| 197 | |
| 198 | if [ "$usersz" -lt 1024 ]; then |
| 199 | echo " User data: $usersz bytes" |
| 200 | elif [ "$usersz" -lt $((1024 * 1024)) ]; then |
| 201 | echo " User data: $(( (usersz + 1023) / 1024 ))K ($usersz)" |
| 202 | elif [ "$usersz" -lt $((1024 * 1024 * 1024)) ]; then |
| 203 | echo " User data: $(( (usersz + 1048575) / 1048576))M ($usersz)" |
| 204 | else |
| 205 | echo " User data: $(( (usersz + 1073741823) / 1073741824))G ($usersz)" |
| 206 | fi |
| 207 | check_truncation "$userimg" "$usersz" |
| 208 | dd if="$userimg" of="$outimg" conv=sparse bs=128K seek=$(( pos / (128 * 1024) )) \ |
| 209 | count=$(( (usersz + 128 * 1024 - 1) / (128 * 1024) )) status=none |
| 210 | pos=$((pos + usersz)) |
| 211 | truncate -s $pos "$outimg" |
| 212 | |
| 213 | echo "" |
| 214 | echo "Instantiate by appending to the qemu command line:" |
| 215 | echo " -drive file=$outimg,if=none,format=raw,id=emmc-img" |
| 216 | echo " -device emmc,boot-partition-size=$bootsz,rpmb-partition-size=$rpmbsz,drive=emmc-img" |