Merge tag 'pull-misc-2026-07-14' of https://repo.or.cz/qemu/armbru into staging

Miscellaneous patches for 2026-07-14

# -----BEGIN PGP SIGNATURE-----
#
# iQJGBAABCgAwFiEENUvIs9frKmtoZ05fOHC0AOuRhlMFAmpV/gsSHGFybWJydUBy
# ZWRoYXQuY29tAAoJEDhwtADrkYZTi/oP/1B9i3crkWVFgzZ3YyvDFG5aAPtMZ4MU
# TFEh3xKtzOMIu/jvFvapSNAHG/4kPuiHTJXsi8VYtvzoKhhJggczSJI1K3JSxEtg
# 8j7RVKevIwEk3kBR4+L3yegGerNJd2cFfyJLK6DzljxqwRd1bo6/iigxwrg5WzG6
# YDyPqeOtrOBVDjjRXH5T1k+rL5HgmUp5JQWsc+gZSqdVSLWPnTEOtg1Xs1ipEIRm
# JRiKG/5ADwao8Ml6iGmmQQsFvNe6EYWbm6NGdavzuD7mp6Roxuq4CtxD+6jT3roK
# GxQKsig2Un2vBWgvI9ZokwULcpB4esiUAvz9pPYg9WZB/PXi24Fjivhjm2w69E8N
# QIU1TimQFkR0nIFkChYPAfQvBbzdTGv71dZ4/vxVI5ha7kAYw3xWMAGuMhX4FivW
# mEGu7j999IlRSWZYBLUsbsBCM4gXuxzPjs9Cp8xghLi9mnF+MITz0zCHKx2FQwE7
# 06yTeEvjPe8LWqx6mfeFq/k4c+Hpiib+D4cOyIBLqldGjFowbaaTmLP8W6VUGM07
# QsuPIE0Py9EOBwOXqYTYrv/SsBLPp03j3Om/P4Bo6E3Zhms0gF8+WXACyJX1t1H0
# V+GtgloccjgfP9hynHa5RaoFeDxOZwksV6cxbuWm38yhhcyaq0iZMXqGWjfO4h5f
# HgNAI8X660MA
# =fEwp
# -----END PGP SIGNATURE-----
# gpg: Signature made Tue 14 Jul 2026 10:14:51 BST
# gpg:                using RSA key 354BC8B3D7EB2A6B68674E5F3870B400EB918653
# gpg:                issuer "armbru@redhat.com"
# gpg: Good signature from "Markus Armbruster <armbru@redhat.com>" [full]
# gpg:                 aka "Markus Armbruster <armbru@pond.sub.org>" [full]
# Primary key fingerprint: 354B C8B3 D7EB 2A6B 6867  4E5F 3870 B400 EB91 8653

* tag 'pull-misc-2026-07-14' of https://repo.or.cz/qemu/armbru:
  json-parser: fix formatting of comment
  MAINTAINERS: Regularise the status fields (again)
  qom: Fix device-list-properties & friends to show legacy-FOO props
  qapi: Fix misspelled section tags in doc comments

Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
diff --git a/.mailmap b/.mailmap
index 593b6c2..e5dc644 100644
--- a/.mailmap
+++ b/.mailmap
@@ -36,9 +36,11 @@
 Marek Dolata <mkdolata@us.ibm.com> mkdolata@us.ibm.com <mkdolata@us.ibm.com>
 Michael Ellerman <mpe@ellerman.id.au> michael@ozlabs.org <michael@ozlabs.org>
 Nick Hudson <hnick@vmware.com> hnick@vmware.com <hnick@vmware.com>
+Ryan Zhang <ryan.zhang@nxp.com> Ryan Zhang <mailto>
 Timothée Cocault <timothee.cocault@gmail.com> timothee.cocault@gmail.com <timothee.cocault@gmail.com>
 Stefan Weil <sw@weilnetz.de> <weil@mail.berlios.de>
 Stefan Weil <sw@weilnetz.de> Stefan Weil <stefan@kiwi.(none)>
+Zhongyao Chen <chen.zhongyao@zte.com.cn> chen.zhongyao@zte.com.cn <chen.zhongyao@zte.com.cn>
 
 # There is also a:
 #    (no author) <(no author)@c046a42c-6fe2-441c-8c8c-71466251a162>
@@ -122,6 +124,7 @@
 Song Gao <17746591750@163.com> <gaosong@loongson.cn>
 Taylor Simpson <ltaylorsimpson@gmail.com> <tsimpson@quicinc.com>
 Yongbok Kim <yongbok.kim@mips.com> <yongbok.kim@imgtec.com>
+Alexander Mikhalitsyn <alexander@mihalicyn.com> <aleksandr.mikhalitsyn@futurfusion.io>
 
 # Also list preferred name forms where people have changed their
 # git author config, or had utf8/latin1 encoding issues.
diff --git a/MAINTAINERS b/MAINTAINERS
index 8f84f3c..6ea4db6 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1760,8 +1760,10 @@
 F: tests/tcg/riscv64/test-crc32.S
 
 Microchip PolarFire SoC Icicle Kit
+M: Conor Dooley <conor@kernel.org>
+M: Sebastian Huber <sebastian.huber@embedded-brains.de>
 L: qemu-riscv@nongnu.org
-S: Supported
+S: Odd Fixes
 F: docs/system/riscv/microchip-icicle-kit.rst
 F: hw/riscv/microchip_pfsoc.c
 F: hw/char/mchp_pfsoc_mmuart.c
@@ -1828,6 +1830,7 @@
 F: hw/watchdog/k230_wdt.c
 F: include/hw/riscv/k230.h
 F: include/hw/watchdog/k230_wdt.h
+F: tests/functional/riscv64/test_k230.py
 F: tests/qtest/k230-wdt-test.c
 
 RX Machines
@@ -4611,7 +4614,7 @@
 M: Alex Bennée <alex.bennee@linaro.org>
 S: Odd Fixes
 F: gitdm.config
