From: Ahmad Fatoum <a.fatoum@barebox.org>
To: barebox@lists.infradead.org
Cc: Ahmad Fatoum <a.fatoum@barebox.org>
Subject: [PATCH v2 9/9] scripts: qemu_interactive.py: add new --sdblk and --emmcblk options
Date: Fri, 26 Jun 2026 18:19:40 +0200 [thread overview]
Message-ID: <20260626162136.1885999-9-a.fatoum@barebox.org> (raw)
In-Reply-To: <20260626162136.1885999-1-a.fatoum@barebox.org>
These options will add a SD-Card or an eMMC block device, respectively.
The eMMC emulation is a recent addition to QEMU and isn't usable in our
Debian Trixie container yet, but it would be very useful to exercise the
RPMB supplicant code as well as EFI variables in future.
The eMMC image has optional BOOT1, BOOT2 and RPMB partitions, which if
available are located on that order in front of the user area.
To make it easy to use multiple separate images, we create a VMDK image
that references the supplied files.
Example usage:
scripts/qemu_interactive.py --emmcblk user=hd.img,boot1=boot1.img,rpmb=rpmb.img
or
scripts/qemu_interactive.py --sdblk hd.img
To make it readily usable on multi_v8_defconfig.yaml, also add a SDHCI
PCI device to the virtual machine.
Assisted-by: Codex:gpt-5.5
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
v1 -> v2:
- new commit
---
scripts/qemu_interactive.py | 174 ++++++++++++++++++++++++++
test/arm/virt@multi_v8_defconfig.yaml | 1 +
2 files changed, 175 insertions(+)
diff --git a/scripts/qemu_interactive.py b/scripts/qemu_interactive.py
index 77ff6f9fc8b4..90b17e4f6422 100755
--- a/scripts/qemu_interactive.py
+++ b/scripts/qemu_interactive.py
@@ -6,17 +6,26 @@
# SPDX-License-Identifier: GPL-2.0-only
import argparse
+import atexit
import glob
import os
import re
import subprocess
import sys
+import tempfile
MODE_INTERACTIVE = "qemu_interactive"
MODE_DRY_RUN = "qemu_dry_run"
MODE_DUMP_DTB = "qemu_dump_dtb"
+EMMC_VMDK_SECTOR_SIZE = 512
+EMMC_BOOT_RPMB_ALIGNMENT = 128 * 1024
+EMMC_USER_ALIGNMENT = 1024 * 1024
+EMMC_PARTITION_HWPARTS = ("boot1", "boot2", "rpmb", "user")
+
+_temporary_emmc_vmdks = []
+
class QemuInteractiveError(Exception):
def __init__(self, message, returncode=1):
@@ -35,6 +44,18 @@ def _assignment(arg):
return arg.split("=", 1)
+def _cleanup_emmc_vmdks():
+ while _temporary_emmc_vmdks:
+ path = _temporary_emmc_vmdks.pop()
+ try:
+ os.unlink(path)
+ except FileNotFoundError:
+ pass
+
+
+atexit.register(_cleanup_emmc_vmdks)
+
+
def register_shared_options(parser):
_add_option(parser, "--graphic", "--graphics", action="store_true",
dest="qemu_graphics", help="enable QEMU graphics output")
@@ -49,6 +70,14 @@ def register_shared_options(parser):
_add_option(parser, "--blk", action="append", dest="qemu_block",
default=[], metavar="FILE",
help="Pass block device to emulated barebox. Can be specified more than once")
+ _add_option(parser, "--sdblk", action="append", dest="qemu_sdblock",
+ default=[], metavar="FILE",
+ help="Pass SD-Card block device to emulated barebox. Can be specified more than once")
+ _add_option(parser, "--emmcblk", action="append", dest="qemu_emmcblock",
+ default=[], metavar="FILE|HWPART=FILE[,HWPART=FILE...]",
+ help="Pass eMMC block device to emulated barebox. Can be "
+ "specified more than once. HWPART may be boot1, boot2, rpmb, "
+ "or user")
_add_option(parser, "--nvmeblk", action="append", dest="qemu_nvmeblock",
default=[], metavar="FILE",
help="Pass NVMe block device with 4K sector size to emulated barebox. Can be specified more than once")
@@ -182,6 +211,141 @@ def _append_qemu_bootargs(strategy, fail, args):
strategy.console.boot_args += " ".join(args)
+def _alignment_name(alignment):
+ if alignment % (1024 * 1024) == 0:
+ return f"{alignment // (1024 * 1024)} MiB"
+
+ return f"{alignment // 1024} KiB"
+
+
+def _emmc_partition_alignment(hwpart):
+ if hwpart == "user":
+ return EMMC_USER_ALIGNMENT
+
+ return EMMC_BOOT_RPMB_ALIGNMENT
+
+
+def _parse_emmc_partition_spec(spec, fail):
+ partitions = {}
+
+ for entry in spec.split(","):
+ if not entry:
+ fail("--emmcblk: empty partition entry")
+
+ hwpart, sep, value = entry.partition("=")
+ if not sep:
+ fail(f"--emmcblk: partition entry '{entry}' lacks '='")
+ if hwpart not in EMMC_PARTITION_HWPARTS:
+ fail(f"--emmcblk: unknown partition '{hwpart}'")
+ if hwpart in partitions:
+ fail(f"--emmcblk: duplicate partition '{hwpart}'")
+ if not value:
+ fail(f"--emmcblk: missing path for '{hwpart}'")
+ if '"' in value or "\n" in value or "\r" in value:
+ fail(f"--emmcblk: unsupported character in path for '{hwpart}'")
+ if not os.path.exists(value):
+ fail(f"--emmcblk: file for '{hwpart}' does not exist: {value}")
+ if not os.path.isfile(value):
+ fail(f"--emmcblk: path for '{hwpart}' is not a regular file: {value}")
+
+ size = os.path.getsize(value)
+ if size == 0:
+ fail(f"--emmcblk: file for '{hwpart}' is empty: {value}")
+
+ alignment = _emmc_partition_alignment(hwpart)
+ if size % alignment:
+ fail(f"--emmcblk: size of '{hwpart}' must be aligned to "
+ f"{_alignment_name(alignment)}: {value}")
+
+ partitions[hwpart] = {
+ "path": os.path.abspath(value),
+ "size": size,
+ }
+
+ if not partitions:
+ fail("--emmcblk: no partitions specified")
+
+ boot1 = partitions.get("boot1")
+ boot2 = partitions.get("boot2")
+
+ if boot1 and boot2 and boot1["size"] != boot2["size"]:
+ fail("--emmcblk: boot1 and boot2 must have the same size")
+
+ return partitions
+
+
+def _emmc_vmdk_escape_path(path):
+ if '"' in path or "\n" in path or "\r" in path:
+ raise QemuInteractiveError(
+ f"--emmcblk: unsupported character in VMDK path: {path}")
+
+ return path
+
+
+def _write_emmc_vmdk(extents):
+ with tempfile.NamedTemporaryFile(
+ mode="w", encoding="ascii", prefix="barebox-emmc-",
+ suffix=".vmdk", delete=False) as vmdk:
+ vmdk.write("# Disk DescriptorFile\n")
+ vmdk.write("version=1\n")
+ vmdk.write("CID=fffffffe\n")
+ vmdk.write("parentCID=ffffffff\n")
+ vmdk.write('createType="monolithicFlat"\n\n')
+ vmdk.write("# Extent description\n")
+
+ for path, size in extents:
+ sectors = size // EMMC_VMDK_SECTOR_SIZE
+ vmdk.write(
+ f'RW {sectors} FLAT "{_emmc_vmdk_escape_path(path)}" 0\n')
+
+ vmdk.write("\n# The Disk Data Base\n")
+ vmdk.write("#DDB\n")
+ vmdk.write('ddb.adapterType = "ide"\n')
+
+ _temporary_emmc_vmdks.append(vmdk.name)
+
+ return vmdk.name
+
+
+def _create_emmc_vmdk(partitions):
+ boot1 = partitions.get("boot1")
+ boot2 = partitions.get("boot2")
+ rpmb = partitions.get("rpmb")
+ user = partitions.get("user")
+ boot_size = 0
+ rpmb_size = 0
+ extents = []
+
+ if boot1 or boot2:
+ boot_size = (boot1 or boot2)["size"]
+ extents.append(((boot1 or {"path": "/dev/zero"})["path"], boot_size))
+ extents.append(((boot2 or {"path": "/dev/zero"})["path"], boot_size))
+
+ if rpmb:
+ rpmb_size = rpmb["size"]
+ extents.append((rpmb["path"], rpmb_size))
+
+ if user:
+ extents.append((user["path"], user["size"]))
+
+ return _write_emmc_vmdk(extents), boot_size, rpmb_size
+
+
+def _qemu_emmc_args(index, blk, fail):
+ if not ("=" in blk or "," in blk):
+ blk = f"user={blk}"
+
+ partitions = _parse_emmc_partition_spec(blk, fail)
+ vmdk, boot_size, rpmb_size = _create_emmc_vmdk(partitions)
+
+ return (
+ "-drive", f"if=none,format=vmdk,id=emmc{index},file={vmdk}",
+ "-device",
+ f"emmc,drive=emmc{index},boot-partition-size={boot_size},"
+ f"rpmb-partition-size={rpmb_size}",
+ )
+
+
def apply_shared_options(strategy, target, options, *, interactive, fail=None): # noqa: max-complexity=30
if fail is None:
fail = _fail
@@ -263,6 +427,16 @@ def apply_shared_options(strategy, target, options, *, interactive, fail=None):
else:
fail("--blk unsupported for target\n")
+ for i, blk in enumerate(_get_list_option(options, "qemu_sdblock")):
+ _append_qemu_args(
+ strategy, fail,
+ "-drive", f"if=none,format=raw,id=sdcard{i},file={blk}",
+ "-device", f"sd-card,drive=sdcard{i}"
+ )
+
+ for i, blk in enumerate(_get_list_option(options, "qemu_emmcblock")):
+ _append_qemu_args(strategy, fail, *_qemu_emmc_args(i, blk, fail))
+
for i, blk in enumerate(_get_list_option(options, "qemu_nvmeblock")):
_append_qemu_args(
strategy, fail,
diff --git a/test/arm/virt@multi_v8_defconfig.yaml b/test/arm/virt@multi_v8_defconfig.yaml
index 209b59c8fff7..ba430df8b3c2 100644
--- a/test/arm/virt@multi_v8_defconfig.yaml
+++ b/test/arm/virt@multi_v8_defconfig.yaml
@@ -10,6 +10,7 @@ targets:
display: qemu-default
extra_args: >
-device qemu-xhci
+ -device sdhci-pci
BareboxDriver:
prompt: 'barebox@[^:]+:[^ ]+ '
bootstring: 'commandline:'
--
2.47.3
prev parent reply other threads:[~2026-06-26 16:23 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-06-26 16:19 [PATCH v2 1/9] scripts: duplicate conftest.py as qemu_interactive.py Ahmad Fatoum
2026-06-26 16:19 ` [PATCH v2 2/9] test: move interactive QEMU launcher out of pytest Ahmad Fatoum
2026-06-26 16:19 ` [PATCH v2 3/9] usb: xhci-hcd: add XHCI over PCI driver Ahmad Fatoum
2026-06-26 16:19 ` [PATCH v2 4/9] scripts: qemu_interactive.py: add new --usbblk option Ahmad Fatoum
2026-06-26 16:19 ` [PATCH v2 5/9] scripts: qemu_interactive.py: add new --nvmeblk option Ahmad Fatoum
2026-06-26 16:19 ` [PATCH v2 6/9] mci: sdhci: define same SDHCI_INT_* constants as Linux Ahmad Fatoum
2026-06-26 16:19 ` [PATCH v2 7/9] mci: add PCI SDHCI controller support Ahmad Fatoum
2026-06-26 16:19 ` [PATCH v2 8/9] ARM: multi_v8_defconfig: enable OP-TEE and RPMB support Ahmad Fatoum
2026-06-26 16:19 ` Ahmad Fatoum [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260626162136.1885999-9-a.fatoum@barebox.org \
--to=a.fatoum@barebox.org \
--cc=barebox@lists.infradead.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox