* [PATCH v2 2/9] test: move interactive QEMU launcher out of pytest
2026-06-26 16:19 [PATCH v2 1/9] scripts: duplicate conftest.py as qemu_interactive.py Ahmad Fatoum
@ 2026-06-26 16:19 ` Ahmad Fatoum
2026-06-26 16:19 ` [PATCH v2 3/9] usb: xhci-hcd: add XHCI over PCI driver Ahmad Fatoum
` (6 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Ahmad Fatoum @ 2026-06-26 16:19 UTC (permalink / raw)
To: barebox; +Cc: Ahmad Fatoum
pytest --interactive apparently starts two QEMUs, the interactive one in
addition to the one started directly by Labgrid. This leads to issues
when QEMU does something mutually exclusive like listening on a port or
taking a file lock.
Arguably, running an interactive QEMU with pytest as a runner was
a strange idea in the first place. Let's stop doing that and add a
dedicated qemu_interactive.py script.
The new script is imported by conftest.py to get support for custom
options like --blk or --rng. --interactive becomes an error.
To avoid cross-dependencies between test suite and the new script, the
CONFIG_NAME resolution is reimplemented without test/py/helper.py, but
apart from that it's mostly copy-paste and adding some boilerplate.
In the future[1], we can adapt this to use the new QEMUDriver.interact().
[1]: https://github.com/labgrid-project/labgrid/pull/1892
Assisted-by: Codex:gpt-5.5
Reported-by: Marc Kleine-Budde <mkl@pengutronix.de>
Signed-off-by: Ahmad Fatoum <a.fatoum@barebox.org>
---
v1 -> v2:
- no change
---
Documentation/boards/emulated.rst | 10 +-
Documentation/devel/contributing.rst | 8 +-
Documentation/user/security-policies.rst | 2 +-
conftest.py | 243 ++---------
scripts/qemu_interactive.py | 534 +++++++++++++++--------
5 files changed, 375 insertions(+), 422 deletions(-)
mode change 100644 => 100755 scripts/qemu_interactive.py
diff --git a/Documentation/boards/emulated.rst b/Documentation/boards/emulated.rst
index 0d6dd85dc759..bc3e2566eaec 100644
--- a/Documentation/boards/emulated.rst
+++ b/Documentation/boards/emulated.rst
@@ -23,15 +23,15 @@ VirtIO over PCI.
Running in QEMU
---------------
-Emulated targets can be started interactively with ``pytest --interactive``::
+Emulated targets can be started interactively with ``scripts/qemu_interactive.py``::
# Run x86 VM runnig the EFI payload from efi_defconfig
- pytest --lg-env test/x86/efi_defconfig.yaml --interactive
+ scripts/qemu_interactive.py test/x86/efi_defconfig.yaml
# Identical to above, provided the CONFIG_NAME=efi_defconfig
- pytest --interactive
+ scripts/qemu_interactive.py
-The test suite can be run by omitting the ``--interactive``.
+The test suite can be run with ``pytest`` instead.
For more information, see the :ref:`labgrid` section in the
:ref:`contributing` guide.
@@ -43,7 +43,7 @@ be used in combination with QEMU. With user-mode networking (SLIRP),
guest-to-host UDP works via NAT out of the box,
but unsolicited host-to-guest UDP requires an explicit port forward::
- pytest --lg-env test/arm/multi_v8_defconfig.yaml --interactive \
+ scripts/qemu_interactive.py test/arm/multi_v8_defconfig.yaml \
--env nv/dev.netconsole.ip=10.0.2.2 \
--env nv/dev.netconsole.port=6666 \
--env init/netconsole="ifup -a1; netconsole.active=ioe" \
diff --git a/Documentation/devel/contributing.rst b/Documentation/devel/contributing.rst
index cb3820f23c63..8b92c6f483aa 100644
--- a/Documentation/devel/contributing.rst
+++ b/Documentation/devel/contributing.rst
@@ -134,7 +134,7 @@ it directly from PyPI instead of your distro's package repositories::
Example usage::
# Run x86 VM runnig the EFI payload from efi_defconfig
- pytest --lg-env test/x86/efi_defconfig.yaml --interactive
+ scripts/qemu_interactive.py test/x86/efi_defconfig.yaml
# Run the test suite against the same
pytest --lg-env test/x86/efi_defconfig.yaml
@@ -145,10 +145,10 @@ built out-of-tree, the build directory must be pointed at by
``LG_BUILDDIR``, ``KBUILD_OUTPUT`` or a ``build`` symlink.
Additional QEMU command-line options can be added by specifying
-them after the ``--qemu`` option::
+them after ``--qemu``::
# appends -device ? to the command line. Add --dry-run to see the final result
- pytest --lg-env test/riscv/rv64i_defconfig.yaml --interactive --qemu -device '?'
+ scripts/qemu_interactive.py test/riscv/rv64i_defconfig.yaml --qemu -device '?'
Some of the QEMU options that are used more often also have explicit
support in the test runner, so paravirtualized devices can be added
@@ -158,7 +158,7 @@ more easily::
pytest --lg-env test/arm/virt@multi_v8_defconfig.yaml --blk=rootfs.ext4
# Run interactively with graphics output
- pytest --lg-env test/mips/qemu-malta_defconfig.yaml --interactive --graphics
+ scripts/qemu_interactive.py test/mips/qemu-malta_defconfig.yaml --graphics
For testing, the QEMU fw_cfg and virtfs support is particularly useful::
diff --git a/Documentation/user/security-policies.rst b/Documentation/user/security-policies.rst
index 8d515508d106..296191c4f8e5 100644
--- a/Documentation/user/security-policies.rst
+++ b/Documentation/user/security-policies.rst
@@ -97,7 +97,7 @@ policy development and evaluation. ``images/barebox-dt-2nd.img`` that results
from building it can be passed as argument to ``qemu-system-arm -M virt -kernel``.
The easiest way to do this is probably installing labgrid and running
-``pytest --interactive`` after having built the config.
+``scripts/qemu_interactive.py`` after having built the config.
Differences from Kconfig
------------------------
diff --git a/conftest.py b/conftest.py
index 6f76586e010b..4ab76b295e59 100644
--- a/conftest.py
+++ b/conftest.py
@@ -3,6 +3,7 @@ import pytest
import os
import argparse
from labgrid.exceptions import NoDriverFoundError
+from scripts import qemu_interactive
from test.py import helper
@@ -38,68 +39,24 @@ def barebox_config(request, strategy, target):
return helper.get_config(command)
-def get_enabled_arch(config):
- # Get the absolute path to the directory containing this script
- base_dir = os.path.dirname(os.path.abspath(__file__))
-
- # Path to the 'arch' directory relative to this script
- arch_dir = os.path.join(base_dir, "arch")
-
- if not os.path.isdir(arch_dir):
- return None
-
- # Optional mapping from directory names to config key suffixes
- arch_map = {"powerpc": "ppc"}
-
- for entry in os.listdir(arch_dir):
- path = os.path.join(arch_dir, entry)
- if os.path.isdir(path):
- key_suffix = arch_map.get(entry, entry).upper()
- config_key = f"CONFIG_{key_suffix}"
- if config.get(config_key):
- return entry
-
- return None
-
-
-def guess_lg_env():
- config_file = helper.open_config_file(os.environ['LG_BUILDDIR'] + "/.config")
- config = helper.parse_config(config_file)
- if not config or not config.get('CONFIG_NAME'):
- return None
- arch = get_enabled_arch(config)
- if not arch:
- return None
- filename = os.path.join("test", arch, f"{config['CONFIG_NAME']}.yaml")
- if os.path.exists(filename):
- return filename
- return None
-
-
lg_env = lg_builddir = None
def pytest_configure(config):
- if 'LG_BUILDDIR' not in os.environ:
- if 'KBUILD_OUTPUT' in os.environ:
- os.environ['LG_BUILDDIR'] = os.environ['KBUILD_OUTPUT']
- elif os.path.isdir('build'):
- os.environ['LG_BUILDDIR'] = os.path.realpath('build')
- else:
- os.environ['LG_BUILDDIR'] = os.getcwd()
-
- if os.environ['LG_BUILDDIR'] is not None:
- os.environ['LG_BUILDDIR'] = os.path.realpath(os.environ['LG_BUILDDIR'])
+ removed_mode = getattr(config.option, 'qemu_interactive_removed', None)
+ if removed_mode is not None:
+ pytest.exit(qemu_interactive.replacement_message(removed_mode),
+ returncode=2)
global lg_env
global lg_builddir
- lg_builddir = os.environ['LG_BUILDDIR']
+ lg_builddir = qemu_interactive.resolve_lg_builddir()
lg_env = config.option.lg_env
if lg_env is None:
lg_env = os.environ.get('LG_ENV')
if lg_env is None:
- if lg_env := guess_lg_env():
+ if lg_env := qemu_interactive.guess_lg_env(lg_builddir):
os.environ['LG_ENV'] = lg_env
@@ -113,39 +70,22 @@ def pytest_report_header(config):
def pytest_addoption(parser):
- def assignment(arg):
- return arg.split('=', 1)
+ parser.addoption('--interactive', action='store_const',
+ const='--interactive', dest='qemu_interactive_removed',
+ help=argparse.SUPPRESS)
+ parser.addoption('--dry-run', action='store_const',
+ const='--dry-run', dest='qemu_interactive_removed',
+ help=argparse.SUPPRESS)
+ parser.addoption('--dump-dtb', action='store_const',
+ const='--dump-dtb', dest='qemu_interactive_removed',
+ help=argparse.SUPPRESS)
- parser.addoption('--interactive', action='store_const', const='qemu_interactive',
- dest='lg_initial_state',
- help=('(for debugging) skip tests and just start Qemu interactively'))
- parser.addoption('--dry-run', action='store_const', const='qemu_dry_run',
- dest='lg_initial_state',
- help=('(for debugging) skip tests and just print Qemu command line'))
- parser.addoption('--dump-dtb', action='store_const', const='qemu_dump_dtb',
- dest='lg_initial_state',
- help=('(for debugging) skip tests and just dump the Qemu device tree'))
- parser.addoption('--graphic', '--graphics', action='store_true', dest='qemu_graphics',
- help=('enable QEMU graphics output'))
- parser.addoption('--rng', action='count', dest='qemu_rng',
- help=('instantiate Virt I/O random number generator'))
- parser.addoption('--console', action='count', dest='qemu_console', default=0,
- help=('Pass an extra console (Virt I/O or ns16550_pci) to emulated barebox'))
- parser.addoption('--fs', action='append', dest='qemu_fs',
- default=[], metavar="[tag=]DIR", type=assignment,
- help=('Pass directory trees to emulated barebox. Can be specified more than once')) # noqa
- parser.addoption('--blk', action='append', dest='qemu_block',
- default=[], metavar="FILE",
- help=('Pass block device to emulated barebox. Can be specified more than once')) # noqa
- parser.addoption('--env', action='append', dest='qemu_fw_cfg', type=assignment,
- default=[], metavar="[envpath=]content | [envpath=]@filepath",
- help=('Pass barebox environment files to barebox. Can be specified more than once')) # noqa
- parser.addoption('--qemu', dest='qemu_arg', nargs=argparse.REMAINDER, default=[],
- help=('Pass all remaining options to QEMU as is'))
- parser.addoption('--bootarg', action='append', dest='bootarg', default=[],
- help=('Pass boot arguments to barebox for debugging purposes'))
- parser.addoption('--port-forward', metavar="PORT", action='append', dest='qemu_port', default=[],
- help=('Forward incoming TCP or UDP connections on specified PORT'))
+ group = parser.getgroup('barebox-qemu', 'barebox QEMU options')
+ qemu_interactive.register_shared_options(group)
+
+
+def _pytest_exit(message, returncode=1):
+ pytest.exit(message, returncode=returncode)
@pytest.fixture(scope="session")
@@ -155,142 +95,9 @@ def strategy(request, target, pytestconfig): # noqa: max-complexity=30
except NoDriverFoundError as e:
pytest.exit(e)
- try:
- main = target.env.config.data["targets"]["main"]
- features = main["features"]
- except KeyError:
- features = []
-
- try:
- main = target.env.config.data["targets"]["main"]
- yaml_env = main["env"]
- except KeyError:
- yaml_env = {}
-
- try:
- main = target.env.config.data["targets"]["main"]
- qemu_bin = main["drivers"]["QEMUDriver"]["qemu_bin"]
- features.append("qemu")
- except KeyError:
- pass
-
- virtio = None
-
- if "virtio-mmio" in features:
- virtio = "device"
- strategy.append_qemu_args('-global virtio-mmio.force-legacy=false')
- if "virtio-pci" in features:
- virtio = "pci,disable-modern=off"
- features.append("pci")
-
- if virtio and pytestconfig.option.qemu_rng:
- for i in range(pytestconfig.option.qemu_rng):
- strategy.append_qemu_args("-device", f"virtio-rng-{virtio}")
-
- for i in range(pytestconfig.option.qemu_console):
- if virtio and i == 0:
- strategy.append_qemu_args(
- "-device", f"virtio-serial-{virtio}",
- "-chardev", f"pty,id=virtcon{i}",
- "-device", f"virtconsole,chardev=virtcon{i},name=console.virtcon{i}"
- )
- continue
-
- # ns16550 serial driver only works with x86 so far
- if 'pci' in features:
- strategy.append_qemu_args(
- "-chardev", f"pty,id=pcicon{i}",
- "-device", f"pci-serial,chardev=pcicon{i}"
- )
- else:
- pytest.exit("barebox currently supports only a single extra virtio console\n", 1)
-
- if "qemu" in features:
- if not pytestconfig.option.qemu_graphics:
- graphics = '-nographic'
- elif qemu_bin == "qemu-system-x86_64":
- graphics = '-device isa-vga'
- elif 'pci' in features:
- graphics = '-device VGA'
- elif virtio:
- graphics = '-vga none -device ramfb'
- graphics += f' -device virtio-keyboard-{virtio}'
- else:
- pytest.exit("--graphics unsupported for target\n", 1)
-
- if graphics is not None and \
- pytestconfig.option.lg_initial_state != 'qemu_interactive':
- graphics += ' -display none'
-
- strategy.append_qemu_args(graphics)
-
- for i, blk in enumerate(pytestconfig.option.qemu_block):
- if virtio:
- strategy.append_qemu_args(
- "-drive", f"if=none,format=raw,id=hd{i},file={blk}",
- "-device", f"virtio-blk-{virtio},drive=hd{i}"
- )
- else:
- pytest.exit("--blk unsupported for target\n", 1)
-
- envopts = {}
-
- for i, fw_cfg in enumerate(pytestconfig.option.qemu_fw_cfg):
- value = fw_cfg.pop()
- envpath = fw_cfg.pop() if fw_cfg else f"data/fw_cfg{i}"
-
- envopts[envpath] = value
-
- for envpath, value in (yaml_env | envopts).items():
- if virtio:
- if isinstance(value, str) and value.startswith('@'):
- source = f"file='{value[1:]}'"
- else:
- source = f"string='{value}'"
-
- strategy.append_qemu_args(
- '-fw_cfg', f'name=opt/org.barebox.env/{envpath},{source}'
- )
- else:
- pytest.exit("env unsupported for target\n", 1)
-
- if len(pytestconfig.option.bootarg) > 0:
- strategy.append_qemu_bootargs(pytestconfig.option.bootarg)
-
- for arg in pytestconfig.option.qemu_arg:
- strategy.append_qemu_args(arg)
-
- qemu_nic = "user,id=net0"
-
- for port in pytestconfig.option.qemu_port:
- qemu_nic += f",hostfwd=udp:127.0.0.2:{port}-:{port}"
- qemu_nic += f",hostfwd=tcp:127.0.0.2:{port}-:{port}"
-
- if "testfs" in features:
- if not any(fs and fs[0] == "testfs" for fs in pytestconfig.option.qemu_fs):
- testfs_path = os.path.join(os.environ["LG_BUILDDIR"], "testfs")
- pytestconfig.option.qemu_fs.append(["testfs", testfs_path])
- os.makedirs(testfs_path, exist_ok=True)
- qemu_nic += f",tftp={testfs_path}"
-
- if "qemu" in features:
- strategy.append_qemu_args("-nic", qemu_nic)
-
- for i, fs in enumerate(pytestconfig.option.qemu_fs):
- if virtio:
- path = fs.pop()
- tag = fs.pop() if fs else f"fs{i}"
-
- strategy.append_qemu_args(
- "-fsdev", f"local,security_model=none,id=fs{i},path={path}",
- "-device", f"virtio-9p-{virtio},id=fs{i},fsdev=fs{i},mount_tag={tag}"
- )
- else:
- pytest.exit("--fs unsupported for target\n", 1)
-
- state = request.config.option.lg_initial_state
- if state is not None:
- strategy.force(state)
+ qemu_interactive.apply_shared_options(
+ strategy, target, pytestconfig.option, interactive=False,
+ fail=_pytest_exit)
return strategy
diff --git a/scripts/qemu_interactive.py b/scripts/qemu_interactive.py
old mode 100644
new mode 100755
index 6f76586e010b..a8605d23b132
--- a/scripts/qemu_interactive.py
+++ b/scripts/qemu_interactive.py
@@ -1,195 +1,218 @@
+#!/usr/bin/env python3
+# /// script
+# requires-python = ">=3.9"
+# dependencies = ["labgrid"]
+# ///
# SPDX-License-Identifier: GPL-2.0-only
-import pytest
-import os
+
import argparse
-from labgrid.exceptions import NoDriverFoundError
-from test.py import helper
+import glob
+import os
+import re
+import subprocess
+import sys
-def transition_to_barebox(request, strategy):
- try:
- strategy.transition('barebox')
- except Exception as e:
- # If a normal strategy transition fails, there's no point in
- # continuing the test. Let's print stderr and exit.
- capmanager = request.config.pluginmanager.getplugin("capturemanager")
- with capmanager.global_and_fixture_disabled():
- _, stderr = capmanager.read_global_capture()
- pytest.exit(f"{type(e).__name__}(\"{e}\"). Standard error:\n{stderr}",
- returncode=3)
+MODE_INTERACTIVE = "qemu_interactive"
+MODE_DRY_RUN = "qemu_dry_run"
+MODE_DUMP_DTB = "qemu_dump_dtb"
-@pytest.fixture(scope='function')
-def barebox(request, strategy, target):
- transition_to_barebox(request, strategy)
- return target.get_driver('BareboxDriver')
+class QemuInteractiveError(Exception):
+ def __init__(self, message, returncode=1):
+ super().__init__(message)
+ self.returncode = returncode
-@pytest.fixture(scope='function')
-def shell(strategy, target):
- strategy.transition('shell')
- return target.get_driver('ShellDriver')
+def _add_option(parser, *opts, **attrs):
+ if hasattr(parser, "addoption"):
+ return parser.addoption(*opts, **attrs)
+
+ return parser.add_argument(*opts, **attrs)
-@pytest.fixture(scope="session")
-def barebox_config(request, strategy, target):
- transition_to_barebox(request, strategy)
- command = target.get_driver("BareboxDriver")
- return helper.get_config(command)
+def _assignment(arg):
+ return arg.split("=", 1)
-def get_enabled_arch(config):
- # Get the absolute path to the directory containing this script
- base_dir = os.path.dirname(os.path.abspath(__file__))
-
- # Path to the 'arch' directory relative to this script
- arch_dir = os.path.join(base_dir, "arch")
-
- if not os.path.isdir(arch_dir):
- return None
-
- # Optional mapping from directory names to config key suffixes
- arch_map = {"powerpc": "ppc"}
-
- for entry in os.listdir(arch_dir):
- path = os.path.join(arch_dir, entry)
- if os.path.isdir(path):
- key_suffix = arch_map.get(entry, entry).upper()
- config_key = f"CONFIG_{key_suffix}"
- if config.get(config_key):
- return entry
-
- return None
+def register_shared_options(parser):
+ _add_option(parser, "--graphic", "--graphics", action="store_true",
+ dest="qemu_graphics", help="enable QEMU graphics output")
+ _add_option(parser, "--rng", action="count", dest="qemu_rng",
+ help="instantiate Virt I/O random number generator")
+ _add_option(parser, "--console", action="count", dest="qemu_console",
+ default=0,
+ help="Pass an extra console (Virt I/O or ns16550_pci) to emulated barebox")
+ _add_option(parser, "--fs", action="append", dest="qemu_fs",
+ default=[], metavar="[tag=]DIR", type=_assignment,
+ help="Pass directory trees to emulated barebox. Can be specified more than once")
+ _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, "--env", action="append", dest="qemu_fw_cfg",
+ type=_assignment, default=[],
+ metavar="[envpath=]content | [envpath=]@filepath",
+ help="Pass barebox environment files to barebox. Can be specified more than once")
+ _add_option(parser, "--qemu", dest="qemu_arg",
+ nargs=argparse.REMAINDER, default=[],
+ help="Pass all remaining options to QEMU as is")
+ _add_option(parser, "--bootarg", action="append", dest="bootarg",
+ default=[],
+ help="Pass boot arguments to barebox for debugging purposes")
+ _add_option(parser, "--port-forward", metavar="PORT", action="append",
+ dest="qemu_port", default=[],
+ help="Forward incoming TCP or UDP connections on specified PORT")
-def guess_lg_env():
- config_file = helper.open_config_file(os.environ['LG_BUILDDIR'] + "/.config")
- config = helper.parse_config(config_file)
- if not config or not config.get('CONFIG_NAME'):
- return None
- arch = get_enabled_arch(config)
- if not arch:
- return None
- filename = os.path.join("test", arch, f"{config['CONFIG_NAME']}.yaml")
- if os.path.exists(filename):
- return filename
- return None
+def register_interactive_options(parser):
+ _add_option(parser, "--lg-env", dest="lg_env", metavar="LG_ENV",
+ help="labgrid environment config file")
+
+ if hasattr(parser, "add_mutually_exclusive_group"):
+ mode_parser = parser.add_mutually_exclusive_group()
+ else:
+ mode_parser = parser
+
+ _add_option(mode_parser, "--dry-run", action="store_const",
+ const=MODE_DRY_RUN, default=MODE_INTERACTIVE,
+ dest="qemu_mode",
+ help="print the QEMU command line instead of executing it")
+ _add_option(mode_parser, "--dump-dtb", action="store_const",
+ const=MODE_DUMP_DTB, dest="qemu_mode",
+ help="run QEMU only to dump the device tree")
-lg_env = lg_builddir = None
+def repo_root():
+ return os.path.realpath(os.path.join(os.path.dirname(__file__), os.pardir))
-def pytest_configure(config):
- if 'LG_BUILDDIR' not in os.environ:
- if 'KBUILD_OUTPUT' in os.environ:
- os.environ['LG_BUILDDIR'] = os.environ['KBUILD_OUTPUT']
- elif os.path.isdir('build'):
- os.environ['LG_BUILDDIR'] = os.path.realpath('build')
+def resolve_lg_builddir():
+ if "LG_BUILDDIR" not in os.environ:
+ if "KBUILD_OUTPUT" in os.environ:
+ os.environ["LG_BUILDDIR"] = os.environ["KBUILD_OUTPUT"]
+ elif os.path.isdir("build"):
+ os.environ["LG_BUILDDIR"] = os.path.realpath("build")
else:
- os.environ['LG_BUILDDIR'] = os.getcwd()
+ os.environ["LG_BUILDDIR"] = os.getcwd()
- if os.environ['LG_BUILDDIR'] is not None:
- os.environ['LG_BUILDDIR'] = os.path.realpath(os.environ['LG_BUILDDIR'])
-
- global lg_env
- global lg_builddir
-
- lg_builddir = os.environ['LG_BUILDDIR']
- lg_env = config.option.lg_env
- if lg_env is None:
- lg_env = os.environ.get('LG_ENV')
- if lg_env is None:
- if lg_env := guess_lg_env():
- os.environ['LG_ENV'] = lg_env
+ os.environ["LG_BUILDDIR"] = os.path.realpath(os.environ["LG_BUILDDIR"])
+ return os.environ["LG_BUILDDIR"]
-def pytest_report_header(config):
- report = []
- if lg_builddir is not None:
- report += [f"Build diectory: {lg_builddir}"]
- if lg_env is not None:
- report += [f"Labgrid Environment: {lg_env}"]
- return "\n".join(report)
-
-
-def pytest_addoption(parser):
- def assignment(arg):
- return arg.split('=', 1)
-
- parser.addoption('--interactive', action='store_const', const='qemu_interactive',
- dest='lg_initial_state',
- help=('(for debugging) skip tests and just start Qemu interactively'))
- parser.addoption('--dry-run', action='store_const', const='qemu_dry_run',
- dest='lg_initial_state',
- help=('(for debugging) skip tests and just print Qemu command line'))
- parser.addoption('--dump-dtb', action='store_const', const='qemu_dump_dtb',
- dest='lg_initial_state',
- help=('(for debugging) skip tests and just dump the Qemu device tree'))
- parser.addoption('--graphic', '--graphics', action='store_true', dest='qemu_graphics',
- help=('enable QEMU graphics output'))
- parser.addoption('--rng', action='count', dest='qemu_rng',
- help=('instantiate Virt I/O random number generator'))
- parser.addoption('--console', action='count', dest='qemu_console', default=0,
- help=('Pass an extra console (Virt I/O or ns16550_pci) to emulated barebox'))
- parser.addoption('--fs', action='append', dest='qemu_fs',
- default=[], metavar="[tag=]DIR", type=assignment,
- help=('Pass directory trees to emulated barebox. Can be specified more than once')) # noqa
- parser.addoption('--blk', action='append', dest='qemu_block',
- default=[], metavar="FILE",
- help=('Pass block device to emulated barebox. Can be specified more than once')) # noqa
- parser.addoption('--env', action='append', dest='qemu_fw_cfg', type=assignment,
- default=[], metavar="[envpath=]content | [envpath=]@filepath",
- help=('Pass barebox environment files to barebox. Can be specified more than once')) # noqa
- parser.addoption('--qemu', dest='qemu_arg', nargs=argparse.REMAINDER, default=[],
- help=('Pass all remaining options to QEMU as is'))
- parser.addoption('--bootarg', action='append', dest='bootarg', default=[],
- help=('Pass boot arguments to barebox for debugging purposes'))
- parser.addoption('--port-forward', metavar="PORT", action='append', dest='qemu_port', default=[],
- help=('Forward incoming TCP or UDP connections on specified PORT'))
-
-
-@pytest.fixture(scope="session")
-def strategy(request, target, pytestconfig): # noqa: max-complexity=30
+def get_config_name(builddir):
try:
- strategy = target.get_driver("Strategy")
- except NoDriverFoundError as e:
- pytest.exit(e)
-
- try:
- main = target.env.config.data["targets"]["main"]
- features = main["features"]
- except KeyError:
- features = []
-
- try:
- main = target.env.config.data["targets"]["main"]
- yaml_env = main["env"]
- except KeyError:
- yaml_env = {}
-
- try:
- main = target.env.config.data["targets"]["main"]
- qemu_bin = main["drivers"]["QEMUDriver"]["qemu_bin"]
- features.append("qemu")
- except KeyError:
+ with open(os.path.join(builddir, ".config")) as config:
+ for line in config:
+ match = re.match(r'^CONFIG_NAME="(.*)"$', line)
+ if match:
+ return match.group(1)
+ except OSError:
pass
+ return None
+
+
+def guess_lg_env(builddir=None):
+ if builddir is None:
+ builddir = os.environ.get("LG_BUILDDIR")
+ if builddir is None:
+ return None
+
+ config_name = get_config_name(builddir)
+ if config_name is None:
+ return None
+
+ matches = glob.glob(os.path.join(repo_root(), "test", "*",
+ f"{config_name}.yaml"))
+ if matches:
+ return matches[0]
+
+ return None
+
+
+def resolve_lg_env(lg_env=None):
+ if lg_env is None:
+ lg_env = os.environ.get("LG_ENV")
+ if lg_env is None:
+ lg_env = guess_lg_env()
+
+ if lg_env is not None:
+ os.environ["LG_ENV"] = lg_env
+
+ return lg_env
+
+
+def _get_option(options, name, default=None):
+ if isinstance(options, dict):
+ return options.get(name, default)
+
+ return getattr(options, name, default)
+
+
+def _get_list_option(options, name):
+ value = _get_option(options, name, [])
+ if value is None:
+ return []
+ return list(value)
+
+
+def _fail(message, returncode=1):
+ raise QemuInteractiveError(message, returncode)
+
+
+def _append_qemu_args(strategy, fail, *args):
+ if strategy.qemu is None:
+ fail("Qemu option supplied for non-Qemu target")
+
+ for arg in args:
+ strategy.console.extra_args += " " + arg
+
+
+def _append_qemu_bootargs(strategy, fail, args):
+ if strategy.qemu is None:
+ fail("Qemu option supplied for non-Qemu target")
+ if strategy.console.boot_args is None:
+ strategy.console.boot_args = ""
+ strategy.console.boot_args += " ".join(args)
+
+
+def apply_shared_options(strategy, target, options, *, interactive, fail=None): # noqa: max-complexity=30
+ if fail is None:
+ fail = _fail
+
+ try:
+ main = target.env.config.data["targets"]["main"]
+ except KeyError:
+ main = {}
+
+ features = list(main.get("features", []))
+ yaml_env = dict(main.get("env", {}))
+ qemu_driver = main.get("drivers", {}).get("QEMUDriver")
+ qemu_bin = None
+
+ if qemu_driver is not None:
+ qemu_bin = qemu_driver.get("qemu_bin")
+ features.append("qemu")
+
virtio = None
if "virtio-mmio" in features:
virtio = "device"
- strategy.append_qemu_args('-global virtio-mmio.force-legacy=false')
+ _append_qemu_args(strategy, fail, '-global virtio-mmio.force-legacy=false')
if "virtio-pci" in features:
virtio = "pci,disable-modern=off"
features.append("pci")
- if virtio and pytestconfig.option.qemu_rng:
- for i in range(pytestconfig.option.qemu_rng):
- strategy.append_qemu_args("-device", f"virtio-rng-{virtio}")
+ qemu_rng = _get_option(options, "qemu_rng", 0) or 0
+ for _ in range(qemu_rng):
+ if virtio:
+ _append_qemu_args(strategy, fail, "-device", f"virtio-rng-{virtio}")
- for i in range(pytestconfig.option.qemu_console):
+ qemu_console = _get_option(options, "qemu_console", 0) or 0
+ for i in range(qemu_console):
if virtio and i == 0:
- strategy.append_qemu_args(
+ _append_qemu_args(
+ strategy, fail,
"-device", f"virtio-serial-{virtio}",
"-chardev", f"pty,id=virtcon{i}",
"-device", f"virtconsole,chardev=virtcon{i},name=console.virtcon{i}"
@@ -198,15 +221,16 @@ def strategy(request, target, pytestconfig): # noqa: max-complexity=30
# ns16550 serial driver only works with x86 so far
if 'pci' in features:
- strategy.append_qemu_args(
+ _append_qemu_args(
+ strategy, fail,
"-chardev", f"pty,id=pcicon{i}",
"-device", f"pci-serial,chardev=pcicon{i}"
)
else:
- pytest.exit("barebox currently supports only a single extra virtio console\n", 1)
+ fail("barebox currently supports only a single extra virtio console\n")
if "qemu" in features:
- if not pytestconfig.option.qemu_graphics:
+ if not _get_option(options, "qemu_graphics", False):
graphics = '-nographic'
elif qemu_bin == "qemu-system-x86_64":
graphics = '-device isa-vga'
@@ -216,89 +240,211 @@ def strategy(request, target, pytestconfig): # noqa: max-complexity=30
graphics = '-vga none -device ramfb'
graphics += f' -device virtio-keyboard-{virtio}'
else:
- pytest.exit("--graphics unsupported for target\n", 1)
+ fail("--graphics unsupported for target\n")
- if graphics is not None and \
- pytestconfig.option.lg_initial_state != 'qemu_interactive':
+ if graphics is not None and not interactive:
graphics += ' -display none'
- strategy.append_qemu_args(graphics)
+ _append_qemu_args(strategy, fail, graphics)
- for i, blk in enumerate(pytestconfig.option.qemu_block):
+ for i, blk in enumerate(_get_list_option(options, "qemu_block")):
if virtio:
- strategy.append_qemu_args(
+ _append_qemu_args(
+ strategy, fail,
"-drive", f"if=none,format=raw,id=hd{i},file={blk}",
"-device", f"virtio-blk-{virtio},drive=hd{i}"
)
else:
- pytest.exit("--blk unsupported for target\n", 1)
+ fail("--blk unsupported for target\n")
envopts = {}
- for i, fw_cfg in enumerate(pytestconfig.option.qemu_fw_cfg):
+ for i, fw_cfg in enumerate(_get_list_option(options, "qemu_fw_cfg")):
value = fw_cfg.pop()
envpath = fw_cfg.pop() if fw_cfg else f"data/fw_cfg{i}"
envopts[envpath] = value
- for envpath, value in (yaml_env | envopts).items():
+ for envpath, value in {**yaml_env, **envopts}.items():
if virtio:
if isinstance(value, str) and value.startswith('@'):
source = f"file='{value[1:]}'"
else:
source = f"string='{value}'"
- strategy.append_qemu_args(
+ _append_qemu_args(
+ strategy, fail,
'-fw_cfg', f'name=opt/org.barebox.env/{envpath},{source}'
)
else:
- pytest.exit("env unsupported for target\n", 1)
+ fail("env unsupported for target\n")
- if len(pytestconfig.option.bootarg) > 0:
- strategy.append_qemu_bootargs(pytestconfig.option.bootarg)
+ bootargs = _get_list_option(options, "bootarg")
+ if bootargs:
+ _append_qemu_bootargs(strategy, fail, bootargs)
- for arg in pytestconfig.option.qemu_arg:
- strategy.append_qemu_args(arg)
+ for arg in _get_list_option(options, "qemu_arg"):
+ _append_qemu_args(strategy, fail, arg)
qemu_nic = "user,id=net0"
- for port in pytestconfig.option.qemu_port:
+ for port in _get_list_option(options, "qemu_port"):
qemu_nic += f",hostfwd=udp:127.0.0.2:{port}-:{port}"
qemu_nic += f",hostfwd=tcp:127.0.0.2:{port}-:{port}"
+ qemu_fs = [list(fs) for fs in _get_list_option(options, "qemu_fs")]
+
if "testfs" in features:
- if not any(fs and fs[0] == "testfs" for fs in pytestconfig.option.qemu_fs):
+ if not any(fs and fs[0] == "testfs" for fs in qemu_fs):
testfs_path = os.path.join(os.environ["LG_BUILDDIR"], "testfs")
- pytestconfig.option.qemu_fs.append(["testfs", testfs_path])
+ qemu_fs.append(["testfs", testfs_path])
os.makedirs(testfs_path, exist_ok=True)
qemu_nic += f",tftp={testfs_path}"
if "qemu" in features:
- strategy.append_qemu_args("-nic", qemu_nic)
+ _append_qemu_args(strategy, fail, "-nic", qemu_nic)
- for i, fs in enumerate(pytestconfig.option.qemu_fs):
+ for i, fs in enumerate(qemu_fs):
if virtio:
path = fs.pop()
tag = fs.pop() if fs else f"fs{i}"
- strategy.append_qemu_args(
+ _append_qemu_args(
+ strategy, fail,
"-fsdev", f"local,security_model=none,id=fs{i},path={path}",
"-device", f"virtio-9p-{virtio},id=fs{i},fsdev=fs{i},mount_tag={tag}"
)
else:
- pytest.exit("--fs unsupported for target\n", 1)
-
- state = request.config.option.lg_initial_state
- if state is not None:
- strategy.force(state)
-
- return strategy
+ fail("--fs unsupported for target\n")
-@pytest.fixture(scope="session")
-def testfs(strategy, env):
- if "testfs" not in env.get_target_features():
- pytest.skip("testfs not supported on this platform")
+def replacement_message(mode):
+ return (f"pytest {mode} is no longer supported. Use "
+ "scripts/qemu_interactive.py instead.")
- path = os.path.join(os.environ["LG_BUILDDIR"], "testfs")
- return path
+
+def _split_qemu_args(argv):
+ for i, arg in enumerate(argv):
+ if arg in ("--qemu", "--"):
+ return argv[:i], argv[i + 1:]
+ if arg.startswith("--qemu="):
+ return argv[:i], [arg.split("=", 1)[1]] + argv[i + 1:]
+
+ return argv, []
+
+
+def build_arg_parser():
+ parser = argparse.ArgumentParser(
+ description="Start a barebox labgrid QEMU target interactively",
+ epilog="Raw QEMU arguments may follow either --qemu or --."
+ )
+
+ register_interactive_options(
+ parser.add_argument_group("qemu_interactive.py options"))
+ register_shared_options(parser.add_argument_group("QEMU options"))
+ parser.add_argument("lg_env_pos", nargs="*", metavar="LG_ENV",
+ help="labgrid environment config file")
+
+ return parser
+
+
+def parse_args(argv=None):
+ if argv is None:
+ argv = sys.argv[1:]
+
+ parser = build_arg_parser()
+ argv, qemu_args = _split_qemu_args(list(argv))
+ args = parser.parse_args(argv)
+
+ if len(args.lg_env_pos) > 1:
+ parser.error("only one positional labgrid environment may be specified")
+
+ if args.lg_env_pos:
+ if args.lg_env is not None:
+ parser.error("labgrid environment specified both positionally and with --lg-env")
+ args.lg_env = args.lg_env_pos[0]
+
+ del args.lg_env_pos
+ args.qemu_arg = qemu_args
+
+ return args
+
+
+def quote_cmd(cmd):
+ quoted = map(lambda s: s if s.find(" ") == -1 else "'" + s + "'", cmd)
+ return " ".join(quoted)
+
+
+def build_qemu_command(strategy, mode):
+ if strategy.qemu is None:
+ raise QemuInteractiveError(f"Can't enter {mode} for non-QEMU target")
+
+ if mode != MODE_DRY_RUN:
+ strategy.transition("off")
+
+ if mode == MODE_DUMP_DTB:
+ strategy.qemu.machine += f",dumpdtb={strategy.target.name}.dtb"
+
+ cmd = strategy.qemu.get_qemu_base_args()
+ cmd.extend(["-serial", "mon:stdio", "-trace", "file=/dev/null"])
+
+ return cmd
+
+
+def run_qemu(strategy, mode):
+ if mode not in (MODE_INTERACTIVE, MODE_DRY_RUN, MODE_DUMP_DTB):
+ raise QemuInteractiveError(
+ "Can only force to: qemu_dry_run, qemu_interactive, qemu_dump_dtb")
+
+ cmd = build_qemu_command(strategy, mode)
+ command = "running: \n{}\n".format(quote_cmd(cmd))
+
+ if mode == MODE_DRY_RUN:
+ print(command, end="")
+ return 0
+
+ with open("/dev/tty", "r+b", buffering=0) as tty:
+ tty.write(bytes(command, "UTF-8"))
+ return subprocess.run(cmd, stdin=tty, stdout=tty, stderr=tty).returncode
+
+
+def main(argv=None):
+ from labgrid import Environment
+ from labgrid.exceptions import NoDriverFoundError
+
+ args = parse_args(argv)
+ env = None
+
+ try:
+ resolve_lg_builddir()
+ lg_env = resolve_lg_env(args.lg_env)
+ if lg_env is None:
+ raise QemuInteractiveError(
+ "no labgrid environment specified; pass --lg-env, "
+ "a positional LG_ENV, set LG_ENV, or build a config with CONFIG_NAME",
+ 2)
+
+ env = Environment(lg_env)
+ target = env.get_target("main")
+ if target is None:
+ raise QemuInteractiveError("labgrid environment has no main target")
+
+ try:
+ strategy = target.get_driver("Strategy")
+ except NoDriverFoundError as e:
+ raise QemuInteractiveError(str(e)) from e
+
+ apply_shared_options(strategy, target, args,
+ interactive=args.qemu_mode != MODE_DUMP_DTB)
+
+ return run_qemu(strategy, args.qemu_mode)
+ except QemuInteractiveError as e:
+ print(f"error: {e}", file=sys.stderr)
+ return e.returncode
+ finally:
+ if env is not None:
+ env.cleanup()
+
+
+if __name__ == "__main__":
+ sys.exit(main())
--
2.47.3
^ permalink raw reply [flat|nested] 9+ messages in thread* [PATCH v2 9/9] scripts: qemu_interactive.py: add new --sdblk and --emmcblk options
2026-06-26 16:19 [PATCH v2 1/9] scripts: duplicate conftest.py as qemu_interactive.py Ahmad Fatoum
` (6 preceding siblings ...)
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
7 siblings, 0 replies; 9+ messages in thread
From: Ahmad Fatoum @ 2026-06-26 16:19 UTC (permalink / raw)
To: barebox; +Cc: Ahmad Fatoum
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
^ permalink raw reply [flat|nested] 9+ messages in thread