-F: contrib/gitdm/*
+F: scripts/gitdm/*
 
 Incompatible changes
 L: devel@lists.libvirt.org
diff --git a/VERSION b/VERSION
index e12cd6b..a4efc32 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-11.0.50
+11.0.90
diff --git a/contrib/gitdm/group-map-ibm b/contrib/gitdm/group-map-ibm
deleted file mode 100644
index 24d8dc1..0000000
--- a/contrib/gitdm/group-map-ibm
+++ /dev/null
@@ -1,15 +0,0 @@
-#
-# Some IBM contributors submit via another domain
-#
-
-aik@ozlabs.ru
-andrew@aj.id.au
-benh@kernel.crashing.org
-clg@kaod.org
-danielhb413@gmail.com
-groug@kaod.org
-jcfaracco@gmail.com
-joel@jms.id.au
-sjitindarsingh@gmail.com
-tommusta@gmail.com
-idan.horowitz@gmail.com
diff --git a/contrib/plugins/dlcall.c b/contrib/plugins/dlcall.c
new file mode 100644
index 0000000..9d2230b
--- /dev/null
+++ b/contrib/plugins/dlcall.c
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2026, Ziyang Zhang <functioner@sjtu.edu.cn>
+ *
+ * dlcall (Dynamic Linking Call) plugin: lets a linux-user guest invoke host
+ * functions by issuing a magic system call. The guest can ask QEMU to dlopen()
+ * a host shared library, dlsym() a symbol, and call it with guest-supplied
+ * arguments.
+ *
+ * The plugin intentionally keeps the QEMU side lightweight and prescribes
+ * nothing about how a library is thunked. Any toolchain can implement the
+ * userspace side. Lorelei is one end-to-end implementation (guest/host
+ * runtimes plus a thunk compiler that generates thunks from a library's
+ * headers):
+ * https://github.com/rover2024/lorelei
+ *
+ * See docs/about/emulation.rst|Dynamic Linking Call for details and examples.
+ *
+ * WARNING: trusted guests only. The guest can load arbitrary host libraries
+ * and execute arbitrary host code with arbitrary arguments, i.e. full code
+ * execution in the QEMU host process. It is NOT a sandbox and provides no
+ * isolation; only load it for guests you fully trust.
+ *
+ * WARNING: requires guest_base == 0, which is qemu-user's default. Pointer
+ * operands are dereferenced as host addresses directly, and the invoked host
+ * functions dereference guest pointers with no address translation, so guest
+ * and host must share a single address space. A non-zero guest_base (e.g. set
+ * via -B/-R) would make every pointer off by guest_base and hit unrelated
+ * host memory.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+#include <glib.h>
+#include <dlfcn.h>
+
+#include <qemu-plugin.h>
+
+QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION;
+
+/*
+ * The magic system call number for dlcall.
+ *
+ * It defaults to DLCALL_SYSCALL_DEFAULT and can be overridden at load time
+ * with the "syscall_num=N" argument. To avoid hijacking a real syscall the
+ * guest might issue, N must be at least DLCALL_SYSCALL_MIN: every Linux ABI
+ * keeps its syscall numbers well below this; numbers from here up are free.
+ */
+enum {
+    DLCALL_SYSCALL_DEFAULT = 4096,
+    DLCALL_SYSCALL_MIN = 4096,
+};
+
+static int64_t dlcall_syscall_num = DLCALL_SYSCALL_DEFAULT;
+
+/*
+ * dlcall calling convention.
+ *
+ * The guest issues the magic system call (dlcall_syscall_num). The first
+ * argument (a1) is one of the call IDs below; the remaining arguments (a2, a3,
+ * a4, ...) are that ID's operands. All pointer operands are guest virtual
+ * addresses that the plugin dereferences as host addresses directly (see the
+ * guest_base requirement above). Results are written back through
+ * caller-provided "out" pointers rather than returned in the syscall value.
+ *
+ * The syscall return value (*sysret) only reports dispatch status: 0 on a
+ * recognised ID, -EINVAL for an unknown one. The actual success/failure of an
+ * operation (e.g. a NULL handle from dlopen) is delivered through its out
+ * pointer, exactly like the underlying libdl call.
+ *
+ * Operands per ID:
+ *
+ *   DLCALL_ID_GET_HOST_ATTRIBUTE
+ *     a2  const char *key        in:  attribute name to query
+ *     a3  const char **attr_ptr  out: matching value, or NULL if unknown
+ *
+ *   DLCALL_ID_LOAD_LIBRARY                            (wraps dlopen)
+ *     a2  const char *path       in:  library path
+ *     a3  int flags              in:  dlopen() flags (e.g. RTLD_NOW)
+ *     a4  void **handle_ptr      out: library handle, or NULL on failure
+ *
+ *   DLCALL_ID_GET_PROC_ADDRESS                        (wraps dlsym)
+ *     a2  void *handle           in:  library handle
+ *     a3  const char *name       in:  symbol name
+ *     a4  void **entry_ptr       out: symbol address, or NULL if not found
+ *
+ *   DLCALL_ID_FREE_LIBRARY                            (wraps dlclose)
+ *     a2  void *handle           in:  library handle
+ *     a3  int *ret_ptr           out: dlclose() return value (0 on success)
+ *
+ *   DLCALL_ID_GET_LIBRARY_ERROR                       (wraps dlerror)
+ *     a2  const char **error_ptr out: last libdl error string, or NULL
+ *
+ *   DLCALL_ID_INVOKE_PROC                             (calls the symbol)
+ *     a2  void *proc             in:  function pointer, signature
+ *                                     void (*)(void *arg1, void *arg2)
+ *     a3  void *arg1             in:  first argument forwarded to proc
+ *     a4  void *arg2             in:  second argument forwarded to proc
+ */
+enum DlcallID {
+    DLCALL_ID_GET_HOST_ATTRIBUTE,
+    DLCALL_ID_LOAD_LIBRARY,
+    DLCALL_ID_GET_PROC_ADDRESS,
+    DLCALL_ID_FREE_LIBRARY,
+    DLCALL_ID_GET_LIBRARY_ERROR,
+    DLCALL_ID_INVOKE_PROC,
+};
+
+static inline const char *query_host_attribute(const char *key)
+{
+    if (strcmp(key, "emu") == 0) {
+        return "qemu";
+    }
+    return NULL;
+}
+
+static inline void invoke_proc(void *proc, void *arg1, void *arg2)
+{
+    typedef void (*Func)(void * /*arg1*/, void * /*arg2*/);
+    Func func = (Func) proc;
+    func(arg1, arg2);
+}
+
+static bool vcpu_syscall_filter(unsigned int vcpu_index,
+                                int64_t num, uint64_t a1, uint64_t a2,
+                                uint64_t a3, uint64_t a4, uint64_t a5,
+                                uint64_t a6, uint64_t a7, uint64_t a8,
+                                int64_t *sysret, void *userdata)
+{
+    if (num == dlcall_syscall_num) {
+        switch (a1) {
+        /* Query host attribute by a reserved key. */
+        case DLCALL_ID_GET_HOST_ATTRIBUTE: {
+            const char *key = (const char *) a2;
+            const char **attr_ptr = (const char **) a3;
+            assert(attr_ptr);
+            *attr_ptr = query_host_attribute(key);
+            *sysret = 0;
+            break;
+        }
+
+        /* Load a shared library. */
+        case DLCALL_ID_LOAD_LIBRARY: {
+            const char *path = (const char *) a2;
+            int flags = (int) a3;
+            void **handle_ptr = (void **) a4;
+            assert(handle_ptr);
+            *handle_ptr = dlopen(path, flags);
+            *sysret = 0;
+            break;
+        }
+
+        /* Get the address of a function in a shared library. */
+        case DLCALL_ID_GET_PROC_ADDRESS: {
+            void *handle = (void *) a2;
+            const char *name = (const char *) a3;
+            void **entry_ptr = (void **) a4;
+            assert(entry_ptr);
+            *entry_ptr = dlsym(handle, name);
+            *sysret = 0;
+            break;
+        }
+
+        /* Free a shared library. */
+        case DLCALL_ID_FREE_LIBRARY: {
+            void *handle = (void *) a2;
+            int *ret_ptr = (int *) a3;
+            *ret_ptr = dlclose(handle);
+            *sysret = 0;
+            break;
+        }
+
+        /* Get the last error message for a library event. */
+        case DLCALL_ID_GET_LIBRARY_ERROR: {
+            const char **error_ptr = (const char **) a2;
+            *error_ptr = dlerror();
+            *sysret = 0;
+            break;
+        }
+
+        /* Invoke a function of a common interface. */
+        case DLCALL_ID_INVOKE_PROC: {
+            void *proc = (void *) a2;
+            void *arg1 = (void *) a3;
+            void *arg2 = (void *) a4;
+            assert(proc);
+            invoke_proc(proc, arg1, arg2);
+            *sysret = 0;
+            break;
+        }
+
+        default:
+            *sysret = -EINVAL;
+            break;
+        }
+        return true;
+    }
+    return false;
+}
+
+QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id,
+                                           const qemu_info_t *info,
+                                           int argc, char **argv)
+{
+    if (info->system_emulation) {
+        fprintf(stderr, "plugin dlcall: only useful for user emulation\n");
+        return -1;
+    }
+
+    for (int i = 0; i < argc; i++) {
+        char *opt = argv[i];
+        g_auto(GStrv) tokens = g_strsplit(opt, "=", 2);
+        if (g_strcmp0(tokens[0], "syscall_num") == 0) {
+            const char *val = tokens[1];
+            char *endptr = NULL;
+            guint64 num;
+            if (!val || *val == '\0') {
+                fprintf(stderr,
+                        "plugin dlcall: missing value for syscall_num\n");
+                return -1;
+            }
+            num = g_ascii_strtoull(val, &endptr, 0);
+            if (*endptr != '\0' || g_strrstr(val, "-") != NULL) {
+                fprintf(stderr,
+                        "plugin dlcall: invalid syscall_num '%s'\n", val);
+                return -1;
+            }
+            if (num < DLCALL_SYSCALL_MIN || num > G_MAXINT64) {
+                fprintf(stderr,
+                        "plugin dlcall: syscall_num %s is out of range; "
+                        "it must be >= %d to avoid clashing with a real "
+                        "syscall\n", val, DLCALL_SYSCALL_MIN);
+                return -1;
+            }
+            dlcall_syscall_num = (int64_t) num;
+        } else {
+            fprintf(stderr, "plugin dlcall: unknown option '%s'\n", opt);
+            return -1;
+        }
+    }
+
+    qemu_plugin_register_vcpu_syscall_filter_cb(id, vcpu_syscall_filter, NULL);
+
+    return 0;
+}
diff --git a/contrib/plugins/meson.build b/contrib/plugins/meson.build
index 099319e..e7fc4d6 100644
--- a/contrib/plugins/meson.build
+++ b/contrib/plugins/meson.build
@@ -19,6 +19,11 @@
   contrib_plugins += 'lockstep.c'
 endif
 
+if host_os == 'linux'
+  # dlcall passes guest calls through to host libraries; linux-user only
+  contrib_plugins += 'dlcall.c'
+endif
+
 if 'cpp' in all_languages
   contrib_plugins += 'cpp.cpp'
 endif
diff --git a/contrib/plugins/uftrace.c b/contrib/plugins/uftrace.c
index 9b0a496..063e322 100644
--- a/contrib/plugins/uftrace.c
+++ b/contrib/plugins/uftrace.c
@@ -109,6 +109,8 @@
     RISCV64_SUPERVISOR,
     RISCV64_RESERVED,
     RISCV64_MACHINE,
+    RISCV64_VUSER,
+    RISCV64_VSUPERVISOR,
     RISCV64_PRIVILEGE_LEVEL_MAX,
 } Riscv64PrivilegeLevel;
 
@@ -153,8 +155,10 @@
     const char *path = "./uftrace.data/sid-0.map";
 
     if (system_emulation && access(path, F_OK) == 0) {
-        /* do not erase existing map in system emulation, as a custom one might
-         * already have been generated by uftrace_symbols.py */
+        /*
+         * do not erase existing map in system emulation, as a custom one might
+         * already have been generated by uftrace_symbols.py
+         */
         return;
     }
 
@@ -706,6 +710,8 @@
     case RISCV64_SUPERVISOR: return "Supervisor";
     case RISCV64_RESERVED: return "Unknown";
     case RISCV64_MACHINE: return "Machine";
+    case RISCV64_VUSER: return "VUser";
+    case RISCV64_VSUPERVISOR: return "VSupervisor";
     default:
         g_assert_not_reached();
     }
@@ -986,7 +992,8 @@
 
     score = qemu_plugin_scoreboard_new(sizeof(Cpu));
     qemu_plugin_register_vcpu_init_cb(id, vcpu_init, NULL);
-    qemu_plugin_register_atexit_cb(id, at_exit, (void *) info->system_emulation);
+    qemu_plugin_register_atexit_cb(id, at_exit,
+                                   (void *) info->system_emulation);
     qemu_plugin_register_vcpu_tb_trans_cb(id, vcpu_tb_trans, NULL);
 
     return 0;
