xref: /openbmc/qemu/scripts/mkemmc.sh (revision deed5c8e9380509862d2996909b4406ee9dea186)
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
12usage() {
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
37process_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 < "$image_file" 2>/dev/null); 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
99check_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 < "$image_file" 2>/dev/null); 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
114userimg=
115outimg=
116bootimg1=
117bootimg2=/dev/zero
118bootsz=0
119rpmbimg=
120rpmbsz=0
121
122while [ $# -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
159done
160
161[ -n "$outimg" ] || usage 1
162
163if [ "$bootsz" -gt $((32640 * 1024)) ]; then
164    echo "Boot image size is larger than 32640K." >&2
165    exit 1
166fi
167if [ "$rpmbsz" -gt $((16384 * 1024)) ]; then
168    echo "RPMB image size is larger than 16384K." >&2
169    exit 1
170fi
171
172echo "Creating eMMC image"
173
174truncate -s 0 "$outimg"
175pos=0
176
177if [ "$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))
187fi
188
189if [ "$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))
196fi
197
198if [ "$usersz" -lt 1024 ]; then
199    echo "  User data:                $usersz bytes"
200elif [ "$usersz" -lt $((1024 * 1024)) ]; then
201    echo "  User data:                $(( (usersz + 1023) / 1024 ))K ($usersz)"
202elif [ "$usersz" -lt $((1024 * 1024 * 1024)) ]; then
203    echo "  User data:                $(( (usersz + 1048575) / 1048576))M ($usersz)"
204else
205    echo "  User data:                $(( (usersz + 1073741823) / 1073741824))G ($usersz)"
206fi
207check_truncation "$userimg" "$usersz"
208dd if="$userimg" of="$outimg" conv=sparse bs=128K seek=$(( pos / (128 * 1024) )) \
209    count=$(( (usersz + 128 * 1024 - 1) / (128 * 1024) )) status=none
210pos=$((pos + usersz))
211truncate -s $pos "$outimg"
212
213echo ""
214echo "Instantiate by appending to the qemu command line:"
215echo "  -drive file=$outimg,if=none,format=raw,id=emmc-img"
216echo "  -device emmc,boot-partition-size=$bootsz,rpmb-partition-size=$rpmbsz,drive=emmc-img"
217