From bf63a60bb7fc2ade3a0269b2743f8ceb18a6ae71 Mon Sep 17 00:00:00 2001 From: nhitar Date: Mon, 16 Mar 2026 22:19:54 +0300 Subject: [PATCH 1/3] feat: add qemu v9.1.0 and test-disk patch --- .../qemu/nativesdk-qemu-helper_1.0.bb | 34 + .../qemu/qemu-helper-native_1.0.bb | 24 + .../qemu/qemu-helper/qemu-oe-bridge-helper.c | 40 + .../recipes-devtools/qemu/qemu-native.inc | 7 + .../qemu/qemu-native_9.1.0.bb | 10 + .../qemu/qemu-system-native_9.1.0.bb | 32 + .../recipes-devtools/qemu/qemu-targets.inc | 28 + .../recipes-devtools/qemu/qemu.inc | 272 ++ .../qemu/qemu/0000-QEMU-test-disk.patch | 4190 +++++++++++++++++ ...n-environment-space-to-boot-loader-q.patch | 32 + ...ed_attr-Do-not-define-for-glibc-2.41.patch | 45 + .../0002-apic-fixup-fallthrough-to-PIC.patch | 43 + ...mu-Do-not-include-file-if-not-exists.patch | 31 + ...er-space-mmap-tweaks-to-address-musl.patch | 47 + .../qemu/0006-qemu-Determinism-fixes.patch | 30 + ...d-use-relative-path-to-refer-to-file.patch | 43 + ...and-MAP_SHARED_VALIDATE-on-needed-li.patch | 45 + ...gure-lookup-meson-exutable-from-PATH.patch | 28 + ...and-the-python-venv-aren-t-used-for-.patch | 51 + .../qemu/qemu/fix-strerrorname_np.patch | 35 + .../qemu/qemu/powerpc_rom.bin | Bin 0 -> 4096 bytes .../qemu/qemu/qemu-guest-agent.init | 75 + .../qemu/qemu/qemu-guest-agent.udev | 2 + .../recipes-devtools/qemu/qemu/run-ptest | 13 + .../recipes-devtools/qemu/qemu_9.1.0.bb | 27 + .../qemu/qemuwrapper-cross_1.0.bb | 41 + 26 files changed, 5225 insertions(+) create mode 100644 layers/meta-image-tests/recipes-devtools/qemu/nativesdk-qemu-helper_1.0.bb create mode 100644 layers/meta-image-tests/recipes-devtools/qemu/qemu-helper-native_1.0.bb create mode 100644 layers/meta-image-tests/recipes-devtools/qemu/qemu-helper/qemu-oe-bridge-helper.c create mode 100644 layers/meta-image-tests/recipes-devtools/qemu/qemu-native.inc create mode 100644 layers/meta-image-tests/recipes-devtools/qemu/qemu-native_9.1.0.bb create mode 100644 layers/meta-image-tests/recipes-devtools/qemu/qemu-system-native_9.1.0.bb create mode 100644 layers/meta-image-tests/recipes-devtools/qemu/qemu-targets.inc create mode 100644 layers/meta-image-tests/recipes-devtools/qemu/qemu.inc create mode 100644 layers/meta-image-tests/recipes-devtools/qemu/qemu/0000-QEMU-test-disk.patch create mode 100644 layers/meta-image-tests/recipes-devtools/qemu/qemu/0001-qemu-Add-addition-environment-space-to-boot-loader-q.patch create mode 100644 layers/meta-image-tests/recipes-devtools/qemu/qemu/0001-sched_attr-Do-not-define-for-glibc-2.41.patch create mode 100644 layers/meta-image-tests/recipes-devtools/qemu/qemu/0002-apic-fixup-fallthrough-to-PIC.patch create mode 100644 layers/meta-image-tests/recipes-devtools/qemu/qemu/0004-qemu-Do-not-include-file-if-not-exists.patch create mode 100644 layers/meta-image-tests/recipes-devtools/qemu/qemu/0005-qemu-Add-some-user-space-mmap-tweaks-to-address-musl.patch create mode 100644 layers/meta-image-tests/recipes-devtools/qemu/qemu/0006-qemu-Determinism-fixes.patch create mode 100644 layers/meta-image-tests/recipes-devtools/qemu/qemu/0007-tests-meson.build-use-relative-path-to-refer-to-file.patch create mode 100644 layers/meta-image-tests/recipes-devtools/qemu/qemu/0008-Define-MAP_SYNC-and-MAP_SHARED_VALIDATE-on-needed-li.patch create mode 100644 layers/meta-image-tests/recipes-devtools/qemu/qemu/0010-configure-lookup-meson-exutable-from-PATH.patch create mode 100644 layers/meta-image-tests/recipes-devtools/qemu/qemu/0011-qemu-Ensure-pip-and-the-python-venv-aren-t-used-for-.patch create mode 100644 layers/meta-image-tests/recipes-devtools/qemu/qemu/fix-strerrorname_np.patch create mode 100644 layers/meta-image-tests/recipes-devtools/qemu/qemu/powerpc_rom.bin create mode 100644 layers/meta-image-tests/recipes-devtools/qemu/qemu/qemu-guest-agent.init create mode 100644 layers/meta-image-tests/recipes-devtools/qemu/qemu/qemu-guest-agent.udev create mode 100644 layers/meta-image-tests/recipes-devtools/qemu/qemu/run-ptest create mode 100644 layers/meta-image-tests/recipes-devtools/qemu/qemu_9.1.0.bb create mode 100644 layers/meta-image-tests/recipes-devtools/qemu/qemuwrapper-cross_1.0.bb diff --git a/layers/meta-image-tests/recipes-devtools/qemu/nativesdk-qemu-helper_1.0.bb b/layers/meta-image-tests/recipes-devtools/qemu/nativesdk-qemu-helper_1.0.bb new file mode 100644 index 00000000..90bba84b --- /dev/null +++ b/layers/meta-image-tests/recipes-devtools/qemu/nativesdk-qemu-helper_1.0.bb @@ -0,0 +1,34 @@ +SUMMARY = "Qemu helper scripts" +LICENSE = "GPL-2.0-only" +RDEPENDS:${PN} = "nativesdk-qemu nativesdk-unfs3 nativesdk-pseudo \ + nativesdk-python3-shell nativesdk-python3-fcntl nativesdk-python3-logging \ + " + + +LIC_FILES_CHKSUM = "file://${COREBASE}/scripts/runqemu;beginline=5;endline=10;md5=ac2b489a58739c7628a2604698db5e7f" + + +SRC_URI = "file://${COREBASE}/scripts/runqemu \ + file://${COREBASE}/scripts/runqemu-addptable2image \ + file://${COREBASE}/scripts/runqemu-gen-tapdevs \ + file://${COREBASE}/scripts/runqemu-ifup \ + file://${COREBASE}/scripts/runqemu-ifdown \ + file://${COREBASE}/scripts/oe-find-native-sysroot \ + file://${COREBASE}/scripts/runqemu-extract-sdk \ + file://${COREBASE}/scripts/runqemu-export-rootfs \ + " + +S = "${WORKDIR}/sources" +UNPACKDIR = "${S}" + +inherit nativesdk + +do_compile() { + : +} + +do_install() { + install -d ${D}${bindir} + install -m 0755 ${S}${COREBASE}/scripts/oe-* ${D}${bindir}/ + install -m 0755 ${S}${COREBASE}/scripts/runqemu* ${D}${bindir}/ +} diff --git a/layers/meta-image-tests/recipes-devtools/qemu/qemu-helper-native_1.0.bb b/layers/meta-image-tests/recipes-devtools/qemu/qemu-helper-native_1.0.bb new file mode 100644 index 00000000..28a3b95c --- /dev/null +++ b/layers/meta-image-tests/recipes-devtools/qemu/qemu-helper-native_1.0.bb @@ -0,0 +1,24 @@ +SUMMARY = "Helper utilities needed by the runqemu script" +LICENSE = "GPL-2.0-only" +RDEPENDS:${PN} = "qemu-system-native" + +LIC_FILES_CHKSUM = "file://${S}/qemu-oe-bridge-helper.c;endline=4;md5=ae00a3bab86f2caaa8462eacda77f4d7" + +SRC_URI = "file://qemu-oe-bridge-helper.c" + +S = "${WORKDIR}/sources" +UNPACKDIR = "${S}" + +inherit native + +do_compile() { + ${CC} ${CFLAGS} ${LDFLAGS} -Wall qemu-oe-bridge-helper.c -o qemu-oe-bridge-helper +} + +do_install() { + install -d ${D}${bindir} + install qemu-oe-bridge-helper ${D}${bindir}/ +} + +DEPENDS += "qemu-system-native unfs3-native pseudo-native" +addtask addto_recipe_sysroot after do_populate_sysroot before do_build diff --git a/layers/meta-image-tests/recipes-devtools/qemu/qemu-helper/qemu-oe-bridge-helper.c b/layers/meta-image-tests/recipes-devtools/qemu/qemu-helper/qemu-oe-bridge-helper.c new file mode 100644 index 00000000..c34aa005 --- /dev/null +++ b/layers/meta-image-tests/recipes-devtools/qemu/qemu-helper/qemu-oe-bridge-helper.c @@ -0,0 +1,40 @@ +/* + * Copyright 2022 Garmin Ltd. or its subsidiaries + * + * SPDX-License-Identifier: GPL-2.0 + * + * Attempts to find and exec the host qemu-bridge-helper program + */ + +#include +#include +#include +#include + +void try_program(char const* path, char** args) { + if (access(path, X_OK) == 0) { + execv(path, args); + } +} + +int main(int argc, char** argv) { + char* var; + + var = getenv("QEMU_BRIDGE_HELPER"); + if (var && var[0] != '\0') { + execvp(var, argv); + return 1; + } + + if (argc == 2 && strcmp(argv[1], "--help") == 0) { + fprintf(stderr, "Helper function to find and exec qemu-bridge-helper. Set QEMU_BRIDGE_HELPER to override default search path\n"); + return 0; + } + + try_program("/usr/libexec/qemu-bridge-helper", argv); + try_program("/usr/lib/qemu/qemu-bridge-helper", argv); + + fprintf(stderr, "No bridge helper found\n"); + return 1; +} + diff --git a/layers/meta-image-tests/recipes-devtools/qemu/qemu-native.inc b/layers/meta-image-tests/recipes-devtools/qemu/qemu-native.inc new file mode 100644 index 00000000..d074d7d1 --- /dev/null +++ b/layers/meta-image-tests/recipes-devtools/qemu/qemu-native.inc @@ -0,0 +1,7 @@ +require qemu.inc + +inherit_defer native + +EXTRA_OEMAKE:append = " LD='${LD}' AR='${AR}' OBJCOPY='${OBJCOPY}' LDFLAGS='${LDFLAGS}'" + +LDFLAGS += "-fuse-ld=bfd" diff --git a/layers/meta-image-tests/recipes-devtools/qemu/qemu-native_9.1.0.bb b/layers/meta-image-tests/recipes-devtools/qemu/qemu-native_9.1.0.bb new file mode 100644 index 00000000..26fa84c1 --- /dev/null +++ b/layers/meta-image-tests/recipes-devtools/qemu/qemu-native_9.1.0.bb @@ -0,0 +1,10 @@ +BPN = "qemu" + +DEPENDS += "glib-2.0-native zlib-native" + +require qemu-native.inc + +EXTRA_OECONF:append = " --target-list=${@get_qemu_usermode_target_list(d)} --disable-tools --disable-install-blobs --disable-guest-agent" + +PACKAGECONFIG ??= "pie" + diff --git a/layers/meta-image-tests/recipes-devtools/qemu/qemu-system-native_9.1.0.bb b/layers/meta-image-tests/recipes-devtools/qemu/qemu-system-native_9.1.0.bb new file mode 100644 index 00000000..e7bbe5d2 --- /dev/null +++ b/layers/meta-image-tests/recipes-devtools/qemu/qemu-system-native_9.1.0.bb @@ -0,0 +1,32 @@ +BPN = "qemu" + +require qemu-native.inc + +# As some of the files installed by qemu-native and qemu-system-native +# are the same, we depend on qemu-native to get the full installation set +# and avoid file clashes +DEPENDS += "glib-2.0-native zlib-native pixman-native qemu-native" + +EXTRA_OECONF:append = " --target-list=${@get_qemu_system_target_list(d)}" + +PACKAGECONFIG ??= "fdt alsa kvm pie slirp png \ + ${@bb.utils.contains('DISTRO_FEATURES', 'opengl', 'virglrenderer epoxy', '', d)} \ +" + +# Handle distros such as CentOS 5 32-bit that do not have kvm support +PACKAGECONFIG:remove = "${@'kvm' if not os.path.exists('/usr/include/linux/kvm.h') else ''}" + +do_install:append() { + install -Dm 0755 ${UNPACKDIR}/powerpc_rom.bin ${D}${datadir}/qemu + + # The following is also installed by qemu-native + rm -f ${D}${datadir}/qemu/trace-events-all + rm -rf ${D}${datadir}/qemu/keymaps + rm -rf ${D}${datadir}/icons/ + rm -rf ${D}${includedir}/qemu-plugin.h + + # Install qmp.py to be used with testimage + install -d ${D}${libdir}/qemu-python/qmp/ + install -D ${S}/python/qemu/qmp/* ${D}${libdir}/qemu-python/qmp/ +} + diff --git a/layers/meta-image-tests/recipes-devtools/qemu/qemu-targets.inc b/layers/meta-image-tests/recipes-devtools/qemu/qemu-targets.inc new file mode 100644 index 00000000..24f9a039 --- /dev/null +++ b/layers/meta-image-tests/recipes-devtools/qemu/qemu-targets.inc @@ -0,0 +1,28 @@ +# possible arch values are: +# aarch64 arm armeb alpha cris i386 x86_64 m68k microblaze +# mips mipsel mips64 mips64el ppc ppc64 ppc64abi32 ppcemb +# riscv32 riscv64 sparc sparc32 sparc32plus + +def get_qemu_target_list(d): + import bb + archs = d.getVar('QEMU_TARGETS').split() + tos = d.getVar('HOST_OS') + softmmuonly = "" + for arch in ['ppcemb', 'lm32']: + if arch in archs: + softmmuonly += arch + "-softmmu," + archs.remove(arch) + linuxuseronly = "" + for arch in ['armeb', 'alpha', 'ppc64abi32', 'ppc64le', 'sparc32plus', 'aarch64_be']: + if arch in archs: + linuxuseronly += arch + "-linux-user," + archs.remove(arch) + if 'linux' not in tos: + return softmmuonly + ''.join([arch + "-softmmu" + "," for arch in archs]).rstrip(',') + return softmmuonly + linuxuseronly + ''.join([arch + "-linux-user" + "," + arch + "-softmmu" + "," for arch in archs]).rstrip(',') + +def get_qemu_usermode_target_list(d): + return ",".join(filter(lambda i: "-linux-user" in i, get_qemu_target_list(d).split(','))) + +def get_qemu_system_target_list(d): + return ",".join(filter(lambda i: "-linux-user" not in i, get_qemu_target_list(d).split(','))) diff --git a/layers/meta-image-tests/recipes-devtools/qemu/qemu.inc b/layers/meta-image-tests/recipes-devtools/qemu/qemu.inc new file mode 100644 index 00000000..16b543fb --- /dev/null +++ b/layers/meta-image-tests/recipes-devtools/qemu/qemu.inc @@ -0,0 +1,272 @@ +SUMMARY = "Fast open source processor emulator" +DESCRIPTION = "QEMU is a hosted virtual machine monitor: it emulates the \ +machine's processor through dynamic binary translation and provides a set \ +of different hardware and device models for the machine, enabling it to run \ +a variety of guest operating systems" +HOMEPAGE = "http://qemu.org" +LICENSE = "GPL-2.0-only & LGPL-2.1-only" + +DEPENDS += "bison-native meson-native ninja-native" + +RDEPENDS:${PN}-ptest = "bash" + +require qemu-targets.inc +# https://gitlab.com/qemu-project/qemu/-/commit/81e2b198a8cb4ee5fdf108bd438f44b193ee3a36 means +# we need a full python3-native setup +inherit pkgconfig ptest update-rc.d systemd python3native + +LIC_FILES_CHKSUM = "file://COPYING;md5=441c28d2cf86e15a37fa47e15a72fbac \ + file://COPYING.LIB;endline=24;md5=8c5efda6cf1e1b03dcfd0e6c0d271c7f" + +SRC_URI = "https://download.qemu.org/${BPN}-${PV}.tar.xz \ + file://powerpc_rom.bin \ + file://run-ptest \ + file://fix-strerrorname_np.patch \ + file://0000-QEMU-test-disk.patch \ + file://0001-qemu-Add-addition-environment-space-to-boot-loader-q.patch \ + file://0002-apic-fixup-fallthrough-to-PIC.patch \ + file://0004-qemu-Do-not-include-file-if-not-exists.patch \ + file://0005-qemu-Add-some-user-space-mmap-tweaks-to-address-musl.patch \ + file://0006-qemu-Determinism-fixes.patch \ + file://0007-tests-meson.build-use-relative-path-to-refer-to-file.patch \ + file://0008-Define-MAP_SYNC-and-MAP_SHARED_VALIDATE-on-needed-li.patch \ + file://0010-configure-lookup-meson-exutable-from-PATH.patch \ + file://0011-qemu-Ensure-pip-and-the-python-venv-aren-t-used-for-.patch \ + file://0001-sched_attr-Do-not-define-for-glibc-2.41.patch \ + file://qemu-guest-agent.init \ + file://qemu-guest-agent.udev \ + " +# file index at download.qemu.org isn't reliable: https://gitlab.com/qemu-project/qemu-web/-/issues/9 +UPSTREAM_CHECK_URI = "https://www.qemu.org" +UPSTREAM_CHECK_REGEX = "qemu-(?P\d+(\.\d+)+)\.tar" + +SRC_URI[sha256sum] = "816b7022a8ba7c2ac30e2e0cf973e826f6bcc8505339603212c5ede8e94d7834" + +CVE_STATUS[CVE-2007-0998] = "not-applicable-config: The VNC server can expose host files uder some circumstances. We don't enable it by default." + +# https://bugzilla.redhat.com/show_bug.cgi?id=1609015#c11 +CVE_STATUS[CVE-2018-18438] = "disputed: The issues identified by this CVE were determined to not constitute a vulnerability." + +# As per https://bugzilla.redhat.com/show_bug.cgi?id=2203387 +CVE_STATUS[CVE-2023-2680] = "not-applicable-platform: RHEL specific issue." + +# NVD DB has this CVE as version-less (with "-") +CVE_STATUS[CVE-2024-6505] = "fixed-version: this CVE is fixed since 9.1.0" + +CVE_STATUS[CVE-2023-1386] = "disputed: not an issue as per https://bugzilla.redhat.com/show_bug.cgi?id=2223985" + +COMPATIBLE_HOST:mipsarchn32 = "null" +COMPATIBLE_HOST:mipsarchn64 = "null" +COMPATIBLE_HOST:riscv32 = "null" + +# Per https://lists.nongnu.org/archive/html/qemu-devel/2020-09/msg03873.html +# upstream states qemu doesn't work without optimization +DEBUG_BUILD = "0" + +do_install:append() { + # Prevent QA warnings about installed ${localstatedir}/run + if [ -d ${D}${localstatedir}/run ]; then rmdir ${D}${localstatedir}/run; fi +} + +do_install_ptest() { + cp -rL ${B}/tests ${D}${PTEST_PATH} + find ${D}${PTEST_PATH}/tests -type f -name "*.[Sshcodp]" | xargs -i rm -rf {} + + # Don't check the file genreated by configure + sed -i -e "1s,#!/usr/bin/bash,#!${base_bindir}/bash," ${D}${PTEST_PATH}/tests/data/acpi/disassemle-aml.sh + + # Strip the paths from the QEMU variable, we can use PATH + makfiles=$(find ${D}${PTEST_PATH} -name "*.mak") + sed -i -e "s#^QEMU=.*/qemu-#QEMU=qemu-#g" $makfiles + + # Strip compiler flags as they break reproducibility + sed -i -e "s,^CC=.*,CC=gcc," \ + -e "s,^CCAS=.*,CCAS=gcc," \ + -e "s,^LD=.*,LD=ld," $makfiles + + # Update SRC_PATH variable to the right place on target + sed -i -e "s#^SRC_PATH=.*#SRC_PATH=${PTEST_PATH}#g" $makfiles + + # https://gitlab.com/qemu-project/qemu/-/issues/1403 + rm ${D}${PTEST_PATH}/tests/unit/test-io-channel-command +} + +# QEMU_TARGETS is overridable variable +QEMU_TARGETS ?= "arm aarch64 i386 loongarch64 mips mipsel mips64 mips64el ppc ppc64 ppc64le riscv32 riscv64 sh4 x86_64" + +EXTRA_OECONF = " \ + --prefix=${prefix} \ + --bindir=${bindir} \ + --includedir=${includedir} \ + --libdir=${libdir} \ + --mandir=${mandir} \ + --datadir=${datadir} \ + --docdir=${docdir}/${BPN} \ + --sysconfdir=${sysconfdir} \ + --libexecdir=${libexecdir} \ + --localstatedir=${localstatedir} \ + --with-suffix=${BPN} \ + --disable-strip \ + --disable-werror \ + --extra-cflags='${CFLAGS}' \ + --extra-ldflags='${LDFLAGS}' \ + --disable-download \ + --disable-docs \ + --host-cc='${BUILD_CC}' \ + --disable-af-xdp \ + ${PACKAGECONFIG_CONFARGS} \ + " + +EXTRA_OECONF:append:class-target = " --cross-prefix=${HOST_PREFIX}" +EXTRA_OECONF:append:class-nativesdk = " --cross-prefix=${HOST_PREFIX}" + +B = "${WORKDIR}/build" + +#EXTRA_OECONF:append = " --python=${HOSTTOOLS_DIR}/python3" + +do_configure:prepend:class-native() { + # Append build host pkg-config paths for native target since the host may provide sdl + BHOST_PKGCONFIG_PATH=$(PATH=/usr/bin:/bin pkg-config --variable pc_path pkg-config || echo "") + if [ ! -z "$BHOST_PKGCONFIG_PATH" ]; then + export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:$BHOST_PKGCONFIG_PATH + fi +} + +do_configure() { + export PKG_CONFIG=pkg-config + ${S}/configure ${EXTRA_OECONF} +} +do_configure[cleandirs] += "${B}" + +do_install () { + export STRIP="" + oe_runmake 'DESTDIR=${D}' install + + # If we built the guest agent, also install startup/udev rules + if [ -e "${D}${bindir}/qemu-ga" ]; then + install -d ${D}${sysconfdir}/init.d/ + install -m 0755 ${UNPACKDIR}/qemu-guest-agent.init ${D}${sysconfdir}/init.d/qemu-guest-agent + sed -i 's:@bindir@:${bindir}:' ${D}${sysconfdir}/init.d/qemu-guest-agent + + install -d ${D}${sysconfdir}/udev/rules.d/ + install -m 0644 ${UNPACKDIR}/qemu-guest-agent.udev ${D}${sysconfdir}/udev/rules.d/60-qemu-guest-agent.rules + + install -d ${D}${systemd_unitdir}/system/ + install -m 0644 ${S}/contrib/systemd/qemu-guest-agent.service ${D}${systemd_unitdir}/system + sed -i -e 's,-/usr/bin/,-${bindir}/,g' ${D}${systemd_unitdir}/system/qemu-guest-agent.service + fi + # ELF binary /usr/share/qemu/s390-netboot.img has relocations in .text + rm ${D}${datadir}/qemu/s390-netboot.img -f + # ELF binary /usr/share/qemu/s390-ccw.img has relocations in .text [textrel] + rm ${D}${datadir}/qemu/s390-ccw.img -f + # We don't support PARISC and these cause strip and SDK relocation errors + rm ${D}${datadir}/qemu/hppa* -f +} + +# Disable kvm/virgl/mesa on targets that do not support it +PACKAGECONFIG:remove:darwin = "kvm virglrenderer epoxy gtk+" +PACKAGECONFIG:remove:mingw32 = "kvm virglrenderer epoxy gtk+ pie" + +PACKAGECONFIG[sdl] = "--enable-sdl,--disable-sdl,libsdl2" +PACKAGECONFIG[png] = "--enable-png,--disable-png,libpng" +PACKAGECONFIG[virtfs] = "--enable-virtfs --enable-attr --enable-cap-ng,--disable-virtfs,libcap-ng attr," +PACKAGECONFIG[aio] = "--enable-linux-aio,--disable-linux-aio,libaio," +PACKAGECONFIG[uring] = "--enable-linux-io-uring,--disable-linux-io-uring,liburing" +PACKAGECONFIG[xen] = "--enable-xen,--disable-xen,xen-tools,xen-tools-libxenstore xen-tools-libxenctrl xen-tools-libxenguest" +PACKAGECONFIG[vnc-sasl] = "--enable-vnc --enable-vnc-sasl,--disable-vnc-sasl,cyrus-sasl," +PACKAGECONFIG[vnc-jpeg] = "--enable-vnc --enable-vnc-jpeg,--disable-vnc-jpeg,jpeg," +PACKAGECONFIG[libcurl] = "--enable-curl,--disable-curl,curl," +PACKAGECONFIG[nss] = "--enable-smartcard,--disable-smartcard,nss," +PACKAGECONFIG[curses] = "--enable-curses,--disable-curses,ncurses," +PACKAGECONFIG[gtk+] = "--enable-gtk,--disable-gtk,gtk+3 gettext-native" +PACKAGECONFIG[vte] = "--enable-vte,--disable-vte,vte gettext-native" +PACKAGECONFIG[libcap-ng] = "--enable-cap-ng,--disable-cap-ng,libcap-ng," +PACKAGECONFIG[ssh] = "--enable-libssh,--disable-libssh,libssh," +PACKAGECONFIG[gcrypt] = "--enable-gcrypt,--disable-gcrypt,libgcrypt," +PACKAGECONFIG[nettle] = "--enable-nettle,--disable-nettle,nettle" +PACKAGECONFIG[libusb] = "--enable-libusb,--disable-libusb,libusb1" +PACKAGECONFIG[fdt] = "--enable-fdt,--disable-fdt,dtc" +PACKAGECONFIG[alsa] = "--audio-drv-list=default,,alsa-lib" +PACKAGECONFIG[epoxy] = "--enable-opengl,--disable-opengl,libepoxy" +PACKAGECONFIG[lzo] = "--enable-lzo,--disable-lzo,lzo" +PACKAGECONFIG[dax] = "--enable-libdaxctl,--disable-libdaxctl,ndctl" +PACKAGECONFIG[numa] = "--enable-numa,--disable-numa,numactl" +PACKAGECONFIG[gnutls] = "--enable-gnutls,--disable-gnutls,gnutls" +PACKAGECONFIG[bzip2] = "--enable-bzip2,--disable-bzip2,bzip2" +PACKAGECONFIG[libiscsi] = "--enable-libiscsi,--disable-libiscsi" +PACKAGECONFIG[kvm] = "--enable-kvm,--disable-kvm" +PACKAGECONFIG[virglrenderer] = "--enable-virglrenderer,--disable-virglrenderer,virglrenderer" +# spice will be in meta-networking layer +PACKAGECONFIG[spice] = "--enable-spice,--disable-spice,spice" +# usbredir will be in meta-networking layer +PACKAGECONFIG[dbus-display] = "--enable-dbus-display,--disable-dbus-display,glib-2.0-native,dbus" +PACKAGECONFIG[usb-redir] = "--enable-usb-redir,--disable-usb-redir,usbredir" +PACKAGECONFIG[snappy] = "--enable-snappy,--disable-snappy,snappy" +PACKAGECONFIG[glusterfs] = "--enable-glusterfs,--disable-glusterfs,glusterfs" +PACKAGECONFIG[xkbcommon] = "--enable-xkbcommon,--disable-xkbcommon,libxkbcommon" +PACKAGECONFIG[libudev] = "--enable-libudev,--disable-libudev,udev" +PACKAGECONFIG[attr] = "--enable-attr,--disable-attr,attr," +PACKAGECONFIG[rbd] = "--enable-rbd,--disable-rbd,ceph,ceph" +PACKAGECONFIG[vhost] = "--enable-vhost-net,--disable-vhost-net,," +PACKAGECONFIG[ust] = "--enable-trace-backends=ust,,lttng-ust," +PACKAGECONFIG[pie] = "--enable-pie,--disable-pie,," +PACKAGECONFIG[seccomp] = "--enable-seccomp,--disable-seccomp,libseccomp" +# libnfs is currently provided by meta-kodi +PACKAGECONFIG[libnfs] = "--enable-libnfs,--disable-libnfs,libnfs" +PACKAGECONFIG[pmem] = "--enable-libpmem,--disable-libpmem,pmdk" +PACKAGECONFIG[pulseaudio] = "--enable-pa,--disable-pa,pulseaudio" +PACKAGECONFIG[selinux] = "--enable-selinux,--disable-selinux" +PACKAGECONFIG[bpf] = "--enable-bpf,--disable-bpf,libbpf" +PACKAGECONFIG[capstone] = "--enable-capstone,--disable-capstone" +PACKAGECONFIG[rdma] = "--enable-rdma,--disable-rdma" +PACKAGECONFIG[vde] = "--enable-vde,--disable-vde" +PACKAGECONFIG[fuse] = "--enable-fuse --enable-fuse-lseek,--disable-fuse --disable-fuse-lseek,fuse3" +PACKAGECONFIG[slirp] = "--enable-slirp,--disable-slirp,libslirp" +PACKAGECONFIG[brlapi] = "--enable-brlapi,--disable-brlapi" +PACKAGECONFIG[jack] = "--enable-jack,--disable-jack,jack," +PACKAGECONFIG[debuginfo] = "--enable-libdw,--disable-libdw,elfutils" +PACKAGECONFIG[pipewire] = "--enable-pipewire,--disable-pipewire,pipewire" +PACKAGECONFIG[sndio] = "--enable-sndio,--disable-sndio,sndio" + +INSANE_SKIP:${PN}-common = "arch" + +FILES:${PN} += "${datadir}/icons" + +# For user who want to install all arch packages +PACKAGES =+ "${PN}-common" +RDEPENDS:${PN} += "${PN}-common" + +ALLOW_EMPTY:${PN} = "1" +FILES:${PN} = "" + +FILES:${PN}-common = "${bindir}/* ${includedir}/* ${libexecdir}/* ${datadir}/* ${localstatedir}" + +PACKAGES_DYNAMIC += "^${PN}-user-.* ^${PN}-system-.*" + +PACKAGESPLITFUNCS =+ "split_qemu_packages" + +python split_qemu_packages () { + archdir = d.expand('${bindir}/') + subpackages = do_split_packages(d, archdir, r'^qemu-system-(.*)$', '${PN}-system-%s', 'QEMU full system emulation binaries(%s)' , prepend=True, extra_depends='${PN}-common') + + subpackages += do_split_packages(d, archdir, r'^qemu-((?!system|edid|ga|img|io|nbd|pr-helper|storage-daemon).*)$', '${PN}-user-%s', 'QEMU full user emulation binaries(%s)' , prepend=True, extra_depends='${PN}-common') + if subpackages: + d.appendVar('RDEPENDS:' + d.getVar('PN'), ' ' + ' '.join(subpackages)) +} + +# Put the guest agent in a separate package +PACKAGES =+ "${PN}-guest-agent" +SUMMARY:${PN}-guest-agent = "QEMU guest agent" +FILES:${PN}-guest-agent += " \ + ${bindir}/qemu-ga \ + ${sysconfdir}/udev/rules.d/60-qemu-guest-agent.rules \ + ${sysconfdir}/init.d/qemu-guest-agent \ + ${systemd_unitdir}/system/qemu-guest-agent.service \ +" + +INITSCRIPT_PACKAGES = "${PN}-guest-agent" +INITSCRIPT_NAME:${PN}-guest-agent = "qemu-guest-agent" +INITSCRIPT_PARAMS:${PN}-guest-agent = "defaults" + +SYSTEMD_PACKAGES = "${PN}-guest-agent" +SYSTEMD_SERVICE:${PN}-guest-agent = "qemu-guest-agent.service" diff --git a/layers/meta-image-tests/recipes-devtools/qemu/qemu/0000-QEMU-test-disk.patch b/layers/meta-image-tests/recipes-devtools/qemu/qemu/0000-QEMU-test-disk.patch new file mode 100644 index 00000000..33f12152 --- /dev/null +++ b/layers/meta-image-tests/recipes-devtools/qemu/qemu/0000-QEMU-test-disk.patch @@ -0,0 +1,4190 @@ +From 7ca343c91fd44cc992a8785fc2cc6270f71bca21 Mon Sep 17 00:00:00 2001 +From: Kirill Karpunin +Date: Tue, 13 Jan 2026 19:53:51 +0300 +Subject: [PATCH 01/28] Add test scsi disk +Upstream-Status: Pending + +--- + .gitignore | 1 + + hw/scsi/Kconfig | 5 +++++ + hw/scsi/meson.build | 1 + + hw/scsi/test_disk.c | 38 +++++++++++++++++++++++++++++++++++++ + include/hw/scsi/test_disk.h | 15 +++++++++++++++ + 5 files changed, 60 insertions(+) + create mode 100644 hw/scsi/test_disk.c + create mode 100644 include/hw/scsi/test_disk.h + +diff --git a/.gitignore b/.gitignore +index 61fa39967b..a0687c79d5 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -2,6 +2,7 @@ + /build/ + /.cache/ + /.vscode/ ++/.idea/ + *.pyc + .sdk + .stgit-* +diff --git a/hw/scsi/Kconfig b/hw/scsi/Kconfig +index 1feab84c4c..26f8466783 100644 +--- a/hw/scsi/Kconfig ++++ b/hw/scsi/Kconfig +@@ -64,3 +64,8 @@ config VHOST_USER_SCSI + default y if VIRTIO_PCI + select VHOST_SCSI_COMMON + depends on VIRTIO && VHOST_USER && LINUX ++ ++config TEST_DISK ++ bool ++ default y ++ depends on SCSI +diff --git a/hw/scsi/meson.build b/hw/scsi/meson.build +index bb7d289aa0..37b44f63d0 100644 +--- a/hw/scsi/meson.build ++++ b/hw/scsi/meson.build +@@ -15,6 +15,7 @@ scsi_ss.add(when: 'CONFIG_LSI_SCSI_PCI', if_true: files('lsi53c895a.c')) + scsi_ss.add(when: 'CONFIG_MEGASAS_SCSI_PCI', if_true: files('megasas.c')) + scsi_ss.add(when: 'CONFIG_MPTSAS_SCSI_PCI', if_true: files('mptsas.c', 'mptconfig.c', 'mptendian.c')) + scsi_ss.add(when: 'CONFIG_VMW_PVSCSI_SCSI_PCI', if_true: files('vmw_pvscsi.c')) ++scsi_ss.add(when: 'CONFIG_TEST_DISK', if_true: files('test_disk.c')) + + virtio_scsi_ss.add(files('virtio-scsi-dataplane.c')) + virtio_scsi_ss.add(when: 'CONFIG_VHOST_SCSI', if_true: files('vhost-scsi.c')) +diff --git a/hw/scsi/test_disk.c b/hw/scsi/test_disk.c +new file mode 100644 +index 0000000000..a8c774182b +--- /dev/null ++++ b/hw/scsi/test_disk.c +@@ -0,0 +1,38 @@ ++#include "qemu/osdep.h" ++#include "hw/scsi/test_disk.h" ++#include "hw/scsi/scsi.h" ++#include "qapi/error.h" ++ ++static int test_disk_parse_cdb(SCSIDevice *dev, SCSICommand *cmd, uint8_t *buf, size_t buf_len, void *hba_private) { ++ printf("MySCSIDevice: Received CDB command 0x%02x\n", buf[0]); ++ ++ return scsi_req_parse_cdb(dev, cmd, buf, buf_len); ++} ++ ++static void test_disk_realize(SCSIDevice *dev, Error **errp) { ++ printf("MySCSIDevice: Device realized (initialized)\n"); ++} ++ ++static void test_disk_class_init(ObjectClass *klass, const void *data) { ++ DeviceClass *dc = DEVICE_CLASS(klass); ++ SCSIDeviceClass *sc = SCSI_DEVICE_CLASS(klass); ++ ++ sc->realize = test_disk_realize; ++ sc->parse_cdb = test_disk_parse_cdb; ++ ++ set_bit(DEVICE_CATEGORY_STORAGE, dc->categories); ++ dc->desc = "Test SCSI Disk"; ++} ++ ++static const TypeInfo test_disk_info = { ++ .name = TYPE_TEST_DISK, ++ .parent = TYPE_SCSI_DEVICE, ++ .instance_size = sizeof(TestDisk), ++ .class_init = test_disk_class_init, ++}; ++ ++static void test_disk_register_types(void) { ++ type_register_static(&test_disk_info); ++} ++ ++type_init(test_disk_register_types) +diff --git a/include/hw/scsi/test_disk.h b/include/hw/scsi/test_disk.h +new file mode 100644 +index 0000000000..be2f7ccbd3 +--- /dev/null ++++ b/include/hw/scsi/test_disk.h +@@ -0,0 +1,15 @@ ++#ifndef HW_SCSI_TEST_DISK_H ++#define HW_SCSI_TEST_DISK_H ++ ++#include "hw/scsi/scsi.h" ++#include "qom/object.h" ++ ++#define TYPE_TEST_DISK "test_disk" ++OBJECT_DECLARE_SIMPLE_TYPE(TestDisk, TEST_DISK) ++ ++struct TestDisk { ++ SCSIDevice parent_obj; ++}; ++ ++#endif //HW_SCSI_TEST_DISK_H ++./ +\ No newline at end of file +-- +2.53.0 + + +From 77eca0c9efe909535da6ae02e14e94bd241075a4 Mon Sep 17 00:00:00 2001 +From: Kirill Karpunin +Date: Thu, 15 Jan 2026 18:12:16 +0300 +Subject: [PATCH 02/28] Add basic request handling + +--- + hw/scsi/test_disk.c | 104 +++++++++++++++++++++++++++++++++++- + include/hw/scsi/test_disk.h | 3 +- + 2 files changed, 103 insertions(+), 4 deletions(-) + +diff --git a/hw/scsi/test_disk.c b/hw/scsi/test_disk.c +index a8c774182b..42a4f220f1 100644 +--- a/hw/scsi/test_disk.c ++++ b/hw/scsi/test_disk.c +@@ -2,15 +2,114 @@ + #include "hw/scsi/test_disk.h" + #include "hw/scsi/scsi.h" + #include "qapi/error.h" ++#include "scsi/constants.h" + + static int test_disk_parse_cdb(SCSIDevice *dev, SCSICommand *cmd, uint8_t *buf, size_t buf_len, void *hba_private) { +- printf("MySCSIDevice: Received CDB command 0x%02x\n", buf[0]); ++ printf("TEST_DISK: received CDB command 0x%02x\n", buf[0]); + + return scsi_req_parse_cdb(dev, cmd, buf, buf_len); + } + ++typedef struct TestDiskReq { ++ SCSIRequest req; ++ uint8_t buf[256]; ++} TestDiskReq; ++ ++static int32_t test_disk_send_command(SCSIRequest* req, uint8_t* buf) { ++ TestDiskReq *r = DO_UPCAST(TestDiskReq, req, req); ++ uint8_t command = buf[0]; ++ uint8_t* outbuf = r->buf; ++ ++ switch (command) { ++ case 0x00: ++ printf("\tTEST_DISK: TEST UNIT READY\n"); ++ ++ scsi_req_complete(req, GOOD); ++ break; ++ ++ case 0x12: ++ printf("\tTEST_DISK: INQUIRY\n"); ++ memset(outbuf, 0, 36); ++ outbuf[0] = 0; // Direct-access block device ++ outbuf[1] = 0; // non-removable ++ outbuf[2] = 5; // SPC-3 ++ outbuf[3] = 2; // response data format ++ outbuf[4] = 31; // additional length ++ memcpy(&outbuf[8], "QEMU ", 8); ++ memcpy(&outbuf[16], "TEST_DISK ", 16); ++ ++ scsi_req_data(req, 36); ++ break; ++ ++ case 0x25: ++ printf("\tTEST_DISK: READ CAPACITY (10)\n"); ++ ++ memset(outbuf, 0, 8); ++ ++ const uint32_t block_size = 512; ++ const uint32_t total_blocks = 2097152U; ++ ++ uint32_t last_lba = total_blocks - 1; ++ ++ outbuf[0] = (last_lba >> 24) & 0xff; ++ outbuf[1] = (last_lba >> 16) & 0xff; ++ outbuf[2] = (last_lba >> 8) & 0xff; ++ outbuf[3] = last_lba & 0xff; ++ ++ outbuf[4] = (block_size >> 24) & 0xff; ++ outbuf[5] = (block_size >> 16) & 0xff; ++ outbuf[6] = (block_size >> 8) & 0xff; ++ outbuf[7] = block_size & 0xff; ++ ++ scsi_req_data(req, 8); ++ ++ break; ++ ++ case 0x1A: ++ printf("\tTEST_DISK: MODE SENSE (6)\n"); ++ ++ memset(outbuf, 0, 4); ++ outbuf[0] = 3; // length ++ outbuf[2] = 0x80; // write protection ++ ++ scsi_req_data(req, 4); ++ break; ++ ++ default: ++ printf("\tTEST_DISK: rejecting command 0x%02x\n", command); ++ ++ scsi_req_build_sense(req, SENSE_CODE(INVALID_OPCODE)); ++ scsi_req_complete(req, CHECK_CONDITION); ++ ++ break; ++ } ++ ++ return 0; ++} ++ ++static void test_disk_read_data(SCSIRequest *req) { ++ scsi_req_complete(req, GOOD); ++} ++ ++static uint8_t* test_disk_get_buf(SCSIRequest *req) ++{ ++ TestDiskReq *r = DO_UPCAST(TestDiskReq, req, req); ++ return r->buf; ++} ++ ++static const SCSIReqOps test_disk_req_ops = { ++ .size = sizeof(TestDiskReq), ++ .send_command = test_disk_send_command, ++ .read_data = test_disk_read_data, ++ .get_buf = test_disk_get_buf ++}; ++ ++static SCSIRequest* test_disk_alloc_req(SCSIDevice *s, uint32_t tag, uint32_t lun, uint8_t *buf, void *hba_private) { ++ return scsi_req_alloc(&test_disk_req_ops, s, tag, lun, hba_private); ++} ++ + static void test_disk_realize(SCSIDevice *dev, Error **errp) { +- printf("MySCSIDevice: Device realized (initialized)\n"); ++ printf("TEST_DISK: realized\n"); + } + + static void test_disk_class_init(ObjectClass *klass, const void *data) { +@@ -19,6 +118,7 @@ static void test_disk_class_init(ObjectClass *klass, const void *data) { + + sc->realize = test_disk_realize; + sc->parse_cdb = test_disk_parse_cdb; ++ sc->alloc_req = test_disk_alloc_req; + + set_bit(DEVICE_CATEGORY_STORAGE, dc->categories); + dc->desc = "Test SCSI Disk"; +diff --git a/include/hw/scsi/test_disk.h b/include/hw/scsi/test_disk.h +index be2f7ccbd3..ac9e278372 100644 +--- a/include/hw/scsi/test_disk.h ++++ b/include/hw/scsi/test_disk.h +@@ -11,5 +11,4 @@ struct TestDisk { + SCSIDevice parent_obj; + }; + +-#endif //HW_SCSI_TEST_DISK_H +-./ +\ No newline at end of file ++#endif //HW_SCSI_TEST_DISK_H +\ No newline at end of file +-- +2.53.0 + + +From 011fd427f23cd142f639d7ceb926fb7e9651115e Mon Sep 17 00:00:00 2001 +From: Kirill Karpunin +Date: Tue, 20 Jan 2026 20:24:58 +0300 +Subject: [PATCH 03/28] Add socket connection with server + +--- + hw/scsi/test_disk.c | 72 +++++++++++++++++++++++++------------ + include/hw/scsi/test_disk.h | 2 ++ + 2 files changed, 52 insertions(+), 22 deletions(-) + +diff --git a/hw/scsi/test_disk.c b/hw/scsi/test_disk.c +index 42a4f220f1..01481301fa 100644 +--- a/hw/scsi/test_disk.c ++++ b/hw/scsi/test_disk.c +@@ -1,10 +1,9 @@ + #include "qemu/osdep.h" + #include "hw/scsi/test_disk.h" + #include "hw/scsi/scsi.h" +-#include "qapi/error.h" + #include "scsi/constants.h" + +-static int test_disk_parse_cdb(SCSIDevice *dev, SCSICommand *cmd, uint8_t *buf, size_t buf_len, void *hba_private) { ++static int test_disk_parse_cdb(SCSIDevice* dev, SCSICommand* cmd, uint8_t* buf, size_t buf_len, void* hba_private) { + printf("TEST_DISK: received CDB command 0x%02x\n", buf[0]); + + return scsi_req_parse_cdb(dev, cmd, buf, buf_len); +@@ -16,10 +15,16 @@ typedef struct TestDiskReq { + } TestDiskReq; + + static int32_t test_disk_send_command(SCSIRequest* req, uint8_t* buf) { +- TestDiskReq *r = DO_UPCAST(TestDiskReq, req, req); ++ TestDiskReq* r = DO_UPCAST(TestDiskReq, req, req); ++ TestDisk* disk = DO_UPCAST(TestDisk, parent_obj, req->dev); ++ + uint8_t command = buf[0]; + uint8_t* outbuf = r->buf; + ++ if (disk->socket) { ++ qio_channel_write_all(QIO_CHANNEL(disk->socket), (char*)&command, 1, NULL); ++ } ++ + switch (command) { + case 0x00: + printf("\tTEST_DISK: TEST UNIT READY\n"); +@@ -30,12 +35,12 @@ static int32_t test_disk_send_command(SCSIRequest* req, uint8_t* buf) { + case 0x12: + printf("\tTEST_DISK: INQUIRY\n"); + memset(outbuf, 0, 36); +- outbuf[0] = 0; // Direct-access block device +- outbuf[1] = 0; // non-removable +- outbuf[2] = 5; // SPC-3 +- outbuf[3] = 2; // response data format +- outbuf[4] = 31; // additional length +- memcpy(&outbuf[8], "QEMU ", 8); ++ outbuf[0] = 0; // Direct-access block device ++ outbuf[1] = 0; // non-removable ++ outbuf[2] = 5; // SPC-3 ++ outbuf[3] = 2; // response data format ++ outbuf[4] = 31; // additional length ++ memcpy(&outbuf[8], "QEMU ", 8); + memcpy(&outbuf[16], "TEST_DISK ", 16); + + scsi_req_data(req, 36); +@@ -55,12 +60,12 @@ static int32_t test_disk_send_command(SCSIRequest* req, uint8_t* buf) { + outbuf[1] = (last_lba >> 16) & 0xff; + outbuf[2] = (last_lba >> 8) & 0xff; + outbuf[3] = last_lba & 0xff; +- ++ + outbuf[4] = (block_size >> 24) & 0xff; + outbuf[5] = (block_size >> 16) & 0xff; + outbuf[6] = (block_size >> 8) & 0xff; + outbuf[7] = block_size & 0xff; +- ++ + scsi_req_data(req, 8); + + break; +@@ -69,8 +74,8 @@ static int32_t test_disk_send_command(SCSIRequest* req, uint8_t* buf) { + printf("\tTEST_DISK: MODE SENSE (6)\n"); + + memset(outbuf, 0, 4); +- outbuf[0] = 3; // length +- outbuf[2] = 0x80; // write protection ++ outbuf[0] = 3; // length ++ outbuf[2] = 0x80; // write protection + + scsi_req_data(req, 4); + break; +@@ -87,13 +92,12 @@ static int32_t test_disk_send_command(SCSIRequest* req, uint8_t* buf) { + return 0; + } + +-static void test_disk_read_data(SCSIRequest *req) { ++static void test_disk_read_data(SCSIRequest* req) { + scsi_req_complete(req, GOOD); + } + +-static uint8_t* test_disk_get_buf(SCSIRequest *req) +-{ +- TestDiskReq *r = DO_UPCAST(TestDiskReq, req, req); ++static uint8_t* test_disk_get_buf(SCSIRequest* req) { ++ TestDiskReq* r = DO_UPCAST(TestDiskReq, req, req); + return r->buf; + } + +@@ -104,17 +108,41 @@ static const SCSIReqOps test_disk_req_ops = { + .get_buf = test_disk_get_buf + }; + +-static SCSIRequest* test_disk_alloc_req(SCSIDevice *s, uint32_t tag, uint32_t lun, uint8_t *buf, void *hba_private) { ++static SCSIRequest* test_disk_alloc_req(SCSIDevice* s, uint32_t tag, uint32_t lun, uint8_t* buf, void* hba_private) { + return scsi_req_alloc(&test_disk_req_ops, s, tag, lun, hba_private); + } + +-static void test_disk_realize(SCSIDevice *dev, Error **errp) { ++static void test_disk_realize(SCSIDevice* dev, Error** errp) { ++ TestDisk* disk = DO_UPCAST(TestDisk, parent_obj, dev); ++ ++ SocketAddress addr = { ++ .type = SOCKET_ADDRESS_TYPE_INET, ++ .u.inet = { ++ .host = g_strdup("127.0.0.1"), ++ .port = g_strdup("31234") ++ } ++ }; ++ ++ disk->socket = qio_channel_socket_new(); ++ ++ if (qio_channel_socket_connect_sync(disk->socket, &addr, NULL) < 0) { ++ printf("SOCKET: failed to connect\n"); ++ ++ object_unref(OBJECT(disk->socket)); ++ disk->socket = NULL; ++ } else { ++ printf("SOCKET: connected\n"); ++ } ++ ++ g_free(addr.u.inet.host); ++ g_free(addr.u.inet.port); ++ + printf("TEST_DISK: realized\n"); + } + +-static void test_disk_class_init(ObjectClass *klass, const void *data) { +- DeviceClass *dc = DEVICE_CLASS(klass); +- SCSIDeviceClass *sc = SCSI_DEVICE_CLASS(klass); ++static void test_disk_class_init(ObjectClass* klass, const void* data) { ++ DeviceClass* dc = DEVICE_CLASS(klass); ++ SCSIDeviceClass* sc = SCSI_DEVICE_CLASS(klass); + + sc->realize = test_disk_realize; + sc->parse_cdb = test_disk_parse_cdb; +diff --git a/include/hw/scsi/test_disk.h b/include/hw/scsi/test_disk.h +index ac9e278372..c6a07db5dd 100644 +--- a/include/hw/scsi/test_disk.h ++++ b/include/hw/scsi/test_disk.h +@@ -2,6 +2,7 @@ + #define HW_SCSI_TEST_DISK_H + + #include "hw/scsi/scsi.h" ++#include "io/channel-socket.h" + #include "qom/object.h" + + #define TYPE_TEST_DISK "test_disk" +@@ -9,6 +10,7 @@ OBJECT_DECLARE_SIMPLE_TYPE(TestDisk, TEST_DISK) + + struct TestDisk { + SCSIDevice parent_obj; ++ QIOChannelSocket* socket; + }; + + #endif //HW_SCSI_TEST_DISK_H +\ No newline at end of file +-- +2.53.0 + + +From ec529fae9d439c122d91c638a8cd7572d396dde6 Mon Sep 17 00:00:00 2001 +From: Kirill Karpunin +Date: Wed, 21 Jan 2026 20:00:40 +0300 +Subject: [PATCH 04/28] Add read command handler with delay + +--- + hw/scsi/test_disk.c | 69 ++++++++++++++++++++++++++++++++++++++++++--- + 1 file changed, 65 insertions(+), 4 deletions(-) + +diff --git a/hw/scsi/test_disk.c b/hw/scsi/test_disk.c +index 01481301fa..a4bf98bf5f 100644 +--- a/hw/scsi/test_disk.c ++++ b/hw/scsi/test_disk.c +@@ -3,6 +3,9 @@ + #include "hw/scsi/scsi.h" + #include "scsi/constants.h" + ++// block size * max blocks ++#define MAX_READ_XFER_SIZE (512 * 2048) // 1MB ++ + static int test_disk_parse_cdb(SCSIDevice* dev, SCSICommand* cmd, uint8_t* buf, size_t buf_len, void* hba_private) { + printf("TEST_DISK: received CDB command 0x%02x\n", buf[0]); + +@@ -11,9 +14,25 @@ static int test_disk_parse_cdb(SCSIDevice* dev, SCSICommand* cmd, uint8_t* buf, + + typedef struct TestDiskReq { + SCSIRequest req; +- uint8_t buf[256]; ++ QEMUTimer* timer; ++ int len; ++ uint8_t buf[MAX_READ_XFER_SIZE]; + } TestDiskReq; + ++static void test_disk_read_callback(void* opaque) { ++ printf("CALLBACK: from READ command\n"); ++ ++ TestDiskReq* r = opaque; ++ ++ uint8_t* outbuf = r->buf; ++ memset(outbuf, 0, r->len); ++ ++ scsi_req_data(&r->req, r->len); ++ ++ timer_free(r->timer); ++ r->timer = NULL; ++} ++ + static int32_t test_disk_send_command(SCSIRequest* req, uint8_t* buf) { + TestDiskReq* r = DO_UPCAST(TestDiskReq, req, req); + TestDisk* disk = DO_UPCAST(TestDisk, parent_obj, req->dev); +@@ -52,7 +71,7 @@ static int32_t test_disk_send_command(SCSIRequest* req, uint8_t* buf) { + memset(outbuf, 0, 8); + + const uint32_t block_size = 512; +- const uint32_t total_blocks = 2097152U; ++ const uint32_t total_blocks = 2048U; + + uint32_t last_lba = total_blocks - 1; + +@@ -67,7 +86,6 @@ static int32_t test_disk_send_command(SCSIRequest* req, uint8_t* buf) { + outbuf[7] = block_size & 0xff; + + scsi_req_data(req, 8); +- + break; + + case 0x1A: +@@ -80,12 +98,52 @@ static int32_t test_disk_send_command(SCSIRequest* req, uint8_t* buf) { + scsi_req_data(req, 4); + break; + ++ case 0x5A: ++ printf("\tTEST_DISK: MODE SENSE (10)\n"); ++ memset(outbuf, 0, 8); ++ ++ outbuf[0] = 0; ++ outbuf[1] = 6; ++ ++ outbuf[3] = 0x80; ++ ++ scsi_req_data(req, 8); ++ break; ++ ++ case 0x08: ++ case 0x28: ++ int read_type = command == 0x08 ? 6 : 10; ++ printf("\tTEST_DISK: READ (%d)\n", read_type); ++ ++ int target_len = (int)req->cmd.xfer; ++ printf("\tTARGET_LEN: %d\n", target_len); ++ ++ if (target_len == 0) { ++ scsi_req_complete(req, GOOD); ++ break; ++ } ++ ++ if (target_len > sizeof(r->buf)) { ++ printf("\tTEST_DISK: error - request too big\n"); ++ ++ scsi_req_build_sense(req, SENSE_CODE(LBA_OUT_OF_RANGE)); ++ scsi_req_complete(req, CHECK_CONDITION); ++ break; ++ } ++ ++ r->len = target_len; ++ r->timer = timer_new_ms(QEMU_CLOCK_REALTIME, test_disk_read_callback, r); ++ ++ uint8_t delay_ms = 0; ++ timer_mod(r->timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + delay_ms); ++ ++ return 0; ++ + default: + printf("\tTEST_DISK: rejecting command 0x%02x\n", command); + + scsi_req_build_sense(req, SENSE_CODE(INVALID_OPCODE)); + scsi_req_complete(req, CHECK_CONDITION); +- + break; + } + +@@ -113,6 +171,9 @@ static SCSIRequest* test_disk_alloc_req(SCSIDevice* s, uint32_t tag, uint32_t lu + } + + static void test_disk_realize(SCSIDevice* dev, Error** errp) { ++ dev->type = TYPE_DISK; ++ dev->blocksize = 512; ++ + TestDisk* disk = DO_UPCAST(TestDisk, parent_obj, dev); + + SocketAddress addr = { +-- +2.53.0 + + +From aba4962aeafcc3212ba53748d2f3ceb7c387a662 Mon Sep 17 00:00:00 2001 +From: Kirill Karpunin +Date: Thu, 22 Jan 2026 18:37:00 +0300 +Subject: [PATCH 05/28] Add ability to set constants + +--- + hw/scsi/test_disk.c | 47 +++++++++++++++++++++++++++++-------- + include/hw/scsi/test_disk.h | 5 ++++ + 2 files changed, 42 insertions(+), 10 deletions(-) + +diff --git a/hw/scsi/test_disk.c b/hw/scsi/test_disk.c +index a4bf98bf5f..0669c92192 100644 +--- a/hw/scsi/test_disk.c ++++ b/hw/scsi/test_disk.c +@@ -16,9 +16,16 @@ typedef struct TestDiskReq { + SCSIRequest req; + QEMUTimer* timer; + int len; +- uint8_t buf[MAX_READ_XFER_SIZE]; ++ ++ uint8_t* buf; + } TestDiskReq; + ++static Property test_disk_properties[] = { ++ DEFINE_PROP_SIZE("size", TestDisk, size, 1 * 1024 * 1024 * 1024), ++ DEFINE_PROP_SIZE("bs", TestDisk, block_size, 512), ++ DEFINE_PROP_SIZE("max_xfer_size", TestDisk, max_xfer_size, 1 * 1024 * 1024), ++}; ++ + static void test_disk_read_callback(void* opaque) { + printf("CALLBACK: from READ command\n"); + +@@ -70,8 +77,8 @@ static int32_t test_disk_send_command(SCSIRequest* req, uint8_t* buf) { + + memset(outbuf, 0, 8); + +- const uint32_t block_size = 512; +- const uint32_t total_blocks = 2048U; ++ const uint32_t block_size = disk->block_size; ++ const uint32_t total_blocks = disk->size / disk->block_size; + + uint32_t last_lba = total_blocks - 1; + +@@ -123,10 +130,10 @@ static int32_t test_disk_send_command(SCSIRequest* req, uint8_t* buf) { + break; + } + +- if (target_len > sizeof(r->buf)) { ++ if (target_len > disk->max_xfer_size) { + printf("\tTEST_DISK: error - request too big\n"); + +- scsi_req_build_sense(req, SENSE_CODE(LBA_OUT_OF_RANGE)); ++ scsi_req_build_sense(req, SENSE_CODE(INVALID_FIELD)); + scsi_req_complete(req, CHECK_CONDITION); + break; + } +@@ -159,23 +166,41 @@ static uint8_t* test_disk_get_buf(SCSIRequest* req) { + return r->buf; + } + ++static void test_disk_free_req(SCSIRequest* req) { ++ TestDiskReq* r = DO_UPCAST(TestDiskReq, req, req); ++ ++ if (r->buf) { ++ g_free(r->buf); ++ r->buf = NULL; ++ } ++} ++ + static const SCSIReqOps test_disk_req_ops = { + .size = sizeof(TestDiskReq), + .send_command = test_disk_send_command, + .read_data = test_disk_read_data, +- .get_buf = test_disk_get_buf ++ .get_buf = test_disk_get_buf, ++ .free_req = test_disk_free_req + }; + + static SCSIRequest* test_disk_alloc_req(SCSIDevice* s, uint32_t tag, uint32_t lun, uint8_t* buf, void* hba_private) { +- return scsi_req_alloc(&test_disk_req_ops, s, tag, lun, hba_private); ++ TestDisk* disk = DO_UPCAST(TestDisk, parent_obj, s); ++ uint64_t max_xfer_size = disk->max_xfer_size; ++ ++ SCSIRequest* req = scsi_req_alloc(&test_disk_req_ops, s, tag, lun, hba_private); ++ TestDiskReq* r = DO_UPCAST(TestDiskReq, req, req); ++ ++ r->buf = g_malloc0(max_xfer_size); ++ ++ return req; + } + + static void test_disk_realize(SCSIDevice* dev, Error** errp) { +- dev->type = TYPE_DISK; +- dev->blocksize = 512; +- + TestDisk* disk = DO_UPCAST(TestDisk, parent_obj, dev); + ++ dev->type = TYPE_DISK; ++ dev->blocksize = (int)disk->block_size; ++ + SocketAddress addr = { + .type = SOCKET_ADDRESS_TYPE_INET, + .u.inet = { +@@ -211,6 +236,8 @@ static void test_disk_class_init(ObjectClass* klass, const void* data) { + + set_bit(DEVICE_CATEGORY_STORAGE, dc->categories); + dc->desc = "Test SCSI Disk"; ++ ++ device_class_set_props(dc, test_disk_properties); + } + + static const TypeInfo test_disk_info = { +diff --git a/include/hw/scsi/test_disk.h b/include/hw/scsi/test_disk.h +index c6a07db5dd..a86908d965 100644 +--- a/include/hw/scsi/test_disk.h ++++ b/include/hw/scsi/test_disk.h +@@ -11,6 +11,11 @@ OBJECT_DECLARE_SIMPLE_TYPE(TestDisk, TEST_DISK) + struct TestDisk { + SCSIDevice parent_obj; + QIOChannelSocket* socket; ++ ++ uint64_t size; ++ uint64_t block_size; ++ ++ uint64_t max_xfer_size; + }; + + #endif //HW_SCSI_TEST_DISK_H +\ No newline at end of file +-- +2.53.0 + + +From 68c86c37cf64c8d4850da4852bac4c9947222feb Mon Sep 17 00:00:00 2001 +From: Kirill Karpunin +Date: Fri, 23 Jan 2026 01:28:47 +0300 +Subject: [PATCH 06/28] Reafactor + async socket connection and reconnection + mechanism + +--- + hw/scsi/test_disk.c | 348 +++++++++++++++++----------- + {include/hw => hw}/scsi/test_disk.h | 8 +- + 2 files changed, 216 insertions(+), 140 deletions(-) + rename {include/hw => hw}/scsi/test_disk.h (74%) + +diff --git a/hw/scsi/test_disk.c b/hw/scsi/test_disk.c +index 0669c92192..f0262586a9 100644 +--- a/hw/scsi/test_disk.c ++++ b/hw/scsi/test_disk.c +@@ -1,205 +1,199 @@ + #include "qemu/osdep.h" +-#include "hw/scsi/test_disk.h" + #include "hw/scsi/scsi.h" ++#include "test_disk.h" ++#include "qapi/error.h" + #include "scsi/constants.h" + +-// block size * max blocks +-#define MAX_READ_XFER_SIZE (512 * 2048) // 1MB + +-static int test_disk_parse_cdb(SCSIDevice* dev, SCSICommand* cmd, uint8_t* buf, size_t buf_len, void* hba_private) { +- printf("TEST_DISK: received CDB command 0x%02x\n", buf[0]); ++// Device properties ++static const Property test_disk_properties[] = { ++ DEFINE_PROP_SIZE("size", TestDisk, size, 1 * 1024 * 1024 * 1024), ++ DEFINE_PROP_SIZE("bs", TestDisk, block_size, 512), ++ DEFINE_PROP_SIZE("max_xfer_size", TestDisk, max_xfer_size, 1 * 1024 * 1024), ++}; + +- return scsi_req_parse_cdb(dev, cmd, buf, buf_len); +-} + ++// Request + typedef struct TestDiskReq { +- SCSIRequest req; ++ SCSIRequest parent_scsi_req; + QEMUTimer* timer; +- int len; + +- uint8_t* buf; ++ uint8_t* response_buf; ++ int response_buf_len; + } TestDiskReq; + +-static Property test_disk_properties[] = { +- DEFINE_PROP_SIZE("size", TestDisk, size, 1 * 1024 * 1024 * 1024), +- DEFINE_PROP_SIZE("bs", TestDisk, block_size, 512), +- DEFINE_PROP_SIZE("max_xfer_size", TestDisk, max_xfer_size, 1 * 1024 * 1024), ++ ++// Request handlers ++static int32_t test_disk_send_command(SCSIRequest* scsi_req, uint8_t* request_buf); ++static void test_disk_read_data(SCSIRequest* scsi_req); ++static uint8_t* test_disk_get_buf(SCSIRequest* scsi_req); ++static void test_disk_free_req(SCSIRequest* scsi_req); ++ ++ ++// Request handlers map ++static const SCSIReqOps test_disk_req_ops = { ++ .size = sizeof(TestDiskReq), ++ .send_command = test_disk_send_command, ++ .read_data = test_disk_read_data, ++ .get_buf = test_disk_get_buf, ++ .free_req = test_disk_free_req + }; + +-static void test_disk_read_callback(void* opaque) { +- printf("CALLBACK: from READ command\n"); + +- TestDiskReq* r = opaque; ++// Helper functions ++static void test_disk_socket_start_connection(void* opaque); ++static void test_disk_socket_connected(QIOTask* task, gpointer opaque); ++static void test_disk_read_callback(void* opaque); + +- uint8_t* outbuf = r->buf; +- memset(outbuf, 0, r->len); + +- scsi_req_data(&r->req, r->len); ++// ============================================================================ + +- timer_free(r->timer); +- r->timer = NULL; +-} + +-static int32_t test_disk_send_command(SCSIRequest* req, uint8_t* buf) { +- TestDiskReq* r = DO_UPCAST(TestDiskReq, req, req); +- TestDisk* disk = DO_UPCAST(TestDisk, parent_obj, req->dev); ++static int32_t test_disk_send_command(SCSIRequest* scsi_req, uint8_t* request_buf) { ++ TestDiskReq* disk_req = DO_UPCAST(TestDiskReq, parent_scsi_req, scsi_req); ++ TestDisk* disk = DO_UPCAST(TestDisk, parent_scsi_dev, scsi_req->dev); + +- uint8_t command = buf[0]; +- uint8_t* outbuf = r->buf; ++ uint8_t command = request_buf[0]; + +- if (disk->socket) { +- qio_channel_write_all(QIO_CHANNEL(disk->socket), (char*)&command, 1, NULL); +- } ++ // INQUIRY request is handled even without socket ++ if (command == 0x12) { // INQUIRY ++ memset(disk_req->response_buf, 0, 36); + +- switch (command) { +- case 0x00: +- printf("\tTEST_DISK: TEST UNIT READY\n"); ++ disk_req->response_buf[0] = 0; // Direct-access block device ++ disk_req->response_buf[1] = 0; // non-removable ++ disk_req->response_buf[2] = 5; // SPC-3 ++ disk_req->response_buf[3] = 2; // response data format ++ disk_req->response_buf[4] = 31; // additional length + +- scsi_req_complete(req, GOOD); +- break; ++ memcpy(&disk_req->response_buf[8], "QEMU", 4); // vendor ++ memcpy(&disk_req->response_buf[16], "TEST_DISK", 9); // product + +- case 0x12: +- printf("\tTEST_DISK: INQUIRY\n"); +- memset(outbuf, 0, 36); +- outbuf[0] = 0; // Direct-access block device +- outbuf[1] = 0; // non-removable +- outbuf[2] = 5; // SPC-3 +- outbuf[3] = 2; // response data format +- outbuf[4] = 31; // additional length +- memcpy(&outbuf[8], "QEMU ", 8); +- memcpy(&outbuf[16], "TEST_DISK ", 16); +- +- scsi_req_data(req, 36); +- break; ++ scsi_req_data(scsi_req, 36); ++ return 0; ++ } + +- case 0x25: +- printf("\tTEST_DISK: READ CAPACITY (10)\n"); ++ if (!disk->socket) { ++ scsi_req_build_sense(scsi_req, SENSE_CODE(NOT_READY)); ++ scsi_req_complete(scsi_req, CHECK_CONDITION); ++ return 0; ++ } ++ ++ switch (command) { ++ case 0x00: // TEST UNIT READY ++ scsi_req_complete(scsi_req, GOOD); ++ break; + +- memset(outbuf, 0, 8); ++ case 0x25: // READ CAPACITY (10) ++ memset(disk_req->response_buf, 0, 8); + + const uint32_t block_size = disk->block_size; + const uint32_t total_blocks = disk->size / disk->block_size; + +- uint32_t last_lba = total_blocks - 1; ++ const uint32_t last_lba = total_blocks - 1; + +- outbuf[0] = (last_lba >> 24) & 0xff; +- outbuf[1] = (last_lba >> 16) & 0xff; +- outbuf[2] = (last_lba >> 8) & 0xff; +- outbuf[3] = last_lba & 0xff; ++ disk_req->response_buf[0] = (last_lba >> 24) & 0xff; ++ disk_req->response_buf[1] = (last_lba >> 16) & 0xff; ++ disk_req->response_buf[2] = (last_lba >> 8) & 0xff; ++ disk_req->response_buf[3] = last_lba & 0xff; + +- outbuf[4] = (block_size >> 24) & 0xff; +- outbuf[5] = (block_size >> 16) & 0xff; +- outbuf[6] = (block_size >> 8) & 0xff; +- outbuf[7] = block_size & 0xff; ++ disk_req->response_buf[4] = (block_size >> 24) & 0xff; ++ disk_req->response_buf[5] = (block_size >> 16) & 0xff; ++ disk_req->response_buf[6] = (block_size >> 8) & 0xff; ++ disk_req->response_buf[7] = block_size & 0xff; + +- scsi_req_data(req, 8); ++ scsi_req_data(scsi_req, 8); + break; + +- case 0x1A: +- printf("\tTEST_DISK: MODE SENSE (6)\n"); ++ case 0x1A: // MODE SENSE (6) ++ memset(disk_req->response_buf, 0, 4); + +- memset(outbuf, 0, 4); +- outbuf[0] = 3; // length +- outbuf[2] = 0x80; // write protection ++ disk_req->response_buf[0] = 3; // length ++ disk_req->response_buf[2] = 0x80; // write protection + +- scsi_req_data(req, 4); ++ scsi_req_data(scsi_req, 4); + break; + +- case 0x5A: +- printf("\tTEST_DISK: MODE SENSE (10)\n"); +- memset(outbuf, 0, 8); +- +- outbuf[0] = 0; +- outbuf[1] = 6; ++ case 0x5A: // MODE SENSE (10) ++ memset(disk_req->response_buf, 0, 8); + +- outbuf[3] = 0x80; ++ disk_req->response_buf[0] = 0; ++ disk_req->response_buf[1] = 6; ++ disk_req->response_buf[3] = 0x80; + +- scsi_req_data(req, 8); ++ scsi_req_data(scsi_req, 8); + break; + +- case 0x08: +- case 0x28: +- int read_type = command == 0x08 ? 6 : 10; +- printf("\tTEST_DISK: READ (%d)\n", read_type); ++ case 0x08: // READ (6) ++ case 0x28: // READ (10) + +- int target_len = (int)req->cmd.xfer; +- printf("\tTARGET_LEN: %d\n", target_len); ++ int req_target_len = (int)scsi_req->cmd.xfer; + +- if (target_len == 0) { +- scsi_req_complete(req, GOOD); ++ if (req_target_len == 0) { ++ scsi_req_complete(scsi_req, GOOD); + break; + } + +- if (target_len > disk->max_xfer_size) { +- printf("\tTEST_DISK: error - request too big\n"); ++ if (req_target_len > disk->max_xfer_size) { ++ printf("[READ]: target xfer size from request is greater than disk max xfer size\n"); + +- scsi_req_build_sense(req, SENSE_CODE(INVALID_FIELD)); +- scsi_req_complete(req, CHECK_CONDITION); ++ scsi_req_build_sense(scsi_req, SENSE_CODE(INVALID_FIELD)); ++ scsi_req_complete(scsi_req, CHECK_CONDITION); + break; + } + +- r->len = target_len; +- r->timer = timer_new_ms(QEMU_CLOCK_REALTIME, test_disk_read_callback, r); ++ disk_req->response_buf_len = req_target_len; ++ disk_req->timer = timer_new_ms(QEMU_CLOCK_REALTIME, test_disk_read_callback, disk_req); ++ ++ // if (qio_channel_write_all(QIO_CHANNEL(disk->socket), (char*)&command, 1, NULL) == 0) { ++ // delay_ms = 5; ++ // } else { ++ // printf("[SOCKET]: connection is broken\n"); ++ // ++ // object_unref(OBJECT(disk->socket)); ++ // disk->socket = NULL; ++ // ++ // test_disk_socket_start_connection(disk); ++ // } + +- uint8_t delay_ms = 0; +- timer_mod(r->timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + delay_ms); ++ timer_mod(disk_req->timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + disk->latency_ms); + +- return 0; ++ break; + + default: +- printf("\tTEST_DISK: rejecting command 0x%02x\n", command); ++ printf("[DISK]: rejecting command 0x%02x\n", command); + +- scsi_req_build_sense(req, SENSE_CODE(INVALID_OPCODE)); +- scsi_req_complete(req, CHECK_CONDITION); ++ scsi_req_build_sense(scsi_req, SENSE_CODE(INVALID_OPCODE)); ++ scsi_req_complete(scsi_req, CHECK_CONDITION); + break; + } + + return 0; + } + +-static void test_disk_read_data(SCSIRequest* req) { +- scsi_req_complete(req, GOOD); ++static void test_disk_read_data(SCSIRequest* scsi_req) { ++ scsi_req_complete(scsi_req, GOOD); + } + +-static uint8_t* test_disk_get_buf(SCSIRequest* req) { +- TestDiskReq* r = DO_UPCAST(TestDiskReq, req, req); +- return r->buf; ++static uint8_t* test_disk_get_buf(SCSIRequest* scsi_req) { ++ TestDiskReq* disk_req = DO_UPCAST(TestDiskReq, parent_scsi_req, scsi_req); ++ return disk_req->response_buf; + } + +-static void test_disk_free_req(SCSIRequest* req) { +- TestDiskReq* r = DO_UPCAST(TestDiskReq, req, req); + +- if (r->buf) { +- g_free(r->buf); +- r->buf = NULL; +- } +-} ++// ============================================================================ + +-static const SCSIReqOps test_disk_req_ops = { +- .size = sizeof(TestDiskReq), +- .send_command = test_disk_send_command, +- .read_data = test_disk_read_data, +- .get_buf = test_disk_get_buf, +- .free_req = test_disk_free_req +-}; +- +-static SCSIRequest* test_disk_alloc_req(SCSIDevice* s, uint32_t tag, uint32_t lun, uint8_t* buf, void* hba_private) { +- TestDisk* disk = DO_UPCAST(TestDisk, parent_obj, s); +- uint64_t max_xfer_size = disk->max_xfer_size; + +- SCSIRequest* req = scsi_req_alloc(&test_disk_req_ops, s, tag, lun, hba_private); +- TestDiskReq* r = DO_UPCAST(TestDiskReq, req, req); ++static void test_disk_socket_start_connection(void* opaque) { ++ TestDisk* disk = opaque; + +- r->buf = g_malloc0(max_xfer_size); +- +- return req; +-} ++ if (disk->socket || disk->connecting) { ++ return; ++ } + +-static void test_disk_realize(SCSIDevice* dev, Error** errp) { +- TestDisk* disk = DO_UPCAST(TestDisk, parent_obj, dev); ++ printf("[SOCKET]: connecting\n"); + +- dev->type = TYPE_DISK; +- dev->blocksize = (int)disk->block_size; ++ disk->connecting = true; ++ disk->socket = qio_channel_socket_new(); + + SocketAddress addr = { + .type = SOCKET_ADDRESS_TYPE_INET, +@@ -209,30 +203,106 @@ static void test_disk_realize(SCSIDevice* dev, Error** errp) { + } + }; + +- disk->socket = qio_channel_socket_new(); ++ qio_channel_socket_connect_async(disk->socket, &addr, test_disk_socket_connected, disk, NULL, NULL); + +- if (qio_channel_socket_connect_sync(disk->socket, &addr, NULL) < 0) { +- printf("SOCKET: failed to connect\n"); ++ g_free(addr.u.inet.host); ++ g_free(addr.u.inet.port); ++} + ++static void test_disk_socket_connected(QIOTask* task, gpointer opaque) { ++ TestDisk* disk = opaque; ++ Error* err = NULL; ++ ++ disk->connecting = false; ++ ++ if (qio_task_propagate_error(task, &err)) { ++ error_free(err); + object_unref(OBJECT(disk->socket)); + disk->socket = NULL; +- } else { +- printf("SOCKET: connected\n"); ++ ++ timer_mod(disk->socket_connect_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 5000); ++ return; + } + +- g_free(addr.u.inet.host); +- g_free(addr.u.inet.port); ++ uint32_t latency_be = 0; ++ if (qio_channel_read_all(QIO_CHANNEL(disk->socket), (char*) &latency_be, sizeof(latency_be), NULL) < 0) { ++ object_unref(OBJECT(disk->socket)); ++ disk->socket = NULL; ++ ++ timer_mod(disk->socket_connect_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 5000); ++ return; ++ } ++ ++ disk->latency_ms = be32_to_cpu(latency_be); ++ ++ printf("[SOCKET]: connected successfully\n"); ++} ++ ++static void test_disk_read_callback(void* opaque) { ++ TestDiskReq* req = opaque; ++ ++ // read request returns zeros ++ memset(req->response_buf, 0, req->response_buf_len); ++ scsi_req_data(&req->parent_scsi_req, req->response_buf_len); ++ ++ timer_free(req->timer); ++ req->timer = NULL; ++} ++ ++ ++// ============================================================================ ++ + +- printf("TEST_DISK: realized\n"); ++static SCSIRequest* test_disk_alloc_req(SCSIDevice* scsi_dev, uint32_t tag, uint32_t lun, uint8_t* request_buf, void* hba_private) { ++ TestDisk* disk = DO_UPCAST(TestDisk, parent_scsi_dev, scsi_dev); ++ uint64_t max_xfer_size = disk->max_xfer_size; ++ ++ SCSIRequest* scsi_req = scsi_req_alloc(&test_disk_req_ops, scsi_dev, tag, lun, hba_private); ++ TestDiskReq* disk_req = DO_UPCAST(TestDiskReq, parent_scsi_req, scsi_req); ++ ++ disk_req->response_buf = g_malloc0(max_xfer_size); ++ ++ return scsi_req; ++} ++ ++static void test_disk_free_req(SCSIRequest* scsi_req) { ++ TestDiskReq* disk_req = DO_UPCAST(TestDiskReq, parent_scsi_req, scsi_req); ++ ++ if (disk_req->response_buf) { ++ g_free(disk_req->response_buf); ++ disk_req->response_buf = NULL; ++ } ++} ++ ++static int test_disk_parse_cdb(SCSIDevice* scsi_dev, SCSICommand* cmd, uint8_t* request_buf, size_t request_buf_len, void* hba_private) { ++ return scsi_req_parse_cdb(scsi_dev, cmd, request_buf, request_buf_len); + } + ++static void test_disk_realize(SCSIDevice* scsi_dev, Error** errp) { ++ TestDisk* disk = DO_UPCAST(TestDisk, parent_scsi_dev, scsi_dev); ++ ++ scsi_dev->type = TYPE_DISK; ++ scsi_dev->blocksize = (int)disk->block_size; ++ ++ disk->latency_ms = 0; ++ ++ disk->socket_connect_timer = timer_new_ms(QEMU_CLOCK_REALTIME, test_disk_socket_start_connection, disk); ++ test_disk_socket_start_connection(disk); ++ ++ printf("[DISK]: realized\n"); ++} ++ ++ ++// ============================================================================ ++ ++ + static void test_disk_class_init(ObjectClass* klass, const void* data) { + DeviceClass* dc = DEVICE_CLASS(klass); + SCSIDeviceClass* sc = SCSI_DEVICE_CLASS(klass); + +- sc->realize = test_disk_realize; +- sc->parse_cdb = test_disk_parse_cdb; + sc->alloc_req = test_disk_alloc_req; ++ sc->parse_cdb = test_disk_parse_cdb; ++ sc->realize = test_disk_realize; + + set_bit(DEVICE_CATEGORY_STORAGE, dc->categories); + dc->desc = "Test SCSI Disk"; +diff --git a/include/hw/scsi/test_disk.h b/hw/scsi/test_disk.h +similarity index 74% +rename from include/hw/scsi/test_disk.h +rename to hw/scsi/test_disk.h +index a86908d965..568f975961 100644 +--- a/include/hw/scsi/test_disk.h ++++ b/hw/scsi/test_disk.h +@@ -8,14 +8,20 @@ + #define TYPE_TEST_DISK "test_disk" + OBJECT_DECLARE_SIMPLE_TYPE(TestDisk, TEST_DISK) + ++// Device + struct TestDisk { +- SCSIDevice parent_obj; ++ SCSIDevice parent_scsi_dev; + QIOChannelSocket* socket; + + uint64_t size; + uint64_t block_size; + + uint64_t max_xfer_size; ++ ++ uint32_t latency_ms; ++ ++ QEMUTimer* socket_connect_timer; ++ bool connecting; + }; + + #endif //HW_SCSI_TEST_DISK_H +\ No newline at end of file +-- +2.53.0 + + +From 9b185b32fd75b77a5c6ba1fcceafe34f145e85ec Mon Sep 17 00:00:00 2001 +From: Kirill Karpunin +Date: Fri, 23 Jan 2026 12:55:40 +0300 +Subject: [PATCH 07/28] Minor refactoring + +--- + hw/scsi/test_disk.c | 23 +++++------------------ + hw/scsi/test_disk.h | 2 -- + 2 files changed, 5 insertions(+), 20 deletions(-) + +diff --git a/hw/scsi/test_disk.c b/hw/scsi/test_disk.c +index f0262586a9..a7fa2a4a77 100644 +--- a/hw/scsi/test_disk.c ++++ b/hw/scsi/test_disk.c +@@ -4,12 +4,13 @@ + #include "qapi/error.h" + #include "scsi/constants.h" + ++#define MAX_XFER_SIZE (1 * 1024 * 1024) ++ + + // Device properties + static const Property test_disk_properties[] = { + DEFINE_PROP_SIZE("size", TestDisk, size, 1 * 1024 * 1024 * 1024), + DEFINE_PROP_SIZE("bs", TestDisk, block_size, 512), +- DEFINE_PROP_SIZE("max_xfer_size", TestDisk, max_xfer_size, 1 * 1024 * 1024), + }; + + +@@ -133,7 +134,7 @@ static int32_t test_disk_send_command(SCSIRequest* scsi_req, uint8_t* request_bu + break; + } + +- if (req_target_len > disk->max_xfer_size) { ++ if (req_target_len > MAX_XFER_SIZE) { + printf("[READ]: target xfer size from request is greater than disk max xfer size\n"); + + scsi_req_build_sense(scsi_req, SENSE_CODE(INVALID_FIELD)); +@@ -144,17 +145,6 @@ static int32_t test_disk_send_command(SCSIRequest* scsi_req, uint8_t* request_bu + disk_req->response_buf_len = req_target_len; + disk_req->timer = timer_new_ms(QEMU_CLOCK_REALTIME, test_disk_read_callback, disk_req); + +- // if (qio_channel_write_all(QIO_CHANNEL(disk->socket), (char*)&command, 1, NULL) == 0) { +- // delay_ms = 5; +- // } else { +- // printf("[SOCKET]: connection is broken\n"); +- // +- // object_unref(OBJECT(disk->socket)); +- // disk->socket = NULL; +- // +- // test_disk_socket_start_connection(disk); +- // } +- + timer_mod(disk_req->timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + disk->latency_ms); + + break; +@@ -235,7 +225,7 @@ static void test_disk_socket_connected(QIOTask* task, gpointer opaque) { + + disk->latency_ms = be32_to_cpu(latency_be); + +- printf("[SOCKET]: connected successfully\n"); ++ printf("[SOCKET]: connected successfully. latency: %dms\n", disk->latency_ms); + } + + static void test_disk_read_callback(void* opaque) { +@@ -254,13 +244,10 @@ static void test_disk_read_callback(void* opaque) { + + + static SCSIRequest* test_disk_alloc_req(SCSIDevice* scsi_dev, uint32_t tag, uint32_t lun, uint8_t* request_buf, void* hba_private) { +- TestDisk* disk = DO_UPCAST(TestDisk, parent_scsi_dev, scsi_dev); +- uint64_t max_xfer_size = disk->max_xfer_size; +- + SCSIRequest* scsi_req = scsi_req_alloc(&test_disk_req_ops, scsi_dev, tag, lun, hba_private); + TestDiskReq* disk_req = DO_UPCAST(TestDiskReq, parent_scsi_req, scsi_req); + +- disk_req->response_buf = g_malloc0(max_xfer_size); ++ disk_req->response_buf = g_malloc0(MAX_XFER_SIZE); + + return scsi_req; + } +diff --git a/hw/scsi/test_disk.h b/hw/scsi/test_disk.h +index 568f975961..91aecea9b1 100644 +--- a/hw/scsi/test_disk.h ++++ b/hw/scsi/test_disk.h +@@ -16,8 +16,6 @@ struct TestDisk { + uint64_t size; + uint64_t block_size; + +- uint64_t max_xfer_size; +- + uint32_t latency_ms; + + QEMUTimer* socket_connect_timer; +-- +2.53.0 + + +From 1c47bf51331d7526074c84b85dd5dd3db0988c22 Mon Sep 17 00:00:00 2001 +From: Kirill Karpunin +Date: Fri, 23 Jan 2026 16:12:43 +0300 +Subject: [PATCH 08/28] Add socket disconnection watcher + +--- + hw/scsi/test_disk.c | 47 +++++++++++++++++++++++++++++++++++++++++++-- + hw/scsi/test_disk.h | 2 ++ + 2 files changed, 47 insertions(+), 2 deletions(-) + +diff --git a/hw/scsi/test_disk.c b/hw/scsi/test_disk.c +index a7fa2a4a77..7e5c1d5de4 100644 +--- a/hw/scsi/test_disk.c ++++ b/hw/scsi/test_disk.c +@@ -44,6 +44,7 @@ static const SCSIReqOps test_disk_req_ops = { + // Helper functions + static void test_disk_socket_start_connection(void* opaque); + static void test_disk_socket_connected(QIOTask* task, gpointer opaque); ++static gboolean test_disk_socket_watch(QIOChannel* ioc, GIOCondition condition, gpointer opaque); + static void test_disk_read_callback(void* opaque); + + +@@ -79,6 +80,14 @@ static int32_t test_disk_send_command(SCSIRequest* scsi_req, uint8_t* request_bu + return 0; + } + ++ if (disk->unit_attention) { ++ disk->unit_attention = false; ++ ++ scsi_req_build_sense(scsi_req, SENSE_CODE(CAPACITY_CHANGED)); ++ scsi_req_complete(scsi_req, CHECK_CONDITION); ++ return 0; ++ } ++ + switch (command) { + case 0x00: // TEST UNIT READY + scsi_req_complete(scsi_req, GOOD); +@@ -222,12 +231,45 @@ static void test_disk_socket_connected(QIOTask* task, gpointer opaque) { + timer_mod(disk->socket_connect_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 5000); + return; + } +- + disk->latency_ms = be32_to_cpu(latency_be); + ++ qio_channel_add_watch(QIO_CHANNEL(disk->socket), G_IO_IN | G_IO_HUP | G_IO_ERR, test_disk_socket_watch, disk, NULL); ++ ++ disk->unit_attention = true; ++ scsi_device_report_change(&disk->parent_scsi_dev, SENSE_CODE(CAPACITY_CHANGED)); ++ + printf("[SOCKET]: connected successfully. latency: %dms\n", disk->latency_ms); + } + ++static gboolean test_disk_socket_watch(QIOChannel* ioc, GIOCondition condition, gpointer opaque) { ++ TestDisk* disk = opaque; ++ ++ bool closed = false; ++ if (condition & G_IO_IN) { ++ char buf[1]; ++ if (qio_channel_read_all_eof(ioc, buf, 1, NULL) <= 0) { ++ closed = true; ++ } ++ } else if (condition & (G_IO_HUP | G_IO_ERR)) { ++ closed = true; ++ } ++ ++ if (closed) { ++ printf("[SOCKET]: disconnected\n"); ++ ++ if (disk->socket) { ++ object_unref(OBJECT(disk->socket)); ++ disk->socket = NULL; ++ ++ timer_mod(disk->socket_connect_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 1000); ++ } ++ ++ return FALSE; ++ } ++ ++ return TRUE; ++} ++ + static void test_disk_read_callback(void* opaque) { + TestDiskReq* req = opaque; + +@@ -272,9 +314,10 @@ static void test_disk_realize(SCSIDevice* scsi_dev, Error** errp) { + scsi_dev->blocksize = (int)disk->block_size; + + disk->latency_ms = 0; ++ disk->unit_attention = false; + + disk->socket_connect_timer = timer_new_ms(QEMU_CLOCK_REALTIME, test_disk_socket_start_connection, disk); +- test_disk_socket_start_connection(disk); ++ timer_mod(disk->socket_connect_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 1000); + + printf("[DISK]: realized\n"); + } +diff --git a/hw/scsi/test_disk.h b/hw/scsi/test_disk.h +index 91aecea9b1..1a9c70eacf 100644 +--- a/hw/scsi/test_disk.h ++++ b/hw/scsi/test_disk.h +@@ -20,6 +20,8 @@ struct TestDisk { + + QEMUTimer* socket_connect_timer; + bool connecting; ++ ++ bool unit_attention; + }; + + #endif //HW_SCSI_TEST_DISK_H +\ No newline at end of file +-- +2.53.0 + + +From b23673d900254f3e270cde84a03902de855db79f Mon Sep 17 00:00:00 2001 +From: Kirill Karpunin +Date: Sun, 25 Jan 2026 13:14:46 +0300 +Subject: [PATCH 09/28] Move server to contrib folder + +--- + .gitignore | 2 +- + contrib/test_disk-server/CMakeLists.txt | 11 +++++ + contrib/test_disk-server/server.cpp | 63 +++++++++++++++++++++++++ + contrib/test_disk-server/structs.h | 4 ++ + 4 files changed, 79 insertions(+), 1 deletion(-) + create mode 100644 contrib/test_disk-server/CMakeLists.txt + create mode 100644 contrib/test_disk-server/server.cpp + create mode 100644 contrib/test_disk-server/structs.h + +diff --git a/.gitignore b/.gitignore +index a0687c79d5..8bd68a1839 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -2,7 +2,7 @@ + /build/ + /.cache/ + /.vscode/ +-/.idea/ ++.idea/ + *.pyc + .sdk + .stgit-* +diff --git a/contrib/test_disk-server/CMakeLists.txt b/contrib/test_disk-server/CMakeLists.txt +new file mode 100644 +index 0000000000..271c60b05c +--- /dev/null ++++ b/contrib/test_disk-server/CMakeLists.txt +@@ -0,0 +1,11 @@ ++cmake_minimum_required(VERSION 3.31) ++project(qemu_server LANGUAGES CXX) ++ ++set(CMAKE_CXX_STANDARD 20) ++ ++add_executable(${PROJECT_NAME}) ++ ++target_sources(${PROJECT_NAME} ++ PRIVATE ++ server.cpp ++) +diff --git a/contrib/test_disk-server/server.cpp b/contrib/test_disk-server/server.cpp +new file mode 100644 +index 0000000000..bf4e523bee +--- /dev/null ++++ b/contrib/test_disk-server/server.cpp +@@ -0,0 +1,63 @@ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++ ++int main() { ++ sockaddr_in addr = { ++ .sin_family = AF_INET, ++ .sin_port = htons(31234), ++ .sin_zero = 0 ++ }; ++ ++ inet_aton("127.0.0.1", &addr.sin_addr); ++ ++ int fd = socket(AF_INET, SOCK_STREAM, 0); ++ ++ int opt = 1; ++ setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); ++ ++ bind(fd, reinterpret_cast(&addr), sizeof(addr)); ++ ++ listen(fd, 1); ++ ++ uint32_t latency_ms = 1; ++ ++ uint8_t latency_ms_be[4]; ++ latency_ms_be[0] = latency_ms >> 24; ++ latency_ms_be[1] = latency_ms >> 16; ++ latency_ms_be[2] = latency_ms >> 8; ++ latency_ms_be[3] = latency_ms; ++ ++ while (true) { ++ int client_fd = accept(fd, nullptr, nullptr); ++ std::cout << std::format("---- New connection. Latency: {}ms ----", latency_ms) << std::endl; ++ send(client_fd, latency_ms_be, sizeof(latency_ms_be), 0); ++ ++ char buffer[1024] = {}; ++ while (true) { ++ ssize_t bytes = recv(client_fd, buffer, sizeof(buffer), 0); ++ if (bytes < 0) { ++ break; ++ } ++ ++ std::string str(static_cast(bytes), '\0'); ++ std::memcpy(str.data(), buffer, bytes); ++ ++ if (str.empty()) { ++ break; ++ } ++ ++ // for (const char ch: str) { ++ // std::cout << "0x" << std::hex << std::setw(2) << std::setfill('0') << static_cast(ch) << ' '; ++ // } ++ // std::cout << std::endl; ++ } ++ std::cout << "---- Connection closed ----" << std::endl; ++ } ++} +diff --git a/contrib/test_disk-server/structs.h b/contrib/test_disk-server/structs.h +new file mode 100644 +index 0000000000..73808f620f +--- /dev/null ++++ b/contrib/test_disk-server/structs.h +@@ -0,0 +1,4 @@ ++#ifndef STRUCTS_H ++#define STRUCTS_H ++ ++#endif //STRUCTS_H +-- +2.53.0 + + +From 6d178a2e61d30902b24739daddf22ddd968f6b4c Mon Sep 17 00:00:00 2001 +From: Kirill Karpunin +Date: Sun, 25 Jan 2026 19:33:53 +0300 +Subject: [PATCH 10/28] Update server + +--- + contrib/test_disk-server/server.cpp | 35 +++++++++++++---------------- + contrib/test_disk-server/structs.h | 8 +++++++ + 2 files changed, 23 insertions(+), 20 deletions(-) + +diff --git a/contrib/test_disk-server/server.cpp b/contrib/test_disk-server/server.cpp +index bf4e523bee..dca2d12727 100644 +--- a/contrib/test_disk-server/server.cpp ++++ b/contrib/test_disk-server/server.cpp +@@ -5,8 +5,11 @@ + #include + #include + #include ++#include + #include + ++#include "structs.h" ++ + + int main() { + sockaddr_in addr = { +@@ -26,37 +29,29 @@ int main() { + + listen(fd, 1); + +- uint32_t latency_ms = 1; +- +- uint8_t latency_ms_be[4]; +- latency_ms_be[0] = latency_ms >> 24; +- latency_ms_be[1] = latency_ms >> 16; +- latency_ms_be[2] = latency_ms >> 8; +- latency_ms_be[3] = latency_ms; ++ uint32_t latency_ms = 5; + + while (true) { + int client_fd = accept(fd, nullptr, nullptr); + std::cout << std::format("---- New connection. Latency: {}ms ----", latency_ms) << std::endl; +- send(client_fd, latency_ms_be, sizeof(latency_ms_be), 0); + +- char buffer[1024] = {}; + while (true) { +- ssize_t bytes = recv(client_fd, buffer, sizeof(buffer), 0); +- if (bytes < 0) { ++ ServerRequest server_req; ++ ssize_t bytes = recv(client_fd, &server_req, sizeof(server_req), 0); ++ if (bytes <= 0) { + break; + } + +- std::string str(static_cast(bytes), '\0'); +- std::memcpy(str.data(), buffer, bytes); ++ std::cout << "Received: command = 0x" << std::hex << std::setw(2) << std::setfill('0') << static_cast(server_req.command) << std::endl; + +- if (str.empty()) { +- break; +- } ++ ServerResponse server_res; ++ server_res.latency = latency_ms; ++ ++ send(client_fd, &server_res, sizeof(server_res), 0); ++ ++ std::this_thread::sleep_for(std::chrono::milliseconds(50)); + +- // for (const char ch: str) { +- // std::cout << "0x" << std::hex << std::setw(2) << std::setfill('0') << static_cast(ch) << ' '; +- // } +- // std::cout << std::endl; ++ std::cout << "Sent: latency = " << latency_ms << std::endl; + } + std::cout << "---- Connection closed ----" << std::endl; + } +diff --git a/contrib/test_disk-server/structs.h b/contrib/test_disk-server/structs.h +index 73808f620f..7cca000034 100644 +--- a/contrib/test_disk-server/structs.h ++++ b/contrib/test_disk-server/structs.h +@@ -1,4 +1,12 @@ + #ifndef STRUCTS_H + #define STRUCTS_H + ++typedef struct ServerRequest { ++ uint8_t command; ++} ServerRequest; ++ ++typedef struct ServerResponse { ++ uint32_t latency; ++} ServerResponse; ++ + #endif //STRUCTS_H +-- +2.53.0 + + +From f5ced68344de677aeff619de00afc3bfbffc114e Mon Sep 17 00:00:00 2001 +From: Kirill Karpunin +Date: Sun, 25 Jan 2026 19:36:53 +0300 +Subject: [PATCH 11/28] Change logic of acquiring latency value from server + +--- + hw/scsi/test_disk.c | 79 +++++++++++++++++++++++++++++++++++---------- + hw/scsi/test_disk.h | 4 +-- + 2 files changed, 64 insertions(+), 19 deletions(-) + +diff --git a/hw/scsi/test_disk.c b/hw/scsi/test_disk.c +index 7e5c1d5de4..4124631d23 100644 +--- a/hw/scsi/test_disk.c ++++ b/hw/scsi/test_disk.c +@@ -2,7 +2,10 @@ + #include "hw/scsi/scsi.h" + #include "test_disk.h" + #include "qapi/error.h" ++#include "qemu/bswap.h" + #include "scsi/constants.h" ++#include "contrib/test_disk-server/structs.h" ++#include "system/cpu-timers.h" + + #define MAX_XFER_SIZE (1 * 1024 * 1024) + +@@ -83,7 +86,7 @@ static int32_t test_disk_send_command(SCSIRequest* scsi_req, uint8_t* request_bu + if (disk->unit_attention) { + disk->unit_attention = false; + +- scsi_req_build_sense(scsi_req, SENSE_CODE(CAPACITY_CHANGED)); ++ scsi_req_build_sense(scsi_req, SENSE_CODE(MEDIUM_CHANGED)); + scsi_req_complete(scsi_req, CHECK_CONDITION); + return 0; + } +@@ -151,10 +154,63 @@ static int32_t test_disk_send_command(SCSIRequest* scsi_req, uint8_t* request_bu + break; + } + ++ uint32_t latency_ms = 0; ++ ++ // --- ++ cpu_disable_ticks(); ++ ++ qio_channel_set_blocking(QIO_CHANNEL(disk->socket), true, NULL); ++ ++ if (disk->socket_watcher_tag > 0) { ++ g_source_remove(disk->socket_watcher_tag); ++ disk->socket_watcher_tag = 0; ++ } ++ ++ ServerRequest server_req = { ++ .command = command ++ }; ++ ++ bool socket_alive = true; ++ if (qio_channel_write_all(QIO_CHANNEL(disk->socket), &server_req, sizeof(server_req), NULL) == -1) { ++ socket_alive = false; ++ } ++ ++ if (socket_alive) { ++ ServerResponse server_res; ++ ++ if (qio_channel_read_all(QIO_CHANNEL(disk->socket), &server_res, sizeof(server_res), NULL) == -1) { ++ socket_alive = false; ++ } ++ ++ if (socket_alive) { ++ latency_ms = server_res.latency; ++ } ++ } ++ ++ disk->socket_watcher_tag = qio_channel_add_watch(QIO_CHANNEL(disk->socket), G_IO_IN | G_IO_HUP | G_IO_ERR, test_disk_socket_watch, disk, NULL); ++ ++ qio_channel_set_blocking(QIO_CHANNEL(disk->socket), false, NULL); ++ ++ cpu_enable_ticks(); ++ // --- ++ ++ if (!socket_alive) { ++ object_unref(OBJECT(disk->socket)); ++ disk->socket = NULL; ++ ++ g_source_remove(disk->socket_watcher_tag); ++ disk->socket_watcher_tag = 0; ++ ++ timer_mod(disk->socket_connect_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 5000); ++ ++ scsi_req_build_sense(scsi_req, SENSE_CODE(NOT_READY)); ++ scsi_req_complete(scsi_req, CHECK_CONDITION); ++ return 0; ++ } ++ + disk_req->response_buf_len = req_target_len; + disk_req->timer = timer_new_ms(QEMU_CLOCK_REALTIME, test_disk_read_callback, disk_req); +- +- timer_mod(disk_req->timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + disk->latency_ms); ++ timer_mod(disk_req->timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + latency_ms); + + break; + +@@ -223,22 +279,12 @@ static void test_disk_socket_connected(QIOTask* task, gpointer opaque) { + return; + } + +- uint32_t latency_be = 0; +- if (qio_channel_read_all(QIO_CHANNEL(disk->socket), (char*) &latency_be, sizeof(latency_be), NULL) < 0) { +- object_unref(OBJECT(disk->socket)); +- disk->socket = NULL; +- +- timer_mod(disk->socket_connect_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 5000); +- return; +- } +- disk->latency_ms = be32_to_cpu(latency_be); +- +- qio_channel_add_watch(QIO_CHANNEL(disk->socket), G_IO_IN | G_IO_HUP | G_IO_ERR, test_disk_socket_watch, disk, NULL); ++ disk->socket_watcher_tag = qio_channel_add_watch(QIO_CHANNEL(disk->socket), G_IO_IN | G_IO_HUP | G_IO_ERR, test_disk_socket_watch, disk, NULL); + + disk->unit_attention = true; +- scsi_device_report_change(&disk->parent_scsi_dev, SENSE_CODE(CAPACITY_CHANGED)); ++ scsi_device_report_change(&disk->parent_scsi_dev, SENSE_CODE(MEDIUM_CHANGED)); + +- printf("[SOCKET]: connected successfully. latency: %dms\n", disk->latency_ms); ++ printf("[SOCKET]: connected successfully\n"); + } + + static gboolean test_disk_socket_watch(QIOChannel* ioc, GIOCondition condition, gpointer opaque) { +@@ -313,7 +359,6 @@ static void test_disk_realize(SCSIDevice* scsi_dev, Error** errp) { + scsi_dev->type = TYPE_DISK; + scsi_dev->blocksize = (int)disk->block_size; + +- disk->latency_ms = 0; + disk->unit_attention = false; + + disk->socket_connect_timer = timer_new_ms(QEMU_CLOCK_REALTIME, test_disk_socket_start_connection, disk); +diff --git a/hw/scsi/test_disk.h b/hw/scsi/test_disk.h +index 1a9c70eacf..acc1285d62 100644 +--- a/hw/scsi/test_disk.h ++++ b/hw/scsi/test_disk.h +@@ -16,12 +16,12 @@ struct TestDisk { + uint64_t size; + uint64_t block_size; + +- uint32_t latency_ms; +- + QEMUTimer* socket_connect_timer; + bool connecting; + + bool unit_attention; ++ ++ guint socket_watcher_tag; + }; + + #endif //HW_SCSI_TEST_DISK_H +\ No newline at end of file +-- +2.53.0 + + +From c767d313939dcf7d3cb25ac52de2073daf920d31 Mon Sep 17 00:00:00 2001 +From: Kirill Karpunin +Date: Tue, 27 Jan 2026 15:11:41 +0300 +Subject: [PATCH 12/28] Add WRITE command handling + +--- + hw/scsi/test_disk.c | 19 +++++++++++++------ + 1 file changed, 13 insertions(+), 6 deletions(-) + +diff --git a/hw/scsi/test_disk.c b/hw/scsi/test_disk.c +index 4124631d23..216d38a394 100644 +--- a/hw/scsi/test_disk.c ++++ b/hw/scsi/test_disk.c +@@ -30,6 +30,7 @@ typedef struct TestDiskReq { + // Request handlers + static int32_t test_disk_send_command(SCSIRequest* scsi_req, uint8_t* request_buf); + static void test_disk_read_data(SCSIRequest* scsi_req); ++static void test_disk_write_data(SCSIRequest* scsi_req); + static uint8_t* test_disk_get_buf(SCSIRequest* scsi_req); + static void test_disk_free_req(SCSIRequest* scsi_req); + +@@ -39,6 +40,7 @@ static const SCSIReqOps test_disk_req_ops = { + .size = sizeof(TestDiskReq), + .send_command = test_disk_send_command, + .read_data = test_disk_read_data, ++ .write_data = test_disk_write_data, + .get_buf = test_disk_get_buf, + .free_req = test_disk_free_req + }; +@@ -48,7 +50,7 @@ static const SCSIReqOps test_disk_req_ops = { + static void test_disk_socket_start_connection(void* opaque); + static void test_disk_socket_connected(QIOTask* task, gpointer opaque); + static gboolean test_disk_socket_watch(QIOChannel* ioc, GIOCondition condition, gpointer opaque); +-static void test_disk_read_callback(void* opaque); ++static void test_disk_io_callback(void* opaque); + + + // ============================================================================ +@@ -121,7 +123,7 @@ static int32_t test_disk_send_command(SCSIRequest* scsi_req, uint8_t* request_bu + memset(disk_req->response_buf, 0, 4); + + disk_req->response_buf[0] = 3; // length +- disk_req->response_buf[2] = 0x80; // write protection ++ // disk_req->response_buf[2] = 0x80; // write protection + + scsi_req_data(scsi_req, 4); + break; +@@ -131,13 +133,15 @@ static int32_t test_disk_send_command(SCSIRequest* scsi_req, uint8_t* request_bu + + disk_req->response_buf[0] = 0; + disk_req->response_buf[1] = 6; +- disk_req->response_buf[3] = 0x80; ++ // disk_req->response_buf[3] = 0x80; + + scsi_req_data(scsi_req, 8); + break; + + case 0x08: // READ (6) + case 0x28: // READ (10) ++ case 0x0A: // WRITE (6) ++ case 0x2A: // WRITE (10) + + int req_target_len = (int)scsi_req->cmd.xfer; + +@@ -209,7 +213,7 @@ static int32_t test_disk_send_command(SCSIRequest* scsi_req, uint8_t* request_bu + } + + disk_req->response_buf_len = req_target_len; +- disk_req->timer = timer_new_ms(QEMU_CLOCK_REALTIME, test_disk_read_callback, disk_req); ++ disk_req->timer = timer_new_ms(QEMU_CLOCK_REALTIME, test_disk_io_callback, disk_req); + timer_mod(disk_req->timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + latency_ms); + + break; +@@ -229,6 +233,10 @@ static void test_disk_read_data(SCSIRequest* scsi_req) { + scsi_req_complete(scsi_req, GOOD); + } + ++static void test_disk_write_data(SCSIRequest* scsi_req) { ++ scsi_req_complete(scsi_req, GOOD); ++} ++ + static uint8_t* test_disk_get_buf(SCSIRequest* scsi_req) { + TestDiskReq* disk_req = DO_UPCAST(TestDiskReq, parent_scsi_req, scsi_req); + return disk_req->response_buf; +@@ -316,10 +324,9 @@ static gboolean test_disk_socket_watch(QIOChannel* ioc, GIOCondition condition, + return TRUE; + } + +-static void test_disk_read_callback(void* opaque) { ++static void test_disk_io_callback(void* opaque) { + TestDiskReq* req = opaque; + +- // read request returns zeros + memset(req->response_buf, 0, req->response_buf_len); + scsi_req_data(&req->parent_scsi_req, req->response_buf_len); + +-- +2.53.0 + + +From 8e08e1427aa1ebce20c6a96404c55ed2b4b6adc0 Mon Sep 17 00:00:00 2001 +From: Kirill Karpunin +Date: Tue, 27 Jan 2026 15:12:39 +0300 +Subject: [PATCH 13/28] Server: Add WRITE latency parameter + +--- + contrib/test_disk-server/server.cpp | 13 +++++++------ + 1 file changed, 7 insertions(+), 6 deletions(-) + +diff --git a/contrib/test_disk-server/server.cpp b/contrib/test_disk-server/server.cpp +index dca2d12727..65f3c59cc8 100644 +--- a/contrib/test_disk-server/server.cpp ++++ b/contrib/test_disk-server/server.cpp +@@ -29,11 +29,12 @@ int main() { + + listen(fd, 1); + +- uint32_t latency_ms = 5; ++ uint32_t read_latency_ms = 5; ++ uint32_t write_latency_ms = 5; + + while (true) { + int client_fd = accept(fd, nullptr, nullptr); +- std::cout << std::format("---- New connection. Latency: {}ms ----", latency_ms) << std::endl; ++ std::cout << std::format("---- New connection ---- ") << std::endl; + + while (true) { + ServerRequest server_req; +@@ -42,16 +43,16 @@ int main() { + break; + } + +- std::cout << "Received: command = 0x" << std::hex << std::setw(2) << std::setfill('0') << static_cast(server_req.command) << std::endl; ++ std::cout << std::format("Received: command = 0x{:02x}", static_cast(server_req.command)) << std::endl; + + ServerResponse server_res; +- server_res.latency = latency_ms; ++ server_res.latency = server_req.command == 0x28 || server_req.command == 0x08 ? read_latency_ms : write_latency_ms; + + send(client_fd, &server_res, sizeof(server_res), 0); + +- std::this_thread::sleep_for(std::chrono::milliseconds(50)); ++ // std::this_thread::sleep_for(std::chrono::milliseconds(50)); + +- std::cout << "Sent: latency = " << latency_ms << std::endl; ++ std::cout << "Sent: latency = " << server_res.latency << std::endl; + } + std::cout << "---- Connection closed ----" << std::endl; + } +-- +2.53.0 + + +From 8ff4fd73462c44ac11b62adcdee287e6e50f4de2 Mon Sep 17 00:00:00 2001 +From: Kirill Karpunin +Date: Fri, 30 Jan 2026 20:58:46 +0300 +Subject: [PATCH 14/28] Remove unit_attention and MEDIUM_CHANGE call causing + SEGFAULT + +--- + hw/scsi/test_disk.c | 26 ++++++-------------------- + hw/scsi/test_disk.h | 2 -- + 2 files changed, 6 insertions(+), 22 deletions(-) + +diff --git a/hw/scsi/test_disk.c b/hw/scsi/test_disk.c +index 216d38a394..6ab9300711 100644 +--- a/hw/scsi/test_disk.c ++++ b/hw/scsi/test_disk.c +@@ -85,14 +85,6 @@ static int32_t test_disk_send_command(SCSIRequest* scsi_req, uint8_t* request_bu + return 0; + } + +- if (disk->unit_attention) { +- disk->unit_attention = false; +- +- scsi_req_build_sense(scsi_req, SENSE_CODE(MEDIUM_CHANGED)); +- scsi_req_complete(scsi_req, CHECK_CONDITION); +- return 0; +- } +- + switch (command) { + case 0x00: // TEST UNIT READY + scsi_req_complete(scsi_req, GOOD); +@@ -191,9 +183,11 @@ static int32_t test_disk_send_command(SCSIRequest* scsi_req, uint8_t* request_bu + } + } + +- disk->socket_watcher_tag = qio_channel_add_watch(QIO_CHANNEL(disk->socket), G_IO_IN | G_IO_HUP | G_IO_ERR, test_disk_socket_watch, disk, NULL); ++ if (socket_alive) { ++ disk->socket_watcher_tag = qio_channel_add_watch(QIO_CHANNEL(disk->socket), G_IO_IN | G_IO_HUP | G_IO_ERR, test_disk_socket_watch, disk, NULL); + +- qio_channel_set_blocking(QIO_CHANNEL(disk->socket), false, NULL); ++ qio_channel_set_blocking(QIO_CHANNEL(disk->socket), false, NULL); ++ } + + cpu_enable_ticks(); + // --- +@@ -202,9 +196,6 @@ static int32_t test_disk_send_command(SCSIRequest* scsi_req, uint8_t* request_bu + object_unref(OBJECT(disk->socket)); + disk->socket = NULL; + +- g_source_remove(disk->socket_watcher_tag); +- disk->socket_watcher_tag = 0; +- + timer_mod(disk->socket_connect_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 5000); + + scsi_req_build_sense(scsi_req, SENSE_CODE(NOT_READY)); +@@ -289,9 +280,6 @@ static void test_disk_socket_connected(QIOTask* task, gpointer opaque) { + + disk->socket_watcher_tag = qio_channel_add_watch(QIO_CHANNEL(disk->socket), G_IO_IN | G_IO_HUP | G_IO_ERR, test_disk_socket_watch, disk, NULL); + +- disk->unit_attention = true; +- scsi_device_report_change(&disk->parent_scsi_dev, SENSE_CODE(MEDIUM_CHANGED)); +- + printf("[SOCKET]: connected successfully\n"); + } + +@@ -314,10 +302,10 @@ static gboolean test_disk_socket_watch(QIOChannel* ioc, GIOCondition condition, + if (disk->socket) { + object_unref(OBJECT(disk->socket)); + disk->socket = NULL; +- +- timer_mod(disk->socket_connect_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 1000); + } + ++ disk->socket_watcher_tag = 0; ++ timer_mod(disk->socket_connect_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 1000); + return FALSE; + } + +@@ -366,8 +354,6 @@ static void test_disk_realize(SCSIDevice* scsi_dev, Error** errp) { + scsi_dev->type = TYPE_DISK; + scsi_dev->blocksize = (int)disk->block_size; + +- disk->unit_attention = false; +- + disk->socket_connect_timer = timer_new_ms(QEMU_CLOCK_REALTIME, test_disk_socket_start_connection, disk); + timer_mod(disk->socket_connect_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 1000); + +diff --git a/hw/scsi/test_disk.h b/hw/scsi/test_disk.h +index acc1285d62..4ef28ee37a 100644 +--- a/hw/scsi/test_disk.h ++++ b/hw/scsi/test_disk.h +@@ -19,8 +19,6 @@ struct TestDisk { + QEMUTimer* socket_connect_timer; + bool connecting; + +- bool unit_attention; +- + guint socket_watcher_tag; + }; + +-- +2.53.0 + + +From 77f760be6995bce8e053f77b1b0f3a274b2de339 Mon Sep 17 00:00:00 2001 +From: Kirill Karpunin +Date: Sun, 1 Feb 2026 15:52:46 +0300 +Subject: [PATCH 15/28] Use vm_stop() to freeze virtual machine + +--- + hw/scsi/test_disk.c | 144 +++++++++++++++++++++++++++----------------- + 1 file changed, 90 insertions(+), 54 deletions(-) + +diff --git a/hw/scsi/test_disk.c b/hw/scsi/test_disk.c +index 6ab9300711..3529ce1288 100644 +--- a/hw/scsi/test_disk.c ++++ b/hw/scsi/test_disk.c +@@ -1,11 +1,15 @@ + #include "qemu/osdep.h" ++#include "qemu/main-loop.h" + #include "hw/scsi/scsi.h" + #include "test_disk.h" ++#include "block/block-common.h" + #include "qapi/error.h" + #include "qemu/bswap.h" + #include "scsi/constants.h" + #include "contrib/test_disk-server/structs.h" ++#include "system/block-backend-global-state.h" + #include "system/cpu-timers.h" ++#include "system/runstate.h" + + #define MAX_XFER_SIZE (1 * 1024 * 1024) + +@@ -50,9 +54,19 @@ static const SCSIReqOps test_disk_req_ops = { + static void test_disk_socket_start_connection(void* opaque); + static void test_disk_socket_connected(QIOTask* task, gpointer opaque); + static gboolean test_disk_socket_watch(QIOChannel* ioc, GIOCondition condition, gpointer opaque); ++static void test_disk_bh_routine(void* opaque); + static void test_disk_io_callback(void* opaque); + + ++// Helper structs ++typedef struct TestDiskBHArgs { ++ TestDisk* disk; ++ SCSIRequest* scsi_req; ++ uint8_t command; ++ int req_target_len; ++} TestDiskBHArgs; ++ ++ + // ============================================================================ + + +@@ -150,62 +164,15 @@ static int32_t test_disk_send_command(SCSIRequest* scsi_req, uint8_t* request_bu + break; + } + +- uint32_t latency_ms = 0; +- +- // --- +- cpu_disable_ticks(); +- +- qio_channel_set_blocking(QIO_CHANNEL(disk->socket), true, NULL); +- +- if (disk->socket_watcher_tag > 0) { +- g_source_remove(disk->socket_watcher_tag); +- disk->socket_watcher_tag = 0; +- } +- +- ServerRequest server_req = { +- .command = command +- }; ++ TestDiskBHArgs* args = g_new0(TestDiskBHArgs, 1); ++ args->disk = disk; ++ args->scsi_req = scsi_req; ++ args->command = command; ++ args->req_target_len = req_target_len; + +- bool socket_alive = true; +- if (qio_channel_write_all(QIO_CHANNEL(disk->socket), &server_req, sizeof(server_req), NULL) == -1) { +- socket_alive = false; +- } +- +- if (socket_alive) { +- ServerResponse server_res; +- +- if (qio_channel_read_all(QIO_CHANNEL(disk->socket), &server_res, sizeof(server_res), NULL) == -1) { +- socket_alive = false; +- } +- +- if (socket_alive) { +- latency_ms = server_res.latency; +- } +- } +- +- if (socket_alive) { +- disk->socket_watcher_tag = qio_channel_add_watch(QIO_CHANNEL(disk->socket), G_IO_IN | G_IO_HUP | G_IO_ERR, test_disk_socket_watch, disk, NULL); +- +- qio_channel_set_blocking(QIO_CHANNEL(disk->socket), false, NULL); +- } ++ scsi_req_ref(scsi_req); + +- cpu_enable_ticks(); +- // --- +- +- if (!socket_alive) { +- object_unref(OBJECT(disk->socket)); +- disk->socket = NULL; +- +- timer_mod(disk->socket_connect_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 5000); +- +- scsi_req_build_sense(scsi_req, SENSE_CODE(NOT_READY)); +- scsi_req_complete(scsi_req, CHECK_CONDITION); +- return 0; +- } +- +- disk_req->response_buf_len = req_target_len; +- disk_req->timer = timer_new_ms(QEMU_CLOCK_REALTIME, test_disk_io_callback, disk_req); +- timer_mod(disk_req->timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + latency_ms); ++ aio_bh_schedule_oneshot(qemu_get_aio_context(), test_disk_bh_routine, args); + + break; + +@@ -312,6 +279,73 @@ static gboolean test_disk_socket_watch(QIOChannel* ioc, GIOCondition condition, + return TRUE; + } + ++static void test_disk_bh_routine(void* opaque) { ++ TestDiskBHArgs* args = opaque; ++ ++ TestDisk* disk = args->disk; ++ SCSIRequest* scsi_req = args->scsi_req; ++ TestDiskReq* disk_req = DO_UPCAST(TestDiskReq, parent_scsi_req, scsi_req); ++ ++ if (disk->socket_watcher_tag > 0) { ++ g_source_remove(disk->socket_watcher_tag); // i. remove watcher ++ disk->socket_watcher_tag = 0; ++ } ++ ++ vm_stop(RUN_STATE_PAUSED); // ii. pause vm ++ ++ ServerRequest server_req = {.command = args->command}; ++ ServerResponse server_res; ++ ++ qio_channel_set_blocking(QIO_CHANNEL(disk->socket), true, NULL); // iii. set socket blocking true ++ ++ uint32_t latency_ms = 0; ++ bool socket_alive = true; ++ ++ if (qio_channel_write_all(QIO_CHANNEL(disk->socket), &server_req, sizeof(server_req), NULL) == -1) { ++ socket_alive = false; ++ } ++ ++ if (socket_alive) { ++ if (qio_channel_read_all(QIO_CHANNEL(disk->socket), &server_res, sizeof(server_res), NULL) == -1) { ++ socket_alive = false; ++ } ++ ++ if (socket_alive) { ++ latency_ms = server_res.latency; ++ } ++ } ++ ++ if (socket_alive) { ++ qio_channel_set_blocking(QIO_CHANNEL(disk->socket), false, NULL); // iii. set socket blocking false ++ } ++ ++ vm_start(); // ii. start vm ++ ++ if (socket_alive) { ++ disk->socket_watcher_tag = qio_channel_add_watch(QIO_CHANNEL(disk->socket), G_IO_IN | G_IO_HUP | G_IO_ERR, test_disk_socket_watch, disk, NULL); // i. add watcher ++ } else { ++ object_unref(OBJECT(disk->socket)); ++ disk->socket = NULL; ++ ++ timer_mod(disk->socket_connect_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 5000); ++ ++ scsi_req_build_sense(scsi_req, SENSE_CODE(NOT_READY)); ++ scsi_req_complete(scsi_req, CHECK_CONDITION); ++ ++ scsi_req_unref(scsi_req); ++ g_free(args); ++ ++ return; ++ } ++ ++ disk_req->response_buf_len = args->req_target_len; ++ disk_req->timer = timer_new_ms(QEMU_CLOCK_VIRTUAL, test_disk_io_callback, disk_req); ++ timer_mod(disk_req->timer, qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + latency_ms); ++ ++ scsi_req_unref(scsi_req); ++ g_free(args); ++} ++ + static void test_disk_io_callback(void* opaque) { + TestDiskReq* req = opaque; + +@@ -354,6 +388,8 @@ static void test_disk_realize(SCSIDevice* scsi_dev, Error** errp) { + scsi_dev->type = TYPE_DISK; + scsi_dev->blocksize = (int)disk->block_size; + ++ scsi_dev->conf.blk = blk_new(qemu_get_aio_context(), BLK_PERM_ALL, BLK_PERM_ALL); ++ + disk->socket_connect_timer = timer_new_ms(QEMU_CLOCK_REALTIME, test_disk_socket_start_connection, disk); + timer_mod(disk->socket_connect_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 1000); + +-- +2.53.0 + + +From 34b6fff56232dbab6ea53631f8503ac720bf04bf Mon Sep 17 00:00:00 2001 +From: Kirill Karpunin +Date: Sun, 1 Feb 2026 16:27:56 +0300 +Subject: [PATCH 16/28] Extend ServerRequest and ServerResponse structs + +--- + contrib/test_disk-server/server.cpp | 8 ++++++-- + contrib/test_disk-server/structs.h | 20 +++++++++++++++++++- + hw/scsi/test_disk.c | 7 ++++++- + hw/scsi/test_disk.h | 3 +++ + 4 files changed, 34 insertions(+), 4 deletions(-) + +diff --git a/contrib/test_disk-server/server.cpp b/contrib/test_disk-server/server.cpp +index 65f3c59cc8..a7a4148459 100644 +--- a/contrib/test_disk-server/server.cpp ++++ b/contrib/test_disk-server/server.cpp +@@ -43,10 +43,14 @@ int main() { + break; + } + +- std::cout << std::format("Received: command = 0x{:02x}", static_cast(server_req.command)) << std::endl; ++ if (server_req.source != SOURCE_TEST_DISK) { ++ continue; ++ } ++ ++ std::cout << std::format("Received: id = {}, command = 0x{:02x}", server_req.test_disk_payload.disk_id, static_cast(server_req.test_disk_payload.command)) << std::endl; + + ServerResponse server_res; +- server_res.latency = server_req.command == 0x28 || server_req.command == 0x08 ? read_latency_ms : write_latency_ms; ++ server_res.latency = server_req.test_disk_payload.command == 0x28 || server_req.test_disk_payload.command == 0x08 ? read_latency_ms : write_latency_ms; + + send(client_fd, &server_res, sizeof(server_res), 0); + +diff --git a/contrib/test_disk-server/structs.h b/contrib/test_disk-server/structs.h +index 7cca000034..14b180cc18 100644 +--- a/contrib/test_disk-server/structs.h ++++ b/contrib/test_disk-server/structs.h +@@ -1,11 +1,29 @@ + #ifndef STRUCTS_H + #define STRUCTS_H + +-typedef struct ServerRequest { ++#define SOURCE_TEST_DISK 1 ++#define SOURCE_CHANGE_PARAMETERS 2 ++ ++typedef struct TestDiskPayload { ++ uint8_t disk_id; + uint8_t command; ++ uint32_t sector; ++ uint64_t req_target_len; ++} TestDiskPayload; ++ ++typedef struct ChangeParametersPayload { ++ uint32_t read_latency_ms; ++ uint32_t write_latency_ms; ++} ChangeParametersPayload; ++ ++typedef struct ServerRequest { ++ int source; ++ TestDiskPayload test_disk_payload; ++ ChangeParametersPayload change_parameters_payload; + } ServerRequest; + + typedef struct ServerResponse { ++ uint8_t error_code; + uint32_t latency; + } ServerResponse; + +diff --git a/hw/scsi/test_disk.c b/hw/scsi/test_disk.c +index 3529ce1288..37403eda0d 100644 +--- a/hw/scsi/test_disk.c ++++ b/hw/scsi/test_disk.c +@@ -18,6 +18,7 @@ + static const Property test_disk_properties[] = { + DEFINE_PROP_SIZE("size", TestDisk, size, 1 * 1024 * 1024 * 1024), + DEFINE_PROP_SIZE("bs", TestDisk, block_size, 512), ++ DEFINE_PROP_UINT8("disk-id", TestDisk, id, 0) + }; + + +@@ -293,7 +294,11 @@ static void test_disk_bh_routine(void* opaque) { + + vm_stop(RUN_STATE_PAUSED); // ii. pause vm + +- ServerRequest server_req = {.command = args->command}; ++ ServerRequest server_req = { ++ .source = SOURCE_TEST_DISK, ++ .test_disk_payload.disk_id = disk->id, ++ .test_disk_payload.command = args->command ++ }; + ServerResponse server_res; + + qio_channel_set_blocking(QIO_CHANNEL(disk->socket), true, NULL); // iii. set socket blocking true +diff --git a/hw/scsi/test_disk.h b/hw/scsi/test_disk.h +index 4ef28ee37a..a2deee5318 100644 +--- a/hw/scsi/test_disk.h ++++ b/hw/scsi/test_disk.h +@@ -11,6 +11,9 @@ OBJECT_DECLARE_SIMPLE_TYPE(TestDisk, TEST_DISK) + // Device + struct TestDisk { + SCSIDevice parent_scsi_dev; ++ ++ uint8_t id; ++ + QIOChannelSocket* socket; + + uint64_t size; +-- +2.53.0 + + +From f61abc344b7035428ae4c8bb96645ff7f93079fc Mon Sep 17 00:00:00 2001 +From: Kirill Karpunin +Date: Thu, 5 Feb 2026 19:15:49 +0300 +Subject: [PATCH 17/28] Improve server logic and create a toolkit + +--- + contrib/test_disk-server/CMakeLists.txt | 21 +++ + .../test_disk-server/change_parameters.cpp | 5 + + contrib/test_disk-server/server.cpp | 77 ++++------ + .../server_toolkit/server_toolkit.cpp | 132 ++++++++++++++++++ + .../server_toolkit/server_toolkit.h | 28 ++++ + 5 files changed, 210 insertions(+), 53 deletions(-) + create mode 100644 contrib/test_disk-server/change_parameters.cpp + create mode 100644 contrib/test_disk-server/server_toolkit/server_toolkit.cpp + create mode 100644 contrib/test_disk-server/server_toolkit/server_toolkit.h + +diff --git a/contrib/test_disk-server/CMakeLists.txt b/contrib/test_disk-server/CMakeLists.txt +index 271c60b05c..3ce451587f 100644 +--- a/contrib/test_disk-server/CMakeLists.txt ++++ b/contrib/test_disk-server/CMakeLists.txt +@@ -8,4 +8,25 @@ add_executable(${PROJECT_NAME}) + target_sources(${PROJECT_NAME} + PRIVATE + server.cpp ++ server_toolkit/server_toolkit.cpp ++) ++ ++target_include_directories(${PROJECT_NAME} ++ PUBLIC ++ ${PROJECT_SOURCE_DIR} ++) ++ ++# ============================================================== ++ ++add_executable(${PROJECT_NAME}_change_parameters) ++ ++target_sources(${PROJECT_NAME}_change_parameters ++ PRIVATE ++ change_parameters.cpp ++ server_toolkit/server_toolkit.cpp ++) ++ ++target_include_directories(${PROJECT_NAME} ++ PUBLIC ++ ${PROJECT_SOURCE_DIR} + ) +diff --git a/contrib/test_disk-server/change_parameters.cpp b/contrib/test_disk-server/change_parameters.cpp +new file mode 100644 +index 0000000000..a6ba9efd1d +--- /dev/null ++++ b/contrib/test_disk-server/change_parameters.cpp +@@ -0,0 +1,5 @@ ++#include "server_toolkit/server_toolkit.h" ++ ++int main() { ++ change_server_parameters("127.0.0.1", 31234, 10, 40); ++} +\ No newline at end of file +diff --git a/contrib/test_disk-server/server.cpp b/contrib/test_disk-server/server.cpp +index a7a4148459..2e095a0389 100644 +--- a/contrib/test_disk-server/server.cpp ++++ b/contrib/test_disk-server/server.cpp +@@ -1,63 +1,34 @@ +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +- +-#include "structs.h" +- ++#include "server_toolkit/server_toolkit.h" + + int main() { +- sockaddr_in addr = { +- .sin_family = AF_INET, +- .sin_port = htons(31234), +- .sin_zero = 0 +- }; +- +- inet_aton("127.0.0.1", &addr.sin_addr); +- +- int fd = socket(AF_INET, SOCK_STREAM, 0); +- +- int opt = 1; +- setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); +- +- bind(fd, reinterpret_cast(&addr), sizeof(addr)); +- +- listen(fd, 1); ++ int server_fd = create_server("127.0.0.1", 31234); ++ if (server_fd == -1) { ++ return -1; ++ } + +- uint32_t read_latency_ms = 5; +- uint32_t write_latency_ms = 5; ++ std::atomic read_latency_ms = 34; ++ std::atomic write_latency_ms = 7; + +- while (true) { +- int client_fd = accept(fd, nullptr, nullptr); +- std::cout << std::format("---- New connection ---- ") << std::endl; ++ run_server(server_fd, [&](const ServerRequest& server_req) { ++ ServerResponse server_res{}; + +- while (true) { +- ServerRequest server_req; +- ssize_t bytes = recv(client_fd, &server_req, sizeof(server_req), 0); +- if (bytes <= 0) { ++ switch (server_req.source) { ++ case SOURCE_CHANGE_PARAMETERS: ++ read_latency_ms = server_req.change_parameters_payload.read_latency_ms; ++ write_latency_ms = server_req.change_parameters_payload.write_latency_ms; + break; +- } ++ case SOURCE_TEST_DISK: ++ std::cout << std::format("Command: 0x{:02x}", server_req.test_disk_payload.command) << std::endl; + +- if (server_req.source != SOURCE_TEST_DISK) { +- continue; +- } +- +- std::cout << std::format("Received: id = {}, command = 0x{:02x}", server_req.test_disk_payload.disk_id, static_cast(server_req.test_disk_payload.command)) << std::endl; +- +- ServerResponse server_res; +- server_res.latency = server_req.test_disk_payload.command == 0x28 || server_req.test_disk_payload.command == 0x08 ? read_latency_ms : write_latency_ms; +- +- send(client_fd, &server_res, sizeof(server_res), 0); ++ server_res.latency = ++ server_req.test_disk_payload.command == 0x28 || server_req.test_disk_payload.command == 0x08 ++ ? read_latency_ms ++ : write_latency_ms; ++ break; ++ } + +- // std::this_thread::sleep_for(std::chrono::milliseconds(50)); ++ // std::this_thread::sleep_for(std::chrono::milliseconds(150)); + +- std::cout << "Sent: latency = " << server_res.latency << std::endl; +- } +- std::cout << "---- Connection closed ----" << std::endl; +- } ++ return server_res; ++ }); + } +diff --git a/contrib/test_disk-server/server_toolkit/server_toolkit.cpp b/contrib/test_disk-server/server_toolkit/server_toolkit.cpp +new file mode 100644 +index 0000000000..a58ce334ee +--- /dev/null ++++ b/contrib/test_disk-server/server_toolkit/server_toolkit.cpp +@@ -0,0 +1,132 @@ ++#include "server_toolkit.h" ++ ++int create_server(const std::string& ip, uint16_t port) { ++ sockaddr_in addr = { ++ .sin_family = AF_INET, ++ .sin_port = htons(port), ++ .sin_zero = 0 ++ }; ++ ++ if (inet_aton(ip.c_str(), &addr.sin_addr) == 0) { ++ std::cerr << "Invalid address" << std::endl; ++ return -1; ++ } ++ ++ int fd = socket(AF_INET, SOCK_STREAM, 0); ++ if (fd == -1) { ++ std::cerr << "Failed to create socket: " << strerror(errno) << std::endl; ++ return -1; ++ } ++ ++ int opt = 1; ++ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1) { ++ close(fd); ++ std::cerr << "Unable to set SO_REUSEADDR option: " << strerror(errno) << std::endl; ++ return -1; ++ } ++ ++ if (bind(fd, reinterpret_cast(&addr), sizeof(addr)) == -1) { ++ close(fd); ++ std::cerr << "Unable to bind socket: " << strerror(errno) << std::endl; ++ return -1; ++ } ++ ++ return fd; ++} ++ ++// ============================================================================ ++ ++int run_server(int server_fd, const Callback& callback) { ++ if (listen(server_fd, 5) == -1) { ++ close(server_fd); ++ std::cerr << "Unable to listen server socket: " << strerror(errno) << std::endl; ++ return -1; ++ } ++ ++ while (true) { ++ int client_fd = accept(server_fd, nullptr, nullptr); ++ if (client_fd == -1) { ++ std::cerr << "Unable to accept client" << std::endl; ++ continue; ++ } ++ ++ std::thread([client_fd, callback]() { ++ client_routine(client_fd, callback); ++ }).detach(); ++ } ++ ++ return 0; ++} ++ ++// ============================================================================ ++ ++void client_routine(int client_fd, const Callback& callback) { ++ std::cout << "---- New connection ---- " << std::endl; ++ ++ while (true) { ++ ServerRequest server_req; ++ ssize_t bytes = recv(client_fd, &server_req, sizeof(server_req), 0); ++ if (bytes <= 0) { ++ break; ++ } ++ ++ ServerResponse server_res = callback(server_req); ++ ++ if (send(client_fd, &server_res, sizeof(server_res), 0) == -1) { ++ std::cerr << "Unable to send response to client socket: " << strerror(errno) << std::endl; ++ } else { ++ std::cout << "Sent: latency = " << server_res.latency << std::endl; ++ } ++ } ++ ++ std::cout << "---- Connection closed ----" << std::endl; ++ ++ close(client_fd); ++} ++ ++// ============================================================================ ++ ++int change_server_parameters(const std::string& ip, uint16_t port, uint32_t read_latency_ms, uint32_t write_latency_ms) { ++ // server address ++ sockaddr_in addr = { ++ .sin_family = AF_INET, ++ .sin_port = htons(port), ++ .sin_zero = 0 ++ }; ++ ++ if (inet_aton(ip.c_str(), &addr.sin_addr) == 0) { ++ std::cerr << "Invalid address" << std::endl; ++ return -1; ++ } ++ ++ // connect to server ++ int fd = socket(AF_INET, SOCK_STREAM, 0); ++ if (fd == -1) { ++ std::cerr << "Failed to create socket: " << strerror(errno) << std::endl; ++ return -1; ++ } ++ ++ if (connect(fd, reinterpret_cast(&addr), sizeof(addr)) == -1) { ++ close(fd); ++ std::cerr << "Unable to connect to server: " << strerror(errno) << std::endl; ++ return -1; ++ } ++ ++ // send request ++ ServerRequest server_req = { ++ .source = SOURCE_CHANGE_PARAMETERS, ++ .change_parameters_payload = { ++ .read_latency_ms = read_latency_ms, ++ .write_latency_ms = write_latency_ms, ++ } ++ }; ++ ++ if (send(fd, &server_req, sizeof(server_req), 0) == -1) { ++ std::cerr << "Unable to send request to client socket: " << strerror(errno) << std::endl; ++ } else { ++ std::cout << "Sent: read_latency_ms = " << read_latency_ms << ", write_latency_ms = " << write_latency_ms << std::endl; ++ } ++ ++ close(fd); ++ return 0; ++} +diff --git a/contrib/test_disk-server/server_toolkit/server_toolkit.h b/contrib/test_disk-server/server_toolkit/server_toolkit.h +new file mode 100644 +index 0000000000..6d9845f770 +--- /dev/null ++++ b/contrib/test_disk-server/server_toolkit/server_toolkit.h +@@ -0,0 +1,28 @@ ++#ifndef SERVER_H ++#define SERVER_H ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "structs.h" ++ ++int create_server(const std::string& ip, uint16_t port); ++ ++using Callback = std::function; ++int run_server(int server_fd, const Callback& callback); ++ ++void client_routine(int client_fd, const Callback& callback); ++ ++int change_server_parameters(const std::string& ip, uint16_t port, uint32_t read_latency_ms, uint32_t write_latency_ms); ++ ++#endif //SERVER_H +-- +2.53.0 + + +From e1096412f16601f37c0241bc1dcc792e52f467c5 Mon Sep 17 00:00:00 2001 +From: Kirill Karpunin +Date: Fri, 6 Feb 2026 18:03:32 +0300 +Subject: [PATCH 18/28] Minor server refactoring + +--- + contrib/test_disk-server/change_parameters.cpp | 11 ++++++++++- + contrib/test_disk-server/server.cpp | 5 +++++ + 2 files changed, 15 insertions(+), 1 deletion(-) + +diff --git a/contrib/test_disk-server/change_parameters.cpp b/contrib/test_disk-server/change_parameters.cpp +index a6ba9efd1d..3cefe8a805 100644 +--- a/contrib/test_disk-server/change_parameters.cpp ++++ b/contrib/test_disk-server/change_parameters.cpp +@@ -1,5 +1,14 @@ ++#include ++ + #include "server_toolkit/server_toolkit.h" + + int main() { +- change_server_parameters("127.0.0.1", 31234, 10, 40); ++ uint32_t read_latency_ms, write_latency_ms; ++ ++ std::cout << "Read latency: "; ++ std::cin >> read_latency_ms; ++ std::cout << "Write latency: "; ++ std::cin >> write_latency_ms; ++ ++ change_server_parameters("127.0.0.1", 31234, read_latency_ms, write_latency_ms); + } +\ No newline at end of file +diff --git a/contrib/test_disk-server/server.cpp b/contrib/test_disk-server/server.cpp +index 2e095a0389..3f703bd4f0 100644 +--- a/contrib/test_disk-server/server.cpp ++++ b/contrib/test_disk-server/server.cpp +@@ -1,3 +1,8 @@ ++#include ++#include ++#include ++#include ++ + #include "server_toolkit/server_toolkit.h" + + int main() { +-- +2.53.0 + + +From 787ee6fe621e6202922bf34b2bd967956a117a7f Mon Sep 17 00:00:00 2001 +From: Kirill Karpunin +Date: Fri, 6 Feb 2026 18:05:04 +0300 +Subject: [PATCH 19/28] Add python bindings + +--- + contrib/test_disk-server/CMakeLists.txt | 27 ++++++++++- + contrib/test_disk-server/cmake/cpm.cmake | 10 +++++ + contrib/test_disk-server/cmake/external.cmake | 5 +++ + .../server_toolkit/bindings.cpp | 45 +++++++++++++++++++ + .../server_toolkit/server_toolkit.cpp | 27 +++++++++-- + .../server_toolkit/server_toolkit.h | 12 +---- + 6 files changed, 111 insertions(+), 15 deletions(-) + create mode 100644 contrib/test_disk-server/cmake/cpm.cmake + create mode 100644 contrib/test_disk-server/cmake/external.cmake + create mode 100644 contrib/test_disk-server/server_toolkit/bindings.cpp + +diff --git a/contrib/test_disk-server/CMakeLists.txt b/contrib/test_disk-server/CMakeLists.txt +index 3ce451587f..2b8b9e6544 100644 +--- a/contrib/test_disk-server/CMakeLists.txt ++++ b/contrib/test_disk-server/CMakeLists.txt +@@ -3,6 +3,11 @@ project(qemu_server LANGUAGES CXX) + + set(CMAKE_CXX_STANDARD 20) + ++include(cmake/cpm.cmake) ++include(cmake/external.cmake) ++ ++# ============================================================== ++ + add_executable(${PROJECT_NAME}) + + target_sources(${PROJECT_NAME} +@@ -26,7 +31,25 @@ target_sources(${PROJECT_NAME}_change_parameters + server_toolkit/server_toolkit.cpp + ) + +-target_include_directories(${PROJECT_NAME} +- PUBLIC ++target_include_directories(${PROJECT_NAME}_change_parameters ++ PUBLIC ++ ${PROJECT_SOURCE_DIR} ++) ++ ++# ============================================================== ++ ++set(BINDINGS_PACKAGE_NAME server_toolkit_py) ++ ++pybind11_add_module(${BINDINGS_PACKAGE_NAME} ++ server_toolkit/bindings.cpp ++ server_toolkit/server_toolkit.cpp ++) ++ ++target_include_directories(${BINDINGS_PACKAGE_NAME} ++ PUBLIC + ${PROJECT_SOURCE_DIR} + ) ++ ++target_link_libraries(${BINDINGS_PACKAGE_NAME} PRIVATE pybind11::module) ++ ++target_compile_definitions(${BINDINGS_PACKAGE_NAME} PRIVATE PYTHON_BINDINGS) +\ No newline at end of file +diff --git a/contrib/test_disk-server/cmake/cpm.cmake b/contrib/test_disk-server/cmake/cpm.cmake +new file mode 100644 +index 0000000000..d6aa096c3d +--- /dev/null ++++ b/contrib/test_disk-server/cmake/cpm.cmake +@@ -0,0 +1,10 @@ ++set(CPM_DOWNLOAD_VERSION 0.42.0) ++set(CPM_DOWNLOAD_LOCATION "${CMAKE_BINARY_DIR}/cmake/cpm_${CPM_DOWNLOAD_VERSION}.cmake") ++ ++file( ++ DOWNLOAD ++ https://github.com/cpm-cmake/CPM.cmake/releases/download/v${CPM_DOWNLOAD_VERSION}/CPM.cmake ++ ${CPM_DOWNLOAD_LOCATION} ++) ++ ++include(${CPM_DOWNLOAD_LOCATION}) +\ No newline at end of file +diff --git a/contrib/test_disk-server/cmake/external.cmake b/contrib/test_disk-server/cmake/external.cmake +new file mode 100644 +index 0000000000..bbd1ca9331 +--- /dev/null ++++ b/contrib/test_disk-server/cmake/external.cmake +@@ -0,0 +1,5 @@ ++CPMAddPackage( ++ NAME pybind11 ++ GIT_REPOSITORY https://github.com/pybind/pybind11.git ++ VERSION 3.0.1 ++) +\ No newline at end of file +diff --git a/contrib/test_disk-server/server_toolkit/bindings.cpp b/contrib/test_disk-server/server_toolkit/bindings.cpp +new file mode 100644 +index 0000000000..29e04abc59 +--- /dev/null ++++ b/contrib/test_disk-server/server_toolkit/bindings.cpp +@@ -0,0 +1,45 @@ ++#include "pybind11/pybind11.h" ++#include "pybind11/stl.h" ++#include "pybind11/functional.h" ++ ++#include "structs.h" ++#include "server_toolkit.h" ++ ++namespace py = pybind11; ++using namespace pybind11::literals; ++ ++PYBIND11_MODULE(server_toolkit_py, m) { ++ // Constants ++ m.attr("SOURCE_TEST_DISK") = SOURCE_TEST_DISK; ++ m.attr("SOURCE_CHANGE_PARAMETERS") = SOURCE_CHANGE_PARAMETERS; ++ ++ // Server Request ++ py::class_(m, "TestDiskPayload") ++ .def(py::init<>()) ++ .def_readwrite("disk_id", &TestDiskPayload::disk_id) ++ .def_readwrite("command", &TestDiskPayload::command) ++ .def_readwrite("sector", &TestDiskPayload::sector) ++ .def_readwrite("req_target_len", &TestDiskPayload::req_target_len); ++ ++ py::class_(m, "ChangeParametersPayload") ++ .def(py::init<>()) ++ .def_readwrite("read_latency_ms", &ChangeParametersPayload::read_latency_ms) ++ .def_readwrite("write_latency_ms", &ChangeParametersPayload::write_latency_ms); ++ ++ py::class_(m, "ServerRequest") ++ .def(py::init<>()) ++ .def_readwrite("source", &ServerRequest::source) ++ .def_readwrite("test_disk_payload", &ServerRequest::test_disk_payload) ++ .def_readwrite("change_parameters_payload", &ServerRequest::change_parameters_payload); ++ ++ // Server Response ++ py::class_(m, "ServerResponse") ++ .def(py::init<>()) ++ .def_readwrite("error_code", &ServerResponse::error_code) ++ .def_readwrite("latency", &ServerResponse::latency); ++ ++ // Functions ++ m.def("create_server", &create_server, "ip"_a, "port"_a); ++ m.def("run_server", &run_server, "server_fd"_a, "callback"_a, py::call_guard()); ++ m.def("change_server_parameters", &change_server_parameters, "ip"_a, "port"_a, "read_latency_ms"_a, "write_latency_ms"_a); ++} +diff --git a/contrib/test_disk-server/server_toolkit/server_toolkit.cpp b/contrib/test_disk-server/server_toolkit/server_toolkit.cpp +index a58ce334ee..74e2822631 100644 +--- a/contrib/test_disk-server/server_toolkit/server_toolkit.cpp ++++ b/contrib/test_disk-server/server_toolkit/server_toolkit.cpp +@@ -1,3 +1,16 @@ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#ifdef PYTHON_BINDINGS ++#include "pybind11/pybind11.h" ++#endif ++ + #include "server_toolkit.h" + + int create_server(const std::string& ip, uint16_t port) { +@@ -51,7 +64,7 @@ int run_server(int server_fd, const Callback& callback) { + } + + std::thread([client_fd, callback]() { +- client_routine(client_fd, callback); ++ _client_routine(client_fd, callback); + }).detach(); + } + +@@ -60,7 +73,7 @@ int run_server(int server_fd, const Callback& callback) { + + // ============================================================================ + +-void client_routine(int client_fd, const Callback& callback) { ++void _client_routine(int client_fd, const Callback& callback) { + std::cout << "---- New connection ---- " << std::endl; + + while (true) { +@@ -70,7 +83,15 @@ void client_routine(int client_fd, const Callback& callback) { + break; + } + +- ServerResponse server_res = callback(server_req); ++ ServerResponse server_res; ++#ifdef PYTHON_BINDINGS ++ { ++ pybind11::gil_scoped_acquire acquire; ++ server_res = callback(server_req); ++ } ++#else ++ server_res = callback(server_req); ++#endif + + if (send(client_fd, &server_res, sizeof(server_res), 0) == -1) { + std::cerr << "Unable to send response to client socket: " << strerror(errno) << std::endl; +diff --git a/contrib/test_disk-server/server_toolkit/server_toolkit.h b/contrib/test_disk-server/server_toolkit/server_toolkit.h +index 6d9845f770..04f9cf6952 100644 +--- a/contrib/test_disk-server/server_toolkit/server_toolkit.h ++++ b/contrib/test_disk-server/server_toolkit/server_toolkit.h +@@ -1,18 +1,10 @@ + #ifndef SERVER_H + #define SERVER_H + +-#include +-#include +-#include + #include + #include +-#include +-#include + #include +-#include +-#include +-#include +-#include ++#include + + #include "structs.h" + +@@ -21,7 +13,7 @@ int create_server(const std::string& ip, uint16_t port); + using Callback = std::function; + int run_server(int server_fd, const Callback& callback); + +-void client_routine(int client_fd, const Callback& callback); ++void _client_routine(int client_fd, const Callback& callback); + + int change_server_parameters(const std::string& ip, uint16_t port, uint32_t read_latency_ms, uint32_t write_latency_ms); + +-- +2.53.0 + + +From 849824b4db294c7d24539ffb83528c472d49fe3c Mon Sep 17 00:00:00 2001 +From: Kirill Karpunin +Date: Sat, 7 Feb 2026 13:59:26 +0300 +Subject: [PATCH 20/28] Minor changes + +--- + hw/scsi/test_disk.c | 25 ++++++++++++++++--------- + 1 file changed, 16 insertions(+), 9 deletions(-) + +diff --git a/hw/scsi/test_disk.c b/hw/scsi/test_disk.c +index 37403eda0d..0c80ca6201 100644 +--- a/hw/scsi/test_disk.c ++++ b/hw/scsi/test_disk.c +@@ -94,7 +94,7 @@ static int32_t test_disk_send_command(SCSIRequest* scsi_req, uint8_t* request_bu + return 0; + } + +- if (!disk->socket) { ++ if (!disk->socket || disk->connecting) { + scsi_req_build_sense(scsi_req, SENSE_CODE(NOT_READY)); + scsi_req_complete(scsi_req, CHECK_CONDITION); + return 0; +@@ -265,15 +265,16 @@ static gboolean test_disk_socket_watch(QIOChannel* ioc, GIOCondition condition, + } + + if (closed) { +- printf("[SOCKET]: disconnected\n"); +- + if (disk->socket) { ++ printf("[SOCKET]: disconnected\n"); ++ + object_unref(OBJECT(disk->socket)); + disk->socket = NULL; ++ ++ timer_mod(disk->socket_connect_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 1000); + } + + disk->socket_watcher_tag = 0; +- timer_mod(disk->socket_connect_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 1000); + return FALSE; + } + +@@ -329,10 +330,12 @@ static void test_disk_bh_routine(void* opaque) { + if (socket_alive) { + disk->socket_watcher_tag = qio_channel_add_watch(QIO_CHANNEL(disk->socket), G_IO_IN | G_IO_HUP | G_IO_ERR, test_disk_socket_watch, disk, NULL); // i. add watcher + } else { ++ printf("[SOCKET]: disconnected\n"); ++ + object_unref(OBJECT(disk->socket)); + disk->socket = NULL; + +- timer_mod(disk->socket_connect_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 5000); ++ timer_mod(disk->socket_connect_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 1000); + + scsi_req_build_sense(scsi_req, SENSE_CODE(NOT_READY)); + scsi_req_complete(scsi_req, CHECK_CONDITION); +@@ -344,7 +347,7 @@ static void test_disk_bh_routine(void* opaque) { + } + + disk_req->response_buf_len = args->req_target_len; +- disk_req->timer = timer_new_ms(QEMU_CLOCK_VIRTUAL, test_disk_io_callback, disk_req); ++ + timer_mod(disk_req->timer, qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + latency_ms); + + scsi_req_unref(scsi_req); +@@ -356,9 +359,6 @@ static void test_disk_io_callback(void* opaque) { + + memset(req->response_buf, 0, req->response_buf_len); + scsi_req_data(&req->parent_scsi_req, req->response_buf_len); +- +- timer_free(req->timer); +- req->timer = NULL; + } + + +@@ -371,6 +371,8 @@ static SCSIRequest* test_disk_alloc_req(SCSIDevice* scsi_dev, uint32_t tag, uint + + disk_req->response_buf = g_malloc0(MAX_XFER_SIZE); + ++ disk_req->timer = timer_new_ms(QEMU_CLOCK_VIRTUAL, test_disk_io_callback, disk_req); ++ + return scsi_req; + } + +@@ -381,6 +383,11 @@ static void test_disk_free_req(SCSIRequest* scsi_req) { + g_free(disk_req->response_buf); + disk_req->response_buf = NULL; + } ++ ++ if (disk_req->timer) { ++ timer_free(disk_req->timer); ++ disk_req->timer = NULL; ++ } + } + + static int test_disk_parse_cdb(SCSIDevice* scsi_dev, SCSICommand* cmd, uint8_t* request_buf, size_t request_buf_len, void* hba_private) { +-- +2.53.0 + + +From 62cc2b1c753c9ec89a8338423ab9c1cbb9387a30 Mon Sep 17 00:00:00 2001 +From: Kirill Karpunin +Date: Sat, 7 Feb 2026 15:39:31 +0300 +Subject: [PATCH 21/28] Add python server + +--- + contrib/test_disk-server/change_parameters.py | 7 +++++ + contrib/test_disk-server/server.py | 29 +++++++++++++++++++ + 2 files changed, 36 insertions(+) + create mode 100644 contrib/test_disk-server/change_parameters.py + create mode 100644 contrib/test_disk-server/server.py + +diff --git a/contrib/test_disk-server/change_parameters.py b/contrib/test_disk-server/change_parameters.py +new file mode 100644 +index 0000000000..57e4256266 +--- /dev/null ++++ b/contrib/test_disk-server/change_parameters.py +@@ -0,0 +1,7 @@ ++import server_toolkit_py as st ++ ++if __name__ == "__main__": ++ read_latency_ms = int(input("Read latency: ")) ++ write_latency_ms = int(input("Write latency: ")) ++ ++ st.change_server_parameters("127.0.0.1", 31234, read_latency_ms, write_latency_ms) +\ No newline at end of file +diff --git a/contrib/test_disk-server/server.py b/contrib/test_disk-server/server.py +new file mode 100644 +index 0000000000..55997c1fc2 +--- /dev/null ++++ b/contrib/test_disk-server/server.py +@@ -0,0 +1,29 @@ ++import server_toolkit_py as st ++ ++read_latency_ms = 0 ++write_latency_ms = 0 ++ ++def callback(server_req: st.ServerRequest) -> st.ServerResponse: ++ global read_latency_ms, write_latency_ms ++ ++ server_res = st.ServerResponse() ++ ++ if server_req.source == st.SOURCE_CHANGE_PARAMETERS: ++ read_latency_ms = server_req.change_parameters_payload.read_latency_ms ++ write_latency_ms = server_req.change_parameters_payload.write_latency_ms ++ ++ elif server_req.source == st.SOURCE_TEST_DISK: ++ print(f"Command: 0x{server_req.test_disk_payload.command:02X}") ++ ++ server_res.latency = read_latency_ms \ ++ if server_req.test_disk_payload.command == 0x28 or server_req.test_disk_payload.command == 0x08 \ ++ else write_latency_ms ++ ++ return server_res ++ ++if __name__ == "__main__": ++ server_fd = st.create_server("127.0.0.1", 31234) ++ if server_fd == -1: ++ exit(-1) ++ ++ st.run_server(server_fd, callback) +-- +2.53.0 + + +From 2165e3d6b793e87dd7bca585ca4d9b1d29e4cc1a Mon Sep 17 00:00:00 2001 +From: Kirill Karpunin +Date: Mon, 16 Feb 2026 18:16:50 +0300 +Subject: [PATCH 22/28] Use BlockBackend + +--- + hw/scsi/test_disk.c | 151 +++++++++++++++++++++++++++++++++++--------- + hw/scsi/test_disk.h | 5 -- + 2 files changed, 122 insertions(+), 34 deletions(-) + +diff --git a/hw/scsi/test_disk.c b/hw/scsi/test_disk.c +index 0c80ca6201..8f6c1a1857 100644 +--- a/hw/scsi/test_disk.c ++++ b/hw/scsi/test_disk.c +@@ -8,6 +8,7 @@ + #include "scsi/constants.h" + #include "contrib/test_disk-server/structs.h" + #include "system/block-backend-global-state.h" ++#include "system/block-backend-io.h" + #include "system/cpu-timers.h" + #include "system/runstate.h" + +@@ -16,8 +17,7 @@ + + // Device properties + static const Property test_disk_properties[] = { +- DEFINE_PROP_SIZE("size", TestDisk, size, 1 * 1024 * 1024 * 1024), +- DEFINE_PROP_SIZE("bs", TestDisk, block_size, 512), ++ DEFINE_BLOCK_PROPERTIES(TestDisk, parent_scsi_dev.conf), + DEFINE_PROP_UINT8("disk-id", TestDisk, id, 0) + }; + +@@ -29,14 +29,21 @@ typedef struct TestDiskReq { + + uint8_t* response_buf; + int response_buf_len; ++ ++ struct iovec iov; ++ QEMUIOVector qiov; + } TestDiskReq; + + + // Request handlers + static int32_t test_disk_send_command(SCSIRequest* scsi_req, uint8_t* request_buf); ++ + static void test_disk_read_data(SCSIRequest* scsi_req); ++ + static void test_disk_write_data(SCSIRequest* scsi_req); ++ + static uint8_t* test_disk_get_buf(SCSIRequest* scsi_req); ++ + static void test_disk_free_req(SCSIRequest* scsi_req); + + +@@ -53,10 +60,18 @@ static const SCSIReqOps test_disk_req_ops = { + + // Helper functions + static void test_disk_socket_start_connection(void* opaque); ++ + static void test_disk_socket_connected(QIOTask* task, gpointer opaque); ++ + static gboolean test_disk_socket_watch(QIOChannel* ioc, GIOCondition condition, gpointer opaque); ++ ++static void test_disk_bh_schedule_routine(TestDisk* disk, SCSIRequest* scsi_req); ++ + static void test_disk_bh_routine(void* opaque); +-static void test_disk_io_callback(void* opaque); ++ ++static void test_disk_latency_callback(void* opaque); ++ ++static void test_disk_blk_aio_complete(void* opaque, int ret); + + + // Helper structs +@@ -100,6 +115,7 @@ static int32_t test_disk_send_command(SCSIRequest* scsi_req, uint8_t* request_bu + return 0; + } + ++ int req_target_len; + switch (command) { + case 0x00: // TEST UNIT READY + scsi_req_complete(scsi_req, GOOD); +@@ -108,8 +124,15 @@ static int32_t test_disk_send_command(SCSIRequest* scsi_req, uint8_t* request_bu + case 0x25: // READ CAPACITY (10) + memset(disk_req->response_buf, 0, 8); + +- const uint32_t block_size = disk->block_size; +- const uint32_t total_blocks = disk->size / disk->block_size; ++ const int64_t disk_size = blk_getlength(disk->parent_scsi_dev.conf.blk); ++ if (disk_size < 0) { ++ scsi_req_build_sense(scsi_req, SENSE_CODE(LUN_NOT_READY)); ++ scsi_req_complete(scsi_req, CHECK_CONDITION); ++ return 0; ++ } ++ ++ const uint32_t block_size = disk->parent_scsi_dev.conf.logical_block_size; ++ const uint32_t total_blocks = disk_size / block_size; + + const uint32_t last_lba = total_blocks - 1; + +@@ -147,16 +170,23 @@ static int32_t test_disk_send_command(SCSIRequest* scsi_req, uint8_t* request_bu + + case 0x08: // READ (6) + case 0x28: // READ (10) +- case 0x0A: // WRITE (6) +- case 0x2A: // WRITE (10) ++ req_target_len = (int) scsi_req->cmd.xfer; + +- int req_target_len = (int)scsi_req->cmd.xfer; ++ if (req_target_len > MAX_XFER_SIZE) { ++ printf("[READ]: target xfer size from request is greater than disk max xfer size\n"); + +- if (req_target_len == 0) { +- scsi_req_complete(scsi_req, GOOD); ++ scsi_req_build_sense(scsi_req, SENSE_CODE(INVALID_FIELD)); ++ scsi_req_complete(scsi_req, CHECK_CONDITION); + break; + } + ++ test_disk_bh_schedule_routine(disk, scsi_req); ++ break; ++ ++ case 0x0A: // WRITE (6) ++ case 0x2A: // WRITE (10) ++ req_target_len = (int) scsi_req->cmd.xfer; ++ + if (req_target_len > MAX_XFER_SIZE) { + printf("[READ]: target xfer size from request is greater than disk max xfer size\n"); + +@@ -165,16 +195,7 @@ static int32_t test_disk_send_command(SCSIRequest* scsi_req, uint8_t* request_bu + break; + } + +- TestDiskBHArgs* args = g_new0(TestDiskBHArgs, 1); +- args->disk = disk; +- args->scsi_req = scsi_req; +- args->command = command; +- args->req_target_len = req_target_len; +- +- scsi_req_ref(scsi_req); +- +- aio_bh_schedule_oneshot(qemu_get_aio_context(), test_disk_bh_routine, args); +- ++ scsi_req_data(scsi_req, req_target_len); // goes to test_disk_write_data() + break; + + default: +@@ -193,7 +214,11 @@ static void test_disk_read_data(SCSIRequest* scsi_req) { + } + + static void test_disk_write_data(SCSIRequest* scsi_req) { +- scsi_req_complete(scsi_req, GOOD); ++ // data now in disk_req->response_buf; ++ ++ TestDisk* disk = DO_UPCAST(TestDisk, parent_scsi_dev, scsi_req->dev); ++ ++ test_disk_bh_schedule_routine(disk, scsi_req); + } + + static uint8_t* test_disk_get_buf(SCSIRequest* scsi_req) { +@@ -281,6 +306,25 @@ static gboolean test_disk_socket_watch(QIOChannel* ioc, GIOCondition condition, + return TRUE; + } + ++static void test_disk_bh_schedule_routine(TestDisk* disk, SCSIRequest* scsi_req) { ++ int req_target_len = (int) scsi_req->cmd.xfer; ++ ++ if (req_target_len == 0) { ++ scsi_req_complete(scsi_req, GOOD); ++ return; ++ } ++ ++ TestDiskBHArgs* args = g_new0(TestDiskBHArgs, 1); ++ args->disk = disk; ++ args->scsi_req = scsi_req; ++ args->command = scsi_req->cmd.buf[0]; ++ args->req_target_len = req_target_len; ++ ++ scsi_req_ref(scsi_req); ++ ++ aio_bh_schedule_oneshot(qemu_get_aio_context(), test_disk_bh_routine, args); ++} ++ + static void test_disk_bh_routine(void* opaque) { + TestDiskBHArgs* args = opaque; + +@@ -354,11 +398,45 @@ static void test_disk_bh_routine(void* opaque) { + g_free(args); + } + +-static void test_disk_io_callback(void* opaque) { +- TestDiskReq* req = opaque; ++static void test_disk_latency_callback(void* opaque) { ++ TestDiskReq* disk_req = opaque; ++ SCSIRequest* scsi_req = &disk_req->parent_scsi_req; ++ ++ TestDisk* disk = DO_UPCAST(TestDisk, parent_scsi_dev, disk_req->parent_scsi_req.dev); ++ BlockBackend *blk = disk->parent_scsi_dev.conf.blk; ++ ++ disk_req->iov.iov_base = disk_req->response_buf; ++ disk_req->iov.iov_len = disk_req->response_buf_len; ++ qemu_iovec_init_external(&disk_req->qiov, &disk_req->iov, 1); ++ ++ int64_t offset = scsi_req->cmd.lba * disk->parent_scsi_dev.blocksize; ++ ++ if (scsi_req->cmd.mode == SCSI_XFER_FROM_DEV) { ++ blk_aio_preadv(blk, offset, &disk_req->qiov, 0, test_disk_blk_aio_complete, disk_req); ++ } else if (scsi_req->cmd.mode == SCSI_XFER_TO_DEV) { ++ blk_aio_pwritev(blk, offset, &disk_req->qiov, 0, test_disk_blk_aio_complete, disk_req); ++ } else { ++ scsi_req_complete(scsi_req, GOOD); ++ } ++} ++ ++static void test_disk_blk_aio_complete(void* opaque, int ret) { ++ TestDiskReq* disk_req = opaque; ++ SCSIRequest* scsi_req = &disk_req->parent_scsi_req; ++ ++ if (ret < 0) { ++ printf("[DISK]: AIO error %d\n", ret); ++ ++ scsi_req_build_sense(scsi_req, SENSE_CODE(IO_ERROR)); ++ scsi_req_complete(scsi_req, CHECK_CONDITION); ++ return; ++ } + +- memset(req->response_buf, 0, req->response_buf_len); +- scsi_req_data(&req->parent_scsi_req, req->response_buf_len); ++ if (scsi_req->cmd.mode == SCSI_XFER_FROM_DEV) { ++ scsi_req_data(scsi_req, disk_req->response_buf_len); // goes to test_disk_read_data() ++ } else { ++ scsi_req_complete(scsi_req, GOOD); ++ } + } + + +@@ -371,7 +449,7 @@ static SCSIRequest* test_disk_alloc_req(SCSIDevice* scsi_dev, uint32_t tag, uint + + disk_req->response_buf = g_malloc0(MAX_XFER_SIZE); + +- disk_req->timer = timer_new_ms(QEMU_CLOCK_VIRTUAL, test_disk_io_callback, disk_req); ++ disk_req->timer = timer_new_ms(QEMU_CLOCK_VIRTUAL, test_disk_latency_callback, disk_req); + + return scsi_req; + } +@@ -395,12 +473,27 @@ static int test_disk_parse_cdb(SCSIDevice* scsi_dev, SCSICommand* cmd, uint8_t* + } + + static void test_disk_realize(SCSIDevice* scsi_dev, Error** errp) { +- TestDisk* disk = DO_UPCAST(TestDisk, parent_scsi_dev, scsi_dev); ++ if (!scsi_dev->conf.blk) { ++ error_setg(errp, "[DISK]: no drive specified"); ++ return; ++ } ++ ++ if (!blkconf_blocksizes(&scsi_dev->conf, errp)) { ++ return; ++ } ++ ++ if (!blkconf_geometry(&scsi_dev->conf, NULL, 65535, 255, 255, errp)) { ++ return; ++ } ++ ++ if (!blkconf_apply_backend_options(&scsi_dev->conf, blk_is_writable(scsi_dev->conf.blk), true, errp)) { ++ return; ++ } + + scsi_dev->type = TYPE_DISK; +- scsi_dev->blocksize = (int)disk->block_size; ++ scsi_dev->blocksize = scsi_dev->conf.logical_block_size; + +- scsi_dev->conf.blk = blk_new(qemu_get_aio_context(), BLK_PERM_ALL, BLK_PERM_ALL); ++ TestDisk* disk = DO_UPCAST(TestDisk, parent_scsi_dev, scsi_dev); + + disk->socket_connect_timer = timer_new_ms(QEMU_CLOCK_REALTIME, test_disk_socket_start_connection, disk); + timer_mod(disk->socket_connect_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 1000); +diff --git a/hw/scsi/test_disk.h b/hw/scsi/test_disk.h +index a2deee5318..0e60ec71e8 100644 +--- a/hw/scsi/test_disk.h ++++ b/hw/scsi/test_disk.h +@@ -15,13 +15,8 @@ struct TestDisk { + uint8_t id; + + QIOChannelSocket* socket; +- +- uint64_t size; +- uint64_t block_size; +- + QEMUTimer* socket_connect_timer; + bool connecting; +- + guint socket_watcher_tag; + }; + +-- +2.53.0 + + +From 18a0a47f7bb96c86980d403ee055b7945c8b6311 Mon Sep 17 00:00:00 2001 +From: Kirill Karpunin +Date: Tue, 24 Feb 2026 20:10:49 +0300 +Subject: [PATCH 23/28] [FIX] Fix for QEMU v9.1.0 + +--- + hw/scsi/test_disk.c | 16 +++++++--------- + 1 file changed, 7 insertions(+), 9 deletions(-) + +diff --git a/hw/scsi/test_disk.c b/hw/scsi/test_disk.c +index 8f6c1a1857..dcddf9f9bb 100644 +--- a/hw/scsi/test_disk.c ++++ b/hw/scsi/test_disk.c +@@ -4,19 +4,17 @@ + #include "test_disk.h" + #include "block/block-common.h" + #include "qapi/error.h" +-#include "qemu/bswap.h" + #include "scsi/constants.h" + #include "contrib/test_disk-server/structs.h" +-#include "system/block-backend-global-state.h" +-#include "system/block-backend-io.h" +-#include "system/cpu-timers.h" +-#include "system/runstate.h" ++#include "sysemu/block-backend.h" ++#include "sysemu/cpu-timers.h" ++#include "sysemu/runstate.h" + + #define MAX_XFER_SIZE (1 * 1024 * 1024) + + + // Device properties +-static const Property test_disk_properties[] = { ++static Property test_disk_properties[] = { + DEFINE_BLOCK_PROPERTIES(TestDisk, parent_scsi_dev.conf), + DEFINE_PROP_UINT8("disk-id", TestDisk, id, 0) + }; +@@ -351,12 +349,12 @@ static void test_disk_bh_routine(void* opaque) { + uint32_t latency_ms = 0; + bool socket_alive = true; + +- if (qio_channel_write_all(QIO_CHANNEL(disk->socket), &server_req, sizeof(server_req), NULL) == -1) { ++ if (qio_channel_write_all(QIO_CHANNEL(disk->socket), (char*) &server_req, sizeof(server_req), NULL) == -1) { + socket_alive = false; + } + + if (socket_alive) { +- if (qio_channel_read_all(QIO_CHANNEL(disk->socket), &server_res, sizeof(server_res), NULL) == -1) { ++ if (qio_channel_read_all(QIO_CHANNEL(disk->socket), (char*) &server_res, sizeof(server_res), NULL) == -1) { + socket_alive = false; + } + +@@ -505,7 +503,7 @@ static void test_disk_realize(SCSIDevice* scsi_dev, Error** errp) { + // ============================================================================ + + +-static void test_disk_class_init(ObjectClass* klass, const void* data) { ++static void test_disk_class_init(ObjectClass* klass, void* data) { + DeviceClass* dc = DEVICE_CLASS(klass); + SCSIDeviceClass* sc = SCSI_DEVICE_CLASS(klass); + +-- +2.53.0 + + +From 0fc0f361873f6d3fe2827733b6933dfab4cee73f Mon Sep 17 00:00:00 2001 +From: Kirill Karpunin +Date: Wed, 25 Feb 2026 22:43:52 +0300 +Subject: [PATCH 24/28] [ADD] Add flag 'latency-stop-vm' + +--- + hw/scsi/test_disk.c | 11 ++++++++--- + hw/scsi/test_disk.h | 2 ++ + 2 files changed, 10 insertions(+), 3 deletions(-) + +diff --git a/hw/scsi/test_disk.c b/hw/scsi/test_disk.c +index dcddf9f9bb..dfb1d5b5d3 100644 +--- a/hw/scsi/test_disk.c ++++ b/hw/scsi/test_disk.c +@@ -16,7 +16,8 @@ + // Device properties + static Property test_disk_properties[] = { + DEFINE_BLOCK_PROPERTIES(TestDisk, parent_scsi_dev.conf), +- DEFINE_PROP_UINT8("disk-id", TestDisk, id, 0) ++ DEFINE_PROP_UINT8("disk-id", TestDisk, id, 0), ++ DEFINE_PROP_BOOL("latency-stop-vm", TestDisk, stop_vm, true) + }; + + +@@ -335,7 +336,9 @@ static void test_disk_bh_routine(void* opaque) { + disk->socket_watcher_tag = 0; + } + +- vm_stop(RUN_STATE_PAUSED); // ii. pause vm ++ if (disk->stop_vm) { ++ vm_stop(RUN_STATE_PAUSED); // ii. pause vm ++ } + + ServerRequest server_req = { + .source = SOURCE_TEST_DISK, +@@ -367,7 +370,9 @@ static void test_disk_bh_routine(void* opaque) { + qio_channel_set_blocking(QIO_CHANNEL(disk->socket), false, NULL); // iii. set socket blocking false + } + +- vm_start(); // ii. start vm ++ if (disk->stop_vm) { ++ vm_start(); // ii. start vm ++ } + + if (socket_alive) { + disk->socket_watcher_tag = qio_channel_add_watch(QIO_CHANNEL(disk->socket), G_IO_IN | G_IO_HUP | G_IO_ERR, test_disk_socket_watch, disk, NULL); // i. add watcher +diff --git a/hw/scsi/test_disk.h b/hw/scsi/test_disk.h +index 0e60ec71e8..c55d30ca92 100644 +--- a/hw/scsi/test_disk.h ++++ b/hw/scsi/test_disk.h +@@ -18,6 +18,8 @@ struct TestDisk { + QEMUTimer* socket_connect_timer; + bool connecting; + guint socket_watcher_tag; ++ ++ bool stop_vm; + }; + + #endif //HW_SCSI_TEST_DISK_H +\ No newline at end of file +-- +2.53.0 + + +From 0f3d88e09d7ba4e448f6aefb0d1e1290779464eb Mon Sep 17 00:00:00 2001 +From: Kirill Karpunin +Date: Thu, 26 Feb 2026 19:03:35 +0300 +Subject: [PATCH 25/28] [ADD] Add logic for using error code received from + server + +--- + .../test_disk-server/change_parameters.cpp | 21 ++++++++++++++++--- + contrib/test_disk-server/change_parameters.py | 12 ++++++++--- + contrib/test_disk-server/server.cpp | 18 ++++++++++++++-- + contrib/test_disk-server/server.py | 14 ++++++++++++- + .../server_toolkit/bindings.cpp | 11 ++++++++-- + .../server_toolkit/server_toolkit.cpp | 12 ++++++++--- + .../server_toolkit/server_toolkit.h | 2 +- + contrib/test_disk-server/structs.h | 10 ++++++++- + hw/scsi/test_disk.c | 13 ++++++++++++ + 9 files changed, 97 insertions(+), 16 deletions(-) + +diff --git a/contrib/test_disk-server/change_parameters.cpp b/contrib/test_disk-server/change_parameters.cpp +index 3cefe8a805..48463c99af 100644 +--- a/contrib/test_disk-server/change_parameters.cpp ++++ b/contrib/test_disk-server/change_parameters.cpp +@@ -4,11 +4,26 @@ + + int main() { + uint32_t read_latency_ms, write_latency_ms; ++ ErrorCode error_code; + +- std::cout << "Read latency: "; ++ std::cout << "Read latency (ms): "; + std::cin >> read_latency_ms; +- std::cout << "Write latency: "; ++ std::cout << "Write latency (ms): "; + std::cin >> write_latency_ms; + +- change_server_parameters("127.0.0.1", 31234, read_latency_ms, write_latency_ms); ++ int t; ++ ++ std::cout << "Sense key (in hex): "; ++ std::cin >> std::hex >> t; ++ error_code.key = static_cast(t); ++ ++ std::cout << "ASC (in hex): "; ++ std::cin >> std::hex >> t; ++ error_code.asc = static_cast(t); ++ ++ std::cout << "ASCQ (in hex): "; ++ std::cin >> std::hex >> t; ++ error_code.ascq = static_cast(t); ++ ++ change_server_parameters("127.0.0.1", 31234, read_latency_ms, write_latency_ms, error_code); + } +\ No newline at end of file +diff --git a/contrib/test_disk-server/change_parameters.py b/contrib/test_disk-server/change_parameters.py +index 57e4256266..0f34e07ea8 100644 +--- a/contrib/test_disk-server/change_parameters.py ++++ b/contrib/test_disk-server/change_parameters.py +@@ -1,7 +1,13 @@ + import server_toolkit_py as st + + if __name__ == "__main__": +- read_latency_ms = int(input("Read latency: ")) +- write_latency_ms = int(input("Write latency: ")) ++ read_latency_ms = int(input("Read latency (ms): ")) ++ write_latency_ms = int(input("Write latency (ms): ")) + +- st.change_server_parameters("127.0.0.1", 31234, read_latency_ms, write_latency_ms) +\ No newline at end of file ++ error_code = st.ErrorCode() ++ ++ error_code.key = int(input("Sense key (in hex): "), 16) ++ error_code.asc = int(input("ASC (in hex): "), 16) ++ error_code.ascq = int(input("ASCQ (in hex): "), 16) ++ ++ st.change_server_parameters("127.0.0.1", 31234, read_latency_ms, write_latency_ms, error_code) +diff --git a/contrib/test_disk-server/server.cpp b/contrib/test_disk-server/server.cpp +index 3f703bd4f0..e61ead5aa4 100644 +--- a/contrib/test_disk-server/server.cpp ++++ b/contrib/test_disk-server/server.cpp +@@ -11,8 +11,12 @@ int main() { + return -1; + } + +- std::atomic read_latency_ms = 34; +- std::atomic write_latency_ms = 7; ++ std::atomic read_latency_ms = 0; ++ std::atomic write_latency_ms = 0; ++ ++ std::atomic sense_key = 0; ++ std::atomic asc = 0; ++ std::atomic ascq = 0; + + run_server(server_fd, [&](const ServerRequest& server_req) { + ServerResponse server_res{}; +@@ -21,6 +25,11 @@ int main() { + case SOURCE_CHANGE_PARAMETERS: + read_latency_ms = server_req.change_parameters_payload.read_latency_ms; + write_latency_ms = server_req.change_parameters_payload.write_latency_ms; ++ ++ sense_key = server_req.change_parameters_payload.error_code.key; ++ asc = server_req.change_parameters_payload.error_code.asc; ++ ascq = server_req.change_parameters_payload.error_code.ascq; ++ + break; + case SOURCE_TEST_DISK: + std::cout << std::format("Command: 0x{:02x}", server_req.test_disk_payload.command) << std::endl; +@@ -29,6 +38,11 @@ int main() { + server_req.test_disk_payload.command == 0x28 || server_req.test_disk_payload.command == 0x08 + ? read_latency_ms + : write_latency_ms; ++ ++ server_res.error_code.key = sense_key; ++ server_res.error_code.asc = asc; ++ server_res.error_code.ascq = ascq; ++ + break; + } + +diff --git a/contrib/test_disk-server/server.py b/contrib/test_disk-server/server.py +index 55997c1fc2..7e7ea48e3f 100644 +--- a/contrib/test_disk-server/server.py ++++ b/contrib/test_disk-server/server.py +@@ -3,8 +3,12 @@ + read_latency_ms = 0 + write_latency_ms = 0 + ++sense_key = 0 ++asc = 0 ++ascq = 0 ++ + def callback(server_req: st.ServerRequest) -> st.ServerResponse: +- global read_latency_ms, write_latency_ms ++ global read_latency_ms, write_latency_ms, sense_key, asc, ascq + + server_res = st.ServerResponse() + +@@ -12,6 +16,10 @@ def callback(server_req: st.ServerRequest) -> st.ServerResponse: + read_latency_ms = server_req.change_parameters_payload.read_latency_ms + write_latency_ms = server_req.change_parameters_payload.write_latency_ms + ++ sense_key = server_req.change_parameters_payload.error_code.key ++ asc = server_req.change_parameters_payload.error_code.asc ++ ascq = server_req.change_parameters_payload.error_code.ascq ++ + elif server_req.source == st.SOURCE_TEST_DISK: + print(f"Command: 0x{server_req.test_disk_payload.command:02X}") + +@@ -19,6 +27,10 @@ def callback(server_req: st.ServerRequest) -> st.ServerResponse: + if server_req.test_disk_payload.command == 0x28 or server_req.test_disk_payload.command == 0x08 \ + else write_latency_ms + ++ server_res.error_code.key = sense_key ++ server_res.error_code.asc = asc ++ server_res.error_code.ascq = ascq ++ + return server_res + + if __name__ == "__main__": +diff --git a/contrib/test_disk-server/server_toolkit/bindings.cpp b/contrib/test_disk-server/server_toolkit/bindings.cpp +index 29e04abc59..0d49cb6bd8 100644 +--- a/contrib/test_disk-server/server_toolkit/bindings.cpp ++++ b/contrib/test_disk-server/server_toolkit/bindings.cpp +@@ -21,10 +21,17 @@ PYBIND11_MODULE(server_toolkit_py, m) { + .def_readwrite("sector", &TestDiskPayload::sector) + .def_readwrite("req_target_len", &TestDiskPayload::req_target_len); + ++ py::class_(m, "ErrorCode") ++ .def(py::init<>()) ++ .def_readwrite("key", &ErrorCode::key) ++ .def_readwrite("asc", &ErrorCode::asc) ++ .def_readwrite("ascq", &ErrorCode::ascq); ++ + py::class_(m, "ChangeParametersPayload") + .def(py::init<>()) + .def_readwrite("read_latency_ms", &ChangeParametersPayload::read_latency_ms) +- .def_readwrite("write_latency_ms", &ChangeParametersPayload::write_latency_ms); ++ .def_readwrite("write_latency_ms", &ChangeParametersPayload::write_latency_ms) ++ .def_readwrite("error_code", &ChangeParametersPayload::error_code); + + py::class_(m, "ServerRequest") + .def(py::init<>()) +@@ -41,5 +48,5 @@ PYBIND11_MODULE(server_toolkit_py, m) { + // Functions + m.def("create_server", &create_server, "ip"_a, "port"_a); + m.def("run_server", &run_server, "server_fd"_a, "callback"_a, py::call_guard()); +- m.def("change_server_parameters", &change_server_parameters, "ip"_a, "port"_a, "read_latency_ms"_a, "write_latency_ms"_a); ++ m.def("change_server_parameters", &change_server_parameters, "ip"_a, "port"_a, "read_latency_ms"_a, "write_latency_ms"_a, "error_code"_a); + } +diff --git a/contrib/test_disk-server/server_toolkit/server_toolkit.cpp b/contrib/test_disk-server/server_toolkit/server_toolkit.cpp +index 74e2822631..b83457a337 100644 +--- a/contrib/test_disk-server/server_toolkit/server_toolkit.cpp ++++ b/contrib/test_disk-server/server_toolkit/server_toolkit.cpp +@@ -96,7 +96,9 @@ void _client_routine(int client_fd, const Callback& callback) { + if (send(client_fd, &server_res, sizeof(server_res), 0) == -1) { + std::cerr << "Unable to send response to client socket: " << strerror(errno) << std::endl; + } else { +- std::cout << "Sent: latency = " << server_res.latency << std::endl; ++ std::cout << "Sent: latency = " << server_res.latency ++ << ", error code = " << std::format("(0x{:02x}|0x{:02x}|0x{:02x})", server_res.error_code.key, server_res.error_code.asc, server_res.error_code.ascq) ++ << std::endl; + } + } + +@@ -107,7 +109,7 @@ void _client_routine(int client_fd, const Callback& callback) { + + // ============================================================================ + +-int change_server_parameters(const std::string& ip, uint16_t port, uint32_t read_latency_ms, uint32_t write_latency_ms) { ++int change_server_parameters(const std::string& ip, uint16_t port, uint32_t read_latency_ms, uint32_t write_latency_ms, ErrorCode error_code) { + // server address + sockaddr_in addr = { + .sin_family = AF_INET, +@@ -139,13 +141,17 @@ int change_server_parameters(const std::string& ip, uint16_t port, uint32_t read + .change_parameters_payload = { + .read_latency_ms = read_latency_ms, + .write_latency_ms = write_latency_ms, ++ .error_code = error_code + } + }; + + if (send(fd, &server_req, sizeof(server_req), 0) == -1) { + std::cerr << "Unable to send request to client socket: " << strerror(errno) << std::endl; + } else { +- std::cout << "Sent: read_latency_ms = " << read_latency_ms << ", write_latency_ms = " << write_latency_ms << std::endl; ++ std::cout << "Sent: read_latency_ms = " << read_latency_ms ++ << ", write_latency_ms = " << write_latency_ms ++ << ", error code = " << std::format("(0x{:02x}|0x{:02x}|0x{:02x})", server_req.change_parameters_payload.error_code.key, server_req.change_parameters_payload.error_code.asc, server_req.change_parameters_payload.error_code.ascq) ++ << std::endl; + } + + close(fd); +diff --git a/contrib/test_disk-server/server_toolkit/server_toolkit.h b/contrib/test_disk-server/server_toolkit/server_toolkit.h +index 04f9cf6952..87010f6d27 100644 +--- a/contrib/test_disk-server/server_toolkit/server_toolkit.h ++++ b/contrib/test_disk-server/server_toolkit/server_toolkit.h +@@ -15,6 +15,6 @@ int run_server(int server_fd, const Callback& callback); + + void _client_routine(int client_fd, const Callback& callback); + +-int change_server_parameters(const std::string& ip, uint16_t port, uint32_t read_latency_ms, uint32_t write_latency_ms); ++int change_server_parameters(const std::string& ip, uint16_t port, uint32_t read_latency_ms, uint32_t write_latency_ms, ErrorCode error_code); + + #endif //SERVER_H +diff --git a/contrib/test_disk-server/structs.h b/contrib/test_disk-server/structs.h +index 14b180cc18..c94905d678 100644 +--- a/contrib/test_disk-server/structs.h ++++ b/contrib/test_disk-server/structs.h +@@ -4,6 +4,12 @@ + #define SOURCE_TEST_DISK 1 + #define SOURCE_CHANGE_PARAMETERS 2 + ++typedef struct ErrorCode { ++ uint8_t key; ++ uint8_t asc; ++ uint8_t ascq; ++} ErrorCode; ++ + typedef struct TestDiskPayload { + uint8_t disk_id; + uint8_t command; +@@ -14,6 +20,8 @@ typedef struct TestDiskPayload { + typedef struct ChangeParametersPayload { + uint32_t read_latency_ms; + uint32_t write_latency_ms; ++ ++ ErrorCode error_code; + } ChangeParametersPayload; + + typedef struct ServerRequest { +@@ -23,7 +31,7 @@ typedef struct ServerRequest { + } ServerRequest; + + typedef struct ServerResponse { +- uint8_t error_code; ++ ErrorCode error_code; + uint32_t latency; + } ServerResponse; + +diff --git a/hw/scsi/test_disk.c b/hw/scsi/test_disk.c +index dfb1d5b5d3..92d731683a 100644 +--- a/hw/scsi/test_disk.c ++++ b/hw/scsi/test_disk.c +@@ -6,6 +6,7 @@ + #include "qapi/error.h" + #include "scsi/constants.h" + #include "contrib/test_disk-server/structs.h" ++#include "qapi/qapi-types-common.h" + #include "sysemu/block-backend.h" + #include "sysemu/cpu-timers.h" + #include "sysemu/runstate.h" +@@ -31,6 +32,8 @@ typedef struct TestDiskReq { + + struct iovec iov; + QEMUIOVector qiov; ++ ++ SCSISense current_error_code; + } TestDiskReq; + + +@@ -395,6 +398,10 @@ static void test_disk_bh_routine(void* opaque) { + + disk_req->response_buf_len = args->req_target_len; + ++ disk_req->current_error_code.key = server_res.error_code.key; ++ disk_req->current_error_code.asc = server_res.error_code.asc; ++ disk_req->current_error_code.ascq = server_res.error_code.ascq; ++ + timer_mod(disk_req->timer, qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + latency_ms); + + scsi_req_unref(scsi_req); +@@ -405,6 +412,12 @@ static void test_disk_latency_callback(void* opaque) { + TestDiskReq* disk_req = opaque; + SCSIRequest* scsi_req = &disk_req->parent_scsi_req; + ++ if ((disk_req->current_error_code.key | disk_req->current_error_code.asc | disk_req->current_error_code.ascq) != 0) { ++ scsi_req_build_sense(scsi_req, disk_req->current_error_code); ++ scsi_req_complete(scsi_req, CHECK_CONDITION); ++ return; ++ } ++ + TestDisk* disk = DO_UPCAST(TestDisk, parent_scsi_dev, disk_req->parent_scsi_req.dev); + BlockBackend *blk = disk->parent_scsi_dev.conf.blk; + +-- +2.53.0 + + +From a20f7707ac3d24e92b5bb1a558e666d12e42ef7b Mon Sep 17 00:00:00 2001 +From: Kirill Karpunin +Date: Fri, 6 Mar 2026 21:30:03 +0300 +Subject: [PATCH 26/28] [FIX] Fix max xfer size + +--- + hw/scsi/test_disk.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/hw/scsi/test_disk.c b/hw/scsi/test_disk.c +index 92d731683a..4adcbcc8ce 100644 +--- a/hw/scsi/test_disk.c ++++ b/hw/scsi/test_disk.c +@@ -11,7 +11,7 @@ + #include "sysemu/cpu-timers.h" + #include "sysemu/runstate.h" + +-#define MAX_XFER_SIZE (1 * 1024 * 1024) ++#define MAX_XFER_SIZE (32 * 1024 * 1024) + + + // Device properties +-- +2.53.0 + + +From b94da7ad1f1253b846a13026c94d644286063d0a Mon Sep 17 00:00:00 2001 +From: Kirill Karpunin +Date: Fri, 6 Mar 2026 22:32:22 +0300 +Subject: [PATCH 27/28] [ADD] Add "is singleshot" parameter + +--- + .../test_disk-server/server_toolkit/bindings.cpp | 3 ++- + .../server_toolkit/server_toolkit.cpp | 14 +++++++++++--- + .../server_toolkit/server_toolkit.h | 2 +- + contrib/test_disk-server/structs.h | 2 ++ + 4 files changed, 16 insertions(+), 5 deletions(-) + +diff --git a/contrib/test_disk-server/server_toolkit/bindings.cpp b/contrib/test_disk-server/server_toolkit/bindings.cpp +index 0d49cb6bd8..90137878f1 100644 +--- a/contrib/test_disk-server/server_toolkit/bindings.cpp ++++ b/contrib/test_disk-server/server_toolkit/bindings.cpp +@@ -31,6 +31,7 @@ PYBIND11_MODULE(server_toolkit_py, m) { + .def(py::init<>()) + .def_readwrite("read_latency_ms", &ChangeParametersPayload::read_latency_ms) + .def_readwrite("write_latency_ms", &ChangeParametersPayload::write_latency_ms) ++ .def_readwrite("is_singleshot", &ChangeParametersPayload::is_singleshot) + .def_readwrite("error_code", &ChangeParametersPayload::error_code); + + py::class_(m, "ServerRequest") +@@ -48,5 +49,5 @@ PYBIND11_MODULE(server_toolkit_py, m) { + // Functions + m.def("create_server", &create_server, "ip"_a, "port"_a); + m.def("run_server", &run_server, "server_fd"_a, "callback"_a, py::call_guard()); +- m.def("change_server_parameters", &change_server_parameters, "ip"_a, "port"_a, "read_latency_ms"_a, "write_latency_ms"_a, "error_code"_a); ++ m.def("change_server_parameters", &change_server_parameters, "ip"_a, "port"_a, "read_latency_ms"_a, "write_latency_ms"_a, "error_code"_a, "is_singleshot"_a = false); + } +diff --git a/contrib/test_disk-server/server_toolkit/server_toolkit.cpp b/contrib/test_disk-server/server_toolkit/server_toolkit.cpp +index b83457a337..8ee18a283c 100644 +--- a/contrib/test_disk-server/server_toolkit/server_toolkit.cpp ++++ b/contrib/test_disk-server/server_toolkit/server_toolkit.cpp +@@ -95,10 +95,16 @@ void _client_routine(int client_fd, const Callback& callback) { + + if (send(client_fd, &server_res, sizeof(server_res), 0) == -1) { + std::cerr << "Unable to send response to client socket: " << strerror(errno) << std::endl; +- } else { ++ } else if (server_req.source == SOURCE_TEST_DISK) { + std::cout << "Sent: latency = " << server_res.latency + << ", error code = " << std::format("(0x{:02x}|0x{:02x}|0x{:02x})", server_res.error_code.key, server_res.error_code.asc, server_res.error_code.ascq) + << std::endl; ++ } else if (server_req.source == SOURCE_CHANGE_PARAMETERS) { ++ std::cout << "Parameters changed: read_latency_ms = " << server_req.change_parameters_payload.read_latency_ms ++ << ", write_latency_ms = " << server_req.change_parameters_payload.write_latency_ms ++ << ", error code = " << std::format("(0x{:02x}|0x{:02x}|0x{:02x})", server_req.change_parameters_payload.error_code.key, server_req.change_parameters_payload.error_code.asc, server_req.change_parameters_payload.error_code.ascq) ++ << ", is singleshot = " << static_cast(server_req.change_parameters_payload.is_singleshot) ++ << std::endl; + } + } + +@@ -109,7 +115,7 @@ void _client_routine(int client_fd, const Callback& callback) { + + // ============================================================================ + +-int change_server_parameters(const std::string& ip, uint16_t port, uint32_t read_latency_ms, uint32_t write_latency_ms, ErrorCode error_code) { ++int change_server_parameters(const std::string& ip, uint16_t port, uint32_t read_latency_ms, uint32_t write_latency_ms, ErrorCode error_code, bool is_singleshot) { + // server address + sockaddr_in addr = { + .sin_family = AF_INET, +@@ -141,6 +147,7 @@ int change_server_parameters(const std::string& ip, uint16_t port, uint32_t read + .change_parameters_payload = { + .read_latency_ms = read_latency_ms, + .write_latency_ms = write_latency_ms, ++ .is_singleshot = is_singleshot, + .error_code = error_code + } + }; +@@ -150,7 +157,8 @@ int change_server_parameters(const std::string& ip, uint16_t port, uint32_t read + } else { + std::cout << "Sent: read_latency_ms = " << read_latency_ms + << ", write_latency_ms = " << write_latency_ms +- << ", error code = " << std::format("(0x{:02x}|0x{:02x}|0x{:02x})", server_req.change_parameters_payload.error_code.key, server_req.change_parameters_payload.error_code.asc, server_req.change_parameters_payload.error_code.ascq) ++ << ", error code = " << std::format("(0x{:02x}|0x{:02x}|0x{:02x})", error_code.key, error_code.asc, error_code.ascq) ++ << ", is singleshot = " << static_cast(is_singleshot) + << std::endl; + } + +diff --git a/contrib/test_disk-server/server_toolkit/server_toolkit.h b/contrib/test_disk-server/server_toolkit/server_toolkit.h +index 87010f6d27..d177998c38 100644 +--- a/contrib/test_disk-server/server_toolkit/server_toolkit.h ++++ b/contrib/test_disk-server/server_toolkit/server_toolkit.h +@@ -15,6 +15,6 @@ int run_server(int server_fd, const Callback& callback); + + void _client_routine(int client_fd, const Callback& callback); + +-int change_server_parameters(const std::string& ip, uint16_t port, uint32_t read_latency_ms, uint32_t write_latency_ms, ErrorCode error_code); ++int change_server_parameters(const std::string& ip, uint16_t port, uint32_t read_latency_ms, uint32_t write_latency_ms, ErrorCode error_code, bool is_singleshot = false); + + #endif //SERVER_H +diff --git a/contrib/test_disk-server/structs.h b/contrib/test_disk-server/structs.h +index c94905d678..4283505673 100644 +--- a/contrib/test_disk-server/structs.h ++++ b/contrib/test_disk-server/structs.h +@@ -21,6 +21,8 @@ typedef struct ChangeParametersPayload { + uint32_t read_latency_ms; + uint32_t write_latency_ms; + ++ bool is_singleshot; ++ + ErrorCode error_code; + } ChangeParametersPayload; + +-- +2.53.0 + + +From 6ecb9d7e8c69a7eb56b1ba0ad33e782943ac3c5c Mon Sep 17 00:00:00 2001 +From: Kirill Karpunin +Date: Fri, 6 Mar 2026 22:45:27 +0300 +Subject: [PATCH 28/28] [UPD] Update tools + +--- + .../test_disk-server/change_parameters.cpp | 7 +++++- + contrib/test_disk-server/change_parameters.py | 4 +++- + contrib/test_disk-server/server.cpp | 22 +++++++++++++++++++ + contrib/test_disk-server/server.py | 22 ++++++++++++++++++- + 4 files changed, 52 insertions(+), 3 deletions(-) + +diff --git a/contrib/test_disk-server/change_parameters.cpp b/contrib/test_disk-server/change_parameters.cpp +index 48463c99af..e27c75b81c 100644 +--- a/contrib/test_disk-server/change_parameters.cpp ++++ b/contrib/test_disk-server/change_parameters.cpp +@@ -25,5 +25,10 @@ int main() { + std::cin >> std::hex >> t; + error_code.ascq = static_cast(t); + +- change_server_parameters("127.0.0.1", 31234, read_latency_ms, write_latency_ms, error_code); ++ bool is_singleshot; ++ ++ std::cout << "Is singleshot (0 - false, 1 - true): "; ++ std::cin >> is_singleshot; ++ ++ change_server_parameters("127.0.0.1", 31234, read_latency_ms, write_latency_ms, error_code, is_singleshot); + } +\ No newline at end of file +diff --git a/contrib/test_disk-server/change_parameters.py b/contrib/test_disk-server/change_parameters.py +index 0f34e07ea8..ffe5736745 100644 +--- a/contrib/test_disk-server/change_parameters.py ++++ b/contrib/test_disk-server/change_parameters.py +@@ -10,4 +10,6 @@ + error_code.asc = int(input("ASC (in hex): "), 16) + error_code.ascq = int(input("ASCQ (in hex): "), 16) + +- st.change_server_parameters("127.0.0.1", 31234, read_latency_ms, write_latency_ms, error_code) ++ is_singleshot = bool(input("Is singleshot (0 - false, 1 - true): ")) ++ ++ st.change_server_parameters("127.0.0.1", 31234, read_latency_ms, write_latency_ms, error_code, is_singleshot) +diff --git a/contrib/test_disk-server/server.cpp b/contrib/test_disk-server/server.cpp +index e61ead5aa4..8496b77521 100644 +--- a/contrib/test_disk-server/server.cpp ++++ b/contrib/test_disk-server/server.cpp +@@ -18,6 +18,12 @@ int main() { + std::atomic asc = 0; + std::atomic ascq = 0; + ++ std::atomic is_singleshot = false; ++ ++ std::atomic prev_sense_key = 0; ++ std::atomic prev_asc = 0; ++ std::atomic prev_ascq = 0; ++ + run_server(server_fd, [&](const ServerRequest& server_req) { + ServerResponse server_res{}; + +@@ -26,6 +32,14 @@ int main() { + read_latency_ms = server_req.change_parameters_payload.read_latency_ms; + write_latency_ms = server_req.change_parameters_payload.write_latency_ms; + ++ if (server_req.change_parameters_payload.is_singleshot) { ++ is_singleshot = true; ++ ++ prev_sense_key = sense_key.load(); ++ prev_asc = asc.load(); ++ prev_ascq = ascq.load(); ++ } ++ + sense_key = server_req.change_parameters_payload.error_code.key; + asc = server_req.change_parameters_payload.error_code.asc; + ascq = server_req.change_parameters_payload.error_code.ascq; +@@ -43,6 +57,14 @@ int main() { + server_res.error_code.asc = asc; + server_res.error_code.ascq = ascq; + ++ if (is_singleshot) { ++ is_singleshot = false; ++ ++ sense_key = prev_sense_key.load(); ++ asc = prev_asc.load(); ++ ascq = prev_ascq.load(); ++ } ++ + break; + } + +diff --git a/contrib/test_disk-server/server.py b/contrib/test_disk-server/server.py +index 7e7ea48e3f..be3d19676b 100644 +--- a/contrib/test_disk-server/server.py ++++ b/contrib/test_disk-server/server.py +@@ -7,8 +7,14 @@ + asc = 0 + ascq = 0 + ++is_singleshot = False ++ ++prev_sense_key = 0 ++prev_asc = 0 ++prev_ascq = 0 ++ + def callback(server_req: st.ServerRequest) -> st.ServerResponse: +- global read_latency_ms, write_latency_ms, sense_key, asc, ascq ++ global read_latency_ms, write_latency_ms, sense_key, asc, ascq, is_singleshot, prev_sense_key, prev_asc, prev_ascq + + server_res = st.ServerResponse() + +@@ -16,6 +22,13 @@ def callback(server_req: st.ServerRequest) -> st.ServerResponse: + read_latency_ms = server_req.change_parameters_payload.read_latency_ms + write_latency_ms = server_req.change_parameters_payload.write_latency_ms + ++ if server_req.change_parameters_payload.is_singleshot: ++ is_singleshot = True ++ ++ prev_sense_key = sense_key ++ prev_asc = asc ++ prev_ascq = ascq ++ + sense_key = server_req.change_parameters_payload.error_code.key + asc = server_req.change_parameters_payload.error_code.asc + ascq = server_req.change_parameters_payload.error_code.ascq +@@ -31,6 +44,13 @@ def callback(server_req: st.ServerRequest) -> st.ServerResponse: + server_res.error_code.asc = asc + server_res.error_code.ascq = ascq + ++ if is_singleshot: ++ is_singleshot = False ++ ++ sense_key = prev_sense_key ++ asc = prev_asc ++ ascq = prev_ascq ++ + return server_res + + if __name__ == "__main__": +-- +2.53.0 + diff --git a/layers/meta-image-tests/recipes-devtools/qemu/qemu/0001-qemu-Add-addition-environment-space-to-boot-loader-q.patch b/layers/meta-image-tests/recipes-devtools/qemu/qemu/0001-qemu-Add-addition-environment-space-to-boot-loader-q.patch new file mode 100644 index 00000000..5e21855c --- /dev/null +++ b/layers/meta-image-tests/recipes-devtools/qemu/qemu/0001-qemu-Add-addition-environment-space-to-boot-loader-q.patch @@ -0,0 +1,32 @@ +From 930b75aeade33690cec4bf748a954401da2be782 Mon Sep 17 00:00:00 2001 +From: Jason Wessel +Date: Fri, 28 Mar 2014 17:42:43 +0800 +Subject: [PATCH] qemu: Add addition environment space to boot loader + qemu-system-mips + +Upstream-Status: Inappropriate - OE uses deep paths + +If you create a project with very long directory names like 128 characters +deep and use NFS, the kernel arguments will be truncated. The kernel will +accept longer strings such as 1024 bytes, but the qemu boot loader defaulted +to only 256 bytes. This patch expands the limit. + +Signed-off-by: Jason Wessel +Signed-off-by: Roy Li +--- + hw/mips/malta.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/hw/mips/malta.c b/hw/mips/malta.c +index 664a2ae0a..5124b158a 100644 +--- a/hw/mips/malta.c ++++ b/hw/mips/malta.c +@@ -64,7 +64,7 @@ + #define ENVP_PADDR 0x2000 + #define ENVP_VADDR cpu_mips_phys_to_kseg0(NULL, ENVP_PADDR) + #define ENVP_NB_ENTRIES 16 +-#define ENVP_ENTRY_SIZE 256 ++#define ENVP_ENTRY_SIZE 1024 + + /* Hardware addresses */ + #define FLASH_ADDRESS 0x1e000000ULL diff --git a/layers/meta-image-tests/recipes-devtools/qemu/qemu/0001-sched_attr-Do-not-define-for-glibc-2.41.patch b/layers/meta-image-tests/recipes-devtools/qemu/qemu/0001-sched_attr-Do-not-define-for-glibc-2.41.patch new file mode 100644 index 00000000..1233a07f --- /dev/null +++ b/layers/meta-image-tests/recipes-devtools/qemu/qemu/0001-sched_attr-Do-not-define-for-glibc-2.41.patch @@ -0,0 +1,45 @@ +From 7030c0cd98de9e6c42e69be314204727ae335912 Mon Sep 17 00:00:00 2001 +From: Khem Raj +Date: Thu, 10 Oct 2024 22:40:32 -0700 +Subject: [PATCH] sched_attr: Do not define for glibc >= 2.41 + +glibc 2.41+ has added [1] definitions for sched_setattr and sched_getattr functions +and struct sched_attr. Therefore, it needs to be checked for here as well before +defining sched_attr + +Define sched_attr conditionally on SCHED_ATTR_SIZE_VER0 + +Fixes builds with glibc/trunk + +[1] https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=21571ca0d70302909cf72707b2a7736cf12190a0;hp=298bc488fdc047da37482f4003023cb9adef78f8 + +Upstream-Status: Submitted [https://patchwork.ozlabs.org/project/qemu-devel/patch/20241011193140.1047648-1-raj.khem@gmail.com/] +Signed-off-by: Khem Raj +Cc: Laurent Vivier +Cc: Paolo Bonzini +--- + linux-user/syscall.c | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/linux-user/syscall.c b/linux-user/syscall.c +index 27aa59594..b2c45b4c4 100644 +--- a/linux-user/syscall.c ++++ b/linux-user/syscall.c +@@ -361,7 +361,8 @@ _syscall3(int, sys_sched_getaffinity, pid_t, pid, unsigned int, len, + #define __NR_sys_sched_setaffinity __NR_sched_setaffinity + _syscall3(int, sys_sched_setaffinity, pid_t, pid, unsigned int, len, + unsigned long *, user_mask_ptr); +-/* sched_attr is not defined in glibc */ ++/* sched_attr is not defined in glibc < 2.41 */ ++#ifndef SCHED_ATTR_SIZE_VER0 + struct sched_attr { + uint32_t size; + uint32_t sched_policy; +@@ -374,6 +375,7 @@ struct sched_attr { + uint32_t sched_util_min; + uint32_t sched_util_max; + }; ++#endif + #define __NR_sys_sched_getattr __NR_sched_getattr + _syscall4(int, sys_sched_getattr, pid_t, pid, struct sched_attr *, attr, + unsigned int, size, unsigned int, flags); diff --git a/layers/meta-image-tests/recipes-devtools/qemu/qemu/0002-apic-fixup-fallthrough-to-PIC.patch b/layers/meta-image-tests/recipes-devtools/qemu/qemu/0002-apic-fixup-fallthrough-to-PIC.patch new file mode 100644 index 00000000..bcb0d0c0 --- /dev/null +++ b/layers/meta-image-tests/recipes-devtools/qemu/qemu/0002-apic-fixup-fallthrough-to-PIC.patch @@ -0,0 +1,43 @@ +From 1ba58dbd2dce4e6ccaf3be3ad5c9ecc22565313f Mon Sep 17 00:00:00 2001 +From: Mark Asselstine +Date: Tue, 26 Feb 2013 11:43:28 -0500 +Subject: [PATCH] apic: fixup fallthrough to PIC + +Commit 0e21e12bb311c4c1095d0269dc2ef81196ccb60a [Don't route PIC +interrupts through the local APIC if the local APIC config says so.] +missed a check to ensure the local APIC is enabled. Since if the local +APIC is disabled it doesn't matter what the local APIC config says. + +If this check isn't done and the guest has disabled the local APIC the +guest will receive a general protection fault, similar to what is seen +here: + +https://lists.gnu.org/archive/html/qemu-devel/2012-12/msg02304.html + +The GPF is caused by an attempt to service interrupt 0xffffffff. This +comes about since cpu_get_pic_interrupt() calls apic_accept_pic_intr() +(with the local APIC disabled apic_get_interrupt() returns -1). +apic_accept_pic_intr() returns 0 and thus the interrupt number which +is returned from cpu_get_pic_interrupt(), and which is attempted to be +serviced, is -1. + +Signed-off-by: Mark Asselstine +Upstream-Status: Submitted [https://lists.gnu.org/archive/html/qemu-devel/2013-04/msg00878.html] +Signed-off-by: He Zhe +--- + hw/intc/apic.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/hw/intc/apic.c b/hw/intc/apic.c +index 4186c57b3..43cd805a9 100644 +--- a/hw/intc/apic.c ++++ b/hw/intc/apic.c +@@ -759,7 +759,7 @@ int apic_accept_pic_intr(DeviceState *dev) + APICCommonState *s = APIC(dev); + uint32_t lvt0; + +- if (!s) ++ if (!s || !(s->spurious_vec & APIC_SV_ENABLE)) + return -1; + + lvt0 = s->lvt[APIC_LVT_LINT0]; diff --git a/layers/meta-image-tests/recipes-devtools/qemu/qemu/0004-qemu-Do-not-include-file-if-not-exists.patch b/layers/meta-image-tests/recipes-devtools/qemu/qemu/0004-qemu-Do-not-include-file-if-not-exists.patch new file mode 100644 index 00000000..2b4f2b17 --- /dev/null +++ b/layers/meta-image-tests/recipes-devtools/qemu/qemu/0004-qemu-Do-not-include-file-if-not-exists.patch @@ -0,0 +1,31 @@ +From 081c1e4ee93b22ec00136db45c36650427213c09 Mon Sep 17 00:00:00 2001 +From: Oleksiy Obitotskyy +Date: Wed, 25 Mar 2020 21:21:35 +0200 +Subject: [PATCH] qemu: Do not include file if not exists + +Script configure checks for if_alg.h and check failed but +if_alg.h still included. + +Upstream-Status: Submitted [https://lists.gnu.org/archive/html/qemu-devel/2020-03/msg07188.html] +Signed-off-by: Oleksiy Obitotskyy + +[update patch context] +Signed-off-by: Sakib Sajal +--- + linux-user/syscall.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/linux-user/syscall.c b/linux-user/syscall.c +index 9d5415674..22df46859 100644 +--- a/linux-user/syscall.c ++++ b/linux-user/syscall.c +@@ -117,7 +117,9 @@ + #include + #include + #include ++#if defined(CONFIG_AF_ALG) + #include ++#endif + #include + #include + #ifdef HAVE_BTRFS_H diff --git a/layers/meta-image-tests/recipes-devtools/qemu/qemu/0005-qemu-Add-some-user-space-mmap-tweaks-to-address-musl.patch b/layers/meta-image-tests/recipes-devtools/qemu/qemu/0005-qemu-Add-some-user-space-mmap-tweaks-to-address-musl.patch new file mode 100644 index 00000000..26ddf5e5 --- /dev/null +++ b/layers/meta-image-tests/recipes-devtools/qemu/qemu/0005-qemu-Add-some-user-space-mmap-tweaks-to-address-musl.patch @@ -0,0 +1,47 @@ +From 76efeccfd73a76834ac1c75d3c94de1899085714 Mon Sep 17 00:00:00 2001 +From: Richard Purdie +Date: Fri, 8 Jan 2021 17:27:06 +0000 +Subject: [PATCH] qemu: Add some user space mmap tweaks to address musl 32 bit + +When using qemu-i386 to build qemux86 webkitgtk on musl, it sits in an +infinite loop of mremap calls of ever decreasing/increasing addresses. + +I suspect something in the musl memory allocation code loops indefinitely +if it only sees ENOMEM and only exits when it hits EFAULT. + +According to the docs, trying to mremap outside the address space +can/should return EFAULT and changing this allows the build to succeed. + +A better return value for the other cases of invalid addresses is EINVAL +rather than ENOMEM so adjust the other part of the test to this. + +Upstream-Status: Submitted [https://lists.gnu.org/archive/html/qemu-devel/2021-01/msg01355.html] +Signed-off-by: Richard Purdie +Date: Mon, 1 Mar 2021 13:00:47 +0000 +Subject: [PATCH] qemu: Determinism fixes + +When sources are included within debug information, a couple of areas of the +qemu build are not reproducible due to either full buildpaths or timestamps. + +Replace the full paths with relative ones. I couldn't figure out how to get +meson to pass relative paths but we can fix that in the script. + +Upstream-Status: Pending [some version of all/part of this may be accepted] +RP 2021/3/1 +--- + scripts/decodetree.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/scripts/decodetree.py b/scripts/decodetree.py +index e8b72da3a..5cd86b142 100644 +--- a/scripts/decodetree.py ++++ b/scripts/decodetree.py +@@ -1558,7 +1558,7 @@ def main(): + toppat = ExcMultiPattern(0) + + for filename in args: +- input_file = filename ++ input_file = os.path.relpath(filename) + f = open(filename, 'rt', encoding='utf-8') + parse_file(f, toppat) + f.close() diff --git a/layers/meta-image-tests/recipes-devtools/qemu/qemu/0007-tests-meson.build-use-relative-path-to-refer-to-file.patch b/layers/meta-image-tests/recipes-devtools/qemu/qemu/0007-tests-meson.build-use-relative-path-to-refer-to-file.patch new file mode 100644 index 00000000..cd919048 --- /dev/null +++ b/layers/meta-image-tests/recipes-devtools/qemu/qemu/0007-tests-meson.build-use-relative-path-to-refer-to-file.patch @@ -0,0 +1,43 @@ +From 1a574aa22c9b3700af7e5c0901abef59c72bb874 Mon Sep 17 00:00:00 2001 +From: Changqing Li +Date: Thu, 14 Jan 2021 06:33:04 +0000 +Subject: [PATCH] tests/meson.build: use relative path to refer to files + +Fix error like: +Fatal error: can't create tests/ptimer-test.p/..._qemu-5.2.0_hw_core_ptimer.c.o: File name too long + +when build path is too long, use meson.source_root() will make this +filename too long. Fixed by using relative path to refer to files + +Upstream-Status: Submitted [send to qemu-devel] + +Signed-off-by: Changqing Li +--- + tests/unit/meson.build | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/tests/unit/meson.build b/tests/unit/meson.build +index 490ab8182..d19ee5606 100644 +--- a/tests/unit/meson.build ++++ b/tests/unit/meson.build +@@ -128,17 +128,17 @@ endif + + if have_system + tests += { +- 'ptimer-test': ['ptimer-test-stubs.c', meson.project_source_root() / 'hw/core/ptimer.c'], ++ 'ptimer-test': ['ptimer-test-stubs.c', '../../hw/core/ptimer.c'], + 'test-iov': [], + 'test-opts-visitor': [testqapi], + 'test-xs-node': [qom], +- 'test-virtio-dmabuf': [meson.project_source_root() / 'hw/display/virtio-dmabuf.c'], ++ 'test-virtio-dmabuf': ['../../hw/display/virtio-dmabuf.c'], + 'test-qmp-cmds': [testqapi], + 'test-xbzrle': [migration], + 'test-util-sockets': ['socket-helpers.c'], + 'test-base64': [], + 'test-bufferiszero': [], +- 'test-smp-parse': [qom, meson.project_source_root() / 'hw/core/machine-smp.c'], ++ 'test-smp-parse': [qom, '../../hw/core/machine-smp.c'], + 'test-vmstate': [migration, io], + 'test-yank': ['socket-helpers.c', qom, io, chardev] + } diff --git a/layers/meta-image-tests/recipes-devtools/qemu/qemu/0008-Define-MAP_SYNC-and-MAP_SHARED_VALIDATE-on-needed-li.patch b/layers/meta-image-tests/recipes-devtools/qemu/qemu/0008-Define-MAP_SYNC-and-MAP_SHARED_VALIDATE-on-needed-li.patch new file mode 100644 index 00000000..7bbd96df --- /dev/null +++ b/layers/meta-image-tests/recipes-devtools/qemu/qemu/0008-Define-MAP_SYNC-and-MAP_SHARED_VALIDATE-on-needed-li.patch @@ -0,0 +1,45 @@ +From 06b5b26a0a1f7687576d90066e492cf2665fff1d Mon Sep 17 00:00:00 2001 +From: Khem Raj +Date: Mon, 21 Mar 2022 10:09:38 -0700 +Subject: [PATCH] Define MAP_SYNC and MAP_SHARED_VALIDATE on needed linux + systems + +linux only wires MAP_SYNC and MAP_SHARED_VALIDATE for architectures +which include asm-generic/mman.h and mips/powerpc are not including this +file in linux/mman.h, therefore these should be defined for such +architectures on Linux as well. This fixes build on mips/musl/linux + +Upstream-Status: Submitted [https://lists.nongnu.org/archive/html/qemu-devel/2022-03/msg05298.html] +Signed-off-by: Khem Raj +Cc: Zhang Yi +Cc: Michael S. Tsirkin +--- + util/mmap-alloc.c | 10 +++++++--- + 1 file changed, 7 insertions(+), 3 deletions(-) + +diff --git a/util/mmap-alloc.c b/util/mmap-alloc.c +index ed14f9c64..038f5b4b5 100644 +--- a/util/mmap-alloc.c ++++ b/util/mmap-alloc.c +@@ -10,14 +10,18 @@ + * later. See the COPYING file in the top-level directory. + */ + ++#include "qemu/osdep.h" + #ifdef CONFIG_LINUX + #include +-#else /* !CONFIG_LINUX */ ++#endif /* CONFIG_LINUX */ ++ ++#ifndef MAP_SYNC + #define MAP_SYNC 0x0 ++#endif /* MAP_SYNC */ ++#ifndef MAP_SHARED_VALIDATE + #define MAP_SHARED_VALIDATE 0x0 +-#endif /* CONFIG_LINUX */ ++#endif /* MAP_SHARED_VALIDATE */ + +-#include "qemu/osdep.h" + #include "qemu/mmap-alloc.h" + #include "qemu/host-utils.h" + #include "qemu/cutils.h" diff --git a/layers/meta-image-tests/recipes-devtools/qemu/qemu/0010-configure-lookup-meson-exutable-from-PATH.patch b/layers/meta-image-tests/recipes-devtools/qemu/qemu/0010-configure-lookup-meson-exutable-from-PATH.patch new file mode 100644 index 00000000..f20851bf --- /dev/null +++ b/layers/meta-image-tests/recipes-devtools/qemu/qemu/0010-configure-lookup-meson-exutable-from-PATH.patch @@ -0,0 +1,28 @@ +From 2e9642b9001df523628ab2cacbbbf2b208c49437 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Martin=20Hundeb=C3=B8ll?= +Date: Wed, 22 May 2024 14:02:55 +0200 +Subject: [PATCH] configure: lookup meson exutable from PATH + +Upstream-Status: Inappropriate [workaround, would need a real fix for upstream] +--- + configure | 7 +------ + 1 file changed, 1 insertion(+), 6 deletions(-) + +diff --git a/configure b/configure +index d08b71f14..af0bd2c66 100755 +--- a/configure ++++ b/configure +@@ -958,12 +958,7 @@ mkvenv="$python ${source_path}/python/scripts/mkvenv.py" + $mkvenv ensuregroup --dir "${source_path}/python/wheels" \ + ${source_path}/pythondeps.toml meson || exit 1 + +-# At this point, we expect Meson to be installed and available. +-# We expect mkvenv or pip to have created pyvenv/bin/meson for us. +-# We ignore PATH completely here: we want to use the venv's Meson +-# *exclusively*. +- +-meson="$(cd pyvenv/bin; pwd)/meson" ++meson=`which meson` + + # Conditionally ensure Sphinx is installed. + diff --git a/layers/meta-image-tests/recipes-devtools/qemu/qemu/0011-qemu-Ensure-pip-and-the-python-venv-aren-t-used-for-.patch b/layers/meta-image-tests/recipes-devtools/qemu/qemu/0011-qemu-Ensure-pip-and-the-python-venv-aren-t-used-for-.patch new file mode 100644 index 00000000..4adb2e5f --- /dev/null +++ b/layers/meta-image-tests/recipes-devtools/qemu/qemu/0011-qemu-Ensure-pip-and-the-python-venv-aren-t-used-for-.patch @@ -0,0 +1,51 @@ +From 68a13afd6606c2aaf1f0debd325d7314aaa61411 Mon Sep 17 00:00:00 2001 +From: Richard Purdie +Date: Wed, 22 May 2024 13:58:23 +0200 +Subject: [PATCH] qemu: Ensure pip and the python venv aren't used for meson + +Qemu wants to use a supported python version and a specific meson version +to "help" users and uses pip and creates a venv to do this. This is a nightmare +for us. Our versions stay up to date and should be supported so we don't +really need/want this wrapping. Tweak things to disable it. + +There was breakage from the wrapper shown by: + +bitbake qemu-system-native + +bitbake qemu-system-native -c configure + +which would crash. The issue is the change in configuration removes pieces +from the sysroot but pyc files remainm as do pieces of pip which causes +problems. + +Ideally we'd convince upstream to allow some way to disable the venv on +the understanding that if/when it breaks, we keep the pieces. The patch +as it stands is a workaround. + +Upstream-Status: Inappropriate [oe specific] +Signed-off-by: Richard Purdie +--- + configure | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/configure b/configure +index af0bd2c66..88dab6a95 100755 +--- a/configure ++++ b/configure +@@ -944,14 +944,14 @@ python="$(command -v "$python")" + echo "python determined to be '$python'" + echo "python version: $($python --version)" + +-python="$($python -B "${source_path}/python/scripts/mkvenv.py" create pyvenv)" ++python=python3 + if test "$?" -ne 0 ; then + error_exit "python venv creation failed" + fi + + # Suppress writing compiled files + python="$python -B" +-mkvenv="$python ${source_path}/python/scripts/mkvenv.py" ++mkvenv=true + + # Finish preparing the virtual environment using vendored .whl files + diff --git a/layers/meta-image-tests/recipes-devtools/qemu/qemu/fix-strerrorname_np.patch b/layers/meta-image-tests/recipes-devtools/qemu/qemu/fix-strerrorname_np.patch new file mode 100644 index 00000000..b57c4811 --- /dev/null +++ b/layers/meta-image-tests/recipes-devtools/qemu/qemu/fix-strerrorname_np.patch @@ -0,0 +1,35 @@ +From 9de5de85fa917af58e7bc9cdd88f8f7ef8da2ca9 Mon Sep 17 00:00:00 2001 +From: Natanael Copa +Date: Wed, 18 Sep 2024 16:19:37 -0700 +Subject: [PATCH] target/riscv/kvm: do not use non-portable strerrorname_np() + +strerrorname_np is non-portable and breaks building with musl libc. + +Use strerror(errno) instead, like we do other places. + +Upstream-Status: Submitted [https://mail.gnu.org/archive/html/qemu-stable/2023-12/msg00069.html] + +Cc: qemu-stable@nongnu.org +Fixes: commit 082e9e4a58ba (target/riscv/kvm: improve 'init_multiext_cfg' error +msg) +Resolves: https://gitlab.com/qemu-project/qemu/-/issues/2041 +Buglink: https://gitlab.alpinelinux.org/alpine/aports/-/issues/15541 +Signed-off-by: Natanael Copa +--- + target/riscv/kvm/kvm-cpu.c | 3 +-- + 1 file changed, 1 insertion(+), 2 deletions(-) + +diff --git a/target/riscv/kvm/kvm-cpu.c b/target/riscv/kvm/kvm-cpu.c +index f6e3156b8..fad9eb21c 100644 +--- a/target/riscv/kvm/kvm-cpu.c ++++ b/target/riscv/kvm/kvm-cpu.c +@@ -1859,8 +1859,7 @@ static bool kvm_cpu_realize(CPUState *cs, Error **errp) + if (riscv_has_ext(&cpu->env, RVV)) { + ret = prctl(PR_RISCV_V_SET_CONTROL, PR_RISCV_V_VSTATE_CTRL_ON); + if (ret) { +- error_setg(errp, "Error in prctl PR_RISCV_V_SET_CONTROL, code: %s", +- strerrorname_np(errno)); ++ error_setg(errp, "Error in prctl PR_RISCV_V_SET_CONTROL, error %d", errno); + return false; + } + } diff --git a/layers/meta-image-tests/recipes-devtools/qemu/qemu/powerpc_rom.bin b/layers/meta-image-tests/recipes-devtools/qemu/qemu/powerpc_rom.bin new file mode 100644 index 0000000000000000000000000000000000000000..c4044296c51529878f5d2a46230949d09ef1d4e2 GIT binary patch literal 4096 zcmeHJQEMDk6h4#Pn8Z}5VUd7UHb$_7oo16ZWvl_Wq7bx}RV;`Ib~1CD44Ivo&djz( z20DZ^Sm;B1^P!YL$|?xr50DxxhcYvi`OX{&F6WBRnw|kl?KhhA=dzmNj!qa7`0aw&RpKS@@VUVzXgU6}SL_e61d_GCWQDdT+4h>0J6J|vY6 z?n%vooL{&VPxp1(X12&CZmJJ@_@6`fnCE!)^YzN9>I}3^S{w(>9Y>+QAbsI`)YLe% zhsb^kg>uN3pwJm3OoIDU=pIs8v?nECzNOo>lb47s&EaP<^|yU5q9tpZ%W9g#!{`Jv zr|0H!wb2^S$51#BeALr&_Cx0<&mZafTbZr#AFp1v*?A3Nlm0 zZzgj>xMKNXQ@2&xYBnqPS-@>HKdpGnx?^IqQWS)y+YFH+ToxY7u|nTegHZRwP8V}b zSae+sEb;%K*M1#ny}qNsYkLXA5B@1?u>#SJLhH*7Xlm<8f;#-X1y`fH6W_i8$-egB zukYdwC~E6a_+TB*MCN`S^Bz_3)<>=KiD-M`L~SkEkMBZtb6dHCdSP>WD2n&sT(mn? z+PpV(Q@b~GE4~eH#;xQaDju($jbT{3o+O+D*Tz`w>>7;I`r2-i+`I$ItrjS_{sHo6 zTN_Ym=RSmE(Ze$(%wLGV!aq8Lpw!l&igq#Dm@1(jj;@#MaSO(y^{Eo=O`A-R1mJvJ aoLIp7h +# For QEMU Debian downstream package + +set -e + +. /etc/init.d/functions + +PATH=/sbin:/usr/sbin:/bin:/usr/bin +DESC="QEMU Guest Agent" +NAME=qemu-ga +DAEMON=@bindir@/$NAME +PIDFILE=/var/run/$NAME.pid + +# config +DAEMON_ARGS="" +# default transport +TRANSPORT=virtio-serial:/dev/virtio-ports/org.qemu.guest_agent.0 +NO_START=0 + +test ! -r /etc/default/qemu-guest-agent || . /etc/default/qemu-guest-agent +test "$NO_START" = "0" || exit 0 +test -x "$DAEMON" || exit 0 + +# +# Function that checks whenever system has necessary environment +# It also splits $TRANSPORT into $method and $path +# +do_check_transport() { + method=${TRANSPORT%%:*}; + path=${TRANSPORT#*:} + case "$method" in + virtio-serial | isa-serial) + if [ ! -e "$path" ]; then + echo "$NAME: transport endpoint not found, not starting" + return 1 + fi + ;; + esac +} + +case "$1" in + start) + do_check_transport || exit 0 + echo -n "Starting $DESC: " + start-stop-daemon -S -p $PIDFILE -x "$DAEMON" -- \ + $DAEMON_ARGS -d -m "$method" -p "$path" + echo "$NAME." + ;; + stop) + echo -n "Stopping $DESC: " + start-stop-daemon -K -x "$DAEMON" -p $PIDFILE + echo "$NAME." + ;; + status) + status "$DAEMON" + exit $? + ;; + restart|force-reload) + do_check_transport || exit 0 + echo -n "Restarting $DESC: " + start-stop-daemon -K -x "$DAEMON" -p $PIDFILE + sleep 1 + start-stop-daemon -S -p $PIDFILE -x "$DAEMON" -- \ + $DAEMON_ARGS -d -m "$method" -p "$path" + echo "$NAME." + ;; + *) + N=/etc/init.d/$NAME + echo "Usage: $N {start|stop|status|restart|force-reload}" >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/layers/meta-image-tests/recipes-devtools/qemu/qemu/qemu-guest-agent.udev b/layers/meta-image-tests/recipes-devtools/qemu/qemu/qemu-guest-agent.udev new file mode 100644 index 00000000..47097057 --- /dev/null +++ b/layers/meta-image-tests/recipes-devtools/qemu/qemu/qemu-guest-agent.udev @@ -0,0 +1,2 @@ +SUBSYSTEM=="virtio-ports", ATTR{name}=="org.qemu.guest_agent.0", \ + TAG+="systemd", ENV{SYSTEMD_WANTS}="qemu-guest-agent.service" diff --git a/layers/meta-image-tests/recipes-devtools/qemu/qemu/run-ptest b/layers/meta-image-tests/recipes-devtools/qemu/qemu/run-ptest new file mode 100644 index 00000000..f9a4e8fb --- /dev/null +++ b/layers/meta-image-tests/recipes-devtools/qemu/qemu/run-ptest @@ -0,0 +1,13 @@ +#!/bin/sh +# +#This script is used to run qemu test suites +# + +ptestdir=$(dirname "$(readlink -f "$0")") +export SRC_PATH=$ptestdir + +cd $ptestdir/tests +tests=$(find . -name "test-*" ! -name "*.p") +for f in $tests; do + $f | sed '/^ok/ s/ok/PASS:/g' +done diff --git a/layers/meta-image-tests/recipes-devtools/qemu/qemu_9.1.0.bb b/layers/meta-image-tests/recipes-devtools/qemu/qemu_9.1.0.bb new file mode 100644 index 00000000..dc135223 --- /dev/null +++ b/layers/meta-image-tests/recipes-devtools/qemu/qemu_9.1.0.bb @@ -0,0 +1,27 @@ +BBCLASSEXTEND = "nativesdk" + +require qemu.inc + +DEPENDS += "glib-2.0 zlib pixman" + +DEPENDS:append:libc-musl = " libucontext" + +CFLAGS += "${@bb.utils.contains('DISTRO_FEATURES', 'x11', '', '-DEGL_NO_X11=1', d)}" + +RDEPENDS:${PN}-common:class-target += "bash" + +EXTRA_OECONF:append:class-target = " --target-list=${@get_qemu_target_list(d)}" +EXTRA_OECONF:append:class-target:mipsarcho32 = "${@bb.utils.contains('BBEXTENDCURR', 'multilib', ' --disable-capstone', '', d)}" +EXTRA_OECONF:append:class-nativesdk = " --target-list=${@get_qemu_target_list(d)}" + +PACKAGECONFIG ??= " \ + fdt sdl kvm pie slirp \ + ${@bb.utils.filter('DISTRO_FEATURES', 'alsa pulseaudio xen', d)} \ + ${@bb.utils.contains('DISTRO_FEATURES', 'opengl', 'virglrenderer epoxy', '', d)} \ + ${@bb.utils.filter('DISTRO_FEATURES', 'seccomp', d)} \ +" +PACKAGECONFIG:class-nativesdk ??= "fdt sdl kvm pie slirp \ + ${@bb.utils.contains('DISTRO_FEATURES', 'opengl', 'virglrenderer epoxy', '', d)} \ +" +# ppc32 hosts are no longer supported in qemu +COMPATIBLE_HOST:powerpc = "null" diff --git a/layers/meta-image-tests/recipes-devtools/qemu/qemuwrapper-cross_1.0.bb b/layers/meta-image-tests/recipes-devtools/qemu/qemuwrapper-cross_1.0.bb new file mode 100644 index 00000000..6d0e7883 --- /dev/null +++ b/layers/meta-image-tests/recipes-devtools/qemu/qemuwrapper-cross_1.0.bb @@ -0,0 +1,41 @@ +SUMMARY = "QEMU wrapper script" +HOMEPAGE = "http://qemu.org" +LICENSE = "MIT" + +S = "${WORKDIR}/sources" +UNPACKDIR = "${S}" + +DEPENDS += "qemu-native" + +inherit qemu + +do_populate_sysroot[depends] = "" + +do_install () { + install -d ${D}${bindir_crossscripts}/ + + qemu_binary=${@qemu_target_binary(d)} + qemu_options='${QEMU_OPTIONS} -E LD_LIBRARY_PATH=$D${libdir}:$D${base_libdir}' + + cat >> ${D}${bindir_crossscripts}/${MLPREFIX}qemuwrapper << EOF +#!/bin/sh +# Wrapper script to run binaries under qemu user-mode emulation +set -x + +if [ ${@bb.utils.contains('MACHINE_FEATURES', 'qemu-usermode', 'True', 'False', d)} = False -a "${PN}" != "nativesdk-qemuwrapper-cross" ]; then + echo "qemuwrapper: qemu usermode is not supported" + exit 1 +fi + + +$qemu_binary $qemu_options "\$@" +EOF + + chmod +x ${D}${bindir_crossscripts}/${MLPREFIX}qemuwrapper +} + +SYSROOT_DIRS += "${bindir_crossscripts}" + +INHIBIT_DEFAULT_DEPS = "1" + +BBCLASSEXTEND = "nativesdk" From 5aeb6edda82e723269399fb98d2abf0facc0b295 Mon Sep 17 00:00:00 2001 From: nhitar Date: Mon, 16 Mar 2026 22:22:37 +0300 Subject: [PATCH 2/3] feat: specify qemu version --- conf/local.conf | 2 ++ 1 file changed, 2 insertions(+) diff --git a/conf/local.conf b/conf/local.conf index adecc3a7..150212d3 100644 --- a/conf/local.conf +++ b/conf/local.conf @@ -76,3 +76,5 @@ QB_CPU = "-smp 4" require packages.conf require added_packages.conf + +PREFERRED_VERSION_qemu = "9.1.0%" From bda57943a03e9959fc83d5e02e2f099647adb8c4 Mon Sep 17 00:00:00 2001 From: nhitar Date: Mon, 16 Mar 2026 23:44:52 +0300 Subject: [PATCH 3/3] fix: pre-commit hooks --- .../qemu/qemu-helper/qemu-oe-bridge-helper.c | 1 - .../qemu/qemu-native_9.1.0.bb | 1 - .../qemu/qemu-system-native_9.1.0.bb | 1 - .../qemu/qemu/0000-QEMU-test-disk.patch | 867 +++++++++--------- ...n-environment-space-to-boot-loader-q.patch | 2 +- .../0002-apic-fixup-fallthrough-to-PIC.patch | 4 +- ...er-space-mmap-tweaks-to-address-musl.patch | 5 +- .../qemu/0006-qemu-Determinism-fixes.patch | 2 +- ...d-use-relative-path-to-refer-to-file.patch | 2 +- ...and-MAP_SHARED_VALIDATE-on-needed-li.patch | 4 +- ...gure-lookup-meson-exutable-from-PATH.patch | 5 +- ...and-the-python-venv-aren-t-used-for-.patch | 7 +- .../recipes-devtools/qemu/qemu/run-ptest | 4 +- 13 files changed, 449 insertions(+), 456 deletions(-) mode change 100644 => 100755 layers/meta-image-tests/recipes-devtools/qemu/qemu/run-ptest diff --git a/layers/meta-image-tests/recipes-devtools/qemu/qemu-helper/qemu-oe-bridge-helper.c b/layers/meta-image-tests/recipes-devtools/qemu/qemu-helper/qemu-oe-bridge-helper.c index c34aa005..6238887a 100644 --- a/layers/meta-image-tests/recipes-devtools/qemu/qemu-helper/qemu-oe-bridge-helper.c +++ b/layers/meta-image-tests/recipes-devtools/qemu/qemu-helper/qemu-oe-bridge-helper.c @@ -37,4 +37,3 @@ int main(int argc, char** argv) { fprintf(stderr, "No bridge helper found\n"); return 1; } - diff --git a/layers/meta-image-tests/recipes-devtools/qemu/qemu-native_9.1.0.bb b/layers/meta-image-tests/recipes-devtools/qemu/qemu-native_9.1.0.bb index 26fa84c1..a7795352 100644 --- a/layers/meta-image-tests/recipes-devtools/qemu/qemu-native_9.1.0.bb +++ b/layers/meta-image-tests/recipes-devtools/qemu/qemu-native_9.1.0.bb @@ -7,4 +7,3 @@ require qemu-native.inc EXTRA_OECONF:append = " --target-list=${@get_qemu_usermode_target_list(d)} --disable-tools --disable-install-blobs --disable-guest-agent" PACKAGECONFIG ??= "pie" - diff --git a/layers/meta-image-tests/recipes-devtools/qemu/qemu-system-native_9.1.0.bb b/layers/meta-image-tests/recipes-devtools/qemu/qemu-system-native_9.1.0.bb index e7bbe5d2..5d2fbcbc 100644 --- a/layers/meta-image-tests/recipes-devtools/qemu/qemu-system-native_9.1.0.bb +++ b/layers/meta-image-tests/recipes-devtools/qemu/qemu-system-native_9.1.0.bb @@ -29,4 +29,3 @@ do_install:append() { install -d ${D}${libdir}/qemu-python/qmp/ install -D ${S}/python/qemu/qmp/* ${D}${libdir}/qemu-python/qmp/ } - diff --git a/layers/meta-image-tests/recipes-devtools/qemu/qemu/0000-QEMU-test-disk.patch b/layers/meta-image-tests/recipes-devtools/qemu/qemu/0000-QEMU-test-disk.patch index 33f12152..babd5622 100644 --- a/layers/meta-image-tests/recipes-devtools/qemu/qemu/0000-QEMU-test-disk.patch +++ b/layers/meta-image-tests/recipes-devtools/qemu/qemu/0000-QEMU-test-disk.patch @@ -48,7 +48,7 @@ index bb7d289aa0..37b44f63d0 100644 scsi_ss.add(when: 'CONFIG_MPTSAS_SCSI_PCI', if_true: files('mptsas.c', 'mptconfig.c', 'mptendian.c')) scsi_ss.add(when: 'CONFIG_VMW_PVSCSI_SCSI_PCI', if_true: files('vmw_pvscsi.c')) +scsi_ss.add(when: 'CONFIG_TEST_DISK', if_true: files('test_disk.c')) - + virtio_scsi_ss.add(files('virtio-scsi-dataplane.c')) virtio_scsi_ss.add(when: 'CONFIG_VHOST_SCSI', if_true: files('vhost-scsi.c')) diff --git a/hw/scsi/test_disk.c b/hw/scsi/test_disk.c @@ -117,7 +117,7 @@ index 0000000000..be2f7ccbd3 +#endif //HW_SCSI_TEST_DISK_H +./ \ No newline at end of file --- +-- 2.53.0 @@ -140,14 +140,14 @@ index a8c774182b..42a4f220f1 100644 #include "hw/scsi/scsi.h" #include "qapi/error.h" +#include "scsi/constants.h" - + static int test_disk_parse_cdb(SCSIDevice *dev, SCSICommand *cmd, uint8_t *buf, size_t buf_len, void *hba_private) { - printf("MySCSIDevice: Received CDB command 0x%02x\n", buf[0]); + printf("TEST_DISK: received CDB command 0x%02x\n", buf[0]); - + return scsi_req_parse_cdb(dev, cmd, buf, buf_len); } - + +typedef struct TestDiskReq { + SCSIRequest req; + uint8_t buf[256]; @@ -193,12 +193,12 @@ index a8c774182b..42a4f220f1 100644 + outbuf[1] = (last_lba >> 16) & 0xff; + outbuf[2] = (last_lba >> 8) & 0xff; + outbuf[3] = last_lba & 0xff; -+ ++ + outbuf[4] = (block_size >> 24) & 0xff; + outbuf[5] = (block_size >> 16) & 0xff; + outbuf[6] = (block_size >> 8) & 0xff; + outbuf[7] = block_size & 0xff; -+ ++ + scsi_req_data(req, 8); + + break; @@ -250,14 +250,14 @@ index a8c774182b..42a4f220f1 100644 - printf("MySCSIDevice: Device realized (initialized)\n"); + printf("TEST_DISK: realized\n"); } - + static void test_disk_class_init(ObjectClass *klass, const void *data) { @@ -19,6 +118,7 @@ static void test_disk_class_init(ObjectClass *klass, const void *data) { - + sc->realize = test_disk_realize; sc->parse_cdb = test_disk_parse_cdb; + sc->alloc_req = test_disk_alloc_req; - + set_bit(DEVICE_CATEGORY_STORAGE, dc->categories); dc->desc = "Test SCSI Disk"; diff --git a/include/hw/scsi/test_disk.h b/include/hw/scsi/test_disk.h @@ -267,13 +267,13 @@ index be2f7ccbd3..ac9e278372 100644 @@ -11,5 +11,4 @@ struct TestDisk { SCSIDevice parent_obj; }; - + -#endif //HW_SCSI_TEST_DISK_H -./ \ No newline at end of file +#endif //HW_SCSI_TEST_DISK_H \ No newline at end of file --- +-- 2.53.0 @@ -297,15 +297,15 @@ index 42a4f220f1..01481301fa 100644 #include "hw/scsi/scsi.h" -#include "qapi/error.h" #include "scsi/constants.h" - + -static int test_disk_parse_cdb(SCSIDevice *dev, SCSICommand *cmd, uint8_t *buf, size_t buf_len, void *hba_private) { +static int test_disk_parse_cdb(SCSIDevice* dev, SCSICommand* cmd, uint8_t* buf, size_t buf_len, void* hba_private) { printf("TEST_DISK: received CDB command 0x%02x\n", buf[0]); - + return scsi_req_parse_cdb(dev, cmd, buf, buf_len); @@ -16,10 +15,16 @@ typedef struct TestDiskReq { } TestDiskReq; - + static int32_t test_disk_send_command(SCSIRequest* req, uint8_t* buf) { - TestDiskReq *r = DO_UPCAST(TestDiskReq, req, req); + TestDiskReq* r = DO_UPCAST(TestDiskReq, req, req); @@ -313,7 +313,7 @@ index 42a4f220f1..01481301fa 100644 + uint8_t command = buf[0]; uint8_t* outbuf = r->buf; - + + if (disk->socket) { + qio_channel_write_all(QIO_CHANNEL(disk->socket), (char*)&command, 1, NULL); + } @@ -338,43 +338,43 @@ index 42a4f220f1..01481301fa 100644 + outbuf[4] = 31; // additional length + memcpy(&outbuf[8], "QEMU ", 8); memcpy(&outbuf[16], "TEST_DISK ", 16); - + scsi_req_data(req, 36); @@ -55,12 +60,12 @@ static int32_t test_disk_send_command(SCSIRequest* req, uint8_t* buf) { outbuf[1] = (last_lba >> 16) & 0xff; outbuf[2] = (last_lba >> 8) & 0xff; outbuf[3] = last_lba & 0xff; -- +- + outbuf[4] = (block_size >> 24) & 0xff; outbuf[5] = (block_size >> 16) & 0xff; outbuf[6] = (block_size >> 8) & 0xff; outbuf[7] = block_size & 0xff; -- +- + scsi_req_data(req, 8); - + break; @@ -69,8 +74,8 @@ static int32_t test_disk_send_command(SCSIRequest* req, uint8_t* buf) { printf("\tTEST_DISK: MODE SENSE (6)\n"); - + memset(outbuf, 0, 4); - outbuf[0] = 3; // length - outbuf[2] = 0x80; // write protection + outbuf[0] = 3; // length + outbuf[2] = 0x80; // write protection - + scsi_req_data(req, 4); break; @@ -87,13 +92,12 @@ static int32_t test_disk_send_command(SCSIRequest* req, uint8_t* buf) { return 0; } - + -static void test_disk_read_data(SCSIRequest *req) { +static void test_disk_read_data(SCSIRequest* req) { scsi_req_complete(req, GOOD); } - + -static uint8_t* test_disk_get_buf(SCSIRequest *req) -{ - TestDiskReq *r = DO_UPCAST(TestDiskReq, req, req); @@ -382,16 +382,16 @@ index 42a4f220f1..01481301fa 100644 + TestDiskReq* r = DO_UPCAST(TestDiskReq, req, req); return r->buf; } - + @@ -104,17 +108,41 @@ static const SCSIReqOps test_disk_req_ops = { .get_buf = test_disk_get_buf }; - + -static SCSIRequest* test_disk_alloc_req(SCSIDevice *s, uint32_t tag, uint32_t lun, uint8_t *buf, void *hba_private) { +static SCSIRequest* test_disk_alloc_req(SCSIDevice* s, uint32_t tag, uint32_t lun, uint8_t* buf, void* hba_private) { return scsi_req_alloc(&test_disk_req_ops, s, tag, lun, hba_private); } - + -static void test_disk_realize(SCSIDevice *dev, Error **errp) { +static void test_disk_realize(SCSIDevice* dev, Error** errp) { + TestDisk* disk = DO_UPCAST(TestDisk, parent_obj, dev); @@ -420,14 +420,14 @@ index 42a4f220f1..01481301fa 100644 + printf("TEST_DISK: realized\n"); } - + -static void test_disk_class_init(ObjectClass *klass, const void *data) { - DeviceClass *dc = DEVICE_CLASS(klass); - SCSIDeviceClass *sc = SCSI_DEVICE_CLASS(klass); +static void test_disk_class_init(ObjectClass* klass, const void* data) { + DeviceClass* dc = DEVICE_CLASS(klass); + SCSIDeviceClass* sc = SCSI_DEVICE_CLASS(klass); - + sc->realize = test_disk_realize; sc->parse_cdb = test_disk_parse_cdb; diff --git a/include/hw/scsi/test_disk.h b/include/hw/scsi/test_disk.h @@ -436,22 +436,22 @@ index ac9e278372..c6a07db5dd 100644 +++ b/include/hw/scsi/test_disk.h @@ -2,6 +2,7 @@ #define HW_SCSI_TEST_DISK_H - + #include "hw/scsi/scsi.h" +#include "io/channel-socket.h" #include "qom/object.h" - + #define TYPE_TEST_DISK "test_disk" @@ -9,6 +10,7 @@ OBJECT_DECLARE_SIMPLE_TYPE(TestDisk, TEST_DISK) - + struct TestDisk { SCSIDevice parent_obj; + QIOChannelSocket* socket; }; - + #endif //HW_SCSI_TEST_DISK_H \ No newline at end of file --- +-- 2.53.0 @@ -471,15 +471,15 @@ index 01481301fa..a4bf98bf5f 100644 @@ -3,6 +3,9 @@ #include "hw/scsi/scsi.h" #include "scsi/constants.h" - + +// block size * max blocks +#define MAX_READ_XFER_SIZE (512 * 2048) // 1MB + static int test_disk_parse_cdb(SCSIDevice* dev, SCSICommand* cmd, uint8_t* buf, size_t buf_len, void* hba_private) { printf("TEST_DISK: received CDB command 0x%02x\n", buf[0]); - + @@ -11,9 +14,25 @@ static int test_disk_parse_cdb(SCSIDevice* dev, SCSICommand* cmd, uint8_t* buf, - + typedef struct TestDiskReq { SCSIRequest req; - uint8_t buf[256]; @@ -487,7 +487,7 @@ index 01481301fa..a4bf98bf5f 100644 + int len; + uint8_t buf[MAX_READ_XFER_SIZE]; } TestDiskReq; - + +static void test_disk_read_callback(void* opaque) { + printf("CALLBACK: from READ command\n"); + @@ -507,25 +507,25 @@ index 01481301fa..a4bf98bf5f 100644 TestDisk* disk = DO_UPCAST(TestDisk, parent_obj, req->dev); @@ -52,7 +71,7 @@ static int32_t test_disk_send_command(SCSIRequest* req, uint8_t* buf) { memset(outbuf, 0, 8); - + const uint32_t block_size = 512; - const uint32_t total_blocks = 2097152U; + const uint32_t total_blocks = 2048U; - + uint32_t last_lba = total_blocks - 1; - + @@ -67,7 +86,6 @@ static int32_t test_disk_send_command(SCSIRequest* req, uint8_t* buf) { outbuf[7] = block_size & 0xff; - + scsi_req_data(req, 8); - break; - + case 0x1A: @@ -80,12 +98,52 @@ static int32_t test_disk_send_command(SCSIRequest* req, uint8_t* buf) { scsi_req_data(req, 4); break; - + + case 0x5A: + printf("\tTEST_DISK: MODE SENSE (10)\n"); + memset(outbuf, 0, 8); @@ -569,24 +569,24 @@ index 01481301fa..a4bf98bf5f 100644 + default: printf("\tTEST_DISK: rejecting command 0x%02x\n", command); - + scsi_req_build_sense(req, SENSE_CODE(INVALID_OPCODE)); scsi_req_complete(req, CHECK_CONDITION); - break; } - + @@ -113,6 +171,9 @@ static SCSIRequest* test_disk_alloc_req(SCSIDevice* s, uint32_t tag, uint32_t lu } - + static void test_disk_realize(SCSIDevice* dev, Error** errp) { + dev->type = TYPE_DISK; + dev->blocksize = 512; + TestDisk* disk = DO_UPCAST(TestDisk, parent_obj, dev); - + SocketAddress addr = { --- +-- 2.53.0 @@ -612,7 +612,7 @@ index a4bf98bf5f..0669c92192 100644 + + uint8_t* buf; } TestDiskReq; - + +static Property test_disk_properties[] = { + DEFINE_PROP_SIZE("size", TestDisk, size, 1 * 1024 * 1024 * 1024), + DEFINE_PROP_SIZE("bs", TestDisk, block_size, 512), @@ -621,26 +621,26 @@ index a4bf98bf5f..0669c92192 100644 + static void test_disk_read_callback(void* opaque) { printf("CALLBACK: from READ command\n"); - + @@ -70,8 +77,8 @@ static int32_t test_disk_send_command(SCSIRequest* req, uint8_t* buf) { - + memset(outbuf, 0, 8); - + - const uint32_t block_size = 512; - const uint32_t total_blocks = 2048U; + const uint32_t block_size = disk->block_size; + const uint32_t total_blocks = disk->size / disk->block_size; - + uint32_t last_lba = total_blocks - 1; - + @@ -123,10 +130,10 @@ static int32_t test_disk_send_command(SCSIRequest* req, uint8_t* buf) { break; } - + - if (target_len > sizeof(r->buf)) { + if (target_len > disk->max_xfer_size) { printf("\tTEST_DISK: error - request too big\n"); - + - scsi_req_build_sense(req, SENSE_CODE(LBA_OUT_OF_RANGE)); + scsi_req_build_sense(req, SENSE_CODE(INVALID_FIELD)); scsi_req_complete(req, CHECK_CONDITION); @@ -649,7 +649,7 @@ index a4bf98bf5f..0669c92192 100644 @@ -159,23 +166,41 @@ static uint8_t* test_disk_get_buf(SCSIRequest* req) { return r->buf; } - + +static void test_disk_free_req(SCSIRequest* req) { + TestDiskReq* r = DO_UPCAST(TestDiskReq, req, req); + @@ -667,7 +667,7 @@ index a4bf98bf5f..0669c92192 100644 + .get_buf = test_disk_get_buf, + .free_req = test_disk_free_req }; - + static SCSIRequest* test_disk_alloc_req(SCSIDevice* s, uint32_t tag, uint32_t lun, uint8_t* buf, void* hba_private) { - return scsi_req_alloc(&test_disk_req_ops, s, tag, lun, hba_private); + TestDisk* disk = DO_UPCAST(TestDisk, parent_obj, s); @@ -680,13 +680,13 @@ index a4bf98bf5f..0669c92192 100644 + + return req; } - + static void test_disk_realize(SCSIDevice* dev, Error** errp) { - dev->type = TYPE_DISK; - dev->blocksize = 512; - TestDisk* disk = DO_UPCAST(TestDisk, parent_obj, dev); - + + dev->type = TYPE_DISK; + dev->blocksize = (int)disk->block_size; + @@ -694,13 +694,13 @@ index a4bf98bf5f..0669c92192 100644 .type = SOCKET_ADDRESS_TYPE_INET, .u.inet = { @@ -211,6 +236,8 @@ static void test_disk_class_init(ObjectClass* klass, const void* data) { - + set_bit(DEVICE_CATEGORY_STORAGE, dc->categories); dc->desc = "Test SCSI Disk"; + + device_class_set_props(dc, test_disk_properties); } - + static const TypeInfo test_disk_info = { diff --git a/include/hw/scsi/test_disk.h b/include/hw/scsi/test_disk.h index c6a07db5dd..a86908d965 100644 @@ -716,10 +716,10 @@ index c6a07db5dd..a86908d965 100644 + + uint64_t max_xfer_size; }; - + #endif //HW_SCSI_TEST_DISK_H \ No newline at end of file --- +-- 2.53.0 @@ -746,10 +746,10 @@ index 0669c92192..f0262586a9 100644 +#include "test_disk.h" +#include "qapi/error.h" #include "scsi/constants.h" - + -// block size * max blocks -#define MAX_READ_XFER_SIZE (512 * 2048) // 1MB - + -static int test_disk_parse_cdb(SCSIDevice* dev, SCSICommand* cmd, uint8_t* buf, size_t buf_len, void* hba_private) { - printf("TEST_DISK: received CDB command 0x%02x\n", buf[0]); +// Device properties @@ -758,22 +758,22 @@ index 0669c92192..f0262586a9 100644 + DEFINE_PROP_SIZE("bs", TestDisk, block_size, 512), + DEFINE_PROP_SIZE("max_xfer_size", TestDisk, max_xfer_size, 1 * 1024 * 1024), +}; - + - return scsi_req_parse_cdb(dev, cmd, buf, buf_len); -} - + +// Request typedef struct TestDiskReq { - SCSIRequest req; + SCSIRequest parent_scsi_req; QEMUTimer* timer; - int len; - + - uint8_t* buf; + uint8_t* response_buf; + int response_buf_len; } TestDiskReq; - + -static Property test_disk_properties[] = { - DEFINE_PROP_SIZE("size", TestDisk, size, 1 * 1024 * 1024 * 1024), - DEFINE_PROP_SIZE("bs", TestDisk, block_size, 512), @@ -794,44 +794,44 @@ index 0669c92192..f0262586a9 100644 + .get_buf = test_disk_get_buf, + .free_req = test_disk_free_req }; - + -static void test_disk_read_callback(void* opaque) { - printf("CALLBACK: from READ command\n"); - + - TestDiskReq* r = opaque; +// Helper functions +static void test_disk_socket_start_connection(void* opaque); +static void test_disk_socket_connected(QIOTask* task, gpointer opaque); +static void test_disk_read_callback(void* opaque); - + - uint8_t* outbuf = r->buf; - memset(outbuf, 0, r->len); - + - scsi_req_data(&r->req, r->len); +// ============================================================================ - + - timer_free(r->timer); - r->timer = NULL; -} - + -static int32_t test_disk_send_command(SCSIRequest* req, uint8_t* buf) { - TestDiskReq* r = DO_UPCAST(TestDiskReq, req, req); - TestDisk* disk = DO_UPCAST(TestDisk, parent_obj, req->dev); +static int32_t test_disk_send_command(SCSIRequest* scsi_req, uint8_t* request_buf) { + TestDiskReq* disk_req = DO_UPCAST(TestDiskReq, parent_scsi_req, scsi_req); + TestDisk* disk = DO_UPCAST(TestDisk, parent_scsi_dev, scsi_req->dev); - + - uint8_t command = buf[0]; - uint8_t* outbuf = r->buf; + uint8_t command = request_buf[0]; - + - if (disk->socket) { - qio_channel_write_all(QIO_CHANNEL(disk->socket), (char*)&command, 1, NULL); - } + // INQUIRY request is handled even without socket + if (command == 0x12) { // INQUIRY + memset(disk_req->response_buf, 0, 36); - + - switch (command) { - case 0x00: - printf("\tTEST_DISK: TEST UNIT READY\n"); @@ -840,12 +840,12 @@ index 0669c92192..f0262586a9 100644 + disk_req->response_buf[2] = 5; // SPC-3 + disk_req->response_buf[3] = 2; // response data format + disk_req->response_buf[4] = 31; // additional length - + - scsi_req_complete(req, GOOD); - break; + memcpy(&disk_req->response_buf[8], "QEMU", 4); // vendor + memcpy(&disk_req->response_buf[16], "TEST_DISK", 9); // product - + - case 0x12: - printf("\tTEST_DISK: INQUIRY\n"); - memset(outbuf, 0, 36); @@ -862,7 +862,7 @@ index 0669c92192..f0262586a9 100644 + scsi_req_data(scsi_req, 36); + return 0; + } - + - case 0x25: - printf("\tTEST_DISK: READ CAPACITY (10)\n"); + if (!disk->socket) { @@ -875,17 +875,17 @@ index 0669c92192..f0262586a9 100644 + case 0x00: // TEST UNIT READY + scsi_req_complete(scsi_req, GOOD); + break; - + - memset(outbuf, 0, 8); + case 0x25: // READ CAPACITY (10) + memset(disk_req->response_buf, 0, 8); - + const uint32_t block_size = disk->block_size; const uint32_t total_blocks = disk->size / disk->block_size; - + - uint32_t last_lba = total_blocks - 1; + const uint32_t last_lba = total_blocks - 1; - + - outbuf[0] = (last_lba >> 24) & 0xff; - outbuf[1] = (last_lba >> 16) & 0xff; - outbuf[2] = (last_lba >> 8) & 0xff; @@ -894,7 +894,7 @@ index 0669c92192..f0262586a9 100644 + disk_req->response_buf[1] = (last_lba >> 16) & 0xff; + disk_req->response_buf[2] = (last_lba >> 8) & 0xff; + disk_req->response_buf[3] = last_lba & 0xff; - + - outbuf[4] = (block_size >> 24) & 0xff; - outbuf[5] = (block_size >> 16) & 0xff; - outbuf[6] = (block_size >> 8) & 0xff; @@ -903,26 +903,26 @@ index 0669c92192..f0262586a9 100644 + disk_req->response_buf[5] = (block_size >> 16) & 0xff; + disk_req->response_buf[6] = (block_size >> 8) & 0xff; + disk_req->response_buf[7] = block_size & 0xff; - + - scsi_req_data(req, 8); + scsi_req_data(scsi_req, 8); break; - + - case 0x1A: - printf("\tTEST_DISK: MODE SENSE (6)\n"); + case 0x1A: // MODE SENSE (6) + memset(disk_req->response_buf, 0, 4); - + - memset(outbuf, 0, 4); - outbuf[0] = 3; // length - outbuf[2] = 0x80; // write protection + disk_req->response_buf[0] = 3; // length + disk_req->response_buf[2] = 0x80; // write protection - + - scsi_req_data(req, 4); + scsi_req_data(scsi_req, 4); break; - + - case 0x5A: - printf("\tTEST_DISK: MODE SENSE (10)\n"); - memset(outbuf, 0, 8); @@ -931,46 +931,46 @@ index 0669c92192..f0262586a9 100644 - outbuf[1] = 6; + case 0x5A: // MODE SENSE (10) + memset(disk_req->response_buf, 0, 8); - + - outbuf[3] = 0x80; + disk_req->response_buf[0] = 0; + disk_req->response_buf[1] = 6; + disk_req->response_buf[3] = 0x80; - + - scsi_req_data(req, 8); + scsi_req_data(scsi_req, 8); break; - + - case 0x08: - case 0x28: - int read_type = command == 0x08 ? 6 : 10; - printf("\tTEST_DISK: READ (%d)\n", read_type); + case 0x08: // READ (6) + case 0x28: // READ (10) - + - int target_len = (int)req->cmd.xfer; - printf("\tTARGET_LEN: %d\n", target_len); + int req_target_len = (int)scsi_req->cmd.xfer; - + - if (target_len == 0) { - scsi_req_complete(req, GOOD); + if (req_target_len == 0) { + scsi_req_complete(scsi_req, GOOD); break; } - + - if (target_len > disk->max_xfer_size) { - printf("\tTEST_DISK: error - request too big\n"); + if (req_target_len > disk->max_xfer_size) { + printf("[READ]: target xfer size from request is greater than disk max xfer size\n"); - + - scsi_req_build_sense(req, SENSE_CODE(INVALID_FIELD)); - scsi_req_complete(req, CHECK_CONDITION); + scsi_req_build_sense(scsi_req, SENSE_CODE(INVALID_FIELD)); + scsi_req_complete(scsi_req, CHECK_CONDITION); break; } - + - r->len = target_len; - r->timer = timer_new_ms(QEMU_CLOCK_REALTIME, test_disk_read_callback, r); + disk_req->response_buf_len = req_target_len; @@ -986,34 +986,34 @@ index 0669c92192..f0262586a9 100644 + // + // test_disk_socket_start_connection(disk); + // } - + - uint8_t delay_ms = 0; - timer_mod(r->timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + delay_ms); + timer_mod(disk_req->timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + disk->latency_ms); - + - return 0; + break; - + default: - printf("\tTEST_DISK: rejecting command 0x%02x\n", command); + printf("[DISK]: rejecting command 0x%02x\n", command); - + - scsi_req_build_sense(req, SENSE_CODE(INVALID_OPCODE)); - scsi_req_complete(req, CHECK_CONDITION); + scsi_req_build_sense(scsi_req, SENSE_CODE(INVALID_OPCODE)); + scsi_req_complete(scsi_req, CHECK_CONDITION); break; } - + return 0; } - + -static void test_disk_read_data(SCSIRequest* req) { - scsi_req_complete(req, GOOD); +static void test_disk_read_data(SCSIRequest* scsi_req) { + scsi_req_complete(scsi_req, GOOD); } - + -static uint8_t* test_disk_get_buf(SCSIRequest* req) { - TestDiskReq* r = DO_UPCAST(TestDiskReq, req, req); - return r->buf; @@ -1021,17 +1021,17 @@ index 0669c92192..f0262586a9 100644 + TestDiskReq* disk_req = DO_UPCAST(TestDiskReq, parent_scsi_req, scsi_req); + return disk_req->response_buf; } - + -static void test_disk_free_req(SCSIRequest* req) { - TestDiskReq* r = DO_UPCAST(TestDiskReq, req, req); - + - if (r->buf) { - g_free(r->buf); - r->buf = NULL; - } -} +// ============================================================================ - + -static const SCSIReqOps test_disk_req_ops = { - .size = sizeof(TestDiskReq), - .send_command = test_disk_send_command, @@ -1043,12 +1043,12 @@ index 0669c92192..f0262586a9 100644 -static SCSIRequest* test_disk_alloc_req(SCSIDevice* s, uint32_t tag, uint32_t lun, uint8_t* buf, void* hba_private) { - TestDisk* disk = DO_UPCAST(TestDisk, parent_obj, s); - uint64_t max_xfer_size = disk->max_xfer_size; - + - SCSIRequest* req = scsi_req_alloc(&test_disk_req_ops, s, tag, lun, hba_private); - TestDiskReq* r = DO_UPCAST(TestDiskReq, req, req); +static void test_disk_socket_start_connection(void* opaque) { + TestDisk* disk = opaque; - + - r->buf = g_malloc0(max_xfer_size); - - return req; @@ -1056,31 +1056,31 @@ index 0669c92192..f0262586a9 100644 + if (disk->socket || disk->connecting) { + return; + } - + -static void test_disk_realize(SCSIDevice* dev, Error** errp) { - TestDisk* disk = DO_UPCAST(TestDisk, parent_obj, dev); + printf("[SOCKET]: connecting\n"); - + - dev->type = TYPE_DISK; - dev->blocksize = (int)disk->block_size; + disk->connecting = true; + disk->socket = qio_channel_socket_new(); - + SocketAddress addr = { .type = SOCKET_ADDRESS_TYPE_INET, @@ -209,30 +203,106 @@ static void test_disk_realize(SCSIDevice* dev, Error** errp) { } }; - + - disk->socket = qio_channel_socket_new(); + qio_channel_socket_connect_async(disk->socket, &addr, test_disk_socket_connected, disk, NULL, NULL); - + - if (qio_channel_socket_connect_sync(disk->socket, &addr, NULL) < 0) { - printf("SOCKET: failed to connect\n"); + g_free(addr.u.inet.host); + g_free(addr.u.inet.port); +} - + +static void test_disk_socket_connected(QIOTask* task, gpointer opaque) { + TestDisk* disk = opaque; + Error* err = NULL; @@ -1097,7 +1097,7 @@ index 0669c92192..f0262586a9 100644 + timer_mod(disk->socket_connect_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 5000); + return; } - + - g_free(addr.u.inet.host); - g_free(addr.u.inet.port); + uint32_t latency_be = 0; @@ -1128,7 +1128,7 @@ index 0669c92192..f0262586a9 100644 + +// ============================================================================ + - + - printf("TEST_DISK: realized\n"); +static SCSIRequest* test_disk_alloc_req(SCSIDevice* scsi_dev, uint32_t tag, uint32_t lun, uint8_t* request_buf, void* hba_private) { + TestDisk* disk = DO_UPCAST(TestDisk, parent_scsi_dev, scsi_dev); @@ -1154,7 +1154,7 @@ index 0669c92192..f0262586a9 100644 +static int test_disk_parse_cdb(SCSIDevice* scsi_dev, SCSICommand* cmd, uint8_t* request_buf, size_t request_buf_len, void* hba_private) { + return scsi_req_parse_cdb(scsi_dev, cmd, request_buf, request_buf_len); } - + +static void test_disk_realize(SCSIDevice* scsi_dev, Error** errp) { + TestDisk* disk = DO_UPCAST(TestDisk, parent_scsi_dev, scsi_dev); + @@ -1176,13 +1176,13 @@ index 0669c92192..f0262586a9 100644 static void test_disk_class_init(ObjectClass* klass, const void* data) { DeviceClass* dc = DEVICE_CLASS(klass); SCSIDeviceClass* sc = SCSI_DEVICE_CLASS(klass); - + - sc->realize = test_disk_realize; - sc->parse_cdb = test_disk_parse_cdb; sc->alloc_req = test_disk_alloc_req; + sc->parse_cdb = test_disk_parse_cdb; + sc->realize = test_disk_realize; - + set_bit(DEVICE_CATEGORY_STORAGE, dc->categories); dc->desc = "Test SCSI Disk"; diff --git a/include/hw/scsi/test_disk.h b/hw/scsi/test_disk.h @@ -1195,16 +1195,16 @@ index a86908d965..568f975961 100644 @@ -8,14 +8,20 @@ #define TYPE_TEST_DISK "test_disk" OBJECT_DECLARE_SIMPLE_TYPE(TestDisk, TEST_DISK) - + +// Device struct TestDisk { - SCSIDevice parent_obj; + SCSIDevice parent_scsi_dev; QIOChannelSocket* socket; - + uint64_t size; uint64_t block_size; - + uint64_t max_xfer_size; + + uint32_t latency_ms; @@ -1212,10 +1212,10 @@ index a86908d965..568f975961 100644 + QEMUTimer* socket_connect_timer; + bool connecting; }; - + #endif //HW_SCSI_TEST_DISK_H \ No newline at end of file --- +-- 2.53.0 @@ -1236,31 +1236,31 @@ index f0262586a9..a7fa2a4a77 100644 @@ -4,12 +4,13 @@ #include "qapi/error.h" #include "scsi/constants.h" - + +#define MAX_XFER_SIZE (1 * 1024 * 1024) + - + // Device properties static const Property test_disk_properties[] = { DEFINE_PROP_SIZE("size", TestDisk, size, 1 * 1024 * 1024 * 1024), DEFINE_PROP_SIZE("bs", TestDisk, block_size, 512), - DEFINE_PROP_SIZE("max_xfer_size", TestDisk, max_xfer_size, 1 * 1024 * 1024), }; - - + + @@ -133,7 +134,7 @@ static int32_t test_disk_send_command(SCSIRequest* scsi_req, uint8_t* request_bu break; } - + - if (req_target_len > disk->max_xfer_size) { + if (req_target_len > MAX_XFER_SIZE) { printf("[READ]: target xfer size from request is greater than disk max xfer size\n"); - + scsi_req_build_sense(scsi_req, SENSE_CODE(INVALID_FIELD)); @@ -144,17 +145,6 @@ static int32_t test_disk_send_command(SCSIRequest* scsi_req, uint8_t* request_bu disk_req->response_buf_len = req_target_len; disk_req->timer = timer_new_ms(QEMU_CLOCK_REALTIME, test_disk_read_callback, disk_req); - + - // if (qio_channel_write_all(QIO_CHANNEL(disk->socket), (char*)&command, 1, NULL) == 0) { - // delay_ms = 5; - // } else { @@ -1273,30 +1273,30 @@ index f0262586a9..a7fa2a4a77 100644 - // } - timer_mod(disk_req->timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + disk->latency_ms); - + break; @@ -235,7 +225,7 @@ static void test_disk_socket_connected(QIOTask* task, gpointer opaque) { - + disk->latency_ms = be32_to_cpu(latency_be); - + - printf("[SOCKET]: connected successfully\n"); + printf("[SOCKET]: connected successfully. latency: %dms\n", disk->latency_ms); } - + static void test_disk_read_callback(void* opaque) { @@ -254,13 +244,10 @@ static void test_disk_read_callback(void* opaque) { - - + + static SCSIRequest* test_disk_alloc_req(SCSIDevice* scsi_dev, uint32_t tag, uint32_t lun, uint8_t* request_buf, void* hba_private) { - TestDisk* disk = DO_UPCAST(TestDisk, parent_scsi_dev, scsi_dev); - uint64_t max_xfer_size = disk->max_xfer_size; - SCSIRequest* scsi_req = scsi_req_alloc(&test_disk_req_ops, scsi_dev, tag, lun, hba_private); TestDiskReq* disk_req = DO_UPCAST(TestDiskReq, parent_scsi_req, scsi_req); - + - disk_req->response_buf = g_malloc0(max_xfer_size); + disk_req->response_buf = g_malloc0(MAX_XFER_SIZE); - + return scsi_req; } diff --git a/hw/scsi/test_disk.h b/hw/scsi/test_disk.h @@ -1306,13 +1306,13 @@ index 568f975961..91aecea9b1 100644 @@ -16,8 +16,6 @@ struct TestDisk { uint64_t size; uint64_t block_size; - + - uint64_t max_xfer_size; - uint32_t latency_ms; - + QEMUTimer* socket_connect_timer; --- +-- 2.53.0 @@ -1336,12 +1336,12 @@ index a7fa2a4a77..7e5c1d5de4 100644 static void test_disk_socket_connected(QIOTask* task, gpointer opaque); +static gboolean test_disk_socket_watch(QIOChannel* ioc, GIOCondition condition, gpointer opaque); static void test_disk_read_callback(void* opaque); - - + + @@ -79,6 +80,14 @@ static int32_t test_disk_send_command(SCSIRequest* scsi_req, uint8_t* request_bu return 0; } - + + if (disk->unit_attention) { + disk->unit_attention = false; + @@ -1359,7 +1359,7 @@ index a7fa2a4a77..7e5c1d5de4 100644 } - disk->latency_ms = be32_to_cpu(latency_be); - + + qio_channel_add_watch(QIO_CHANNEL(disk->socket), G_IO_IN | G_IO_HUP | G_IO_ERR, test_disk_socket_watch, disk, NULL); + + disk->unit_attention = true; @@ -1367,7 +1367,7 @@ index a7fa2a4a77..7e5c1d5de4 100644 + printf("[SOCKET]: connected successfully. latency: %dms\n", disk->latency_ms); } - + +static gboolean test_disk_socket_watch(QIOChannel* ioc, GIOCondition condition, gpointer opaque) { + TestDisk* disk = opaque; + @@ -1399,17 +1399,17 @@ index a7fa2a4a77..7e5c1d5de4 100644 + static void test_disk_read_callback(void* opaque) { TestDiskReq* req = opaque; - + @@ -272,9 +314,10 @@ static void test_disk_realize(SCSIDevice* scsi_dev, Error** errp) { scsi_dev->blocksize = (int)disk->block_size; - + disk->latency_ms = 0; + disk->unit_attention = false; - + disk->socket_connect_timer = timer_new_ms(QEMU_CLOCK_REALTIME, test_disk_socket_start_connection, disk); - test_disk_socket_start_connection(disk); + timer_mod(disk->socket_connect_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 1000); - + printf("[DISK]: realized\n"); } diff --git a/hw/scsi/test_disk.h b/hw/scsi/test_disk.h @@ -1417,16 +1417,16 @@ index 91aecea9b1..1a9c70eacf 100644 --- a/hw/scsi/test_disk.h +++ b/hw/scsi/test_disk.h @@ -20,6 +20,8 @@ struct TestDisk { - + QEMUTimer* socket_connect_timer; bool connecting; + + bool unit_attention; }; - + #endif //HW_SCSI_TEST_DISK_H \ No newline at end of file --- +-- 2.53.0 @@ -1554,7 +1554,7 @@ index 0000000000..73808f620f +#define STRUCTS_H + +#endif //STRUCTS_H --- +-- 2.53.0 @@ -1578,16 +1578,16 @@ index bf4e523bee..dca2d12727 100644 #include +#include #include - + +#include "structs.h" + - + int main() { sockaddr_in addr = { @@ -26,37 +29,29 @@ int main() { - + listen(fd, 1); - + - uint32_t latency_ms = 1; - - uint8_t latency_ms_be[4]; @@ -1596,12 +1596,12 @@ index bf4e523bee..dca2d12727 100644 - latency_ms_be[2] = latency_ms >> 8; - latency_ms_be[3] = latency_ms; + uint32_t latency_ms = 5; - + while (true) { int client_fd = accept(fd, nullptr, nullptr); std::cout << std::format("---- New connection. Latency: {}ms ----", latency_ms) << std::endl; - send(client_fd, latency_ms_be, sizeof(latency_ms_be), 0); - + - char buffer[1024] = {}; while (true) { - ssize_t bytes = recv(client_fd, buffer, sizeof(buffer), 0); @@ -1611,11 +1611,11 @@ index bf4e523bee..dca2d12727 100644 + if (bytes <= 0) { break; } - + - std::string str(static_cast(bytes), '\0'); - std::memcpy(str.data(), buffer, bytes); + std::cout << "Received: command = 0x" << std::hex << std::setw(2) << std::setfill('0') << static_cast(server_req.command) << std::endl; - + - if (str.empty()) { - break; - } @@ -1625,7 +1625,7 @@ index bf4e523bee..dca2d12727 100644 + send(client_fd, &server_res, sizeof(server_res), 0); + + std::this_thread::sleep_for(std::chrono::milliseconds(50)); - + - // for (const char ch: str) { - // std::cout << "0x" << std::hex << std::setw(2) << std::setfill('0') << static_cast(ch) << ' '; - // } @@ -1641,7 +1641,7 @@ index 73808f620f..7cca000034 100644 @@ -1,4 +1,12 @@ #ifndef STRUCTS_H #define STRUCTS_H - + +typedef struct ServerRequest { + uint8_t command; +} ServerRequest; @@ -1651,7 +1651,7 @@ index 73808f620f..7cca000034 100644 +} ServerResponse; + #endif //STRUCTS_H --- +-- 2.53.0 @@ -1677,13 +1677,13 @@ index 7e5c1d5de4..4124631d23 100644 #include "scsi/constants.h" +#include "contrib/test_disk-server/structs.h" +#include "system/cpu-timers.h" - + #define MAX_XFER_SIZE (1 * 1024 * 1024) - + @@ -83,7 +86,7 @@ static int32_t test_disk_send_command(SCSIRequest* scsi_req, uint8_t* request_bu if (disk->unit_attention) { disk->unit_attention = false; - + - scsi_req_build_sense(scsi_req, SENSE_CODE(CAPACITY_CHANGED)); + scsi_req_build_sense(scsi_req, SENSE_CODE(MEDIUM_CHANGED)); scsi_req_complete(scsi_req, CHECK_CONDITION); @@ -1692,7 +1692,7 @@ index 7e5c1d5de4..4124631d23 100644 @@ -151,10 +154,63 @@ static int32_t test_disk_send_command(SCSIRequest* scsi_req, uint8_t* request_bu break; } - + + uint32_t latency_ms = 0; + + // --- @@ -1752,13 +1752,13 @@ index 7e5c1d5de4..4124631d23 100644 - - timer_mod(disk_req->timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + disk->latency_ms); + timer_mod(disk_req->timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + latency_ms); - + break; - + @@ -223,22 +279,12 @@ static void test_disk_socket_connected(QIOTask* task, gpointer opaque) { return; } - + - uint32_t latency_be = 0; - if (qio_channel_read_all(QIO_CHANNEL(disk->socket), (char*) &latency_be, sizeof(latency_be), NULL) < 0) { - object_unref(OBJECT(disk->socket)); @@ -1771,23 +1771,23 @@ index 7e5c1d5de4..4124631d23 100644 - - qio_channel_add_watch(QIO_CHANNEL(disk->socket), G_IO_IN | G_IO_HUP | G_IO_ERR, test_disk_socket_watch, disk, NULL); + disk->socket_watcher_tag = qio_channel_add_watch(QIO_CHANNEL(disk->socket), G_IO_IN | G_IO_HUP | G_IO_ERR, test_disk_socket_watch, disk, NULL); - + disk->unit_attention = true; - scsi_device_report_change(&disk->parent_scsi_dev, SENSE_CODE(CAPACITY_CHANGED)); + scsi_device_report_change(&disk->parent_scsi_dev, SENSE_CODE(MEDIUM_CHANGED)); - + - printf("[SOCKET]: connected successfully. latency: %dms\n", disk->latency_ms); + printf("[SOCKET]: connected successfully\n"); } - + static gboolean test_disk_socket_watch(QIOChannel* ioc, GIOCondition condition, gpointer opaque) { @@ -313,7 +359,6 @@ static void test_disk_realize(SCSIDevice* scsi_dev, Error** errp) { scsi_dev->type = TYPE_DISK; scsi_dev->blocksize = (int)disk->block_size; - + - disk->latency_ms = 0; disk->unit_attention = false; - + disk->socket_connect_timer = timer_new_ms(QEMU_CLOCK_REALTIME, test_disk_socket_start_connection, disk); diff --git a/hw/scsi/test_disk.h b/hw/scsi/test_disk.h index 1a9c70eacf..acc1285d62 100644 @@ -1796,20 +1796,20 @@ index 1a9c70eacf..acc1285d62 100644 @@ -16,12 +16,12 @@ struct TestDisk { uint64_t size; uint64_t block_size; - + - uint32_t latency_ms; - QEMUTimer* socket_connect_timer; bool connecting; - + bool unit_attention; + + guint socket_watcher_tag; }; - + #endif //HW_SCSI_TEST_DISK_H \ No newline at end of file --- +-- 2.53.0 @@ -1833,7 +1833,7 @@ index 4124631d23..216d38a394 100644 +static void test_disk_write_data(SCSIRequest* scsi_req); static uint8_t* test_disk_get_buf(SCSIRequest* scsi_req); static void test_disk_free_req(SCSIRequest* scsi_req); - + @@ -39,6 +40,7 @@ static const SCSIReqOps test_disk_req_ops = { .size = sizeof(TestDiskReq), .send_command = test_disk_send_command, @@ -1848,48 +1848,48 @@ index 4124631d23..216d38a394 100644 static gboolean test_disk_socket_watch(QIOChannel* ioc, GIOCondition condition, gpointer opaque); -static void test_disk_read_callback(void* opaque); +static void test_disk_io_callback(void* opaque); - - + + // ============================================================================ @@ -121,7 +123,7 @@ static int32_t test_disk_send_command(SCSIRequest* scsi_req, uint8_t* request_bu memset(disk_req->response_buf, 0, 4); - + disk_req->response_buf[0] = 3; // length - disk_req->response_buf[2] = 0x80; // write protection + // disk_req->response_buf[2] = 0x80; // write protection - + scsi_req_data(scsi_req, 4); break; @@ -131,13 +133,15 @@ static int32_t test_disk_send_command(SCSIRequest* scsi_req, uint8_t* request_bu - + disk_req->response_buf[0] = 0; disk_req->response_buf[1] = 6; - disk_req->response_buf[3] = 0x80; + // disk_req->response_buf[3] = 0x80; - + scsi_req_data(scsi_req, 8); break; - + case 0x08: // READ (6) case 0x28: // READ (10) + case 0x0A: // WRITE (6) + case 0x2A: // WRITE (10) - + int req_target_len = (int)scsi_req->cmd.xfer; - + @@ -209,7 +213,7 @@ static int32_t test_disk_send_command(SCSIRequest* scsi_req, uint8_t* request_bu } - + disk_req->response_buf_len = req_target_len; - disk_req->timer = timer_new_ms(QEMU_CLOCK_REALTIME, test_disk_read_callback, disk_req); + disk_req->timer = timer_new_ms(QEMU_CLOCK_REALTIME, test_disk_io_callback, disk_req); timer_mod(disk_req->timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + latency_ms); - + break; @@ -229,6 +233,10 @@ static void test_disk_read_data(SCSIRequest* scsi_req) { scsi_req_complete(scsi_req, GOOD); } - + +static void test_disk_write_data(SCSIRequest* scsi_req) { + scsi_req_complete(scsi_req, GOOD); +} @@ -1900,16 +1900,16 @@ index 4124631d23..216d38a394 100644 @@ -316,10 +324,9 @@ static gboolean test_disk_socket_watch(QIOChannel* ioc, GIOCondition condition, return TRUE; } - + -static void test_disk_read_callback(void* opaque) { +static void test_disk_io_callback(void* opaque) { TestDiskReq* req = opaque; - + - // read request returns zeros memset(req->response_buf, 0, req->response_buf_len); scsi_req_data(&req->parent_scsi_req, req->response_buf_len); - --- + +-- 2.53.0 @@ -1927,42 +1927,42 @@ index dca2d12727..65f3c59cc8 100644 --- a/contrib/test_disk-server/server.cpp +++ b/contrib/test_disk-server/server.cpp @@ -29,11 +29,12 @@ int main() { - + listen(fd, 1); - + - uint32_t latency_ms = 5; + uint32_t read_latency_ms = 5; + uint32_t write_latency_ms = 5; - + while (true) { int client_fd = accept(fd, nullptr, nullptr); - std::cout << std::format("---- New connection. Latency: {}ms ----", latency_ms) << std::endl; + std::cout << std::format("---- New connection ---- ") << std::endl; - + while (true) { ServerRequest server_req; @@ -42,16 +43,16 @@ int main() { break; } - + - std::cout << "Received: command = 0x" << std::hex << std::setw(2) << std::setfill('0') << static_cast(server_req.command) << std::endl; + std::cout << std::format("Received: command = 0x{:02x}", static_cast(server_req.command)) << std::endl; - + ServerResponse server_res; - server_res.latency = latency_ms; + server_res.latency = server_req.command == 0x28 || server_req.command == 0x08 ? read_latency_ms : write_latency_ms; - + send(client_fd, &server_res, sizeof(server_res), 0); - + - std::this_thread::sleep_for(std::chrono::milliseconds(50)); + // std::this_thread::sleep_for(std::chrono::milliseconds(50)); - + - std::cout << "Sent: latency = " << latency_ms << std::endl; + std::cout << "Sent: latency = " << server_res.latency << std::endl; } std::cout << "---- Connection closed ----" << std::endl; } --- +-- 2.53.0 @@ -1984,7 +1984,7 @@ index 216d38a394..6ab9300711 100644 @@ -85,14 +85,6 @@ static int32_t test_disk_send_command(SCSIRequest* scsi_req, uint8_t* request_bu return 0; } - + - if (disk->unit_attention) { - disk->unit_attention = false; - @@ -1999,37 +1999,37 @@ index 216d38a394..6ab9300711 100644 @@ -191,9 +183,11 @@ static int32_t test_disk_send_command(SCSIRequest* scsi_req, uint8_t* request_bu } } - + - disk->socket_watcher_tag = qio_channel_add_watch(QIO_CHANNEL(disk->socket), G_IO_IN | G_IO_HUP | G_IO_ERR, test_disk_socket_watch, disk, NULL); + if (socket_alive) { + disk->socket_watcher_tag = qio_channel_add_watch(QIO_CHANNEL(disk->socket), G_IO_IN | G_IO_HUP | G_IO_ERR, test_disk_socket_watch, disk, NULL); - + - qio_channel_set_blocking(QIO_CHANNEL(disk->socket), false, NULL); + qio_channel_set_blocking(QIO_CHANNEL(disk->socket), false, NULL); + } - + cpu_enable_ticks(); // --- @@ -202,9 +196,6 @@ static int32_t test_disk_send_command(SCSIRequest* scsi_req, uint8_t* request_bu object_unref(OBJECT(disk->socket)); disk->socket = NULL; - + - g_source_remove(disk->socket_watcher_tag); - disk->socket_watcher_tag = 0; - timer_mod(disk->socket_connect_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 5000); - + scsi_req_build_sense(scsi_req, SENSE_CODE(NOT_READY)); @@ -289,9 +280,6 @@ static void test_disk_socket_connected(QIOTask* task, gpointer opaque) { - + disk->socket_watcher_tag = qio_channel_add_watch(QIO_CHANNEL(disk->socket), G_IO_IN | G_IO_HUP | G_IO_ERR, test_disk_socket_watch, disk, NULL); - + - disk->unit_attention = true; - scsi_device_report_change(&disk->parent_scsi_dev, SENSE_CODE(MEDIUM_CHANGED)); - printf("[SOCKET]: connected successfully\n"); } - + @@ -314,10 +302,10 @@ static gboolean test_disk_socket_watch(QIOChannel* ioc, GIOCondition condition, if (disk->socket) { object_unref(OBJECT(disk->socket)); @@ -2037,21 +2037,21 @@ index 216d38a394..6ab9300711 100644 - - timer_mod(disk->socket_connect_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 1000); } - + + disk->socket_watcher_tag = 0; + timer_mod(disk->socket_connect_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 1000); return FALSE; } - + @@ -366,8 +354,6 @@ static void test_disk_realize(SCSIDevice* scsi_dev, Error** errp) { scsi_dev->type = TYPE_DISK; scsi_dev->blocksize = (int)disk->block_size; - + - disk->unit_attention = false; - disk->socket_connect_timer = timer_new_ms(QEMU_CLOCK_REALTIME, test_disk_socket_start_connection, disk); timer_mod(disk->socket_connect_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 1000); - + diff --git a/hw/scsi/test_disk.h b/hw/scsi/test_disk.h index acc1285d62..4ef28ee37a 100644 --- a/hw/scsi/test_disk.h @@ -2059,13 +2059,13 @@ index acc1285d62..4ef28ee37a 100644 @@ -19,8 +19,6 @@ struct TestDisk { QEMUTimer* socket_connect_timer; bool connecting; - + - bool unit_attention; - guint socket_watcher_tag; }; - --- + +-- 2.53.0 @@ -2095,17 +2095,17 @@ index 6ab9300711..3529ce1288 100644 +#include "system/block-backend-global-state.h" #include "system/cpu-timers.h" +#include "system/runstate.h" - + #define MAX_XFER_SIZE (1 * 1024 * 1024) - + @@ -50,9 +54,19 @@ static const SCSIReqOps test_disk_req_ops = { static void test_disk_socket_start_connection(void* opaque); static void test_disk_socket_connected(QIOTask* task, gpointer opaque); static gboolean test_disk_socket_watch(QIOChannel* ioc, GIOCondition condition, gpointer opaque); +static void test_disk_bh_routine(void* opaque); static void test_disk_io_callback(void* opaque); - - + + +// Helper structs +typedef struct TestDiskBHArgs { + TestDisk* disk; @@ -2116,12 +2116,12 @@ index 6ab9300711..3529ce1288 100644 + + // ============================================================================ - - + + @@ -150,62 +164,15 @@ static int32_t test_disk_send_command(SCSIRequest* scsi_req, uint8_t* request_bu break; } - + - uint32_t latency_ms = 0; - - // --- @@ -2142,7 +2142,7 @@ index 6ab9300711..3529ce1288 100644 + args->scsi_req = scsi_req; + args->command = command; + args->req_target_len = req_target_len; - + - bool socket_alive = true; - if (qio_channel_write_all(QIO_CHANNEL(disk->socket), &server_req, sizeof(server_req), NULL) == -1) { - socket_alive = false; @@ -2166,7 +2166,7 @@ index 6ab9300711..3529ce1288 100644 - qio_channel_set_blocking(QIO_CHANNEL(disk->socket), false, NULL); - } + scsi_req_ref(scsi_req); - + - cpu_enable_ticks(); - // --- - @@ -2185,13 +2185,13 @@ index 6ab9300711..3529ce1288 100644 - disk_req->timer = timer_new_ms(QEMU_CLOCK_REALTIME, test_disk_io_callback, disk_req); - timer_mod(disk_req->timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + latency_ms); + aio_bh_schedule_oneshot(qemu_get_aio_context(), test_disk_bh_routine, args); - + break; - + @@ -312,6 +279,73 @@ static gboolean test_disk_socket_watch(QIOChannel* ioc, GIOCondition condition, return TRUE; } - + +static void test_disk_bh_routine(void* opaque) { + TestDiskBHArgs* args = opaque; + @@ -2261,17 +2261,17 @@ index 6ab9300711..3529ce1288 100644 + static void test_disk_io_callback(void* opaque) { TestDiskReq* req = opaque; - + @@ -354,6 +388,8 @@ static void test_disk_realize(SCSIDevice* scsi_dev, Error** errp) { scsi_dev->type = TYPE_DISK; scsi_dev->blocksize = (int)disk->block_size; - + + scsi_dev->conf.blk = blk_new(qemu_get_aio_context(), BLK_PERM_ALL, BLK_PERM_ALL); + disk->socket_connect_timer = timer_new_ms(QEMU_CLOCK_REALTIME, test_disk_socket_start_connection, disk); timer_mod(disk->socket_connect_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 1000); - --- + +-- 2.53.0 @@ -2294,20 +2294,20 @@ index 65f3c59cc8..a7a4148459 100644 @@ -43,10 +43,14 @@ int main() { break; } - + - std::cout << std::format("Received: command = 0x{:02x}", static_cast(server_req.command)) << std::endl; + if (server_req.source != SOURCE_TEST_DISK) { + continue; + } + + std::cout << std::format("Received: id = {}, command = 0x{:02x}", server_req.test_disk_payload.disk_id, static_cast(server_req.test_disk_payload.command)) << std::endl; - + ServerResponse server_res; - server_res.latency = server_req.command == 0x28 || server_req.command == 0x08 ? read_latency_ms : write_latency_ms; + server_res.latency = server_req.test_disk_payload.command == 0x28 || server_req.test_disk_payload.command == 0x08 ? read_latency_ms : write_latency_ms; - + send(client_fd, &server_res, sizeof(server_res), 0); - + diff --git a/contrib/test_disk-server/structs.h b/contrib/test_disk-server/structs.h index 7cca000034..14b180cc18 100644 --- a/contrib/test_disk-server/structs.h @@ -2315,7 +2315,7 @@ index 7cca000034..14b180cc18 100644 @@ -1,11 +1,29 @@ #ifndef STRUCTS_H #define STRUCTS_H - + -typedef struct ServerRequest { +#define SOURCE_TEST_DISK 1 +#define SOURCE_CHANGE_PARAMETERS 2 @@ -2337,12 +2337,12 @@ index 7cca000034..14b180cc18 100644 + TestDiskPayload test_disk_payload; + ChangeParametersPayload change_parameters_payload; } ServerRequest; - + typedef struct ServerResponse { + uint8_t error_code; uint32_t latency; } ServerResponse; - + diff --git a/hw/scsi/test_disk.c b/hw/scsi/test_disk.c index 3529ce1288..37403eda0d 100644 --- a/hw/scsi/test_disk.c @@ -2353,12 +2353,12 @@ index 3529ce1288..37403eda0d 100644 DEFINE_PROP_SIZE("bs", TestDisk, block_size, 512), + DEFINE_PROP_UINT8("disk-id", TestDisk, id, 0) }; - - + + @@ -293,7 +294,11 @@ static void test_disk_bh_routine(void* opaque) { - + vm_stop(RUN_STATE_PAUSED); // ii. pause vm - + - ServerRequest server_req = {.command = args->command}; + ServerRequest server_req = { + .source = SOURCE_TEST_DISK, @@ -2366,7 +2366,7 @@ index 3529ce1288..37403eda0d 100644 + .test_disk_payload.command = args->command + }; ServerResponse server_res; - + qio_channel_set_blocking(QIO_CHANNEL(disk->socket), true, NULL); // iii. set socket blocking true diff --git a/hw/scsi/test_disk.h b/hw/scsi/test_disk.h index 4ef28ee37a..a2deee5318 100644 @@ -2380,9 +2380,9 @@ index 4ef28ee37a..a2deee5318 100644 + uint8_t id; + QIOChannelSocket* socket; - + uint64_t size; --- +-- 2.53.0 @@ -2462,7 +2462,7 @@ index a7a4148459..2e095a0389 100644 -#include "structs.h" - +#include "server_toolkit/server_toolkit.h" - + int main() { - sockaddr_in addr = { - .sin_family = AF_INET, @@ -2484,18 +2484,18 @@ index a7a4148459..2e095a0389 100644 + if (server_fd == -1) { + return -1; + } - + - uint32_t read_latency_ms = 5; - uint32_t write_latency_ms = 5; + std::atomic read_latency_ms = 34; + std::atomic write_latency_ms = 7; - + - while (true) { - int client_fd = accept(fd, nullptr, nullptr); - std::cout << std::format("---- New connection ---- ") << std::endl; + run_server(server_fd, [&](const ServerRequest& server_req) { + ServerResponse server_res{}; - + - while (true) { - ServerRequest server_req; - ssize_t bytes = recv(client_fd, &server_req, sizeof(server_req), 0); @@ -2508,7 +2508,7 @@ index a7a4148459..2e095a0389 100644 - } + case SOURCE_TEST_DISK: + std::cout << std::format("Command: 0x{:02x}", server_req.test_disk_payload.command) << std::endl; - + - if (server_req.source != SOURCE_TEST_DISK) { - continue; - } @@ -2525,10 +2525,10 @@ index a7a4148459..2e095a0389 100644 + : write_latency_ms; + break; + } - + - // std::this_thread::sleep_for(std::chrono::milliseconds(50)); + // std::this_thread::sleep_for(std::chrono::milliseconds(150)); - + - std::cout << "Sent: latency = " << server_res.latency << std::endl; - } - std::cout << "---- Connection closed ----" << std::endl; @@ -2708,7 +2708,7 @@ index 0000000000..6d9845f770 +int change_server_parameters(const std::string& ip, uint16_t port, uint32_t read_latency_ms, uint32_t write_latency_ms); + +#endif //SERVER_H --- +-- 2.53.0 @@ -2730,7 +2730,7 @@ index a6ba9efd1d..3cefe8a805 100644 +#include + #include "server_toolkit/server_toolkit.h" - + int main() { - change_server_parameters("127.0.0.1", 31234, 10, 40); + uint32_t read_latency_ms, write_latency_ms; @@ -2754,9 +2754,9 @@ index 2e095a0389..3f703bd4f0 100644 +#include + #include "server_toolkit/server_toolkit.h" - + int main() { --- +-- 2.53.0 @@ -2782,21 +2782,21 @@ index 3ce451587f..2b8b9e6544 100644 --- a/contrib/test_disk-server/CMakeLists.txt +++ b/contrib/test_disk-server/CMakeLists.txt @@ -3,6 +3,11 @@ project(qemu_server LANGUAGES CXX) - + set(CMAKE_CXX_STANDARD 20) - + +include(cmake/cpm.cmake) +include(cmake/external.cmake) + +# ============================================================== + add_executable(${PROJECT_NAME}) - + target_sources(${PROJECT_NAME} @@ -26,7 +31,25 @@ target_sources(${PROJECT_NAME}_change_parameters server_toolkit/server_toolkit.cpp ) - + -target_include_directories(${PROJECT_NAME} - PUBLIC +target_include_directories(${PROJECT_NAME}_change_parameters @@ -2921,30 +2921,30 @@ index a58ce334ee..74e2822631 100644 +#endif + #include "server_toolkit.h" - + int create_server(const std::string& ip, uint16_t port) { @@ -51,7 +64,7 @@ int run_server(int server_fd, const Callback& callback) { } - + std::thread([client_fd, callback]() { - client_routine(client_fd, callback); + _client_routine(client_fd, callback); }).detach(); } - + @@ -60,7 +73,7 @@ int run_server(int server_fd, const Callback& callback) { - + // ============================================================================ - + -void client_routine(int client_fd, const Callback& callback) { +void _client_routine(int client_fd, const Callback& callback) { std::cout << "---- New connection ---- " << std::endl; - + while (true) { @@ -70,7 +83,15 @@ void client_routine(int client_fd, const Callback& callback) { break; } - + - ServerResponse server_res = callback(server_req); + ServerResponse server_res; +#ifdef PYTHON_BINDINGS @@ -2955,7 +2955,7 @@ index a58ce334ee..74e2822631 100644 +#else + server_res = callback(server_req); +#endif - + if (send(client_fd, &server_res, sizeof(server_res), 0) == -1) { std::cerr << "Unable to send response to client socket: " << strerror(errno) << std::endl; diff --git a/contrib/test_disk-server/server_toolkit/server_toolkit.h b/contrib/test_disk-server/server_toolkit/server_toolkit.h @@ -2965,7 +2965,7 @@ index 6d9845f770..04f9cf6952 100644 @@ -1,18 +1,10 @@ #ifndef SERVER_H #define SERVER_H - + -#include -#include -#include @@ -2979,19 +2979,19 @@ index 6d9845f770..04f9cf6952 100644 -#include -#include +#include - + #include "structs.h" - + @@ -21,7 +13,7 @@ int create_server(const std::string& ip, uint16_t port); using Callback = std::function; int run_server(int server_fd, const Callback& callback); - + -void client_routine(int client_fd, const Callback& callback); +void _client_routine(int client_fd, const Callback& callback); - + int change_server_parameters(const std::string& ip, uint16_t port, uint32_t read_latency_ms, uint32_t write_latency_ms); - --- + +-- 2.53.0 @@ -3011,7 +3011,7 @@ index 37403eda0d..0c80ca6201 100644 @@ -94,7 +94,7 @@ static int32_t test_disk_send_command(SCSIRequest* scsi_req, uint8_t* request_bu return 0; } - + - if (!disk->socket) { + if (!disk->socket || disk->connecting) { scsi_req_build_sense(scsi_req, SENSE_CODE(NOT_READY)); @@ -3019,7 +3019,7 @@ index 37403eda0d..0c80ca6201 100644 return 0; @@ -265,15 +265,16 @@ static gboolean test_disk_socket_watch(QIOChannel* ioc, GIOCondition condition, } - + if (closed) { - printf("[SOCKET]: disconnected\n"); - @@ -3031,12 +3031,12 @@ index 37403eda0d..0c80ca6201 100644 + + timer_mod(disk->socket_connect_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 1000); } - + disk->socket_watcher_tag = 0; - timer_mod(disk->socket_connect_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 1000); return FALSE; } - + @@ -329,10 +330,12 @@ static void test_disk_bh_routine(void* opaque) { if (socket_alive) { disk->socket_watcher_tag = qio_channel_add_watch(QIO_CHANNEL(disk->socket), G_IO_IN | G_IO_HUP | G_IO_ERR, test_disk_socket_watch, disk, NULL); // i. add watcher @@ -3045,40 +3045,40 @@ index 37403eda0d..0c80ca6201 100644 + object_unref(OBJECT(disk->socket)); disk->socket = NULL; - + - timer_mod(disk->socket_connect_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 5000); + timer_mod(disk->socket_connect_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 1000); - + scsi_req_build_sense(scsi_req, SENSE_CODE(NOT_READY)); scsi_req_complete(scsi_req, CHECK_CONDITION); @@ -344,7 +347,7 @@ static void test_disk_bh_routine(void* opaque) { } - + disk_req->response_buf_len = args->req_target_len; - disk_req->timer = timer_new_ms(QEMU_CLOCK_VIRTUAL, test_disk_io_callback, disk_req); + timer_mod(disk_req->timer, qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + latency_ms); - + scsi_req_unref(scsi_req); @@ -356,9 +359,6 @@ static void test_disk_io_callback(void* opaque) { - + memset(req->response_buf, 0, req->response_buf_len); scsi_req_data(&req->parent_scsi_req, req->response_buf_len); - - timer_free(req->timer); - req->timer = NULL; } - - + + @@ -371,6 +371,8 @@ static SCSIRequest* test_disk_alloc_req(SCSIDevice* scsi_dev, uint32_t tag, uint - + disk_req->response_buf = g_malloc0(MAX_XFER_SIZE); - + + disk_req->timer = timer_new_ms(QEMU_CLOCK_VIRTUAL, test_disk_io_callback, disk_req); + return scsi_req; } - + @@ -381,6 +383,11 @@ static void test_disk_free_req(SCSIRequest* scsi_req) { g_free(disk_req->response_buf); disk_req->response_buf = NULL; @@ -3089,9 +3089,9 @@ index 37403eda0d..0c80ca6201 100644 + disk_req->timer = NULL; + } } - + static int test_disk_parse_cdb(SCSIDevice* scsi_dev, SCSICommand* cmd, uint8_t* request_buf, size_t request_buf_len, void* hba_private) { --- +-- 2.53.0 @@ -3156,7 +3156,7 @@ index 0000000000..55997c1fc2 + exit(-1) + + st.run_server(server_fd, callback) --- +-- 2.53.0 @@ -3181,9 +3181,9 @@ index 0c80ca6201..8f6c1a1857 100644 +#include "system/block-backend-io.h" #include "system/cpu-timers.h" #include "system/runstate.h" - + @@ -16,8 +17,7 @@ - + // Device properties static const Property test_disk_properties[] = { - DEFINE_PROP_SIZE("size", TestDisk, size, 1 * 1024 * 1024 * 1024), @@ -3191,17 +3191,17 @@ index 0c80ca6201..8f6c1a1857 100644 + DEFINE_BLOCK_PROPERTIES(TestDisk, parent_scsi_dev.conf), DEFINE_PROP_UINT8("disk-id", TestDisk, id, 0) }; - + @@ -29,14 +29,21 @@ typedef struct TestDiskReq { - + uint8_t* response_buf; int response_buf_len; + + struct iovec iov; + QEMUIOVector qiov; } TestDiskReq; - - + + // Request handlers static int32_t test_disk_send_command(SCSIRequest* scsi_req, uint8_t* request_buf); + @@ -3212,10 +3212,10 @@ index 0c80ca6201..8f6c1a1857 100644 static uint8_t* test_disk_get_buf(SCSIRequest* scsi_req); + static void test_disk_free_req(SCSIRequest* scsi_req); - - + + @@ -53,10 +60,18 @@ static const SCSIReqOps test_disk_req_ops = { - + // Helper functions static void test_disk_socket_start_connection(void* opaque); + @@ -3231,13 +3231,13 @@ index 0c80ca6201..8f6c1a1857 100644 +static void test_disk_latency_callback(void* opaque); + +static void test_disk_blk_aio_complete(void* opaque, int ret); - - + + // Helper structs @@ -100,6 +115,7 @@ static int32_t test_disk_send_command(SCSIRequest* scsi_req, uint8_t* request_bu return 0; } - + + int req_target_len; switch (command) { case 0x00: // TEST UNIT READY @@ -3245,7 +3245,7 @@ index 0c80ca6201..8f6c1a1857 100644 @@ -108,8 +124,15 @@ static int32_t test_disk_send_command(SCSIRequest* scsi_req, uint8_t* request_bu case 0x25: // READ CAPACITY (10) memset(disk_req->response_buf, 0, 8); - + - const uint32_t block_size = disk->block_size; - const uint32_t total_blocks = disk->size / disk->block_size; + const int64_t disk_size = blk_getlength(disk->parent_scsi_dev.conf.blk); @@ -3257,28 +3257,28 @@ index 0c80ca6201..8f6c1a1857 100644 + + const uint32_t block_size = disk->parent_scsi_dev.conf.logical_block_size; + const uint32_t total_blocks = disk_size / block_size; - + const uint32_t last_lba = total_blocks - 1; - + @@ -147,16 +170,23 @@ static int32_t test_disk_send_command(SCSIRequest* scsi_req, uint8_t* request_bu - + case 0x08: // READ (6) case 0x28: // READ (10) - case 0x0A: // WRITE (6) - case 0x2A: // WRITE (10) + req_target_len = (int) scsi_req->cmd.xfer; - + - int req_target_len = (int)scsi_req->cmd.xfer; + if (req_target_len > MAX_XFER_SIZE) { + printf("[READ]: target xfer size from request is greater than disk max xfer size\n"); - + - if (req_target_len == 0) { - scsi_req_complete(scsi_req, GOOD); + scsi_req_build_sense(scsi_req, SENSE_CODE(INVALID_FIELD)); + scsi_req_complete(scsi_req, CHECK_CONDITION); break; } - + + test_disk_bh_schedule_routine(disk, scsi_req); + break; + @@ -3288,11 +3288,11 @@ index 0c80ca6201..8f6c1a1857 100644 + if (req_target_len > MAX_XFER_SIZE) { printf("[READ]: target xfer size from request is greater than disk max xfer size\n"); - + @@ -165,16 +195,7 @@ static int32_t test_disk_send_command(SCSIRequest* scsi_req, uint8_t* request_bu break; } - + - TestDiskBHArgs* args = g_new0(TestDiskBHArgs, 1); - args->disk = disk; - args->scsi_req = scsi_req; @@ -3305,11 +3305,11 @@ index 0c80ca6201..8f6c1a1857 100644 - + scsi_req_data(scsi_req, req_target_len); // goes to test_disk_write_data() break; - + default: @@ -193,7 +214,11 @@ static void test_disk_read_data(SCSIRequest* scsi_req) { } - + static void test_disk_write_data(SCSIRequest* scsi_req) { - scsi_req_complete(scsi_req, GOOD); + // data now in disk_req->response_buf; @@ -3318,12 +3318,12 @@ index 0c80ca6201..8f6c1a1857 100644 + + test_disk_bh_schedule_routine(disk, scsi_req); } - + static uint8_t* test_disk_get_buf(SCSIRequest* scsi_req) { @@ -281,6 +306,25 @@ static gboolean test_disk_socket_watch(QIOChannel* ioc, GIOCondition condition, return TRUE; } - + +static void test_disk_bh_schedule_routine(TestDisk* disk, SCSIRequest* scsi_req) { + int req_target_len = (int) scsi_req->cmd.xfer; + @@ -3345,11 +3345,11 @@ index 0c80ca6201..8f6c1a1857 100644 + static void test_disk_bh_routine(void* opaque) { TestDiskBHArgs* args = opaque; - + @@ -354,11 +398,45 @@ static void test_disk_bh_routine(void* opaque) { g_free(args); } - + -static void test_disk_io_callback(void* opaque) { - TestDiskReq* req = opaque; +static void test_disk_latency_callback(void* opaque) { @@ -3385,7 +3385,7 @@ index 0c80ca6201..8f6c1a1857 100644 + scsi_req_complete(scsi_req, CHECK_CONDITION); + return; + } - + - memset(req->response_buf, 0, req->response_buf_len); - scsi_req_data(&req->parent_scsi_req, req->response_buf_len); + if (scsi_req->cmd.mode == SCSI_XFER_FROM_DEV) { @@ -3394,20 +3394,20 @@ index 0c80ca6201..8f6c1a1857 100644 + scsi_req_complete(scsi_req, GOOD); + } } - - + + @@ -371,7 +449,7 @@ static SCSIRequest* test_disk_alloc_req(SCSIDevice* scsi_dev, uint32_t tag, uint - + disk_req->response_buf = g_malloc0(MAX_XFER_SIZE); - + - disk_req->timer = timer_new_ms(QEMU_CLOCK_VIRTUAL, test_disk_io_callback, disk_req); + disk_req->timer = timer_new_ms(QEMU_CLOCK_VIRTUAL, test_disk_latency_callback, disk_req); - + return scsi_req; } @@ -395,12 +473,27 @@ static int test_disk_parse_cdb(SCSIDevice* scsi_dev, SCSICommand* cmd, uint8_t* } - + static void test_disk_realize(SCSIDevice* scsi_dev, Error** errp) { - TestDisk* disk = DO_UPCAST(TestDisk, parent_scsi_dev, scsi_dev); + if (!scsi_dev->conf.blk) { @@ -3426,14 +3426,14 @@ index 0c80ca6201..8f6c1a1857 100644 + if (!blkconf_apply_backend_options(&scsi_dev->conf, blk_is_writable(scsi_dev->conf.blk), true, errp)) { + return; + } - + scsi_dev->type = TYPE_DISK; - scsi_dev->blocksize = (int)disk->block_size; + scsi_dev->blocksize = scsi_dev->conf.logical_block_size; - + - scsi_dev->conf.blk = blk_new(qemu_get_aio_context(), BLK_PERM_ALL, BLK_PERM_ALL); + TestDisk* disk = DO_UPCAST(TestDisk, parent_scsi_dev, scsi_dev); - + disk->socket_connect_timer = timer_new_ms(QEMU_CLOCK_REALTIME, test_disk_socket_start_connection, disk); timer_mod(disk->socket_connect_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 1000); diff --git a/hw/scsi/test_disk.h b/hw/scsi/test_disk.h @@ -3442,7 +3442,7 @@ index a2deee5318..0e60ec71e8 100644 +++ b/hw/scsi/test_disk.h @@ -15,13 +15,8 @@ struct TestDisk { uint8_t id; - + QIOChannelSocket* socket; - - uint64_t size; @@ -3453,8 +3453,8 @@ index a2deee5318..0e60ec71e8 100644 - guint socket_watcher_tag; }; - --- + +-- 2.53.0 @@ -3485,10 +3485,10 @@ index 8f6c1a1857..dcddf9f9bb 100644 +#include "sysemu/block-backend.h" +#include "sysemu/cpu-timers.h" +#include "sysemu/runstate.h" - + #define MAX_XFER_SIZE (1 * 1024 * 1024) - - + + // Device properties -static const Property test_disk_properties[] = { +static Property test_disk_properties[] = { @@ -3498,28 +3498,28 @@ index 8f6c1a1857..dcddf9f9bb 100644 @@ -351,12 +349,12 @@ static void test_disk_bh_routine(void* opaque) { uint32_t latency_ms = 0; bool socket_alive = true; - + - if (qio_channel_write_all(QIO_CHANNEL(disk->socket), &server_req, sizeof(server_req), NULL) == -1) { + if (qio_channel_write_all(QIO_CHANNEL(disk->socket), (char*) &server_req, sizeof(server_req), NULL) == -1) { socket_alive = false; } - + if (socket_alive) { - if (qio_channel_read_all(QIO_CHANNEL(disk->socket), &server_res, sizeof(server_res), NULL) == -1) { + if (qio_channel_read_all(QIO_CHANNEL(disk->socket), (char*) &server_res, sizeof(server_res), NULL) == -1) { socket_alive = false; } - + @@ -505,7 +503,7 @@ static void test_disk_realize(SCSIDevice* scsi_dev, Error** errp) { // ============================================================================ - - + + -static void test_disk_class_init(ObjectClass* klass, const void* data) { +static void test_disk_class_init(ObjectClass* klass, void* data) { DeviceClass* dc = DEVICE_CLASS(klass); SCSIDeviceClass* sc = SCSI_DEVICE_CLASS(klass); - --- + +-- 2.53.0 @@ -3545,28 +3545,28 @@ index dcddf9f9bb..dfb1d5b5d3 100644 + DEFINE_PROP_UINT8("disk-id", TestDisk, id, 0), + DEFINE_PROP_BOOL("latency-stop-vm", TestDisk, stop_vm, true) }; - - + + @@ -335,7 +336,9 @@ static void test_disk_bh_routine(void* opaque) { disk->socket_watcher_tag = 0; } - + - vm_stop(RUN_STATE_PAUSED); // ii. pause vm + if (disk->stop_vm) { + vm_stop(RUN_STATE_PAUSED); // ii. pause vm + } - + ServerRequest server_req = { .source = SOURCE_TEST_DISK, @@ -367,7 +370,9 @@ static void test_disk_bh_routine(void* opaque) { qio_channel_set_blocking(QIO_CHANNEL(disk->socket), false, NULL); // iii. set socket blocking false } - + - vm_start(); // ii. start vm + if (disk->stop_vm) { + vm_start(); // ii. start vm + } - + if (socket_alive) { disk->socket_watcher_tag = qio_channel_add_watch(QIO_CHANNEL(disk->socket), G_IO_IN | G_IO_HUP | G_IO_ERR, test_disk_socket_watch, disk, NULL); // i. add watcher diff --git a/hw/scsi/test_disk.h b/hw/scsi/test_disk.h @@ -3580,10 +3580,10 @@ index 0e60ec71e8..c55d30ca92 100644 + + bool stop_vm; }; - + #endif //HW_SCSI_TEST_DISK_H \ No newline at end of file --- +-- 2.53.0 @@ -3610,18 +3610,18 @@ index 3cefe8a805..48463c99af 100644 --- a/contrib/test_disk-server/change_parameters.cpp +++ b/contrib/test_disk-server/change_parameters.cpp @@ -4,11 +4,26 @@ - + int main() { uint32_t read_latency_ms, write_latency_ms; + ErrorCode error_code; - + - std::cout << "Read latency: "; + std::cout << "Read latency (ms): "; std::cin >> read_latency_ms; - std::cout << "Write latency: "; + std::cout << "Write latency (ms): "; std::cin >> write_latency_ms; - + - change_server_parameters("127.0.0.1", 31234, read_latency_ms, write_latency_ms); + int t; + @@ -3646,13 +3646,13 @@ index 57e4256266..0f34e07ea8 100644 +++ b/contrib/test_disk-server/change_parameters.py @@ -1,7 +1,13 @@ import server_toolkit_py as st - + if __name__ == "__main__": - read_latency_ms = int(input("Read latency: ")) - write_latency_ms = int(input("Write latency: ")) + read_latency_ms = int(input("Read latency (ms): ")) + write_latency_ms = int(input("Write latency (ms): ")) - + - st.change_server_parameters("127.0.0.1", 31234, read_latency_ms, write_latency_ms) \ No newline at end of file + error_code = st.ErrorCode() @@ -3669,7 +3669,7 @@ index 3f703bd4f0..e61ead5aa4 100644 @@ -11,8 +11,12 @@ int main() { return -1; } - + - std::atomic read_latency_ms = 34; - std::atomic write_latency_ms = 7; + std::atomic read_latency_ms = 0; @@ -3678,7 +3678,7 @@ index 3f703bd4f0..e61ead5aa4 100644 + std::atomic sense_key = 0; + std::atomic asc = 0; + std::atomic ascq = 0; - + run_server(server_fd, [&](const ServerRequest& server_req) { ServerResponse server_res{}; @@ -21,6 +25,11 @@ int main() { @@ -3704,7 +3704,7 @@ index 3f703bd4f0..e61ead5aa4 100644 + break; } - + diff --git a/contrib/test_disk-server/server.py b/contrib/test_disk-server/server.py index 55997c1fc2..7e7ea48e3f 100644 --- a/contrib/test_disk-server/server.py @@ -3712,7 +3712,7 @@ index 55997c1fc2..7e7ea48e3f 100644 @@ -3,8 +3,12 @@ read_latency_ms = 0 write_latency_ms = 0 - + +sense_key = 0 +asc = 0 +ascq = 0 @@ -3720,30 +3720,30 @@ index 55997c1fc2..7e7ea48e3f 100644 def callback(server_req: st.ServerRequest) -> st.ServerResponse: - global read_latency_ms, write_latency_ms + global read_latency_ms, write_latency_ms, sense_key, asc, ascq - + server_res = st.ServerResponse() - + @@ -12,6 +16,10 @@ def callback(server_req: st.ServerRequest) -> st.ServerResponse: read_latency_ms = server_req.change_parameters_payload.read_latency_ms write_latency_ms = server_req.change_parameters_payload.write_latency_ms - + + sense_key = server_req.change_parameters_payload.error_code.key + asc = server_req.change_parameters_payload.error_code.asc + ascq = server_req.change_parameters_payload.error_code.ascq + elif server_req.source == st.SOURCE_TEST_DISK: print(f"Command: 0x{server_req.test_disk_payload.command:02X}") - + @@ -19,6 +27,10 @@ def callback(server_req: st.ServerRequest) -> st.ServerResponse: if server_req.test_disk_payload.command == 0x28 or server_req.test_disk_payload.command == 0x08 \ else write_latency_ms - + + server_res.error_code.key = sense_key + server_res.error_code.asc = asc + server_res.error_code.ascq = ascq + return server_res - + if __name__ == "__main__": diff --git a/contrib/test_disk-server/server_toolkit/bindings.cpp b/contrib/test_disk-server/server_toolkit/bindings.cpp index 29e04abc59..0d49cb6bd8 100644 @@ -3752,7 +3752,7 @@ index 29e04abc59..0d49cb6bd8 100644 @@ -21,10 +21,17 @@ PYBIND11_MODULE(server_toolkit_py, m) { .def_readwrite("sector", &TestDiskPayload::sector) .def_readwrite("req_target_len", &TestDiskPayload::req_target_len); - + + py::class_(m, "ErrorCode") + .def(py::init<>()) + .def_readwrite("key", &ErrorCode::key) @@ -3765,7 +3765,7 @@ index 29e04abc59..0d49cb6bd8 100644 - .def_readwrite("write_latency_ms", &ChangeParametersPayload::write_latency_ms); + .def_readwrite("write_latency_ms", &ChangeParametersPayload::write_latency_ms) + .def_readwrite("error_code", &ChangeParametersPayload::error_code); - + py::class_(m, "ServerRequest") .def(py::init<>()) @@ -41,5 +48,5 @@ PYBIND11_MODULE(server_toolkit_py, m) { @@ -3789,11 +3789,11 @@ index 74e2822631..b83457a337 100644 + << std::endl; } } - + @@ -107,7 +109,7 @@ void _client_routine(int client_fd, const Callback& callback) { - + // ============================================================================ - + -int change_server_parameters(const std::string& ip, uint16_t port, uint32_t read_latency_ms, uint32_t write_latency_ms) { +int change_server_parameters(const std::string& ip, uint16_t port, uint32_t read_latency_ms, uint32_t write_latency_ms, ErrorCode error_code) { // server address @@ -3806,7 +3806,7 @@ index 74e2822631..b83457a337 100644 + .error_code = error_code } }; - + if (send(fd, &server_req, sizeof(server_req), 0) == -1) { std::cerr << "Unable to send request to client socket: " << strerror(errno) << std::endl; } else { @@ -3816,19 +3816,19 @@ index 74e2822631..b83457a337 100644 + << ", error code = " << std::format("(0x{:02x}|0x{:02x}|0x{:02x})", server_req.change_parameters_payload.error_code.key, server_req.change_parameters_payload.error_code.asc, server_req.change_parameters_payload.error_code.ascq) + << std::endl; } - + close(fd); diff --git a/contrib/test_disk-server/server_toolkit/server_toolkit.h b/contrib/test_disk-server/server_toolkit/server_toolkit.h index 04f9cf6952..87010f6d27 100644 --- a/contrib/test_disk-server/server_toolkit/server_toolkit.h +++ b/contrib/test_disk-server/server_toolkit/server_toolkit.h @@ -15,6 +15,6 @@ int run_server(int server_fd, const Callback& callback); - + void _client_routine(int client_fd, const Callback& callback); - + -int change_server_parameters(const std::string& ip, uint16_t port, uint32_t read_latency_ms, uint32_t write_latency_ms); +int change_server_parameters(const std::string& ip, uint16_t port, uint32_t read_latency_ms, uint32_t write_latency_ms, ErrorCode error_code); - + #endif //SERVER_H diff --git a/contrib/test_disk-server/structs.h b/contrib/test_disk-server/structs.h index 14b180cc18..c94905d678 100644 @@ -3837,7 +3837,7 @@ index 14b180cc18..c94905d678 100644 @@ -4,6 +4,12 @@ #define SOURCE_TEST_DISK 1 #define SOURCE_CHANGE_PARAMETERS 2 - + +typedef struct ErrorCode { + uint8_t key; + uint8_t asc; @@ -3854,17 +3854,17 @@ index 14b180cc18..c94905d678 100644 + + ErrorCode error_code; } ChangeParametersPayload; - + typedef struct ServerRequest { @@ -23,7 +31,7 @@ typedef struct ServerRequest { } ServerRequest; - + typedef struct ServerResponse { - uint8_t error_code; + ErrorCode error_code; uint32_t latency; } ServerResponse; - + diff --git a/hw/scsi/test_disk.c b/hw/scsi/test_disk.c index dfb1d5b5d3..92d731683a 100644 --- a/hw/scsi/test_disk.c @@ -3878,29 +3878,29 @@ index dfb1d5b5d3..92d731683a 100644 #include "sysemu/cpu-timers.h" #include "sysemu/runstate.h" @@ -31,6 +32,8 @@ typedef struct TestDiskReq { - + struct iovec iov; QEMUIOVector qiov; + + SCSISense current_error_code; } TestDiskReq; - - + + @@ -395,6 +398,10 @@ static void test_disk_bh_routine(void* opaque) { - + disk_req->response_buf_len = args->req_target_len; - + + disk_req->current_error_code.key = server_res.error_code.key; + disk_req->current_error_code.asc = server_res.error_code.asc; + disk_req->current_error_code.ascq = server_res.error_code.ascq; + timer_mod(disk_req->timer, qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + latency_ms); - + scsi_req_unref(scsi_req); @@ -405,6 +412,12 @@ static void test_disk_latency_callback(void* opaque) { TestDiskReq* disk_req = opaque; SCSIRequest* scsi_req = &disk_req->parent_scsi_req; - + + if ((disk_req->current_error_code.key | disk_req->current_error_code.asc | disk_req->current_error_code.ascq) != 0) { + scsi_req_build_sense(scsi_req, disk_req->current_error_code); + scsi_req_complete(scsi_req, CHECK_CONDITION); @@ -3909,8 +3909,8 @@ index dfb1d5b5d3..92d731683a 100644 + TestDisk* disk = DO_UPCAST(TestDisk, parent_scsi_dev, disk_req->parent_scsi_req.dev); BlockBackend *blk = disk->parent_scsi_dev.conf.blk; - --- + +-- 2.53.0 @@ -3930,13 +3930,13 @@ index 92d731683a..4adcbcc8ce 100644 @@ -11,7 +11,7 @@ #include "sysemu/cpu-timers.h" #include "sysemu/runstate.h" - + -#define MAX_XFER_SIZE (1 * 1024 * 1024) +#define MAX_XFER_SIZE (32 * 1024 * 1024) - - + + // Device properties --- +-- 2.53.0 @@ -3962,7 +3962,7 @@ index 0d49cb6bd8..90137878f1 100644 .def_readwrite("write_latency_ms", &ChangeParametersPayload::write_latency_ms) + .def_readwrite("is_singleshot", &ChangeParametersPayload::is_singleshot) .def_readwrite("error_code", &ChangeParametersPayload::error_code); - + py::class_(m, "ServerRequest") @@ -48,5 +49,5 @@ PYBIND11_MODULE(server_toolkit_py, m) { // Functions @@ -3976,7 +3976,7 @@ index b83457a337..8ee18a283c 100644 --- a/contrib/test_disk-server/server_toolkit/server_toolkit.cpp +++ b/contrib/test_disk-server/server_toolkit/server_toolkit.cpp @@ -95,10 +95,16 @@ void _client_routine(int client_fd, const Callback& callback) { - + if (send(client_fd, &server_res, sizeof(server_res), 0) == -1) { std::cerr << "Unable to send response to client socket: " << strerror(errno) << std::endl; - } else { @@ -3992,11 +3992,11 @@ index b83457a337..8ee18a283c 100644 + << std::endl; } } - + @@ -109,7 +115,7 @@ void _client_routine(int client_fd, const Callback& callback) { - + // ============================================================================ - + -int change_server_parameters(const std::string& ip, uint16_t port, uint32_t read_latency_ms, uint32_t write_latency_ms, ErrorCode error_code) { +int change_server_parameters(const std::string& ip, uint16_t port, uint32_t read_latency_ms, uint32_t write_latency_ms, ErrorCode error_code, bool is_singleshot) { // server address @@ -4019,18 +4019,18 @@ index b83457a337..8ee18a283c 100644 + << ", is singleshot = " << static_cast(is_singleshot) << std::endl; } - + diff --git a/contrib/test_disk-server/server_toolkit/server_toolkit.h b/contrib/test_disk-server/server_toolkit/server_toolkit.h index 87010f6d27..d177998c38 100644 --- a/contrib/test_disk-server/server_toolkit/server_toolkit.h +++ b/contrib/test_disk-server/server_toolkit/server_toolkit.h @@ -15,6 +15,6 @@ int run_server(int server_fd, const Callback& callback); - + void _client_routine(int client_fd, const Callback& callback); - + -int change_server_parameters(const std::string& ip, uint16_t port, uint32_t read_latency_ms, uint32_t write_latency_ms, ErrorCode error_code); +int change_server_parameters(const std::string& ip, uint16_t port, uint32_t read_latency_ms, uint32_t write_latency_ms, ErrorCode error_code, bool is_singleshot = false); - + #endif //SERVER_H diff --git a/contrib/test_disk-server/structs.h b/contrib/test_disk-server/structs.h index c94905d678..4283505673 100644 @@ -4039,13 +4039,13 @@ index c94905d678..4283505673 100644 @@ -21,6 +21,8 @@ typedef struct ChangeParametersPayload { uint32_t read_latency_ms; uint32_t write_latency_ms; - + + bool is_singleshot; + ErrorCode error_code; } ChangeParametersPayload; - --- + +-- 2.53.0 @@ -4068,7 +4068,7 @@ index 48463c99af..e27c75b81c 100644 @@ -25,5 +25,10 @@ int main() { std::cin >> std::hex >> t; error_code.ascq = static_cast(t); - + - change_server_parameters("127.0.0.1", 31234, read_latency_ms, write_latency_ms, error_code); + bool is_singleshot; + @@ -4085,7 +4085,7 @@ index 0f34e07ea8..ffe5736745 100644 @@ -10,4 +10,6 @@ error_code.asc = int(input("ASC (in hex): "), 16) error_code.ascq = int(input("ASCQ (in hex): "), 16) - + - st.change_server_parameters("127.0.0.1", 31234, read_latency_ms, write_latency_ms, error_code) + is_singleshot = bool(input("Is singleshot (0 - false, 1 - true): ")) + @@ -4097,7 +4097,7 @@ index e61ead5aa4..8496b77521 100644 @@ -18,6 +18,12 @@ int main() { std::atomic asc = 0; std::atomic ascq = 0; - + + std::atomic is_singleshot = false; + + std::atomic prev_sense_key = 0; @@ -4106,11 +4106,11 @@ index e61ead5aa4..8496b77521 100644 + run_server(server_fd, [&](const ServerRequest& server_req) { ServerResponse server_res{}; - + @@ -26,6 +32,14 @@ int main() { read_latency_ms = server_req.change_parameters_payload.read_latency_ms; write_latency_ms = server_req.change_parameters_payload.write_latency_ms; - + + if (server_req.change_parameters_payload.is_singleshot) { + is_singleshot = true; + @@ -4125,7 +4125,7 @@ index e61ead5aa4..8496b77521 100644 @@ -43,6 +57,14 @@ int main() { server_res.error_code.asc = asc; server_res.error_code.ascq = ascq; - + + if (is_singleshot) { + is_singleshot = false; + @@ -4136,7 +4136,7 @@ index e61ead5aa4..8496b77521 100644 + break; } - + diff --git a/contrib/test_disk-server/server.py b/contrib/test_disk-server/server.py index 7e7ea48e3f..be3d19676b 100644 --- a/contrib/test_disk-server/server.py @@ -4144,7 +4144,7 @@ index 7e7ea48e3f..be3d19676b 100644 @@ -7,8 +7,14 @@ asc = 0 ascq = 0 - + +is_singleshot = False + +prev_sense_key = 0 @@ -4154,13 +4154,13 @@ index 7e7ea48e3f..be3d19676b 100644 def callback(server_req: st.ServerRequest) -> st.ServerResponse: - global read_latency_ms, write_latency_ms, sense_key, asc, ascq + global read_latency_ms, write_latency_ms, sense_key, asc, ascq, is_singleshot, prev_sense_key, prev_asc, prev_ascq - + server_res = st.ServerResponse() - + @@ -16,6 +22,13 @@ def callback(server_req: st.ServerRequest) -> st.ServerResponse: read_latency_ms = server_req.change_parameters_payload.read_latency_ms write_latency_ms = server_req.change_parameters_payload.write_latency_ms - + + if server_req.change_parameters_payload.is_singleshot: + is_singleshot = True + @@ -4174,7 +4174,7 @@ index 7e7ea48e3f..be3d19676b 100644 @@ -31,6 +44,13 @@ def callback(server_req: st.ServerRequest) -> st.ServerResponse: server_res.error_code.asc = asc server_res.error_code.ascq = ascq - + + if is_singleshot: + is_singleshot = False + @@ -4183,8 +4183,7 @@ index 7e7ea48e3f..be3d19676b 100644 + ascq = prev_ascq + return server_res - + if __name__ == "__main__": --- +-- 2.53.0 - diff --git a/layers/meta-image-tests/recipes-devtools/qemu/qemu/0001-qemu-Add-addition-environment-space-to-boot-loader-q.patch b/layers/meta-image-tests/recipes-devtools/qemu/qemu/0001-qemu-Add-addition-environment-space-to-boot-loader-q.patch index 5e21855c..2489aad2 100644 --- a/layers/meta-image-tests/recipes-devtools/qemu/qemu/0001-qemu-Add-addition-environment-space-to-boot-loader-q.patch +++ b/layers/meta-image-tests/recipes-devtools/qemu/qemu/0001-qemu-Add-addition-environment-space-to-boot-loader-q.patch @@ -27,6 +27,6 @@ index 664a2ae0a..5124b158a 100644 #define ENVP_NB_ENTRIES 16 -#define ENVP_ENTRY_SIZE 256 +#define ENVP_ENTRY_SIZE 1024 - + /* Hardware addresses */ #define FLASH_ADDRESS 0x1e000000ULL diff --git a/layers/meta-image-tests/recipes-devtools/qemu/qemu/0002-apic-fixup-fallthrough-to-PIC.patch b/layers/meta-image-tests/recipes-devtools/qemu/qemu/0002-apic-fixup-fallthrough-to-PIC.patch index bcb0d0c0..3aef8737 100644 --- a/layers/meta-image-tests/recipes-devtools/qemu/qemu/0002-apic-fixup-fallthrough-to-PIC.patch +++ b/layers/meta-image-tests/recipes-devtools/qemu/qemu/0002-apic-fixup-fallthrough-to-PIC.patch @@ -35,9 +35,9 @@ index 4186c57b3..43cd805a9 100644 @@ -759,7 +759,7 @@ int apic_accept_pic_intr(DeviceState *dev) APICCommonState *s = APIC(dev); uint32_t lvt0; - + - if (!s) + if (!s || !(s->spurious_vec & APIC_SV_ENABLE)) return -1; - + lvt0 = s->lvt[APIC_LVT_LINT0]; diff --git a/layers/meta-image-tests/recipes-devtools/qemu/qemu/0005-qemu-Add-some-user-space-mmap-tweaks-to-address-musl.patch b/layers/meta-image-tests/recipes-devtools/qemu/qemu/0005-qemu-Add-some-user-space-mmap-tweaks-to-address-musl.patch index 26ddf5e5..f26d7c1d 100644 --- a/layers/meta-image-tests/recipes-devtools/qemu/qemu/0005-qemu-Add-some-user-space-mmap-tweaks-to-address-musl.patch +++ b/layers/meta-image-tests/recipes-devtools/qemu/qemu/0005-qemu-Add-some-user-space-mmap-tweaks-to-address-musl.patch @@ -28,14 +28,14 @@ index e4bf5d5f3..04d920c11 100644 @@ -1105,12 +1105,16 @@ abi_long target_mremap(abi_ulong old_addr, abi_ulong old_size, int prot; void *host_addr; - + - if (!guest_range_valid_untagged(old_addr, old_size) || - ((flags & MREMAP_FIXED) && + if (!guest_range_valid_untagged(old_addr, old_size)) { + errno = EFAULT; + return -1; + } -+ ++ + if (((flags & MREMAP_FIXED) && !guest_range_valid_untagged(new_addr, new_size)) || ((flags & MREMAP_MAYMOVE) == 0 && @@ -44,4 +44,3 @@ index e4bf5d5f3..04d920c11 100644 + errno = EINVAL; return -1; } - diff --git a/layers/meta-image-tests/recipes-devtools/qemu/qemu/0006-qemu-Determinism-fixes.patch b/layers/meta-image-tests/recipes-devtools/qemu/qemu/0006-qemu-Determinism-fixes.patch index 059257bc..4d8e783f 100644 --- a/layers/meta-image-tests/recipes-devtools/qemu/qemu/0006-qemu-Determinism-fixes.patch +++ b/layers/meta-image-tests/recipes-devtools/qemu/qemu/0006-qemu-Determinism-fixes.patch @@ -21,7 +21,7 @@ index e8b72da3a..5cd86b142 100644 +++ b/scripts/decodetree.py @@ -1558,7 +1558,7 @@ def main(): toppat = ExcMultiPattern(0) - + for filename in args: - input_file = filename + input_file = os.path.relpath(filename) diff --git a/layers/meta-image-tests/recipes-devtools/qemu/qemu/0007-tests-meson.build-use-relative-path-to-refer-to-file.patch b/layers/meta-image-tests/recipes-devtools/qemu/qemu/0007-tests-meson.build-use-relative-path-to-refer-to-file.patch index cd919048..594a5404 100644 --- a/layers/meta-image-tests/recipes-devtools/qemu/qemu/0007-tests-meson.build-use-relative-path-to-refer-to-file.patch +++ b/layers/meta-image-tests/recipes-devtools/qemu/qemu/0007-tests-meson.build-use-relative-path-to-refer-to-file.patch @@ -21,7 +21,7 @@ index 490ab8182..d19ee5606 100644 --- a/tests/unit/meson.build +++ b/tests/unit/meson.build @@ -128,17 +128,17 @@ endif - + if have_system tests += { - 'ptimer-test': ['ptimer-test-stubs.c', meson.project_source_root() / 'hw/core/ptimer.c'], diff --git a/layers/meta-image-tests/recipes-devtools/qemu/qemu/0008-Define-MAP_SYNC-and-MAP_SHARED_VALIDATE-on-needed-li.patch b/layers/meta-image-tests/recipes-devtools/qemu/qemu/0008-Define-MAP_SYNC-and-MAP_SHARED_VALIDATE-on-needed-li.patch index 7bbd96df..bf129db3 100644 --- a/layers/meta-image-tests/recipes-devtools/qemu/qemu/0008-Define-MAP_SYNC-and-MAP_SHARED_VALIDATE-on-needed-li.patch +++ b/layers/meta-image-tests/recipes-devtools/qemu/qemu/0008-Define-MAP_SYNC-and-MAP_SHARED_VALIDATE-on-needed-li.patch @@ -24,7 +24,7 @@ index ed14f9c64..038f5b4b5 100644 @@ -10,14 +10,18 @@ * later. See the COPYING file in the top-level directory. */ - + +#include "qemu/osdep.h" #ifdef CONFIG_LINUX #include @@ -38,7 +38,7 @@ index ed14f9c64..038f5b4b5 100644 #define MAP_SHARED_VALIDATE 0x0 -#endif /* CONFIG_LINUX */ +#endif /* MAP_SHARED_VALIDATE */ - + -#include "qemu/osdep.h" #include "qemu/mmap-alloc.h" #include "qemu/host-utils.h" diff --git a/layers/meta-image-tests/recipes-devtools/qemu/qemu/0010-configure-lookup-meson-exutable-from-PATH.patch b/layers/meta-image-tests/recipes-devtools/qemu/qemu/0010-configure-lookup-meson-exutable-from-PATH.patch index f20851bf..b1c9e6f3 100644 --- a/layers/meta-image-tests/recipes-devtools/qemu/qemu/0010-configure-lookup-meson-exutable-from-PATH.patch +++ b/layers/meta-image-tests/recipes-devtools/qemu/qemu/0010-configure-lookup-meson-exutable-from-PATH.patch @@ -15,7 +15,7 @@ index d08b71f14..af0bd2c66 100755 @@ -958,12 +958,7 @@ mkvenv="$python ${source_path}/python/scripts/mkvenv.py" $mkvenv ensuregroup --dir "${source_path}/python/wheels" \ ${source_path}/pythondeps.toml meson || exit 1 - + -# At this point, we expect Meson to be installed and available. -# We expect mkvenv or pip to have created pyvenv/bin/meson for us. -# We ignore PATH completely here: we want to use the venv's Meson @@ -23,6 +23,5 @@ index d08b71f14..af0bd2c66 100755 - -meson="$(cd pyvenv/bin; pwd)/meson" +meson=`which meson` - + # Conditionally ensure Sphinx is installed. - diff --git a/layers/meta-image-tests/recipes-devtools/qemu/qemu/0011-qemu-Ensure-pip-and-the-python-venv-aren-t-used-for-.patch b/layers/meta-image-tests/recipes-devtools/qemu/qemu/0011-qemu-Ensure-pip-and-the-python-venv-aren-t-used-for-.patch index 4adb2e5f..00bde74a 100644 --- a/layers/meta-image-tests/recipes-devtools/qemu/qemu/0011-qemu-Ensure-pip-and-the-python-venv-aren-t-used-for-.patch +++ b/layers/meta-image-tests/recipes-devtools/qemu/qemu/0011-qemu-Ensure-pip-and-the-python-venv-aren-t-used-for-.patch @@ -35,17 +35,16 @@ index af0bd2c66..88dab6a95 100755 @@ -944,14 +944,14 @@ python="$(command -v "$python")" echo "python determined to be '$python'" echo "python version: $($python --version)" - + -python="$($python -B "${source_path}/python/scripts/mkvenv.py" create pyvenv)" +python=python3 if test "$?" -ne 0 ; then error_exit "python venv creation failed" fi - + # Suppress writing compiled files python="$python -B" -mkvenv="$python ${source_path}/python/scripts/mkvenv.py" +mkvenv=true - + # Finish preparing the virtual environment using vendored .whl files - diff --git a/layers/meta-image-tests/recipes-devtools/qemu/qemu/run-ptest b/layers/meta-image-tests/recipes-devtools/qemu/qemu/run-ptest old mode 100644 new mode 100755 index f9a4e8fb..8b9f3a0f --- a/layers/meta-image-tests/recipes-devtools/qemu/qemu/run-ptest +++ b/layers/meta-image-tests/recipes-devtools/qemu/qemu/run-ptest @@ -4,9 +4,9 @@ # ptestdir=$(dirname "$(readlink -f "$0")") -export SRC_PATH=$ptestdir +export SRC_PATH="$ptestdir" -cd $ptestdir/tests +cd "$ptestdir"/tests || return tests=$(find . -name "test-*" ! -name "*.p") for f in $tests; do $f | sed '/^ok/ s/ok/PASS:/g'