diff --git a/docs/about/emulation.rst b/docs/about/emulation.rst
index 3b4c365..b861501 100644
--- a/docs/about/emulation.rst
+++ b/docs/about/emulation.rst
@@ -1046,6 +1046,164 @@
 This plugin counts the number of interrupts (asynchronous events), exceptions
 (synchronous events) and host calls (e.g. semihosting) per cpu.
 
+Dynamic Linking Call
+....................
+
+``contrib/plugins/dlcall.c``
+
+This plugin provides a dynamic linking function call interception mechanism
+for linux-user guests: the guest hands a call off to the host, where the plugin
+runs native code in its place instead of the guest emulating it. Interception
+alone enables several uses, for instance tracing or auditing guest calls.
+One use is acceleration by leveraging the host's native shared libraries. For
+example, a thunk layer can run the stock zlib ``minizip`` utility under
+emulation while forwarding its ``deflate`` calls to the host's native zlib
+library (libz). This avoids emulating those selected library calls instruction
+by instruction.
+
+The guest issues a reserved "magic" system call (4096 by default, configurable
+with ``syscall_num=N``) whose first argument selects a pass-through operation:
+dlopen/dlclose a host library, dlsym a symbol, and invoke a resolved host
+function. The plugin performs the operation on the host and consumes the
+syscall, so the real kernel never sees it.
+
+.. warning::
+
+   Trusted guests only. The guest can load arbitrary host libraries and run
+   arbitrary code in the QEMU host process. The plugin is not a sandbox and
+   provides no isolation. It also requires ``guest_base == 0`` (qemu-user's
+   default), as guest pointers are dereferenced as host addresses with no
+   translation.
+
+The plugin intentionally keeps the QEMU side lightweight and knows nothing
+about any particular library or its calling convention. Turning a real library
+into working thunks, including argument marshalling, callbacks and variadic
+functions, is done entirely in userspace, and any toolchain can implement the
+interface.
+
+Loading the plugin is all that is required from QEMU's side:
+
+.. code-block:: shell
+
+   qemu-x86_64 -plugin contrib/plugins/libdlcall.so <guest-program> ...
+
+`Lorelei <https://github.com/rover2024/lorelei>`_ is one end-to-end userspace
+implementation of this: it provides the guest and host runtimes and an
+automated toolchain that generates the thunks from a library's headers, so guest
+library calls run on the host's native libraries. It supports an x86_64 guest
+running on an x86_64, aarch64 or riscv64 host.
+
+A minimal end-to-end example uses a one-function library, ``libhello.so``, built
+two ways: the guest build tags its output ``(from the guest)`` and the host
+build ``(from the host)``. An unmodified guest program ``main`` calls
+``hello("World", 7)``, and the thunk makes that same binary reach the host build
+in place of its own. The sources live under ``src/``:
+
+.. code-block:: c
+
+   /* src/hello.h */
+   void hello(const char *name, int lucky);
+
+.. code-block:: c
+
+   /* src/hello_guest.c */
+   #include "hello.h"
+   #include <stdio.h>
+
+   void hello(const char *name, int lucky)
+   {
+       printf("Hello, %s! Your lucky number is %d. (from the guest)\n", name, lucky);
+   }
+
+.. code-block:: c
+
+   /* src/hello_host.c */
+   #include "hello.h"
+   #include <stdio.h>
+
+   void hello(const char *name, int lucky)
+   {
+       printf("Hello, %s! Your lucky number is %d. (from the host)\n", name, lucky);
+   }
+
+.. code-block:: c
+
+   /* src/main.c */
+   #include "hello.h"
+
+   int main(void)
+   {
+       hello("World", 7);
+       return 0;
+   }
+
+Lorelei ships a prebuilt toolchain (a "devkit") in its releases. Download the
+one for your host and unpack it:
+
+.. code-block:: shell
+
+   # ARCH is your host's architecture: x86_64, aarch64 or riscv64. This example uses aarch64.
+   # See https://github.com/rover2024/lorelei/releases
+   ARCH=aarch64
+   VERSION=$(curl -fsSL -o /dev/null -w '%{url_effective}' \
+         https://github.com/rover2024/lorelei/releases/latest | sed 's|.*/tag/v||')
+   wget "https://github.com/rover2024/lorelei/releases/download/v$VERSION/lorelei-devkit-$ARCH-$VERSION.tar.xz"
+   tar -xf lorelei-devkit-$ARCH-$VERSION.tar.xz
+   DEVKIT=lorelei-devkit-$ARCH
+
+Build the guest ``libhello.so`` (x86_64) and the host ``libhello.so`` (this
+host's architecture), then the guest program:
+
+.. code-block:: shell
+
+   mkdir -p build/guest build/host
+   $DEVKIT/bin/x86_64-linux-gnu-clang -shared -fPIC src/hello_guest.c -o build/guest/libhello.so
+   cc -shared -fPIC src/hello_host.c -o build/host/libhello.so
+   $DEVKIT/bin/x86_64-linux-gnu-clang src/main.c -Isrc -Lbuild/guest -lhello -o build/guest/main
+
+Run it under qemu:
+
+.. code-block:: shell
+
+   qemu-x86_64 -L /usr/x86_64-linux-gnu/ -E LD_LIBRARY_PATH=build/guest build/guest/main
+
+which prints::
+
+   Hello, World! Your lucky number is 7. (from the guest)
+
+Now generate the thunk from the host ``libhello.so``. This produces a guest-side
+``libhello.so`` that stands in for the guest build, and a host-side thunk library
+that dispatches to the host build:
+
+.. code-block:: shell
+
+   $DEVKIT/bin/LoreMakeThunk.py --name hello --lib build/host/libhello.so \
+       --header hello.h -o thunks -- -Isrc
+
+Run the same ``main`` under the plugin. The call reaches the host build now:
+
+.. code-block:: shell
+
+   LD_LIBRARY_PATH=$DEVKIT/lib:build/host \
+       qemu-x86_64 -plugin contrib/plugins/libdlcall.so \
+       -E LD_LIBRARY_PATH=$DEVKIT/x86_64/lib:thunks/x86_64 \
+       -L /usr/x86_64-linux-gnu/ \
+       build/guest/main
+
+which prints::
+
+   Hello, World! Your lucky number is 7. (from the host)
+
+.. list-table:: Dynamic Linking Call arguments
+  :widths: 20 80
+  :header-rows: 1
+
+  * - Option
+    - Description
+  * - syscall_num=N
+    - The magic syscall number the guest issues (default 4096). Must be high
+      enough not to clash with a real syscall.
+
 Other emulation features
 ------------------------
 
diff --git a/docs/system/arm/emulation.rst b/docs/system/arm/emulation.rst
index 4f413d6..9930974 100644
--- a/docs/system/arm/emulation.rst
+++ b/docs/system/arm/emulation.rst
@@ -31,6 +31,8 @@
 - FEAT_BBML1 (Translation table break-before-make level 1)
 - FEAT_BBML2 (Translation table break-before-make level 2)
 - FEAT_BF16 (AArch64 BFloat16 instructions)
+- FEAT_BigEnd (Support for big-endian at EL1 and above)
+- FEAT_BigEndEL0 (Support for big-endian at EL0)
 - FEAT_BTI (Branch Target Identification)
 - FEAT_CCIDX (Extended cache index)
 - FEAT_CHK (Check Feature Status)
@@ -68,6 +70,7 @@
 - FEAT_EPAC (Enhanced pointer authentication)
 - FEAT_ETS2 (Enhanced Translation Synchronization)
 - FEAT_EVT (Enhanced Virtualization Traps)
+- FEAT_EVT2 (Enhanced Virtualization Traps 2)
 - FEAT_F8F16MM (8-bit floating-point matrix multiply-accumulate to half-precision)
 - FEAT_F8F32MM (8-bit floating-point matrix multiply-accumulate to single-precision)
 - FEAT_F32MM (Single-precision Matrix Multiplication)
@@ -92,6 +95,7 @@
 - FEAT_FlagM2 (Enhancements to flag manipulation instructions)
 - FEAT_GCS (Guarded Control Stack Extension)
 - FEAT_GTG (Guest translation granule size)
+- FEAT_HAF (Hardware management of the Access flag)
 - FEAT_HAFDBS (Hardware management of the access flag and dirty bit state)
 - FEAT_HBC (Hinted conditional branches)
 - FEAT_HCX (Support for the HCRX_EL2 register)
@@ -101,7 +105,10 @@
 - FEAT_I8MM (AArch64 Int8 matrix multiplication instructions)
 - FEAT_IDST (ID space trap handling)
 - FEAT_IESB (Implicit error synchronization event)
+- FEAT_IVIPT (The IVIPT Extension)
 - FEAT_JSCVT (JavaScript conversion instructions)
+- FEAT_LittleEnd (Support for little-endian at EL1 and above)
+- FEAT_LittleEndEL0 (Support for little-endian at EL0)
 - FEAT_LOR (Limited ordering regions)
 - FEAT_LPA (Large Physical Address space)
 - FEAT_LPA2 (Large Physical and virtual Address space v2)
@@ -121,6 +128,7 @@
 - FEAT_MTE (Memory Tagging Extension)
 - FEAT_MTE2 (Memory Tagging Extension)
 - FEAT_MTE3 (MTE Asymmetric Fault Handling)
+- FEAT_MTE4 (Enhanced Memory Tagging Extension)
 - FEAT_MTE_ASYM_FAULT (Memory tagging asymmetric faults)
 - FEAT_MTE_ASYNC (Asynchronous reporting of Tag Check Fault)
 - FEAT_MTE_CANONICAL_TAGS (Canonical tag checking)
@@ -153,10 +161,14 @@
 - FEAT_RNG_TRAP (Trapping support for RNDR/RNDRRS)
 - FEAT_RPRES (Increased precision of FRECPE and FRSQRTE)
 - FEAT_S1PIE (Stage 1 permission indirections)
-- FEAT_S2PIE (Stage 2 permission indirections)
 - FEAT_S2FWB (Stage 2 forced Write-Back)
+- FEAT_S2PIE (Stage 2 permission indirections)
+- FEAT_S2TGran16K (Support for 16KB memory translation granule size at stage 2)
+- FEAT_S2TGran4K (Support for 4KB memory translation granule size at stage 2)
+- FEAT_S2TGran64K (Support for 64KB memory translation granule size at stage 2)
 - FEAT_SB (Speculation Barrier)
 - FEAT_SCTLR2 (Extension to SCTLR_ELx)
+- FEAT_Secure (Support for Secure state)
 - FEAT_SEL2 (Secure EL2)
 - FEAT_SHA1 (SHA1 instructions)
 - FEAT_SHA256 (SHA256 instructions)
diff --git a/gitdm.config b/gitdm.config
index 8764bf2..b9627de 100644
--- a/gitdm.config
+++ b/gitdm.config
@@ -17,13 +17,13 @@
 # the information in the existing .mailmap but in a slightly different
 # form.
 #
-EmailAliases contrib/gitdm/aliases
+EmailAliases scripts/gitdm/aliases
 
 #
 # EmailMap does the main work of mapping addresses onto
 # employers.
 #
-EmailMap contrib/gitdm/domain-map
+EmailMap scripts/gitdm/domain-map
 
 #
 # Use GroupMap to map a file full of addresses to the
@@ -31,31 +31,31 @@
 # identifiable corporate emails. Please keep this list sorted.
 #
 
-GroupMap contrib/gitdm/group-map-alibaba Alibaba
-GroupMap contrib/gitdm/group-map-amd AMD
-GroupMap contrib/gitdm/group-map-cadence Cadence Design Systems
-GroupMap contrib/gitdm/group-map-codeweavers CodeWeavers
-GroupMap contrib/gitdm/group-map-facebook Facebook
-GroupMap contrib/gitdm/group-map-huawei Huawei
-GroupMap contrib/gitdm/group-map-ibm IBM
-GroupMap contrib/gitdm/group-map-janustech Janus Technologies
-GroupMap contrib/gitdm/group-map-microsoft Microsoft
-GroupMap contrib/gitdm/group-map-netflix Netflix
-GroupMap contrib/gitdm/group-map-redhat Red Hat
-GroupMap contrib/gitdm/group-map-wavecomp Wave Computing
+GroupMap scripts/gitdm/group-map-alibaba Alibaba
+GroupMap scripts/gitdm/group-map-amd AMD
+GroupMap scripts/gitdm/group-map-cadence Cadence Design Systems
+GroupMap scripts/gitdm/group-map-codeweavers CodeWeavers
+GroupMap scripts/gitdm/group-map-facebook Facebook
+GroupMap scripts/gitdm/group-map-huawei Huawei
+GroupMap scripts/gitdm/group-map-ibm IBM
+GroupMap scripts/gitdm/group-map-janustech Janus Technologies
+GroupMap scripts/gitdm/group-map-microsoft Microsoft
+GroupMap scripts/gitdm/group-map-netflix Netflix
+GroupMap scripts/gitdm/group-map-redhat Red Hat
+GroupMap scripts/gitdm/group-map-wavecomp Wave Computing
 
 # Also group together our prolific individual contributors
 # and those working under academic or intern auspices
-GroupMap contrib/gitdm/group-map-individuals (None)
-GroupMap contrib/gitdm/group-map-academics Academics (various)
-GroupMap contrib/gitdm/group-map-interns GSoC/Outreachy Interns
+GroupMap scripts/gitdm/group-map-individuals (None)
+GroupMap scripts/gitdm/group-map-academics Academics (various)
+GroupMap scripts/gitdm/group-map-interns GSoC/Outreachy Interns
 
 # Group together robots and other auto-reporters
-GroupMap contrib/gitdm/group-map-robots Robots (various)
+GroupMap scripts/gitdm/group-map-robots Robots (various)
 
 #
 #
 # Use FileTypeMap to map a file types to file names using regular
 # regular expressions.
 #
-FileTypeMap contrib/gitdm/filetypes.txt
+FileTypeMap scripts/gitdm/filetypes.txt
diff --git a/hw/arm/smmuv3-internal.h b/hw/arm/smmuv3-internal.h
index eb482c7..0819a4b 100644
--- a/hw/arm/smmuv3-internal.h
+++ b/hw/arm/smmuv3-internal.h
@@ -359,6 +359,9 @@
 void smmuv3_propagate_event(SMMUv3State *s, Evt *evt);
 int smmu_find_ste(SMMUv3State *s, uint32_t sid, STE *ste, SMMUEventInfo *event);
 
+#define STE_SIZE 6
+#define L1STD_SIZE 3
+
 static inline int oas2bits(int oas_field)
 {
     switch (oas_field) {
diff --git a/hw/arm/smmuv3.c b/hw/arm/smmuv3.c
index 5e5a6a9..ed19536 100644
--- a/hw/arm/smmuv3.c
+++ b/hw/arm/smmuv3.c
@@ -664,7 +664,7 @@
 {
     dma_addr_t addr, strtab_base;
     uint32_t log2size;
-    int strtab_size_shift;
+    int strtab_size;
     int ret;
 
     trace_smmuv3_find_ste(sid, s->features, s->sid_split);
@@ -685,9 +685,9 @@
          * Align strtab base address to table size. For this purpose, assume it
          * is not bounded by SMMU_IDR1_SIDSIZE.
          */
-        strtab_size_shift = MAX(5, (int)log2size - s->sid_split - 1 + 3);
+        strtab_size = MAX(6, (int)log2size - s->sid_split + L1STD_SIZE);
         strtab_base = s->strtab_base & SMMU_BASE_ADDR_MASK &
-                      ~MAKE_64BIT_MASK(0, strtab_size_shift);
+                      ~MAKE_64BIT_MASK(0, strtab_size);
         l1_ste_offset = sid >> s->sid_split;
         l2_ste_offset = sid & ((1 << s->sid_split) - 1);
         l1ptr = (dma_addr_t)(strtab_base + l1_ste_offset * sizeof(l1std));
@@ -707,7 +707,7 @@
 
         span = L1STD_SPAN(&l1std);
 
-        if (!span) {
+        if (!span || span > 11) {
             /* l2ptr is not valid */
             if (!event->inval_ste_allowed) {
                 qemu_log_mask(LOG_GUEST_ERROR,
@@ -716,8 +716,20 @@
             event->type = SMMU_EVT_C_BAD_STREAMID;
             return -EINVAL;
         }
+
+        if (span > s->sid_split + 1) {
+            if (!event->inval_ste_allowed) {
+                qemu_log_mask(LOG_GUEST_ERROR,
+                              "invalid span (0x%x)\n", span);
+            }
+            event->type = SMMU_EVT_C_BAD_STREAMID;
+            return -EINVAL;
+        }
+
         max_l2_ste = (1 << span) - 1;
         l2ptr = l1std_l2ptr(&l1std);
+
+        l2ptr &= ~MAKE_64BIT_MASK(0, 6 + (span - 1));
         trace_smmuv3_find_ste_2lvl(s->strtab_base, l1ptr, l1_ste_offset,
                                    l2ptr, l2_ste_offset, max_l2_ste);
         if (l2_ste_offset > max_l2_ste) {
@@ -729,9 +741,10 @@
         }
         addr = l2ptr + l2_ste_offset * sizeof(*ste);
     } else {
-        strtab_size_shift = log2size + 5;
+        strtab_size = log2size + STE_SIZE;
+        strtab_size = MIN(64, strtab_size);
         strtab_base = s->strtab_base & SMMU_BASE_ADDR_MASK &
-                      ~MAKE_64BIT_MASK(0, strtab_size_shift);
+                      ~MAKE_64BIT_MASK(0, strtab_size);
         addr = strtab_base + sid * sizeof(*ste);
     }
 
@@ -1688,6 +1701,13 @@
         s->strtab_base_cfg = data;
         if (FIELD_EX32(data, STRTAB_BASE_CFG, FMT) == 1) {
             s->sid_split = FIELD_EX32(data, STRTAB_BASE_CFG, SPLIT);
+            if (s->sid_split != 6 && s->sid_split != 8 && s->sid_split != 10) {
+                /* Other values are reserved, behave as 6 */
+                qemu_log_mask(LOG_GUEST_ERROR,
+                              "Invalid STRTAB_BASE_CFG.SPLIT=%u, use 6 instead\n",
+                              s->sid_split);
+                s->sid_split = 6;
+            }
             s->features |= SMMU_FEATURE_2LVL_STE;
         }
         break;
diff --git a/hw/arm/tegra241-cmdqv.c b/hw/arm/tegra241-cmdqv.c
index 7223aa9..273633e 100644
--- a/hw/arm/tegra241-cmdqv.c
+++ b/hw/arm/tegra241-cmdqv.c
@@ -212,7 +212,9 @@
 /*
  * Flush cached register writes into the mmap'd host VINTF page0 after a
  * successful HW_QUEUE_ALLOC, so the guest's earlier writes survive
- * the cache-to-hardware transition.
+ * the cache-to-hardware transition. GERRORN is intentionally not synced,
+ * as overwriting it with the cached value could recreate a GERROR != GERRORN
+ * mismatch and stall the VCMDQ.
  */
 static void tegra241_cmdqv_sync_vcmdq(Tegra241CMDQV *cmdqv, int index)
 {
@@ -229,9 +231,6 @@
 
     ptr = tegra241_cmdqv_vintf_lvcmdq_ptr(cmdqv, index, A_VCMDQ0_CONFIG);
     *ptr = cmdqv->vcmdq_config[index];
-
-    ptr = tegra241_cmdqv_vintf_lvcmdq_ptr(cmdqv, index, A_VCMDQ0_GERRORN);
-    *ptr = cmdqv->vcmdq_gerrorn[index];
 }
 
 /*
diff --git a/hw/arm/virt.c b/hw/arm/virt.c
index 6a3c69b..219597c 100644
--- a/hw/arm/virt.c
+++ b/hw/arm/virt.c
@@ -4417,6 +4417,7 @@
     }
     g_free(vms->oem_id);
     g_free(vms->oem_table_id);
+    g_ptr_array_free(vms->smmuv3_devices, TRUE);
 }
 
 static const TypeInfo virt_machine_info = {
diff --git a/hw/display/sm501.c b/hw/display/sm501.c
index af87004..cacb6c8 100644
--- a/hw/display/sm501.c
+++ b/hw/display/sm501.c
@@ -571,6 +571,25 @@
     return index;
 }
 
+static void set_new_local_mem_size_index(SM501State *s, uint32_t idx)
+{
+    /*
+     * Update local_mem_size_index on guest write. We don't allow this
+     * to be set to larger than the actual RAM size. (The guest will
+     * still read back the SYSTEM_CONTROL.Size bits that it wrote.)
+     */
+    if (idx < ARRAY_SIZE(sm501_mem_local_size) &&
+        sm501_mem_local_size[idx] <= memory_region_size(&s->local_mem_region)) {
+        s->local_mem_size_index = idx;
+        return;
+    }
+    qemu_log_mask(LOG_GUEST_ERROR,
+                  "sm501: Guest set DRAM_CONTROL.Size to 0x%x but "
+                  "local memory is not that large\n",
+                  idx);
+    /* Don't change the effective size, leave it as whatever it was */
+}
+
 static ram_addr_t get_fb_addr(SM501State *s, int crt)
 {
     return (crt ? s->dc_crt_fb_addr : s->dc_panel_fb_addr) & 0x3FFFFF0;
@@ -682,6 +701,28 @@
                             get_fb_addr(s, crt) + start, end - start);
 }
 
+static bool sm501_rect_outside_vram(SM501State *s, uint32_t base,
+                                    uint32_t x, uint32_t y,
+                                    uint32_t width, uint32_t height,
+                                    uint32_t pitch, uint32_t bypp)
+{
+    /*
+     * Return true if the 2D area specified by the arguments is
+     * partially or completely outside the VRAM (a guest error)
+     *
+     * Limits on the input sizes mean we can't overflow as long as
+     * we do all the arithmetic at 64 bits.
+     */
+    uint64_t rect_size, last_addr;
+
+    assert(x <= UINT16_MAX && y <= UINT16_MAX && height <= UINT16_MAX &&
+           pitch <= UINT16_MAX && bypp <= 8);
+    rect_size = (((uint64_t)y + height) * pitch + x + width) * bypp;
+    last_addr = base + rect_size;
+
+    return last_addr >= get_local_mem_size(s);
+}
+
 static void sm501_2d_operation(SM501State *s)
 {
     int cmd = (s->twoD_control >> 16) & 0x1F;
@@ -723,13 +764,16 @@
     }
 
     if (rtl) {
+        if (dst_x < (width - 1) || dst_y < (height - 1)) {
+            qemu_log_mask(LOG_GUEST_ERROR, "sm501: RTL op out of bounds\n");
+            return;
+        }
         dst_x -= width - 1;
         dst_y -= height - 1;
     }
 
-    if (dst_base >= get_local_mem_size(s) ||
-        dst_base + (dst_x + width + (dst_y + height) * dst_pitch) * bypp >=
-        get_local_mem_size(s)) {
+    if (sm501_rect_outside_vram(s, dst_base, dst_x, dst_y, width, height,
+                                dst_pitch, bypp)) {
         qemu_log_mask(LOG_GUEST_ERROR, "sm501: 2D op dest is outside vram.\n");
         return;
     }
@@ -748,13 +792,16 @@
         }
 
         if (rtl) {
+            if (src_x < (width - 1) || src_y < (height - 1)) {
+                qemu_log_mask(LOG_GUEST_ERROR, "sm501: RTL op out of bounds\n");
+                return;
+            }
             src_x -= width - 1;
             src_y -= height - 1;
         }
 
-        if (src_base >= get_local_mem_size(s) ||
-            src_base + (src_x + width + (src_y + height) * src_pitch) * bypp >=
-            get_local_mem_size(s)) {
+        if (sm501_rect_outside_vram(s, src_base, src_x, src_y, width, height,
+                                    src_pitch, bypp)) {
             qemu_log_mask(LOG_GUEST_ERROR,
                           "sm501: 2D op src is outside vram.\n");
             return;
@@ -962,7 +1009,7 @@
         ret = 0x050100A0;
         break;
     case SM501_DRAM_CONTROL:
-        ret = (s->dram_control & 0x07F107C0) | s->local_mem_size_index << 13;
+        ret = (s->dram_control & 0x07F1E7C0);
         break;
     case SM501_ARBTRTN_CONTROL:
         ret = s->arbitration_control;
@@ -1021,8 +1068,7 @@
         s->gpio_63_32_control = value & 0xFF80FFFF;
         break;
     case SM501_DRAM_CONTROL:
-        s->local_mem_size_index = (value >> 13) & 0x7;
-        /* TODO : check validity of size change */
+        set_new_local_mem_size_index(s, (value >> 13) & 0x7);
         s->dram_control &= 0x80000000;
         s->dram_control |= value & 0x7FFFFFC3;
         break;
diff --git a/hw/dma/bcm2835_dma.c b/hw/dma/bcm2835_dma.c
index d07459d..b842aef 100644
--- a/hw/dma/bcm2835_dma.c
+++ b/hw/dma/bcm2835_dma.c
@@ -86,6 +86,23 @@
         }
         xlen_td = xlen;
 
+        if (ch->ti & BCM2708_DMA_D_WIDTH) {
+            qemu_log_mask(LOG_UNIMP, "%s: 128bit transfers not yet supported", __func__);
+            ch->cs |= BCM2708_DMA_ERR;
+            break;
+        }
+
+        /*
+         * Datasheet implies 32bit or 128bit transfers only
+         *
+         * TODO: test on real HW and report back.
+         */
+        if (xlen & 0x3) {
+            qemu_log_mask(LOG_GUEST_ERROR, "%s: bad transfer size\n", __func__);
+            ch->cs |= BCM2708_DMA_ERR;
+            break;
+        }
+
         while (ylen != 0) {
             /* Normal transfer mode */
             while (xlen != 0) {
diff --git a/hw/i2c/pmbus_device.c b/hw/i2c/pmbus_device.c
index b1f9843..1ca117bd 100644
--- a/hw/i2c/pmbus_device.c
+++ b/hw/i2c/pmbus_device.c
@@ -1245,6 +1245,16 @@
     pmdev->in_buf_len = len;
     pmdev->in_buf = buf;
 
+    /* clear the output buffer on any new write transaction */
+    if (pmdev->out_buf_len != 0) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: previous read was not completed, %d bytes dropped\n",
+                      __func__, pmdev->out_buf_len);
+
+        pmdev->out_buf_len = 0;
+        memset(pmdev->out_buf, 0, sizeof(pmdev->out_buf));
+    }
+
     pmdev->code = buf[0]; /* PMBus command code */
 
     if (pmdev->code == PMBUS_CLEAR_FAULTS) {
diff --git a/hw/misc/allwinner-r40-ccu.c b/hw/misc/allwinner-r40-ccu.c
index 8ba4e7a..00840e9 100644
--- a/hw/misc/allwinner-r40-ccu.c
+++ b/hw/misc/allwinner-r40-ccu.c
@@ -71,7 +71,7 @@
     const uint32_t idx = REG_INDEX(offset);
 
     switch (offset) {
-    case 0x324 ... AW_R40_CCU_IOSIZE:
+    case 0x324 ... AW_R40_CCU_IOSIZE - 4:
         qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n",
                       __func__, (uint32_t)offset);
         return 0;
@@ -113,10 +113,10 @@
             val |= REG_PLL_LOCK;
         }
         break;
-    case 0x324 ... AW_R40_CCU_IOSIZE:
+    case 0x324 ... AW_R40_CCU_IOSIZE - 4:
         qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n",
                       __func__, (uint32_t)offset);
-        break;
+        return;
     default:
         qemu_log_mask(LOG_UNIMP, "%s: unimplemented write offset 0x%04x\n",
                       __func__, (uint32_t)offset);
diff --git a/hw/misc/stm32_rcc.c b/hw/misc/stm32_rcc.c
index 74ea29b..5cfb39e 100644
--- a/hw/misc/stm32_rcc.c
+++ b/hw/misc/stm32_rcc.c
@@ -53,6 +53,27 @@
     return value;
 }
 
+static int reg_offset_to_irq_offset(hwaddr addr)
+{
+    /*
+     * The reset and enable registers aren't all consecutive. In getting the
+     * irq index from the register offset, we need to account for the gap
+     * between the AHB regs and the APB regs.
+     */
+    switch (addr) {
+    case STM32_RCC_AHB1_RSTR ... STM32_RCC_AHB3_RSTR:
+        return ((addr - STM32_RCC_AHB1_RSTR) / 4) * 32;
+    case STM32_RCC_APB1_RSTR ... STM32_RCC_APB2_RSTR:
+        return ((addr - STM32_RCC_APB1_RSTR) / 4) * 32 + STM32_RCC_N_AHB_IRQS;
+    case STM32_RCC_AHB1_ENR ... STM32_RCC_AHB3_ENR:
+        return ((addr - STM32_RCC_AHB1_ENR) / 4) * 32;
+    case STM32_RCC_APB1_ENR ... STM32_RCC_APB2_ENR:
+        return ((addr - STM32_RCC_APB1_ENR) / 4) * 32 + STM32_RCC_N_AHB_IRQS;
+    default:
+        g_assert_not_reached();
+    }
+}
+
 static void stm32_rcc_write(void *opaque, hwaddr addr,
                             uint64_t val64, unsigned int size)
 {
@@ -69,11 +90,12 @@
     }
 
     switch (addr) {
-    case STM32_RCC_AHB1_RSTR...STM32_RCC_APB2_RSTR:
+    case STM32_RCC_AHB1_RSTR ... STM32_RCC_AHB3_RSTR:
+    case STM32_RCC_APB1_RSTR ... STM32_RCC_APB2_RSTR:
         prev_value = s->regs[addr / 4];
         s->regs[addr / 4] = value;
 
-        irq_offset = ((addr - STM32_RCC_AHB1_RSTR) / 4) * 32;
+        irq_offset = reg_offset_to_irq_offset(addr);
         for (int i = 0; i < 32; i++) {
             new_value = extract32(value, i, 1);
             if (extract32(prev_value, i, 1) && !new_value) {
@@ -82,11 +104,12 @@
             }
         }
         return;
-    case STM32_RCC_AHB1_ENR...STM32_RCC_APB2_ENR:
+    case STM32_RCC_AHB1_ENR ... STM32_RCC_AHB3_ENR:
+    case STM32_RCC_APB1_ENR ... STM32_RCC_APB2_ENR:
         prev_value = s->regs[addr / 4];
         s->regs[addr / 4] = value;
 
-        irq_offset = ((addr - STM32_RCC_AHB1_ENR) / 4) * 32;
+        irq_offset = reg_offset_to_irq_offset(addr);
         for (int i = 0; i < 32; i++) {
             new_value = extract32(value, i, 1);
             if (!extract32(prev_value, i, 1) && new_value) {
diff --git a/hw/misc/zynq_slcr.c b/hw/misc/zynq_slcr.c
index 0ae5d60..d0f899c 100644
--- a/hw/misc/zynq_slcr.c
+++ b/hw/misc/zynq_slcr.c
@@ -427,7 +427,6 @@
                           = s->regs[R_DDRIOB + 3] = 0x00000e00;
     s->regs[R_DDRIOB + 4] = s->regs[R_DDRIOB + 5] = s->regs[R_DDRIOB + 6]
                           = 0x00000e00;
-    s->regs[R_DDRIOB + 12] = 0x00000021;
 
     s->regs[R_DDRIOB_DCI_CTRL] = 0x00000020;
 }
diff --git a/hw/net/vmxnet3.c b/hw/net/vmxnet3.c
index 97156dd..8569484 100644
--- a/hw/net/vmxnet3.c
+++ b/hw/net/vmxnet3.c
@@ -1079,7 +1079,7 @@
         int tx_queue_idx =
             VMW_MULTIREG_IDX_BY_ADDR(addr, VMXNET3_REG_TXPROD,
                                      VMXNET3_REG_ALIGN);
-        if (tx_queue_idx <= s->txq_num) {
+        if (tx_queue_idx < s->txq_num) {
             vmxnet3_process_tx_queue(s, tx_queue_idx);
         } else {
             qemu_log_mask(LOG_GUEST_ERROR, "vmxnet3: Illegal TX queue %d/%d\n",
diff --git a/hw/s390x/css.c b/hw/s390x/css.c
index 29ba39f..3a67b67 100644
--- a/hw/s390x/css.c
+++ b/hw/s390x/css.c
@@ -1078,6 +1078,12 @@
             ret = -EINVAL;
             break;
         }
+        /* Limit the number of TICs in a given channel program */
+        if (sch->ccw_tic_cnt == 255) {
+            ret = -EINVAL;
+            break;
+        }
+        sch->ccw_tic_cnt++;
         sch->channel_prog = ccw.cda;
         ret = -EAGAIN;
         break;
@@ -1129,6 +1135,7 @@
         sch->ccw_fmt_1 = !!(orb->ctrl0 & ORB_CTRL0_MASK_FMT);
         schib->scsw.flags |= (sch->ccw_fmt_1) ? SCSW_FLAGS_MASK_FMT : 0;
         sch->ccw_no_data_cnt = 0;
+        sch->ccw_tic_cnt = 0;
         suspend_allowed = !!(orb->ctrl0 & ORB_CTRL0_MASK_SPND);
     } else {
         /* Start Function resumed via rsch */
diff --git a/hw/s390x/sclp.c b/hw/s390x/sclp.c
index 2d2cde7..3c8cb16 100644
--- a/hw/s390x/sclp.c
+++ b/hw/s390x/sclp.c
@@ -329,7 +329,8 @@
     /*
      * we want to work on a private copy of the sccb, to prevent guests
      * from playing dirty tricks by modifying the memory content after
-     * the host has checked the values
+     * the host has checked the values.
+     * Reuse the previously fetched header
      */
     work_sccb = g_malloc0(be16_to_cpu(header.length));
     ret = address_space_read(as, sccb, attrs,
@@ -337,6 +338,7 @@
     if (ret != MEMTX_OK) {
         return -PGM_ADDRESSING;
     }
+    work_sccb->h = header;
 
     if (!sclp_command_code_valid(code)) {
         work_sccb->h.response_code = cpu_to_be16(SCLP_RC_INVALID_SCLP_COMMAND);
diff --git a/hw/s390x/sclpcpi.c b/hw/s390x/sclpcpi.c
index 68fc1b8..ec4bdf2 100644
--- a/hw/s390x/sclpcpi.c
+++ b/hw/s390x/sclpcpi.c
@@ -97,6 +97,11 @@
                                              ebh);
     SCLPEventCPI *e = SCLP_EVENT_CPI(event);
 
+    /* Caller checks sccb length, buffer header checking is our duty */
+    if (be16_to_cpu(evt_buf_hdr->length) != sizeof(ControlProgramIdMsg)) {
+        return SCLP_RC_INCONSISTENT_LENGTHS;
+    }
+
     ascii_put(e->system_type, (char *)cpim->data.system_type,
               sizeof(cpim->data.system_type));
     ascii_put(e->system_name, (char *)cpim->data.system_name,
diff --git a/hw/usb/dev-wacom.c b/hw/usb/dev-wacom.c
index c69e247..895edcc 100644
--- a/hw/usb/dev-wacom.c
+++ b/hw/usb/dev-wacom.c
@@ -285,6 +285,10 @@
         b |= 0x04;
     }
 
+    if (len < 3) {
+        return 0;
+    }
+
     buf[0] = b;
     buf[1] = dx;
     buf[2] = dy;
diff --git a/include/hw/misc/stm32_rcc.h b/include/hw/misc/stm32_rcc.h
index 4dccacc..a94781b 100644
--- a/include/hw/misc/stm32_rcc.h
+++ b/include/hw/misc/stm32_rcc.h
@@ -65,7 +65,11 @@
 
 #define STM32_RCC_NREGS ((STM32_RCC_DCKCFGR2 >> 2) + 1)
 #define STM32_RCC_PERIPHERAL_SIZE 0x400
-#define STM32_RCC_NIRQS (32 * 5) /* 32 bits per reg, 5 en/rst regs */
+
+/* 32 bits per reg, 3 AHB regs and 2 APB regs */
+#define STM32_RCC_N_AHB_IRQS (32 * 3)
+#define STM32_RCC_N_APB_IRQS (32 * 2)
+#define STM32_RCC_NIRQS (STM32_RCC_N_AHB_IRQS + STM32_RCC_N_APB_IRQS)
 
 #define STM32_RCC_GPIO_IRQ_OFFSET 0
 
diff --git a/include/hw/s390x/css.h b/include/hw/s390x/css.h
index d332623..79b41f3 100644
--- a/include/hw/s390x/css.h
+++ b/include/hw/s390x/css.h
@@ -132,6 +132,7 @@
     bool ccw_fmt_1;
     bool thinint_active;
     uint8_t ccw_no_data_cnt;
+    uint8_t ccw_tic_cnt;
     uint16_t migrated_schid; /* used for mismatch detection */
     CcwDataStream cds;
     /* transport-provided data: */
diff --git a/linux-user/alpha/elfload.c b/linux-user/alpha/elfload.c
index 1969f62..7be9e46 100644
--- a/linux-user/alpha/elfload.c
+++ b/linux-user/alpha/elfload.c
@@ -6,6 +6,17 @@
 #include "target_elf.h"
 
 
+abi_ulong get_elf_hwcap(CPUState *cs)
+{
+    /*
+     * The Linux kernel computes ELF_HWCAP as ~amask(-1), which clears a bit
+     * for each supported ISA extension.  env->amask stores exactly those bits
+     * set for the extensions supported by the emulated CPU model, matching
+     * the kernel's convention: bit set in AT_HWCAP ↔ extension present.
+     */
+    return cpu_env(cs)->amask;
+}
+
 void elf_core_copy_regs(target_elf_gregset_t *r, const CPUAlphaState *env)
 {
     int i;
diff --git a/linux-user/alpha/target_elf.h b/linux-user/alpha/target_elf.h
index 4987ae3..dd90c6f 100644
--- a/linux-user/alpha/target_elf.h
+++ b/linux-user/alpha/target_elf.h
@@ -12,6 +12,7 @@
 #define ELF_MACHINE             EM_ALPHA
 
 #define HAVE_ELF_CORE_DUMP      1
+#define HAVE_ELF_HWCAP          1
 
 /*
  * Matches the kernel's elf_gregset_t (ELF_NGREG = 33):
diff --git a/linux-user/syscall.c b/linux-user/syscall.c
index b3572a6..3da5530 100644
--- a/linux-user/syscall.c
+++ b/linux-user/syscall.c
@@ -5088,6 +5088,9 @@
 }
 #endif /* CONFIG_USBFS */
 
+#define DM_MAX_TARGETS                  1048576
+#define DM_MAX_TARGET_PARAMS            1024
+
 static abi_long do_ioctl_dm(const IOCTLEntry *ie, uint8_t *buf_temp, int fd,
                             int cmd, abi_long arg)
 {
@@ -5100,6 +5103,7 @@
     abi_long ret;
     void *big_buf = NULL;
     char *host_data;
+    const size_t minimum_data_size = offsetof(struct dm_ioctl, data);
 
     arg_type++;
     target_size = thunk_type_size(arg_type, 0);
@@ -5111,9 +5115,26 @@
     thunk_convert(buf_temp, argptr, arg_type, THUNK_HOST);
     unlock_user(argptr, arg, 0);
 
-    /* buf_temp is too small, so fetch things into a bigger buffer */
-    big_buf = g_malloc0(((struct dm_ioctl*)buf_temp)->data_size * 2);
-    memcpy(big_buf, buf_temp, target_size);
+    /* At this point this includes the size of the fixed dm_ioctl parts */
+    guest_data_size = ((struct dm_ioctl *)buf_temp)->data_size;
+
+    if (guest_data_size < minimum_data_size ||
+        guest_data_size > DM_MAX_TARGETS * DM_MAX_TARGET_PARAMS) {
+        ret = -TARGET_EINVAL;
+        goto out;
+    }
+
+    /*
+     * buf_temp is too small, so fetch things into a bigger buffer. Here
+     * we copy all of the fixed parts of struct dm_ioctl but not the
+     * data at the end (which in the struct is "char data[7]" but in
+     * reality is command-specific and might be nothing or might be
+     * much larger, as defined by data_size). We know struct dm_ioctl's
+     * size is not target specific so we don't need to distinguish between
+     * its minimum size for the host vs the target.
+     */
+    big_buf = g_malloc0(guest_data_size * 2);
+    memcpy(big_buf, buf_temp, minimum_data_size);
     buf_temp = big_buf;
     host_dm = big_buf;
 
@@ -5122,7 +5143,8 @@
         ret = -TARGET_EINVAL;
         goto out;
     }
-    guest_data_size = host_dm->data_size - host_dm->data_start;
+    /* Adjust down to only the size of the payload */
+    guest_data_size -= host_dm->data_start;
     host_data = (char*)host_dm + host_dm->data_start;
 
     argptr = lock_user(VERIFY_READ, guest_data, guest_data_size, 1);
diff --git a/contrib/gitdm/aliases b/scripts/gitdm/aliases
similarity index 96%
rename from contrib/gitdm/aliases
rename to scripts/gitdm/aliases
index e26b00a..f838768 100644
--- a/contrib/gitdm/aliases
+++ b/scripts/gitdm/aliases
@@ -33,6 +33,7 @@
 
 # canonical emails
 liq3ea@163.com liq3ea@gmail.com
+aleksandr.mikhalitsyn@futurfusion.io alexander@mihalicyn.com
 
 # some broken DCO tags
 yuval.shaia.ml.gmail.com yuval.shaia.ml@gmail.com
diff --git a/contrib/gitdm/domain-map b/scripts/gitdm/domain-map
similarity index 93%
rename from contrib/gitdm/domain-map
rename to scripts/gitdm/domain-map
index c07dffc..aef20c4 100644
--- a/contrib/gitdm/domain-map
+++ b/scripts/gitdm/domain-map
@@ -40,7 +40,8 @@
 nvidia.com      NVIDIA
 oracle.com      Oracle
 proxmox.com     Proxmox
-quicinc.com     Qualcomm Innovation Center
+oss.qualcomm.com Qualcomm Technologies, Inc.
+quicinc.com     Qualcomm Technologies, Inc.
 redhat.com      Red Hat
 rev.ng          rev.ng Labs
 rivosinc.com    Rivos Inc
diff --git a/contrib/gitdm/filetypes.txt b/scripts/gitdm/filetypes.txt
similarity index 100%
rename from contrib/gitdm/filetypes.txt
rename to scripts/gitdm/filetypes.txt
diff --git a/contrib/gitdm/group-map-academics b/scripts/gitdm/group-map-academics
similarity index 100%
rename from contrib/gitdm/group-map-academics
rename to scripts/gitdm/group-map-academics
diff --git a/contrib/gitdm/group-map-alibaba b/scripts/gitdm/group-map-alibaba
similarity index 100%
rename from contrib/gitdm/group-map-alibaba
rename to scripts/gitdm/group-map-alibaba
diff --git a/contrib/gitdm/group-map-amd b/scripts/gitdm/group-map-amd
similarity index 100%
rename from contrib/gitdm/group-map-amd
rename to scripts/gitdm/group-map-amd
diff --git a/contrib/gitdm/group-map-cadence b/scripts/gitdm/group-map-cadence
similarity index 100%
rename from contrib/gitdm/group-map-cadence
rename to scripts/gitdm/group-map-cadence
diff --git a/contrib/gitdm/group-map-codeweavers b/scripts/gitdm/group-map-codeweavers
similarity index 100%
rename from contrib/gitdm/group-map-codeweavers
rename to scripts/gitdm/group-map-codeweavers
diff --git a/contrib/gitdm/group-map-facebook b/scripts/gitdm/group-map-facebook
similarity index 100%
rename from contrib/gitdm/group-map-facebook
rename to scripts/gitdm/group-map-facebook
diff --git a/contrib/gitdm/group-map-huawei b/scripts/gitdm/group-map-huawei
similarity index 100%
rename from contrib/gitdm/group-map-huawei
rename to scripts/gitdm/group-map-huawei
diff --git a/scripts/gitdm/group-map-ibm b/scripts/gitdm/group-map-ibm
new file mode 100644
index 0000000..39c53a4
--- /dev/null
+++ b/scripts/gitdm/group-map-ibm
@@ -0,0 +1,7 @@
+#
+# Some IBM contributors submit via another domain
+#
+
+jcfaracco@gmail.com
+tommusta@gmail.com
+idan.horowitz@gmail.com
diff --git a/contrib/gitdm/group-map-individuals b/scripts/gitdm/group-map-individuals
similarity index 95%
rename from contrib/gitdm/group-map-individuals
rename to scripts/gitdm/group-map-individuals
index 178b393..190941b 100644
--- a/contrib/gitdm/group-map-individuals
+++ b/scripts/gitdm/group-map-individuals
@@ -46,3 +46,5 @@
 me@sean.taipei
 kosasihwilliam4@gmail.com
 philmd@mailo.com
+tomitamoeko@gmail.com
+neither@nut.email
diff --git a/contrib/gitdm/group-map-interns b/scripts/gitdm/group-map-interns
similarity index 100%
rename from contrib/gitdm/group-map-interns
rename to scripts/gitdm/group-map-interns
diff --git a/contrib/gitdm/group-map-janustech b/scripts/gitdm/group-map-janustech
similarity index 100%
rename from contrib/gitdm/group-map-janustech
rename to scripts/gitdm/group-map-janustech
diff --git a/contrib/gitdm/group-map-microsoft b/scripts/gitdm/group-map-microsoft
similarity index 100%
rename from contrib/gitdm/group-map-microsoft
rename to scripts/gitdm/group-map-microsoft
diff --git a/contrib/gitdm/group-map-netflix b/scripts/gitdm/group-map-netflix
similarity index 100%
rename from contrib/gitdm/group-map-netflix
rename to scripts/gitdm/group-map-netflix
diff --git a/contrib/gitdm/group-map-redhat b/scripts/gitdm/group-map-redhat
similarity index 100%
rename from contrib/gitdm/group-map-redhat
rename to scripts/gitdm/group-map-redhat
diff --git a/contrib/gitdm/group-map-robots b/scripts/gitdm/group-map-robots
similarity index 100%
rename from contrib/gitdm/group-map-robots
rename to scripts/gitdm/group-map-robots
diff --git a/contrib/gitdm/group-map-wavecomp b/scripts/gitdm/group-map-wavecomp
similarity index 100%
rename from contrib/gitdm/group-map-wavecomp
rename to scripts/gitdm/group-map-wavecomp
diff --git a/target/arm/cpu-max.c b/target/arm/cpu-max.c
index d38bdfc..88b7c04 100644
--- a/target/arm/cpu-max.c
+++ b/target/arm/cpu-max.c
@@ -130,7 +130,7 @@
     t = FIELD_DP32(t, ID_MMFR4, AC2, 1);          /* ACTLR2, HACTLR2 */
     t = FIELD_DP32(t, ID_MMFR4, CNP, 1);          /* FEAT_TTCNP */
     t = FIELD_DP32(t, ID_MMFR4, XNX, 1);          /* FEAT_XNX */
-    t = FIELD_DP32(t, ID_MMFR4, EVT, 2);          /* FEAT_EVT */
+    t = FIELD_DP32(t, ID_MMFR4, EVT, 2);          /* FEAT_EVT2 */
     SET_IDREG(isar, ID_MMFR4, t);
 
     FIELD_DP32_IDREG(isar, ID_MMFR5, ETS, 2);          /* FEAT_ETS2 */
diff --git a/target/arm/tcg/tlb-insns.c b/target/arm/tcg/tlb-insns.c
index 1a0a332..b24eb57 100644
--- a/target/arm/tcg/tlb-insns.c
+++ b/target/arm/tcg/tlb-insns.c
@@ -854,7 +854,7 @@
     gran = tlbi_range_tg_to_gran_size(page_size_granule);
 
     /* The granule encoded in value must match the granule in use. */
-    if (gran != param.gran) {
+    if (gran != param.gran || gran == GranInvalid) {
         qemu_log_mask(LOG_GUEST_ERROR, "Invalid tlbi page size granule %d\n",
                       page_size_granule);
         return ret;
diff --git a/tests/functional/riscv64/meson.build b/tests/functional/riscv64/meson.build
index c9d4c09..d1a3e6c 100644
--- a/tests/functional/riscv64/meson.build
+++ b/tests/functional/riscv64/meson.build
@@ -2,6 +2,7 @@
 
 test_riscv64_timeouts = {
   'boston' : 120,
+  'k230' : 120,
   'tuxrun' : 120,
 }
 
@@ -13,6 +14,7 @@
 tests_riscv64_system_thorough = [
   'endianness',
   'boston',
+  'k230',
   'sifive_u',
   'tt_atlantis',
   'tuxrun',
diff --git a/tests/functional/riscv64/test_k230.py b/tests/functional/riscv64/test_k230.py
new file mode 100755
index 0000000..6ccbf2d
--- /dev/null
+++ b/tests/functional/riscv64/test_k230.py
@@ -0,0 +1,108 @@
+#!/usr/bin/env python3
+#
+# Functional tests that boot Linux on a Kendryte K230 machine.
+#
+# The direct boot test lets QEMU load OpenSBI, Linux, the device tree, and the
+# initramfs.  The firmware boot test starts the K230 SDK U-Boot and uses bootm
+# to launch OpenSBI and Linux from images preloaded into RAM.
+#
+# Author:
+#  Junze Cao
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+import os
+
+from qemu_test import Asset, LinuxKernelTest
+from qemu_test import exec_command_and_wait_for_pattern
+from qemu_test import interrupt_interactive_console_until_pattern
+
+
+class K230Machine(LinuxKernelTest):
+
+    ASSET_KERNEL = Asset(
+        ('https://raw.githubusercontent.com/zevorn/k230-boot-assets/'
+         'c3c32fb46e8307c5063f13e8f367c98bf9273cd1/'
+         'yocto/direct-boot/Image'),
+        '3a44970213fa68ad318d308518adfc0bf4bee72ed1b2926f9b468f82ef7d7829')
+    ASSET_DTB = Asset(
+        ('https://raw.githubusercontent.com/zevorn/k230-boot-assets/'
+         'c3c32fb46e8307c5063f13e8f367c98bf9273cd1/'
+         'yocto/direct-boot/k230-canmv.dtb'),
+        '5050240b48ce0988c73eaefa73e4945a40abca503cf488d22a3adf6ef50bbe4c')
+    ASSET_INITRD = Asset(
+        ('https://raw.githubusercontent.com/zevorn/k230-boot-assets/'
+         'c3c32fb46e8307c5063f13e8f367c98bf9273cd1/'
+         'yocto/direct-boot/rootfs.cpio.gz'),
+        '4e1869a99a232ee60324f71f3a9e84a79b03ccabb5b73f8a727c5ff5be5c0914')
+    ASSET_UBOOT = Asset(
+        ('https://raw.githubusercontent.com/zevorn/k230-boot-assets/'
+         'c3c32fb46e8307c5063f13e8f367c98bf9273cd1/common/u-boot'),
+        '0915b9a92a7c911846a8cf691866ef14ef050a51d04209f884ae8e9ec33f36d2')
+    ASSET_FW_JUMP = Asset(
+        ('https://raw.githubusercontent.com/zevorn/k230-boot-assets/'
+         'c3c32fb46e8307c5063f13e8f367c98bf9273cd1/'
+         'common/fw_jump.uImage'),
+        'cf7788e470f1d6e8c85491ecdc2705518db1b6af54080e8c7a3464bad0d902b7')
+
+    def wait_for_linux_shell(self):
+        self.wait_for_console_pattern('meta-k230 initramfs starting...')
+        self.wait_for_console_pattern('~ #')
+
+    def test_k230_direct_boot(self):
+        self.set_machine('k230')
+        kernel_path = self.ASSET_KERNEL.fetch()
+        dtb_path = self.ASSET_DTB.fetch()
+        initrd_path = self.ASSET_INITRD.fetch()
+
+        self.vm.set_console()
+        self.vm.add_args('-kernel', kernel_path,
+                         '-dtb', dtb_path,
+                         '-initrd', initrd_path,
+                         '-append', 'console=ttyS0,115200 earlycon=sbi',
+                         '-no-reboot')
+        self.vm.launch()
+        self.wait_for_linux_shell()
+
+    def test_k230_uboot_boot(self):
+        self.set_machine('k230')
+        kernel_path = self.ASSET_KERNEL.fetch()
+        dtb_path = self.ASSET_DTB.fetch()
+        initrd_path = self.ASSET_INITRD.fetch()
+        uboot_path = self.ASSET_UBOOT.fetch()
+        fw_jump_path = self.ASSET_FW_JUMP.fetch()
+        initrd_end = 0x0a100000 + os.path.getsize(initrd_path)
+
+        self.vm.set_console()
+        self.vm.add_args(
+            '-bios', uboot_path,
+            '-device',
+            f'loader,file={fw_jump_path},addr=0xc100000,force-raw=on',
+            '-device',
+            f'loader,file={kernel_path},addr=0x8200000,force-raw=on',
+            '-device',
+            f'loader,file={initrd_path},addr=0xa100000,force-raw=on',
+            '-device',
+            f'loader,file={dtb_path},addr=0xa000000,force-raw=on',
+            '-no-reboot')
+        self.vm.launch()
+
+        interrupt_interactive_console_until_pattern(self, 'K230#')
+        commands = (
+            'setenv bootargs console=ttyS0,115200 earlycon=sbi',
+            'fdt addr 0xa000000',
+            'fdt resize 8192',
+            'fdt set /chosen linux,initrd-start <0x0 0xa100000>',
+            f'fdt set /chosen linux,initrd-end <0x0 0x{initrd_end:x}>',
+        )
+        for command in commands:
+            exec_command_and_wait_for_pattern(self, command, 'K230#')
+
+        exec_command_and_wait_for_pattern(
+            self, 'bootm 0xc100000 - 0xa000000',
+            'Starting kernel ...', failure_message='ERROR')
+        self.wait_for_linux_shell()
+
+
+if __name__ == '__main__':
+    LinuxKernelTest.main()
diff --git a/tests/functional/riscv64/test_sifive_u.py b/tests/functional/riscv64/test_sifive_u.py
index 847f709..54a0f9c 100755
--- a/tests/functional/riscv64/test_sifive_u.py
+++ b/tests/functional/riscv64/test_sifive_u.py
@@ -35,9 +35,6 @@ def do_test_riscv64_sifive_u_mmc_spi(self, connect_card):
         kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE +
                                'earlycon=sbi console=ttySIF0 '
                                'root=/dev/mmcblk0 ')
-        self.vm.add_args('-kernel', kernel_path,
-                         '-append', kernel_command_line,
-                         '-no-reboot')
         if connect_card:
             kernel_command_line += 'panic=-1 noreboot rootwait '
             self.vm.add_args('-drive', f'file={rootfs_path},if=sd,format=raw')
@@ -46,6 +43,9 @@ def do_test_riscv64_sifive_u_mmc_spi(self, connect_card):
             kernel_command_line += 'panic=0 noreboot '
             pattern = 'Cannot open root device "mmcblk0" or unknown-block(0,0)'
 
+        self.vm.add_args('-kernel', kernel_path,
+                         '-append', kernel_command_line,
+                         '-no-reboot')
         self.vm.launch()
         self.wait_for_console_pattern(pattern)
 
diff --git a/tests/qtest/adm1266-test.c b/tests/qtest/adm1266-test.c
index 5ae8206..726e475 100644
--- a/tests/qtest/adm1266-test.c
+++ b/tests/qtest/adm1266-test.c
@@ -48,11 +48,13 @@
 static void compare_string(QI2CDevice *i2cdev, uint8_t reg,
                            const char *test_str)
 {
-    uint8_t len = i2c_get8(i2cdev, reg);
-    char i2c_str[SMBUS_DATA_MAX_LEN] = {0};
+    uint8_t expected_len = strlen(test_str);
+    uint8_t resp[SMBUS_DATA_MAX_LEN] = {0};
 
-    i2c_read_block(i2cdev, reg, (uint8_t *)i2c_str, len);
-    g_assert_cmpstr(i2c_str, ==, test_str);
+    g_assert(expected_len + 1 < SMBUS_DATA_MAX_LEN);
+    i2c_read_block(i2cdev, reg, resp, expected_len + 1);
+    g_assert_cmpint(resp[0], ==, expected_len);
+    g_assert_cmpstr((char *)resp + 1, ==, test_str);
 }
 
 static void write_and_compare_string(QI2CDevice *i2cdev, uint8_t reg,