Merge tag 'next-pull-request' of https://gitlab.com/peterx/qemu into staging

migration/mem pull for 11.2

v2: fixes macos build error

- Dongli's patch to add cpr-transfer support for HMP
- Fabiano's doc update for migration on security issues
- Gavin's fix for MMIO access support for memory APIs, reverting ram_device ops
- Sam's migration test build fix for !ASN1
- Peter's a few migration hardening fixes

# -----BEGIN PGP SIGNATURE-----
#
# iIgEABYKADAWIQS5GE3CDMRX2s990ak7X8zN86vXBgUCan3KARIccGV0ZXJ4QHJl
# ZGhhdC5jb20ACgkQO1/MzfOr1wa76QD/eBLnPtDvmpNHNH3+bm/3XC3zwyy7v69U
# bGK3ocwI3sQA/j9o5FCc7xDCA0QaW6RMeerlLXvXR0uwH46UESKKDloF
# =/Jbs
# -----END PGP SIGNATURE-----
# gpg: Signature made Thu 13 Aug 2026 06:43:29 AM PDT
# gpg:                using EDDSA key B9184DC20CC457DACF7DD1A93B5FCCCDF3ABD706
# gpg:                issuer "peterx@redhat.com"
# gpg: Good signature from "Peter Xu <xzpeter@gmail.com>" [unknown]
# gpg:                 aka "Peter Xu <peterx@redhat.com>" [unknown]
# gpg: WARNING: The key's User ID is not certified with a trusted signature!
# gpg:          There is no indication that the signature belongs to the owner.
# Primary key fingerprint: B918 4DC2 0CC4 57DA CF7D  D1A9 3B5F CCCD F3AB D706

* tag 'next-pull-request' of https://gitlab.com/peterx/qemu:
  migration: Fix rare hang of migration_channel_read_peek()
  migration/ram: Check for RAMBlock size mismatch when parsing
  migration/multifd: Replace assert() with error_setg() in recv paths
  migration/multifd: Validate next_packet_size in zlib/zstd recv
  tests/qtest/migration: Only build tls_no_hostname test with TASN1
  system/memory: Make ram device region directly accessible
  system/memory: Use qemu_ram_move() for directly accessible regions
  system/memory: Use memmove() for directly accessible regions
  migration/cpr: Add HMP support for cpr-transfer
  docs: Add security considerations for migration

Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
diff --git a/MAINTAINERS b/MAINTAINERS
index 902db77..f249be7 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -250,6 +250,12 @@
 R: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>
 S: Supported
 F: target/hexagon/
+F: hw/intc/hex-l2vic.c
+F: include/hw/intc/hex-l2vic.h
+F: tests/qtest/l2vic-test.c
+F: hw/timer/qct-qtimer.c
+F: include/hw/timer/qct-qtimer.h
+F: tests/qtest/qct-qtimer-test.c
 X: target/hexagon/idef-parser/
 X: target/hexagon/gen_idef_parser_funcs.py
 F: linux-user/hexagon/
@@ -262,6 +268,7 @@
 F: docs/system/target-hexagon.rst
 F: docs/system/hexagon/
 F: docs/devel/hexagon-sys.rst
+F: docs/devel/hexagon-l2vic.rst
 T: git https://github.com/qualcomm/qemu.git hex-next
 
 Hexagon idef-parser
@@ -1300,6 +1307,15 @@
 S: Maintained
 F: rust/hw/char/pl011/
 
+Axiado SoCs and EVKs
+M: Kuan-Jui Chiu <kchiu@axiado.com>
+L: qemu-arm@nongnu.org
+S: Maintained
+F: hw/arm/ax3000*.c
+F: hw/*/axiado*.c
+F: include/hw/arm/ax3000*.h
+F: include/hw/*/axiado*.h
+
 AVR Machines
 -------------
 
@@ -2160,6 +2176,7 @@
 
 IDE
 M: John Snow <jsnow@redhat.com>
+M: Denis V. Lunev <den@openvz.org>
 L: qemu-block@nongnu.org
 S: Odd Fixes
 F: include/hw/ide/
diff --git a/VERSION b/VERSION
index 68d8f15..cf942bd 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-11.1.0
+11.1.50
diff --git a/crypto/cipher-gcrypt.c.inc b/crypto/cipher-gcrypt.c.inc
index 12eb9dd..fce09a3c 100644
--- a/crypto/cipher-gcrypt.c.inc
+++ b/crypto/cipher-gcrypt.c.inc
@@ -65,6 +65,8 @@
         return GCRY_CIPHER_MODE_CBC;
     case QCRYPTO_CIPHER_MODE_CTR:
         return GCRY_CIPHER_MODE_CTR;
+    case QCRYPTO_CIPHER_MODE_GCM:
+        return GCRY_CIPHER_MODE_GCM;
     default:
         return GCRY_CIPHER_MODE_NONE;
     }
@@ -104,6 +106,10 @@
     case QCRYPTO_CIPHER_MODE_XTS:
     case QCRYPTO_CIPHER_MODE_CTR:
         return true;
+    case QCRYPTO_CIPHER_MODE_GCM:
+        /* GCM requires a 128-bit block cipher. */
+        return gcry_cipher_get_algo_blklen(
+                   qcrypto_cipher_alg_to_gcry_alg(alg)) == 16;
     default:
         return false;
     }
@@ -228,6 +234,99 @@
     .cipher_free = qcrypto_gcrypt_ctx_free,
 };
 
+/*
+ * GCM is an AEAD stream mode: the IV/nonce need not match the block size,
+ * the message length need not be a multiple of the block size, associated
+ * data is fed with gcry_cipher_authenticate() and the authentication tag is
+ * read back with gcry_cipher_gettag().
+ */
+static int qcrypto_gcrypt_gcm_setiv(QCryptoCipher *cipher,
+                                    const uint8_t *iv, size_t niv,
+                                    Error **errp)
+{
+    QCryptoCipherGcrypt *ctx = container_of(cipher, QCryptoCipherGcrypt, base);
+    gcry_error_t err;
+
+    gcry_cipher_reset(ctx->handle);
+    err = gcry_cipher_setiv(ctx->handle, iv, niv);
+    if (err != 0) {
+        error_setg(errp, "Cannot set IV: %s", gcry_strerror(err));
+        return -1;
+    }
+
+    return 0;
+}
+
+static int qcrypto_gcrypt_gcm_setaad(QCryptoCipher *cipher,
+                                     const uint8_t *aad, size_t len,
+                                     Error **errp)
+{
+    QCryptoCipherGcrypt *ctx = container_of(cipher, QCryptoCipherGcrypt, base);
+    gcry_error_t err;
+
+    err = gcry_cipher_authenticate(ctx->handle, aad, len);
+    if (err != 0) {
+        error_setg(errp, "Cannot set AAD: %s", gcry_strerror(err));
+        return -1;
+    }
+
+    return 0;
+}
+
+static int qcrypto_gcrypt_gcm_encrypt(QCryptoCipher *cipher, const void *in,
+                                      void *out, size_t len, Error **errp)
+{
+    QCryptoCipherGcrypt *ctx = container_of(cipher, QCryptoCipherGcrypt, base);
+    gcry_error_t err;
+
+    err = gcry_cipher_encrypt(ctx->handle, out, len, in, len);
+    if (err != 0) {
+        error_setg(errp, "Cannot encrypt data: %s", gcry_strerror(err));
+        return -1;
+    }
+
+    return 0;
+}
+
+static int qcrypto_gcrypt_gcm_decrypt(QCryptoCipher *cipher, const void *in,
+                                      void *out, size_t len, Error **errp)
+{
+    QCryptoCipherGcrypt *ctx = container_of(cipher, QCryptoCipherGcrypt, base);
+    gcry_error_t err;
+
+    err = gcry_cipher_decrypt(ctx->handle, out, len, in, len);
+    if (err != 0) {
+        error_setg(errp, "Cannot decrypt data: %s", gcry_strerror(err));
+        return -1;
+    }
+
+    return 0;
+}
+
+static int qcrypto_gcrypt_gcm_gettag(QCryptoCipher *cipher,
+                                     uint8_t *tag, size_t len, Error **errp)
+{
+    QCryptoCipherGcrypt *ctx = container_of(cipher, QCryptoCipherGcrypt, base);
+    gcry_error_t err;
+
+    err = gcry_cipher_gettag(ctx->handle, tag, len);
+    if (err != 0) {
+        error_setg(errp, "Cannot get tag: %s", gcry_strerror(err));
+        return -1;
+    }
+
+    return 0;
+}
+
+static const struct QCryptoCipherDriver qcrypto_gcrypt_gcm_driver = {
+    .cipher_encrypt = qcrypto_gcrypt_gcm_encrypt,
+    .cipher_decrypt = qcrypto_gcrypt_gcm_decrypt,
+    .cipher_setiv = qcrypto_gcrypt_gcm_setiv,
+    .cipher_setaad = qcrypto_gcrypt_gcm_setaad,
+    .cipher_gettag = qcrypto_gcrypt_gcm_gettag,
+    .cipher_free = qcrypto_gcrypt_ctx_free,
+};
+
 static QCryptoCipher *qcrypto_cipher_ctx_new(QCryptoCipherAlgo alg,
                                              QCryptoCipherMode mode,
                                              const uint8_t *key,
@@ -259,6 +358,8 @@
 
     if (mode == QCRYPTO_CIPHER_MODE_CTR) {
         drv = &qcrypto_gcrypt_ctr_driver;
+    } else if (mode == QCRYPTO_CIPHER_MODE_GCM) {
+        drv = &qcrypto_gcrypt_gcm_driver;
     } else {
         drv = &qcrypto_gcrypt_driver;
     }
diff --git a/crypto/cipher-gnutls.c.inc b/crypto/cipher-gnutls.c.inc
index a8263ff..963b328 100644
--- a/crypto/cipher-gnutls.c.inc
+++ b/crypto/cipher-gnutls.c.inc
@@ -48,6 +48,15 @@
         default:
             return false;
         }
+    case QCRYPTO_CIPHER_MODE_GCM:
+        switch (alg) {
+        case QCRYPTO_CIPHER_ALGO_AES_128:
+        case QCRYPTO_CIPHER_ALGO_AES_192:
+        case QCRYPTO_CIPHER_ALGO_AES_256:
+            return true;
+        default:
+            return false;
+        }
     default:
         return false;
     }
@@ -223,6 +232,147 @@
     .cipher_free = qcrypto_gnutls_cipher_free,
 };
 
+/*
+ * GCM is an AEAD stream mode: the nonce need not match the block size, the
+ * message length need not be a multiple of the block size, associated data is
+ * fed with gnutls_cipher_add_auth() and the authentication tag is read back
+ * with gnutls_cipher_tag().
+ */
+static int
+qcrypto_gnutls_cipher_encrypt_gcm(QCryptoCipher *cipher,
+                                  const void *in, void *out,
+                                  size_t len, Error **errp)
+{
+    QCryptoCipherGnutls *ctx = container_of(cipher, QCryptoCipherGnutls, base);
+    int err;
+
+    err = gnutls_cipher_encrypt2(ctx->handle, in, len, out, len);
+    if (err != 0) {
+        error_setg(errp, "Cannot encrypt data: %s", gnutls_strerror(err));
+        return -1;
+    }
+
+    return 0;
+}
+
+static int
+qcrypto_gnutls_cipher_decrypt_gcm(QCryptoCipher *cipher,
+                                  const void *in, void *out,
+                                  size_t len, Error **errp)
+{
+    QCryptoCipherGnutls *ctx = container_of(cipher, QCryptoCipherGnutls, base);
+    int err;
+
+    err = gnutls_cipher_decrypt2(ctx->handle, in, len, out, len);
+    if (err != 0) {
+        error_setg(errp, "Cannot decrypt data: %s", gnutls_strerror(err));
+        return -1;
+    }
+
+    return 0;
+}
+
+static int
+qcrypto_gnutls_cipher_setiv_gcm(QCryptoCipher *cipher,
+                                const uint8_t *iv, size_t niv,
+                                Error **errp)
+{
+    QCryptoCipherGnutls *ctx = container_of(cipher, QCryptoCipherGnutls, base);
+
+    gnutls_cipher_set_iv(ctx->handle, (void *)iv, niv);
+
+    return 0;
+}
+
+static int
+qcrypto_gnutls_cipher_setaad_gcm(QCryptoCipher *cipher,
+                                 const uint8_t *aad, size_t len,
+                                 Error **errp)
+{
+    QCryptoCipherGnutls *ctx = container_of(cipher, QCryptoCipherGnutls, base);
+    int err;
+
+    err = gnutls_cipher_add_auth(ctx->handle, aad, len);
+    if (err != 0) {
+        error_setg(errp, "Cannot add associated data: %s",
+                   gnutls_strerror(err));
+        return -1;
+    }
+
+    return 0;
+}
+
+static int
+qcrypto_gnutls_cipher_gettag_gcm(QCryptoCipher *cipher,
+                                 uint8_t *tag, size_t len,
+                                 Error **errp)
+{
+    QCryptoCipherGnutls *ctx = container_of(cipher, QCryptoCipherGnutls, base);
+    int err;
+
+    err = gnutls_cipher_tag(ctx->handle, tag, len);
+    if (err != 0) {
+        error_setg(errp, "Cannot get authentication tag: %s",
+                   gnutls_strerror(err));
+        return -1;
+    }
+
+    return 0;
+}
+
+static struct QCryptoCipherDriver gnutls_gcm_driver = {
+    .cipher_encrypt = qcrypto_gnutls_cipher_encrypt_gcm,
+    .cipher_decrypt = qcrypto_gnutls_cipher_decrypt_gcm,
+    .cipher_setiv = qcrypto_gnutls_cipher_setiv_gcm,
+    .cipher_setaad = qcrypto_gnutls_cipher_setaad_gcm,
+    .cipher_gettag = qcrypto_gnutls_cipher_gettag_gcm,
+    .cipher_free = qcrypto_gnutls_cipher_free,
+};
+
+static QCryptoCipher *
+qcrypto_gnutls_aes_gcm_ctx_new(QCryptoCipherAlgo alg, const uint8_t *key,
+                               size_t nkey, Error **errp)
+{
+    gnutls_datum_t gkey = { (unsigned char *)key, nkey };
+    gnutls_cipher_algorithm_t galg = GNUTLS_CIPHER_UNKNOWN;
+    QCryptoCipherGnutls *ctx;
+    int err;
+
+    switch (alg) {
+    case QCRYPTO_CIPHER_ALGO_AES_128:
+        galg = GNUTLS_CIPHER_AES_128_GCM;
+        break;
+    case QCRYPTO_CIPHER_ALGO_AES_192:
+        galg = GNUTLS_CIPHER_AES_192_GCM;
+        break;
+    case QCRYPTO_CIPHER_ALGO_AES_256:
+        galg = GNUTLS_CIPHER_AES_256_GCM;
+        break;
+    default:
+        error_setg(errp, "Unsupported cipher algorithm %s with GCM mode",
+                   QCryptoCipherAlgo_str(alg));
+        return NULL;
+    }
+
+    if (!qcrypto_cipher_validate_key_length(alg, QCRYPTO_CIPHER_MODE_GCM,
+                                            nkey, errp)) {
+        return NULL;
+    }
+
+    ctx = g_new0(QCryptoCipherGnutls, 1);
+    ctx->base.driver = &gnutls_gcm_driver;
+    ctx->blocksize = 16;
+
+    err = gnutls_cipher_init(&ctx->handle, galg, &gkey, NULL);
+    if (err != 0) {
+        error_setg(errp, "Cannot initialize cipher: %s", gnutls_strerror(err));
+        g_free(ctx);
+        return NULL;
+    }
+
+    return &ctx->base;
+}
+
 static QCryptoCipher *qcrypto_cipher_ctx_new(QCryptoCipherAlgo alg,
                                              QCryptoCipherMode mode,
                                              const uint8_t *key,
@@ -234,6 +384,10 @@
     gnutls_cipher_algorithm_t galg = GNUTLS_CIPHER_UNKNOWN;
     int err;
 
+    if (mode == QCRYPTO_CIPHER_MODE_GCM) {
+        return qcrypto_gnutls_aes_gcm_ctx_new(alg, key, nkey, errp);
+    }
+
     switch (mode) {
     case QCRYPTO_CIPHER_MODE_XTS:
         switch (alg) {
diff --git a/crypto/cipher-nettle.c.inc b/crypto/cipher-nettle.c.inc
index 1afdc39..d4847f0 100644
--- a/crypto/cipher-nettle.c.inc
+++ b/crypto/cipher-nettle.c.inc
@@ -27,6 +27,7 @@
 #include <nettle/twofish.h>
 #include <nettle/ctr.h>
 #include <nettle/xts.h>
+#include <nettle/gcm.h>
 #ifdef CONFIG_CRYPTO_SM4
 #include <nettle/sm4.h>
 #endif
@@ -410,6 +411,125 @@
            sm4_encrypt_native, sm4_decrypt_native)
 #endif
 
+/*
+ * GCM is an AEAD mode built on AES (128-bit block only). Drive it through the
+ * generic gcm_* interface, using the block cipher's encrypt function for both
+ * directions; associated data is fed with gcm_update() and the authentication
+ * tag is produced by gcm_digest().
+ */
+typedef struct QCryptoNettleAESGCM {
+    QCryptoCipher base;
+    struct gcm_key gcm_key;
+    struct gcm_ctx gcm_ctx;
+    union {
+        struct aes128_ctx aes128;
+        struct aes192_ctx aes192;
+        struct aes256_ctx aes256;
+    } cipher;
+    nettle_cipher_func *encrypt;
+} QCryptoNettleAESGCM;
+
+static int qcrypto_nettle_aes_gcm_setiv(QCryptoCipher *cipher,
+                                        const uint8_t *iv, size_t niv,
+                                        Error **errp)
+{
+    QCryptoNettleAESGCM *ctx = container_of(cipher, QCryptoNettleAESGCM, base);
+
+    gcm_set_iv(&ctx->gcm_ctx, &ctx->gcm_key, niv, iv);
+    return 0;
+}
+
+static int qcrypto_nettle_aes_gcm_setaad(QCryptoCipher *cipher,
+                                         const uint8_t *aad, size_t len,
+                                         Error **errp)
+{
+    QCryptoNettleAESGCM *ctx = container_of(cipher, QCryptoNettleAESGCM, base);
+
+    gcm_update(&ctx->gcm_ctx, &ctx->gcm_key, len, aad);
+    return 0;
+}
+
+static int qcrypto_nettle_aes_gcm_encrypt(QCryptoCipher *cipher,
+                                          const void *in, void *out,
+                                          size_t len, Error **errp)
+{
+    QCryptoNettleAESGCM *ctx = container_of(cipher, QCryptoNettleAESGCM, base);
+
+    gcm_encrypt(&ctx->gcm_ctx, &ctx->gcm_key, &ctx->cipher, ctx->encrypt,
+                len, out, in);
+    return 0;
+}
+
+static int qcrypto_nettle_aes_gcm_decrypt(QCryptoCipher *cipher,
+                                          const void *in, void *out,
+                                          size_t len, Error **errp)
+{
+    QCryptoNettleAESGCM *ctx = container_of(cipher, QCryptoNettleAESGCM, base);
+
+    gcm_decrypt(&ctx->gcm_ctx, &ctx->gcm_key, &ctx->cipher, ctx->encrypt,
+                len, out, in);
+    return 0;
+}
+
+static int qcrypto_nettle_aes_gcm_gettag(QCryptoCipher *cipher,
+                                         uint8_t *tag, size_t len,
+                                         Error **errp)
+{
+    QCryptoNettleAESGCM *ctx = container_of(cipher, QCryptoNettleAESGCM, base);
+
+    gcm_digest(&ctx->gcm_ctx, &ctx->gcm_key, &ctx->cipher, ctx->encrypt,
+               len, tag);
+    return 0;
+}
+
+static const struct QCryptoCipherDriver qcrypto_nettle_aes_gcm_driver = {
+    .cipher_encrypt = qcrypto_nettle_aes_gcm_encrypt,
+    .cipher_decrypt = qcrypto_nettle_aes_gcm_decrypt,
+    .cipher_setiv = qcrypto_nettle_aes_gcm_setiv,
+    .cipher_setaad = qcrypto_nettle_aes_gcm_setaad,
+    .cipher_gettag = qcrypto_nettle_aes_gcm_gettag,
+    .cipher_free = qcrypto_cipher_ctx_free,
+};
+
+static QCryptoCipher *qcrypto_nettle_aes_gcm_ctx_new(QCryptoCipherAlgo alg,
+                                                     const uint8_t *key,
+                                                     size_t nkey,
+                                                     Error **errp)
+{
+    QCryptoNettleAESGCM *ctx;
+
+    if (!qcrypto_cipher_validate_key_length(alg, QCRYPTO_CIPHER_MODE_GCM,
+                                            nkey, errp)) {
+        return NULL;
+    }
+
+    ctx = g_new0(QCryptoNettleAESGCM, 1);
+    ctx->base.driver = &qcrypto_nettle_aes_gcm_driver;
+
+    switch (alg) {
+    case QCRYPTO_CIPHER_ALGO_AES_128:
+        aes128_set_encrypt_key(&ctx->cipher.aes128, key);
+        ctx->encrypt = aes128_encrypt_native;
+        break;
+    case QCRYPTO_CIPHER_ALGO_AES_192:
+        aes192_set_encrypt_key(&ctx->cipher.aes192, key);
+        ctx->encrypt = aes192_encrypt_native;
+        break;
+    case QCRYPTO_CIPHER_ALGO_AES_256:
+        aes256_set_encrypt_key(&ctx->cipher.aes256, key);
+        ctx->encrypt = aes256_encrypt_native;
+        break;
+    default:
+        error_setg(errp, "Unsupported cipher algorithm %s with GCM mode",
+                   QCryptoCipherAlgo_str(alg));
+        g_free(ctx);
+        return NULL;
+    }
+
+    gcm_set_key(&ctx->gcm_key, &ctx->cipher, ctx->encrypt);
+    return &ctx->base;
+}
+
 bool qcrypto_cipher_supports(QCryptoCipherAlgo alg,
                              QCryptoCipherMode mode)
 {
@@ -440,6 +560,10 @@
     case QCRYPTO_CIPHER_MODE_XTS:
     case QCRYPTO_CIPHER_MODE_CTR:
         return true;
+    case QCRYPTO_CIPHER_MODE_GCM:
+        return alg == QCRYPTO_CIPHER_ALGO_AES_128 ||
+               alg == QCRYPTO_CIPHER_ALGO_AES_192 ||
+               alg == QCRYPTO_CIPHER_ALGO_AES_256;
     default:
         return false;
     }
@@ -451,6 +575,10 @@
                                              size_t nkey,
                                              Error **errp)
 {
+    if (mode == QCRYPTO_CIPHER_MODE_GCM) {
+        return qcrypto_nettle_aes_gcm_ctx_new(alg, key, nkey, errp);
+    }
+
     switch (mode) {
     case QCRYPTO_CIPHER_MODE_ECB:
     case QCRYPTO_CIPHER_MODE_CBC:
diff --git a/crypto/cipher.c b/crypto/cipher.c
index 229710f..1dc912b 100644
--- a/crypto/cipher.c
+++ b/crypto/cipher.c
@@ -66,6 +66,7 @@
     [QCRYPTO_CIPHER_MODE_CBC] = true,
     [QCRYPTO_CIPHER_MODE_XTS] = true,
     [QCRYPTO_CIPHER_MODE_CTR] = true,
+    [QCRYPTO_CIPHER_MODE_GCM] = true,
 };
 
 
@@ -204,6 +205,37 @@
 }
 
 
+int qcrypto_cipher_setaad(QCryptoCipher *cipher,
+                          const uint8_t *aad, size_t len,
+                          Error **errp)
+{
+    const QCryptoCipherDriver *drv = cipher->driver;
+
+    if (!drv->cipher_setaad) {
+        error_setg(errp, "The cipher mode does not support associated data");
+        return -1;
+    }
+
+    return drv->cipher_setaad(cipher, aad, len, errp);
+}
+
+
+int qcrypto_cipher_gettag(QCryptoCipher *cipher,
+                          uint8_t *tag, size_t len,
+                          Error **errp)
+{
+    const QCryptoCipherDriver *drv = cipher->driver;
+
+    if (!drv->cipher_gettag) {
+        error_setg(errp,
+                   "The cipher mode does not produce an authentication tag");
+        return -1;
+    }
+
+    return drv->cipher_gettag(cipher, tag, len, errp);
+}
+
+
 void qcrypto_cipher_free(QCryptoCipher *cipher)
 {
     if (cipher) {
diff --git a/crypto/cipherpriv.h b/crypto/cipherpriv.h
index 64737ce..c4f995f 100644
--- a/crypto/cipherpriv.h
+++ b/crypto/cipherpriv.h
@@ -34,6 +34,14 @@
                         const uint8_t *iv, size_t niv,
                         Error **errp);
 
+    int (*cipher_setaad)(QCryptoCipher *cipher,
+                         const uint8_t *aad, size_t len,
+                         Error **errp);
+
+    int (*cipher_gettag)(QCryptoCipher *cipher,
+                         uint8_t *tag, size_t len,
+                         Error **errp);
+
     void (*cipher_free)(QCryptoCipher *cipher);
 };
 
diff --git a/disas/hexagon.c b/disas/hexagon.c
index 36b8321..e2d3804 100644
--- a/disas/hexagon.c
+++ b/disas/hexagon.c
@@ -31,7 +31,6 @@
 
 int print_insn_hexagon(bfd_vma memaddr, struct disassemble_info *info)
 {
-    const HexagonCPUDef *hex_def = (const HexagonCPUDef *)info->target_info;
     uint32_t words[PACKET_WORDS_MAX];
     bool found_end = false;
     GString *buf;
@@ -58,8 +57,9 @@
         return PACKET_WORDS_MAX * sizeof(uint32_t);
     }
 
+    const HexagonCPUConfig *cfg = info->target_info;
     buf = g_string_sized_new(PACKET_BUFFER_LEN);
-    len = disassemble_hexagon(words, i, memaddr, buf, hex_def);
+    len = disassemble_hexagon(words, i, memaddr, buf, cfg);
     (*info->fprintf_func)(info->stream, "%s", buf->str);
     g_string_free(buf, true);
 
diff --git a/docs/devel/hexagon-l2vic.rst b/docs/devel/hexagon-l2vic.rst
new file mode 100644
index 0000000..9cb2a86
--- /dev/null
+++ b/docs/devel/hexagon-l2vic.rst
@@ -0,0 +1,55 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+Hexagon L2 Vectored Interrupt Controller
+========================================
+
+
+.. code-block:: none
+
+                +-------------+                  +----------------------+
+                |    l2vic    |                  |     hexagon core     |
+                |             |                  |                      |
+    IRQ in ---->|             |                  |                      |
+    IRQ in ---->|    VID0    -|----------------->| irq2                 |
+      ...  ---->|             |                  |   |                  |
+    IRQ in ---->|             |                  |   v                  |
+                |     ...     |                  | <int steering>       |
+                |             |                  |  /   |    |    \     |
+    IRQ in ---->|             |                  | t0   t1   t2   t3 ...|
+    IRQ in ---->|    VIDN    -|                  |                      |
+      ...  ---->|             |                  |                      |
+    IRQ in ---->|             |                  | Global SREG File     |
+                |             |                  |                      |
+                |    State    |                  |                      |
+                | [      ] <--|==================|==> [ VID  ]          |
+                | [      ] <--|==================|==> [ VID1 ]          |
+                |             |                  |                      |
+                +-------------+                  +----------------------+
+
+L2VIC/Core Integration
+----------------------
+
+* hexagon core supports 8 external interrupt sources
+* l2vic supports 1024 input interrupts mapped among 4 output interrupts
+* l2vic has four output signals: { VID0, VID1, VID2, VID3 }
+* l2vic device has a bank of registers per-VID that can be used to query
+  the status or assert new interrupts.
+* Interrupts are 'steered' to threads based on { thread priority, 'EX' state,
+  thread interrupt mask, thread interrupt enable, global interrupt enable,
+  etc. }.
+* Any hardware thread could conceivably handle any input interrupt, dependent
+  on state.
+* The system register transfer instruction can read the VID0-VID3 values from
+  the l2vic when reading from hexagon core system registers "VID" and "VID1".
+* When l2vic VID0 has multiple active interrupts, it pulses the VID0 output
+  IRQ and stores the IRQ number for the VID0 register field.  Only after this
+  interrupt is cleared can the l2vic pulse the VID0 output IRQ again and provide
+  the next interrupt number on the VID0 register.
+* The ``ciad`` instruction clears the l2vic input interrupt and un-disables the
+  core interrupt.  If some/an l2vic VID0 interrupt is pending when this occurs,
+  the next interrupt should fire and any subsequent reads of the VID register
+  should reflect the newly raised interrupt.
+* In QEMU, on an external interrupt or an unmasked-pending interrupt,
+  all vCPUs are triggered (has_work==true) and each will grab the IO lock
+  while considering the steering logic to determine whether they're the thread
+  that must handle the interrupt.
diff --git a/docs/devel/index-internals.rst b/docs/devel/index-internals.rst
index a8f5e31..763cda1 100644
--- a/docs/devel/index-internals.rst
+++ b/docs/devel/index-internals.rst
@@ -15,6 +15,7 @@
    clocks
    ebpf_rss
    hexagon-sys
+   hexagon-l2vic
    migration/index
    multi-process
    reset
diff --git a/docs/system/arm/emulation.rst b/docs/system/arm/emulation.rst
index 9930974..cc42db9 100644
--- a/docs/system/arm/emulation.rst
+++ b/docs/system/arm/emulation.rst
@@ -188,6 +188,7 @@
 - FEAT_SME_I16I64 (16-bit to 64-bit integer widening outer product instructions)
 - FEAT_SME_LUTv2 (Lookup table instructions with 4-bit indices and 8-bit elements)
 - FEAT_SME_MOP4 (Quarter-tile outer product instructions)
+- FEAT_SME_TMOP (Structured sparsity outer product instructions)
 - FEAT_SSVE_AES (Streaming SVE Mode Advanced Encryption Standard and 128-bit polynomial multiply long instructions)
 - FEAT_SSVE_FEXPA (Streaming FEXPA instruction)
 - FEAT_SSVE_FP8DOT2 (SVE2 FP8 2-way dot product to half-precision instructions in Streaming SVE mode)
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 82e0bc2..260d2f0 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -533,7 +533,9 @@
     bool
     default y
     depends on TCG && ARM
+    imply GENERIC_LOADER
     imply PCI_DEVICES
+    imply E1000E_PCI_EXPRESS
     select DS1338
     select FTGMAC100
     select I2C
@@ -551,14 +553,17 @@
     select TMP105
     select TMP421
     select EMC141X
+    select OR_IRQ
     select UNIMP
     select LED
     select PMBUS
     select MAX31785
+    select ADC128D818
     select FSI_APB2OPB_ASPEED
     select AT24C
-    select PCI_EXPRESS
     select PCI_EXPRESS_ASPEED
+    select USB_EHCI_SYSBUS
+    select SDHCI
 
 config MPS2
     bool
@@ -725,3 +730,18 @@
     select UNIMP
     select SSE_COUNTER
     select SSE_TIMER
+
+config AXIADO_SOC
+    bool
+    select ARM_GIC
+    select CADENCE # UART
+    select AXIADO_CLK
+    select CADENCE_GPIO
+    select AXIADO_SDHCI
+    select UNIMP
+
+config AXIADO_EVK
+    bool
+    default y
+    depends on TCG && ARM
+    select AXIADO_SOC
diff --git a/hw/arm/armsse.c b/hw/arm/armsse.c
index ddb210c..55fbc2c 100644
--- a/hw/arm/armsse.c
+++ b/hw/arm/armsse.c
@@ -409,7 +409,7 @@
         .name = "s32kwatchdog",
         .type = TYPE_CMSDK_APB_WATCHDOG,
         .index = 0,
-        .addr = 0x4802e000,
+        .addr = 0x5802e000,
         .ppc = NO_PPC,
         .irq = NMI_0,
         .slowclk = true,
@@ -452,7 +452,7 @@
         .name = "CPU0CORE_PPU",
         .type = TYPE_UNIMPLEMENTED_DEVICE,
         .index = 2,
-        .addr = 0x50023000,
+        .addr = 0x58023000,
         .size = 0x1000,
         .ppc = NO_PPC,
         .irq = NO_IRQ,
@@ -461,7 +461,7 @@
         .name = "MGMT_PPU",
         .type = TYPE_UNIMPLEMENTED_DEVICE,
         .index = 3,
-        .addr = 0x50028000,
+        .addr = 0x58028000,
         .size = 0x1000,
         .ppc = NO_PPC,
         .irq = NO_IRQ,
@@ -470,7 +470,7 @@
         .name = "DEBUG_PPU",
         .type = TYPE_UNIMPLEMENTED_DEVICE,
         .index = 4,
-        .addr = 0x50029000,
+        .addr = 0x58029000,
         .size = 0x1000,
         .ppc = NO_PPC,
         .irq = NO_IRQ,
diff --git a/hw/arm/aspeed.c b/hw/arm/aspeed.c
index a48c442..a9238e6 100644
--- a/hw/arm/aspeed.c
+++ b/hw/arm/aspeed.c
@@ -327,7 +327,7 @@
     int uart_first = aspeed_uart_first(sc->uarts_base);
     int uart_last = aspeed_uart_last(sc->uarts_base, sc->uarts_num);
 
-    if (sscanf(value, "uart%u", &val) != 1) {
+    if (sscanf(value, "uart%d", &val) != 1 || val < 0) {
         error_setg(errp, "Bad value for \"uart\" property");
         return;
     }
diff --git a/hw/arm/aspeed_ast2600_anacapa.c b/hw/arm/aspeed_ast2600_anacapa.c
index a1c8111..65d6b0f 100644
--- a/hw/arm/aspeed_ast2600_anacapa.c
+++ b/hw/arm/aspeed_ast2600_anacapa.c
@@ -1,13 +1,14 @@
 /*
  * Facebook Anacapa
  *
- * Copyright (c) Meta Platforms, Inc. and affiliates.
+ * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
  *
  * SPDX-License-Identifier: GPL-2.0-or-later
  */
 
 #include "qemu/osdep.h"
 #include "qapi/error.h"
+#include "hw/sensor/adc128d818.h"
 #include "hw/arm/machines-qom.h"
 #include "hw/arm/aspeed.h"
 #include "hw/arm/aspeed_soc.h"
@@ -15,7 +16,6 @@
 #include "hw/gpio/pca9552.h"
 #include "hw/nvram/eeprom_at24c.h"
 
-/* Anacapa hardware value */
 #define ANACAPA_BMC_HW_STRAP1 0x00002002
 #define ANACAPA_BMC_HW_STRAP2 0x00000000
 #define ANACAPA_BMC_RAM_SIZE ASPEED_RAM_SIZE(2 * GiB)
@@ -221,6 +221,17 @@
 };
 static const size_t hpm_brd_id_eeprom_len = sizeof(hpm_brd_id_eeprom);
 
+static void anacapa_add_adc128d818(I2CBus *bus, uint8_t addr,
+                                   const char *description)
+{
+    DeviceState *dev = DEVICE(i2c_slave_new(TYPE_ADC128D818, addr));
+    g_autofree char *childname = g_strdup_printf("0x%02x", addr);
+
+    qdev_prop_set_string(dev, "description", description);
+    object_property_add_child(OBJECT(bus), childname, OBJECT(dev));
+    i2c_slave_realize_and_unref(I2C_SLAVE(dev), bus, &error_fatal);
+}
+
 static void anacapa_bmc_i2c_init(AspeedMachineState *bmc)
 {
     /* Reference: aspeed-bmc-facebook-anacapa.dts */
@@ -242,7 +253,7 @@
     /* &i2c1 */
     /* eeprom@50 */
     at24c_eeprom_init(i2c[1], 0x50, 256 * KiB);
-    /* i2c-mux@70 (PCA9546) — 4 channels, empty */
+    /* i2c-mux@70 (PCA9546) - 4 channels, empty */
     i2c_slave_create_simple(i2c[1], TYPE_PCA9546, 0x70);
 
     /* &i2c4 */
@@ -259,7 +270,8 @@
     i2c_mux = i2c_slave_create_simple(i2c[8], TYPE_PCA9546, 0x72);
 
     /* i2c8mux ch0 */
-    /* adc128d818@1f — no model */
+    /* adc128d818@1f - R-PDB ADC (mode 1: 8 voltage channels) */
+    anacapa_add_adc128d818(pca954x_i2c_get_bus(i2c_mux, 0), 0x1f, "i2c8:0:1f");
     /* pca9555@22 */
     i2c_slave_create_simple(pca954x_i2c_get_bus(i2c_mux, 0),
                             TYPE_PCA9552, 0x22);
@@ -305,7 +317,7 @@
     /* i2c-mux@71 (PCA9548) */
     i2c_mux = i2c_slave_create_simple(i2c[11], TYPE_PCA9548, 0x71);
 
-    /* i2c11mux ch0-ch4 — empty */
+    /* i2c11mux ch0-ch4 - empty */
 
     /* i2c11mux ch5 */
     /* pca9555@22 */
@@ -320,7 +332,8 @@
     i2c_mux = i2c_slave_create_simple(i2c[13], TYPE_PCA9548, 0x70);
 
     /* i2c13mux ch3 */
-    /* adc128d818@1f - no model */
+    /* adc128d818@1f - MB ADC (mode 1: 8 voltage channels) */
+    anacapa_add_adc128d818(pca954x_i2c_get_bus(i2c_mux, 3), 0x1f, "i2c13:3:1f");
 
     /* i2c13mux ch4 */
     /* eeprom@51 */
@@ -328,7 +341,7 @@
                           hpm_brd_id_eeprom, hpm_brd_id_eeprom_len);
 
     /* i2c13mux ch7 */
-    /* nfc@28 — no model */
+    /* nfc@28 - no model */
 }
 
 static void aspeed_machine_anacapa_class_init(ObjectClass *oc,
diff --git a/hw/arm/aspeed_ast2600_catalina.c b/hw/arm/aspeed_ast2600_catalina.c
index 65495a5..f714c9d 100644
--- a/hw/arm/aspeed_ast2600_catalina.c
+++ b/hw/arm/aspeed_ast2600_catalina.c
@@ -472,7 +472,16 @@
 
     /* &i2c0 */
     /* i2c-mux@71 (PCA9546) on i2c0 */
-    i2c_slave_create_simple(i2c[0], TYPE_PCA9546, 0x71);
+    i2c_mux = i2c_slave_create_simple(i2c[0], TYPE_PCA9546, 0x71);
+
+    /* i2c0mux0ch0 */
+    /* IOB0 NIC0 temperature-sensor@1f - tmp421 */
+    i2c_slave_create_simple(pca954x_i2c_get_bus(i2c_mux, 0),
+                            TYPE_TMP421, 0x1f);
+    /* i2c0mux0ch2 */
+    /* IOB0 NIC1 temperature-sensor@1f - tmp421 */
+    i2c_slave_create_simple(pca954x_i2c_get_bus(i2c_mux, 2),
+                            TYPE_TMP421, 0x1f);
 
     /* i2c-mux@72 (PCA9546) on i2c0 */
     i2c_mux = i2c_slave_create_simple(i2c[0], TYPE_PCA9546, 0x72);
@@ -489,7 +498,16 @@
     i2c_slave_create_simple(i2c[0], TYPE_PCA9546, 0x73);
 
     /* i2c-mux@75 (PCA9546) on i2c0 */
-    i2c_slave_create_simple(i2c[0], TYPE_PCA9546, 0x75);
+    i2c_mux = i2c_slave_create_simple(i2c[0], TYPE_PCA9546, 0x75);
+
+    /* i2c0mux3ch0 */
+    /* IOB1 NIC0 temperature-sensor@1f - tmp421 */
+    i2c_slave_create_simple(pca954x_i2c_get_bus(i2c_mux, 0),
+                            TYPE_TMP421, 0x1f);
+    /* i2c0mux3ch2 */
+    /* IOB1 NIC1 temperature-sensor@1f - tmp421 */
+    i2c_slave_create_simple(pca954x_i2c_get_bus(i2c_mux, 2),
+                            TYPE_TMP421, 0x1f);
 
     /* i2c-mux@76 (PCA9546) on i2c0 */
     i2c_mux = i2c_slave_create_simple(i2c[0], TYPE_PCA9546, 0x76);
@@ -533,7 +551,7 @@
                             TYPE_PCA9554, 0x27);
     /* io_expander6 - pca9555@25 */
     i2c_slave_create_simple(pca954x_i2c_get_bus(i2c_mux, 6),
-                            TYPE_PCA9552, 0x25);
+                            TYPE_PCA9555, 0x25);
     /* eeprom@51 */
     at24c_eeprom_init_rom(pca954x_i2c_get_bus(i2c_mux, 6), 0x51, 8 * KiB,
                           osfp_eeprom, osfp_eeprom_len);
@@ -544,14 +562,16 @@
                           fio_eeprom, fio_eeprom_len);
     /* temperature-sensor@4b - tmp75 */
     i2c_slave_create_simple(pca954x_i2c_get_bus(i2c_mux, 7), TYPE_TMP75, 0x4b);
+    /* temperature-sensor@4f - tmp75 (FIO remote) */
+    i2c_slave_create_simple(pca954x_i2c_get_bus(i2c_mux, 7), TYPE_TMP75, 0x4f);
 
     /* &i2c2 */
     /* io_expander0 - pca9555@20 */
-    i2c_slave_create_simple(i2c[2], TYPE_PCA9552, 0x20);
+    i2c_slave_create_simple(i2c[2], TYPE_PCA9555, 0x20);
     /* io_expander0 - pca9555@21 */
-    i2c_slave_create_simple(i2c[2], TYPE_PCA9552, 0x21);
+    i2c_slave_create_simple(i2c[2], TYPE_PCA9555, 0x21);
     /* io_expander0 - pca9555@27 */
-    i2c_slave_create_simple(i2c[2], TYPE_PCA9552, 0x27);
+    i2c_slave_create_simple(i2c[2], TYPE_PCA9555, 0x27);
     /* eeprom@50 */
     at24c_eeprom_init(i2c[2], 0x50, 8 * KiB);
     /* eeprom@51 */
@@ -564,21 +584,17 @@
     /* eeprom@52 */
     at24c_eeprom_init_rom(pca954x_i2c_get_bus(i2c_mux, 6), 0x52, 8 * KiB,
                           hdd_eeprom, hdd_eeprom_len);
-    /* i2c5mux0ch7 */
-    /* ina230@40 - no model */
-    /* ina230@41 - no model */
-    /* ina230@44 - no model */
-    /* ina230@45 - no model */
+    /* i2c5mux0ch7 - empty */
 
     /* &i2c6 */
     /* io_expander3 - pca9555@21 */
-    i2c_slave_create_simple(i2c[6], TYPE_PCA9552, 0x21);
+    i2c_slave_create_simple(i2c[6], TYPE_PCA9555, 0x21);
     /* rtc@6f - nct3018y */
     i2c_slave_create_simple(i2c[6], TYPE_DS1338, 0x6f);
 
     /* &i2c9 */
     /* io_expander4 - pca9555@4f */
-    i2c_slave_create_simple(i2c[9], TYPE_PCA9552, 0x4f);
+    i2c_slave_create_simple(i2c[9], TYPE_PCA9555, 0x4f);
     /* temperature-sensor@4b - tpm75 */
     i2c_slave_create_simple(i2c[9], TYPE_TMP75, 0x4b);
     /* eeprom@50 */
@@ -615,17 +631,17 @@
 
     /* &i2c14 */
     /* io_expander9 - pca9555@10 */
-    i2c_slave_create_simple(i2c[14], TYPE_PCA9552, 0x10);
+    i2c_slave_create_simple(i2c[14], TYPE_PCA9555, 0x10);
     /* io_expander10 - pca9555@11 */
-    i2c_slave_create_simple(i2c[14], TYPE_PCA9552, 0x11);
+    i2c_slave_create_simple(i2c[14], TYPE_PCA9555, 0x11);
     /* io_expander11 - pca9555@12 */
-    i2c_slave_create_simple(i2c[14], TYPE_PCA9552, 0x12);
+    i2c_slave_create_simple(i2c[14], TYPE_PCA9555, 0x12);
     /* io_expander12 - pca9555@13 */
-    i2c_slave_create_simple(i2c[14], TYPE_PCA9552, 0x13);
+    i2c_slave_create_simple(i2c[14], TYPE_PCA9555, 0x13);
     /* io_expander13 - pca9555@14 */
-    i2c_slave_create_simple(i2c[14], TYPE_PCA9552, 0x14);
+    i2c_slave_create_simple(i2c[14], TYPE_PCA9555, 0x14);
     /* io_expander14 - pca9555@15 */
-    i2c_slave_create_simple(i2c[14], TYPE_PCA9552, 0x15);
+    i2c_slave_create_simple(i2c[14], TYPE_PCA9555, 0x15);
 
     /* &i2c15 */
     /* temperature-sensor@1f - tmp421 */
diff --git a/hw/arm/aspeed_ast27x0-fc.c b/hw/arm/aspeed_ast27x0-fc.c
index 7d9fade..058cea4 100644
--- a/hw/arm/aspeed_ast27x0-fc.c
+++ b/hw/arm/aspeed_ast27x0-fc.c
@@ -134,11 +134,9 @@
     return true;
 }
 
-static bool ast2700fc_ssp_init(MachineState *machine, Error **errp)
+static bool ast2700fc_ssp_init(Ast2700FCState *s, AspeedSoCState *psp,
+                               Error **errp)
 {
-    Ast2700FCState *s = AST2700FC(machine);
-    AspeedSoCState *psp = ASPEED_SOC(&s->ca35);
-
     s->ssp_sysclk = clock_new(OBJECT(s), "SSP_SYSCLK");
     clock_set_hz(s->ssp_sysclk, 200000000ULL);
 
@@ -158,7 +156,11 @@
     object_property_set_link(OBJECT(&s->ssp), "sram",
                              OBJECT(&psp->sram), &error_abort);
     object_property_set_link(OBJECT(&s->ssp), "scu",
-                             OBJECT(&psp->scu), &error_abort);
+                             OBJECT(&s->ca35.scu), &error_abort);
+    object_property_set_link(OBJECT(&s->ssp), "scuio",
+                             OBJECT(&psp->scuio), &error_abort);
+    object_property_set_link(OBJECT(&s->ssp), "fmc",
+                             OBJECT(&psp->fmc), &error_abort);
     if (!qdev_realize(DEVICE(&s->ssp), NULL, errp)) {
         return false;
     }
@@ -166,11 +168,9 @@
     return true;
 }
 
-static bool ast2700fc_tsp_init(MachineState *machine, Error **errp)
+static bool ast2700fc_tsp_init(Ast2700FCState *s, AspeedSoCState *psp,
+                               Error **errp)
 {
-    Ast2700FCState *s = AST2700FC(machine);
-    AspeedSoCState *psp = ASPEED_SOC(&s->ca35);
-
     s->tsp_sysclk = clock_new(OBJECT(s), "TSP_SYSCLK");
     clock_set_hz(s->tsp_sysclk, 200000000ULL);
 
@@ -190,7 +190,11 @@
     object_property_set_link(OBJECT(&s->tsp), "sram",
                              OBJECT(&psp->sram), &error_abort);
     object_property_set_link(OBJECT(&s->tsp), "scu",
-                             OBJECT(&psp->scu), &error_abort);
+                             OBJECT(&s->ca35.scu), &error_abort);
+    object_property_set_link(OBJECT(&s->tsp), "scuio",
+                             OBJECT(&psp->scuio), &error_abort);
+    object_property_set_link(OBJECT(&s->tsp), "fmc",
+                             OBJECT(&psp->fmc), &error_abort);
     if (!qdev_realize(DEVICE(&s->tsp), NULL, errp)) {
         return false;
     }
@@ -200,9 +204,19 @@
 
 static void ast2700fc_init(MachineState *machine)
 {
+    Ast2700FCState *s = AST2700FC(machine);
+    AspeedSoCState *psp;
+
     ast2700fc_ca35_init(machine, &error_abort);
-    ast2700fc_ssp_init(machine, &error_abort);
-    ast2700fc_tsp_init(machine, &error_abort);
+
+    /*
+     * SSP and TSP use resources owned by the PSP SoC, such as UART,
+     * SRAM, SCU and SCUIO.  Therefore the PSP SoC must be realized
+     * before the coprocessors are initialized.
+     */
+    psp = ASPEED_SOC(&s->ca35);
+    ast2700fc_ssp_init(s, psp, &error_abort);
+    ast2700fc_tsp_init(s, psp, &error_abort);
 }
 
 static void ast2700fc_class_init(ObjectClass *oc, const void *data)
diff --git a/hw/arm/aspeed_ast27x0-ssp.c b/hw/arm/aspeed_ast27x0-ssp.c
index 68a8ab2..e036530 100644
--- a/hw/arm/aspeed_ast27x0-ssp.c
+++ b/hw/arm/aspeed_ast27x0-ssp.c
@@ -27,6 +27,7 @@
     [ASPEED_DEV_TIMER1]    =  0x72C10000,
     [ASPEED_DEV_UART4]     =  0x72C1A000,
     [ASPEED_DEV_IPC0]      =  0x72C1C000,
+    [ASPEED_DEV_FMC]       =  0x74000000,
     [ASPEED_DEV_PRIC1]     =  0x74100000,
     [ASPEED_DEV_SCUIO]     =  0x74C02000,
     [ASPEED_DEV_OTP]       =  0x74C07000,
@@ -142,8 +143,6 @@
                             TYPE_UNIMPLEMENTED_DEVICE);
     object_initialize_child(obj, "ipc1", &a->ipc[1],
                             TYPE_UNIMPLEMENTED_DEVICE);
-    object_initialize_child(obj, "scuio", &a->scuio,
-                            TYPE_UNIMPLEMENTED_DEVICE);
     object_initialize_child(obj, "pric0", &a->pric[0],
                             TYPE_UNIMPLEMENTED_DEVICE);
     object_initialize_child(obj, "pric1", &a->pric[1],
@@ -167,6 +166,24 @@
         return;
     }
 
+    if (!a->scu) {
+        error_setg(errp, TYPE_ASPEED27X0SSP_COPROCESSOR
+                   ": 'scu' link is not set");
+        return;
+    }
+
+    if (!a->scuio) {
+        error_setg(errp, TYPE_ASPEED27X0SSP_COPROCESSOR
+                   ": 'scuio' link is not set");
+        return;
+    }
+
+    if (!a->fmc) {
+        error_setg(errp, TYPE_ASPEED27X0SSP_COPROCESSOR
+                   ": 'fmc' link is not set");
+        return;
+    }
+
     /* AST27X0 SSP Core */
     armv7m = DEVICE(&a->armv7m);
     qdev_prop_set_uint32(armv7m, "num-irq", 256);
@@ -195,11 +212,18 @@
                                 &s->sram_alias);
 
     /* SCU */
-    memory_region_init_alias(&s->scu_alias, OBJECT(s), "scu.alias",
-                             &s->scu->iomem, 0,
-                             memory_region_size(&s->scu->iomem));
+    memory_region_init_alias(&a->scu_alias, OBJECT(a), "scu.alias",
+                             &a->scu->parent_obj.iomem, 0,
+                             memory_region_size(&a->scu->parent_obj.iomem));
     memory_region_add_subregion(s->memory, sc->memmap[ASPEED_DEV_SCU],
-                                &s->scu_alias);
+                                &a->scu_alias);
+
+    /* SCUIO */
+    memory_region_init_alias(&a->scuio_alias, OBJECT(a), "scuio.alias",
+                             &a->scuio->iomem, 0,
+                             memory_region_size(&a->scuio->iomem));
+    memory_region_add_subregion(s->memory, sc->memmap[ASPEED_DEV_SCUIO],
+                                &a->scuio_alias);
 
     /* INTC */
     if (!sysbus_realize(SYS_BUS_DEVICE(&a->intc[0]), errp)) {
@@ -252,6 +276,13 @@
     sysbus_connect_irq(SYS_BUS_DEVICE(s->uart), 0,
                        aspeed_soc_ast27x0ssp_get_irq(s, s->uart_dev));
 
+    /* FMC */
+    memory_region_init_alias(&a->fmc_alias, OBJECT(a), "fmc.alias",
+                             &a->fmc->mmio, 0,
+                             memory_region_size(&a->fmc->mmio));
+    memory_region_add_subregion(s->memory, sc->memmap[ASPEED_DEV_FMC],
+                                &a->fmc_alias);
+
     aspeed_mmio_map_unimplemented(s->memory, SYS_BUS_DEVICE(&s->timerctrl),
                                   "aspeed.timerctrl",
                                   sc->memmap[ASPEED_DEV_TIMER1], 0x200);
@@ -261,9 +292,6 @@
     aspeed_mmio_map_unimplemented(s->memory, SYS_BUS_DEVICE(&a->ipc[1]),
                                   "aspeed.ipc1",
                                   sc->memmap[ASPEED_DEV_IPC1], 0x1000);
-    aspeed_mmio_map_unimplemented(s->memory, SYS_BUS_DEVICE(&a->scuio),
-                                  "aspeed.scuio",
-                                  sc->memmap[ASPEED_DEV_SCUIO], 0x1000);
     aspeed_mmio_map_unimplemented(s->memory, SYS_BUS_DEVICE(&a->pric[0]),
                                   "aspeed.pric0",
                                   sc->memmap[ASPEED_DEV_PRIC0], 0x1000);
@@ -275,6 +303,15 @@
                                   sc->memmap[ASPEED_DEV_OTP], 0x800);
 }
 
+static const Property aspeed_27x0_coprocessor_properties[] = {
+    DEFINE_PROP_LINK("scu", Aspeed27x0CoprocessorState, scu,
+                     TYPE_ASPEED_2700_SCU, Aspeed2700SCUState *),
+    DEFINE_PROP_LINK("scuio", Aspeed27x0CoprocessorState, scuio,
+                     TYPE_ASPEED_SCU, AspeedSCUState *),
+    DEFINE_PROP_LINK("fmc", Aspeed27x0CoprocessorState, fmc, TYPE_ASPEED_SMC,
+                     AspeedSMCState *),
+};
+
 static void aspeed_soc_ast27x0ssp_class_init(ObjectClass *klass,
                                              const void *data)
 {
@@ -288,6 +325,7 @@
     /* Reason: The Aspeed Coprocessor can only be instantiated from a board */
     dc->user_creatable = false;
     dc->realize = aspeed_soc_ast27x0ssp_realize;
+    device_class_set_props(dc, aspeed_27x0_coprocessor_properties);
 
     sc->valid_cpu_types = valid_cpu_types;
     sc->irqmap = aspeed_soc_ast27x0ssp_irqmap;
diff --git a/hw/arm/aspeed_ast27x0-tsp.c b/hw/arm/aspeed_ast27x0-tsp.c
index b8a4f7c..39ba062 100644
--- a/hw/arm/aspeed_ast27x0-tsp.c
+++ b/hw/arm/aspeed_ast27x0-tsp.c
@@ -27,6 +27,7 @@
     [ASPEED_DEV_TIMER1]    =  0x72C10000,
     [ASPEED_DEV_UART4]     =  0x72C1A000,
     [ASPEED_DEV_IPC0]      =  0x72C1C000,
+    [ASPEED_DEV_FMC]       =  0x74000000,
     [ASPEED_DEV_PRIC1]     =  0x74100000,
     [ASPEED_DEV_SCUIO]     =  0x74C02000,
     [ASPEED_DEV_OTP]       =  0x74C07000,
@@ -142,8 +143,6 @@
                             TYPE_UNIMPLEMENTED_DEVICE);
     object_initialize_child(obj, "ipc1", &a->ipc[1],
                             TYPE_UNIMPLEMENTED_DEVICE);
-    object_initialize_child(obj, "scuio", &a->scuio,
-                            TYPE_UNIMPLEMENTED_DEVICE);
     object_initialize_child(obj, "pric0", &a->pric[0],
                             TYPE_UNIMPLEMENTED_DEVICE);
     object_initialize_child(obj, "pric1", &a->pric[1],
@@ -167,6 +166,24 @@
         return;
     }
 
+    if (!a->scu) {
+        error_setg(errp, TYPE_ASPEED27X0TSP_COPROCESSOR
+                   ": 'scu' link is not set");
+        return;
+    }
+
+    if (!a->scuio) {
+        error_setg(errp, TYPE_ASPEED27X0TSP_COPROCESSOR
+                   ": 'scuio' link is not set");
+        return;
+    }
+
+    if (!a->fmc) {
+        error_setg(errp, TYPE_ASPEED27X0TSP_COPROCESSOR
+                   ": 'fmc' link is not set");
+        return;
+    }
+
     /* AST27X0 TSP Core */
     armv7m = DEVICE(&a->armv7m);
     qdev_prop_set_uint32(armv7m, "num-irq", 256);
@@ -195,11 +212,18 @@
                                 &s->sram_alias);
 
     /* SCU */
-    memory_region_init_alias(&s->scu_alias, OBJECT(s), "scu.alias",
-                             &s->scu->iomem, 0,
-                             memory_region_size(&s->scu->iomem));
+    memory_region_init_alias(&a->scu_alias, OBJECT(a), "scu.alias",
+                             &a->scu->parent_obj.iomem, 0,
+                             memory_region_size(&a->scu->parent_obj.iomem));
     memory_region_add_subregion(s->memory, sc->memmap[ASPEED_DEV_SCU],
-                                &s->scu_alias);
+                                &a->scu_alias);
+
+    /* SCUIO */
+    memory_region_init_alias(&a->scuio_alias, OBJECT(a), "scuio.alias",
+                             &a->scuio->iomem, 0,
+                             memory_region_size(&a->scuio->iomem));
+    memory_region_add_subregion(s->memory, sc->memmap[ASPEED_DEV_SCUIO],
+                                &a->scuio_alias);
 
     /* INTC */
     if (!sysbus_realize(SYS_BUS_DEVICE(&a->intc[0]), errp)) {
@@ -252,6 +276,13 @@
     sysbus_connect_irq(SYS_BUS_DEVICE(s->uart), 0,
                        aspeed_soc_ast27x0tsp_get_irq(s, s->uart_dev));
 
+    /* FMC */
+    memory_region_init_alias(&a->fmc_alias, OBJECT(a), "fmc.alias",
+                             &a->fmc->mmio, 0,
+                             memory_region_size(&a->fmc->mmio));
+    memory_region_add_subregion(s->memory, sc->memmap[ASPEED_DEV_FMC],
+                                &a->fmc_alias);
+
     aspeed_mmio_map_unimplemented(s->memory, SYS_BUS_DEVICE(&s->timerctrl),
                                   "aspeed.timerctrl",
                                   sc->memmap[ASPEED_DEV_TIMER1], 0x200);
@@ -261,9 +292,6 @@
     aspeed_mmio_map_unimplemented(s->memory, SYS_BUS_DEVICE(&a->ipc[1]),
                                   "aspeed.ipc1",
                                   sc->memmap[ASPEED_DEV_IPC1], 0x1000);
-    aspeed_mmio_map_unimplemented(s->memory, SYS_BUS_DEVICE(&a->scuio),
-                                  "aspeed.scuio",
-                                  sc->memmap[ASPEED_DEV_SCUIO], 0x1000);
     aspeed_mmio_map_unimplemented(s->memory, SYS_BUS_DEVICE(&a->pric[0]),
                                   "aspeed.pric0",
                                   sc->memmap[ASPEED_DEV_PRIC0], 0x1000);
@@ -275,6 +303,15 @@
                                   sc->memmap[ASPEED_DEV_OTP], 0x800);
 }
 
+static const Property aspeed_27x0_coprocessor_properties[] = {
+    DEFINE_PROP_LINK("scu", Aspeed27x0CoprocessorState, scu,
+                     TYPE_ASPEED_2700_SCU, Aspeed2700SCUState *),
+    DEFINE_PROP_LINK("scuio", Aspeed27x0CoprocessorState, scuio,
+                     TYPE_ASPEED_SCU, AspeedSCUState *),
+    DEFINE_PROP_LINK("fmc", Aspeed27x0CoprocessorState, fmc, TYPE_ASPEED_SMC,
+                     AspeedSMCState *),
+};
+
 static void aspeed_soc_ast27x0tsp_class_init(ObjectClass *klass,
                                              const void *data)
 {
@@ -288,6 +325,7 @@
     /* Reason: The Aspeed Coprocessor can only be instantiated from a board */
     dc->user_creatable = false;
     dc->realize = aspeed_soc_ast27x0tsp_realize;
+    device_class_set_props(dc, aspeed_27x0_coprocessor_properties);
 
     sc->valid_cpu_types = valid_cpu_types;
     sc->irqmap = aspeed_soc_ast27x0tsp_irqmap;
diff --git a/hw/arm/aspeed_ast27x0.c b/hw/arm/aspeed_ast27x0.c
index dddd7d2..6365dbd 100644
--- a/hw/arm/aspeed_ast27x0.c
+++ b/hw/arm/aspeed_ast27x0.c
@@ -435,12 +435,12 @@
 
     object_initialize_child(obj, "gic", &a->gic, gicv3_class_name());
 
-    object_initialize_child(obj, "scu", &s->scu, TYPE_ASPEED_2700_SCU);
-    qdev_prop_set_uint32(DEVICE(&s->scu), "silicon-rev",
+    object_initialize_child(obj, "scu", &a->scu, TYPE_ASPEED_2700_SCU);
+    qdev_prop_set_uint32(DEVICE(&a->scu), "silicon-rev",
                          sc->silicon_rev);
-    object_property_add_alias(obj, "hw-strap1", OBJECT(&s->scu),
+    object_property_add_alias(obj, "hw-strap1", OBJECT(&a->scu),
                               "hw-strap1");
-    object_property_add_alias(obj, "hw-prot-key", OBJECT(&s->scu),
+    object_property_add_alias(obj, "hw-prot-key", OBJECT(&a->scu),
                               "hw-prot-key");
 
     object_initialize_child(obj, "scuio", &s->scuio, TYPE_ASPEED_2700_SCUIO);
@@ -808,10 +808,10 @@
                                 sc->memmap[ASPEED_DEV_VBOOTROM], &s->vbootrom);
 
     /* SCU */
-    if (!sysbus_realize(SYS_BUS_DEVICE(&s->scu), errp)) {
+    if (!sysbus_realize(SYS_BUS_DEVICE(&a->scu), errp)) {
         return;
     }
-    aspeed_mmio_map(s->memory, SYS_BUS_DEVICE(&s->scu), 0,
+    aspeed_mmio_map(s->memory, SYS_BUS_DEVICE(&a->scu), 0,
                     sc->memmap[ASPEED_DEV_SCU]);
 
     /* SCU1 */
@@ -870,6 +870,11 @@
 
     /* EHCI */
     for (i = 0; i < sc->ehcis_num; i++) {
+        object_property_set_int(OBJECT(&s->ehci[i]), "ctrldssegment-default",
+                                sc->memmap[ASPEED_DEV_SDRAM] >> 32,
+                                &error_abort);
+        object_property_set_bool(OBJECT(&s->ehci[i]), "caps-64bit-addr", true,
+                                 &error_abort);
         if (!sysbus_realize(SYS_BUS_DEVICE(&s->ehci[i]), errp)) {
             return;
         }
@@ -929,7 +934,7 @@
         AspeedWDTClass *awc = ASPEED_WDT_GET_CLASS(&s->wdt[i]);
         hwaddr wdt_offset = sc->memmap[ASPEED_DEV_WDT] + i * awc->iosize;
 
-        object_property_set_link(OBJECT(&s->wdt[i]), "scu", OBJECT(&s->scu),
+        object_property_set_link(OBJECT(&s->wdt[i]), "scu", OBJECT(&a->scu),
                                  &error_abort);
         if (!sysbus_realize(SYS_BUS_DEVICE(&s->wdt[i]), errp)) {
             return;
@@ -1032,7 +1037,7 @@
                        aspeed_soc_ast2700_get_irq(s, ASPEED_DEV_EMMC));
 
     /* Timer */
-    object_property_set_link(OBJECT(&s->timerctrl), "scu", OBJECT(&s->scu),
+    object_property_set_link(OBJECT(&s->timerctrl), "scu", OBJECT(&a->scu),
                              &error_abort);
     if (!sysbus_realize(SYS_BUS_DEVICE(&s->timerctrl), errp)) {
         return;
diff --git a/hw/arm/aspeed_coprocessor_common.c b/hw/arm/aspeed_coprocessor_common.c
index a0a4c73..43026d2 100644
--- a/hw/arm/aspeed_coprocessor_common.c
+++ b/hw/arm/aspeed_coprocessor_common.c
@@ -27,8 +27,6 @@
                      TYPE_MEMORY_REGION, MemoryRegion *),
     DEFINE_PROP_LINK("sram", AspeedCoprocessorState, sram, TYPE_MEMORY_REGION,
                      MemoryRegion *),
-    DEFINE_PROP_LINK("scu", AspeedCoprocessorState, scu, TYPE_ASPEED_SCU,
-                     AspeedSCUState *),
     DEFINE_PROP_LINK("uart", AspeedCoprocessorState, uart, TYPE_SERIAL_MM,
                      SerialMM *),
     DEFINE_PROP_INT32("uart-dev", AspeedCoprocessorState, uart_dev, 0),
diff --git a/hw/arm/ax3000-boards.c b/hw/arm/ax3000-boards.c
new file mode 100644
index 0000000..4b8deec
--- /dev/null
+++ b/hw/arm/ax3000-boards.c
@@ -0,0 +1,56 @@
+/*
+ * Axiado Boards
+ *
+ * Author: Kuan-Jui Chiu <kchiu@axiado.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/arm/ax3000-boards.h"
+#include "hw/arm/boot.h"
+#include "hw/arm/machines-qom.h"
+#include "qemu/error-report.h"
+#include "qom/object.h"
+
+static struct arm_boot_info ax3000_binfo = {
+    .loader_start = AX3000_DRAM0_BASE,
+    .board_id = -1,
+};
+
+static void ax3000_machine_init(MachineState *machine)
+{
+    Ax3000MachineState *ams = AX3000_MACHINE(machine);
+
+    ams->soc = AX3000_SOC(object_new(TYPE_AX3000_SOC));
+    object_property_add_child(OBJECT(machine), "soc", OBJECT(ams->soc));
+    sysbus_realize_and_unref(SYS_BUS_DEVICE(ams->soc), &error_fatal);
+
+    ax3000_binfo.ram_size = machine->ram_size;
+    arm_load_kernel(&ams->soc->cpu[0], machine, &ax3000_binfo);
+}
+
+static void ax3000_machine_class_init(ObjectClass *oc, const void *data)
+{
+    MachineClass *mc = MACHINE_CLASS(oc);
+
+    mc->init = ax3000_machine_init;
+    mc->default_cpus = AX3000_NUM_CPUS;
+    mc->min_cpus = AX3000_NUM_CPUS;
+    mc->max_cpus = AX3000_NUM_CPUS;
+    mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-a53");
+}
+
+static const TypeInfo ax3000_machine_types[] = {
+    {
+        .name          = TYPE_AX3000_MACHINE,
+        .parent        = TYPE_MACHINE,
+        .instance_size = sizeof(Ax3000MachineState),
+        .class_size    = sizeof(Ax3000MachineClass),
+        .class_init    = ax3000_machine_class_init,
+        .interfaces    = aarch64_machine_interfaces,
+        .abstract      = true,
+    }
+};
+
+DEFINE_TYPES(ax3000_machine_types)
diff --git a/hw/arm/ax3000-evk.c b/hw/arm/ax3000-evk.c
new file mode 100644
index 0000000..a170848
--- /dev/null
+++ b/hw/arm/ax3000-evk.c
@@ -0,0 +1,27 @@
+/*
+ * Axiado Evaluation Kit Emulation
+ *
+ * Author: Kuan-Jui Chiu <kchiu@axiado.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/arm/ax3000-boards.h"
+
+static void axiado_scm3003_class_init(ObjectClass *oc, const void *data)
+{
+    MachineClass *mc = MACHINE_CLASS(oc);
+
+    mc->desc = "Axiado SCM3003 EVK Board";
+}
+
+static const TypeInfo ax3000_evk_types[] = {
+    {
+        .name          = MACHINE_TYPE_NAME("axiado-scm3003"),
+        .parent        = TYPE_AX3000_MACHINE,
+        .class_init    = axiado_scm3003_class_init,
+    }
+};
+
+DEFINE_TYPES(ax3000_evk_types)
diff --git a/hw/arm/ax3000-soc.c b/hw/arm/ax3000-soc.c
new file mode 100644
index 0000000..71e31c6
--- /dev/null
+++ b/hw/arm/ax3000-soc.c
@@ -0,0 +1,242 @@
+/*
+ * Axiado SoC AX3000
+ *
+ * Author: Kuan-Jui Chiu <kchiu@axiado.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "system/address-spaces.h"
+#include "hw/arm/bsa.h"
+#include "hw/arm/ax3000-soc.h"
+#include "hw/misc/unimp.h"
+#include "system/system.h"
+#include "qobject/qlist.h"
+#include "qom/object.h"
+#include "hw/core/boards.h"
+
+static void ax3000_init(Object *obj)
+{
+    Ax3000SoCState *s = AX3000_SOC(obj);
+    Ax3000SoCClass *sc = AX3000_SOC_GET_CLASS(s);
+
+    for (int i = 0; i < sc->num_cpus; i++) {
+        g_autofree char *name = g_strdup_printf("cpu%d", i);
+        object_initialize_child(obj, name, &s->cpu[i],
+                                ARM_CPU_TYPE_NAME("cortex-a53"));
+    }
+
+    object_initialize_child(obj, "gic", &s->gic, gicv3_class_name());
+
+    for (int i = 0; i < AX3000_NUM_UARTS; i++) {
+        g_autofree char *name = g_strdup_printf("uart%d", i);
+        object_initialize_child(obj, name, &s->uart[i], TYPE_CADENCE_UART);
+    }
+
+    object_initialize_child(obj, "clk", &s->ax3000_clk, TYPE_AX3000_CLK);
+    object_initialize_child(obj, "sdhci0", &s->sdhci0, TYPE_AXIADO_SDHCI);
+
+    for (int i = 0; i < AX3000_NUM_GPIOS; i++) {
+        g_autofree char *name = g_strdup_printf("gpio%d", i);
+        object_initialize_child(obj, name, &s->gpio[i], TYPE_CADENCE_GPIO);
+    }
+}
+
+static void ax3000_realize(DeviceState *dev, Error **errp)
+{
+    Ax3000SoCState *s = AX3000_SOC(dev);
+    Ax3000SoCClass *sc = AX3000_SOC_GET_CLASS(s);
+    SysBusDevice *gic_sbd = SYS_BUS_DEVICE(&s->gic);
+    DeviceState *gic_dev = DEVICE(&s->gic);
+    QList *redist_region_count;
+    SysBusDevice *sdhci0_sbd;
+    DeviceState *card;
+    DriveInfo *dinfo;
+
+    /* CPUs */
+    for (int i = 0; i < sc->num_cpus; i++) {
+        object_property_set_int(OBJECT(&s->cpu[i]), "cntfrq", 8000000,
+                                &error_abort);
+
+        if (object_property_find(OBJECT(&s->cpu[i]), "has_el3")) {
+            object_property_set_bool(OBJECT(&s->cpu[i]), "has_el3",
+                                     false, &error_abort);
+        }
+
+        if (!qdev_realize(DEVICE(&s->cpu[i]), NULL, errp)) {
+            return;
+        }
+    }
+
+    /* GIC */
+    qdev_prop_set_uint32(gic_dev, "num-cpu", sc->num_cpus);
+    qdev_prop_set_uint32(gic_dev, "num-irq",
+                         AX3000_NUM_IRQS + GIC_INTERNAL);
+
+    redist_region_count = qlist_new();
+    qlist_append_int(redist_region_count, sc->num_cpus);
+    qdev_prop_set_array(gic_dev, "redist-region-count", redist_region_count);
+
+    if (!sysbus_realize(gic_sbd, errp)) {
+        return;
+    }
+
+    sysbus_mmio_map(gic_sbd, 0, AX3000_GIC_DIST_BASE);
+    sysbus_mmio_map(gic_sbd, 1, AX3000_GIC_REDIST_BASE);
+
+    /*
+     * Mapping from the output timer irq lines from the CPU to the
+     * GIC PPI inputs.
+     */
+    const int timer_irqs[] = {
+        [GTIMER_PHYS] = ARCH_TIMER_NS_EL1_IRQ,
+        [GTIMER_VIRT] = ARCH_TIMER_VIRT_IRQ,
+        [GTIMER_HYP]  = ARCH_TIMER_NS_EL2_IRQ,
+        [GTIMER_SEC]  = ARCH_TIMER_S_EL1_IRQ
+    };
+
+    /*
+     * Wire the outputs from each CPU's generic timer and the GICv3
+     * maintenance interrupt signal to the appropriate GIC PPI inputs, and
+     * the GIC's IRQ/FIQ interrupt outputs to the CPU's inputs.
+     */
+    for (int i = 0; i < sc->num_cpus; i++) {
+        DeviceState *cpu_dev = DEVICE(&s->cpu[i]);
+        int intidbase = AX3000_NUM_IRQS + i * GIC_INTERNAL;
+        qemu_irq irq;
+
+        for (int j = 0; j < ARRAY_SIZE(timer_irqs); j++) {
+            irq = qdev_get_gpio_in(gic_dev, intidbase + timer_irqs[j]);
+            qdev_connect_gpio_out(cpu_dev, j, irq);
+        }
+
+        irq = qdev_get_gpio_in(gic_dev, intidbase + ARCH_GIC_MAINT_IRQ);
+        qdev_connect_gpio_out_named(cpu_dev, "gicv3-maintenance-interrupt",
+                                        0, irq);
+
+        sysbus_connect_irq(gic_sbd, i,
+                           qdev_get_gpio_in(cpu_dev, ARM_CPU_IRQ));
+        sysbus_connect_irq(gic_sbd, i + sc->num_cpus,
+                           qdev_get_gpio_in(cpu_dev, ARM_CPU_FIQ));
+        sysbus_connect_irq(gic_sbd, i + 2 * sc->num_cpus,
+                           qdev_get_gpio_in(cpu_dev, ARM_CPU_VIRQ));
+        sysbus_connect_irq(gic_sbd, i + 3 * sc->num_cpus,
+                           qdev_get_gpio_in(cpu_dev, ARM_CPU_VFIQ));
+    }
+
+    /* DRAM */
+    const struct {
+        hwaddr addr;
+        size_t size;
+        const char *name;
+    } dram_table[] = {
+        { AX3000_DRAM0_BASE, AX3000_DRAM0_SIZE, "dram0" },
+        { AX3000_DRAM1_BASE, AX3000_DRAM1_SIZE, "dram1" }
+    };
+
+    for (int i = 0; i < AX3000_NUM_BANKS; i++) {
+        memory_region_init_ram(&s->dram[i], OBJECT(s), dram_table[i].name,
+                               dram_table[i].size, &error_fatal);
+        memory_region_add_subregion(get_system_memory(), dram_table[i].addr,
+                                    &s->dram[i]);
+    }
+
+    /* UARTs */
+    const struct {
+        hwaddr addr;
+        unsigned int irq;
+    } serial_table[] = {
+        { AX3000_UART0_BASE, AX3000_UART0_IRQ },
+        { AX3000_UART1_BASE, AX3000_UART1_IRQ },
+        { AX3000_UART2_BASE, AX3000_UART2_IRQ },
+        { AX3000_UART3_BASE, AX3000_UART3_IRQ }
+    };
+
+    for (int i = 0; i < AX3000_NUM_UARTS; i++) {
+        qdev_prop_set_chr(DEVICE(&s->uart[i]), "chardev", serial_hd(i));
+        if (!sysbus_realize(SYS_BUS_DEVICE(&s->uart[i]), errp)) {
+            return;
+        }
+
+        sysbus_mmio_map(SYS_BUS_DEVICE(&s->uart[i]), 0, serial_table[i].addr);
+        sysbus_connect_irq(SYS_BUS_DEVICE(&s->uart[i]), 0,
+                           qdev_get_gpio_in(gic_dev, serial_table[i].irq));
+    }
+
+    /* Timer control */
+    create_unimplemented_device("ax3000.timerctrl", AX3000_TIMER_CTRL, 32);
+
+    /* Clock control */
+    if (!sysbus_realize(SYS_BUS_DEVICE(&s->ax3000_clk), errp)) {
+        return;
+    }
+    sysbus_mmio_map(SYS_BUS_DEVICE(&s->ax3000_clk), 0, AX3000_PLL_BASE);
+
+    /* SDHCI */
+    sdhci0_sbd = SYS_BUS_DEVICE(&s->sdhci0);
+    if (!sysbus_realize(sdhci0_sbd, errp)) {
+        return;
+    }
+
+    sysbus_mmio_map(sdhci0_sbd, 0, AX3000_SDHCI0_BASE);
+    sysbus_mmio_map(sdhci0_sbd, 1, AX3000_EMMC_PHY_BASE);
+    sysbus_connect_irq(sdhci0_sbd, 0,
+                       qdev_get_gpio_in(gic_dev, AX3000_SDHCI0_IRQ));
+
+    dinfo = drive_get(IF_SD, 0, 0);
+    if (dinfo) {
+        card = qdev_new(TYPE_SD_CARD);
+        qdev_prop_set_drive_err(card, "drive",
+                                blk_by_legacy_dinfo(dinfo),
+                                &error_fatal);
+        qdev_realize_and_unref(card, s->sdhci0.sd_bus, &error_fatal);
+    }
+
+    /* GPIOs */
+    const struct {
+        hwaddr addr;
+        unsigned int irq;
+    } gpio_table[] = {
+        { AX3000_GPIO0_BASE, AX3000_GPIO0_IRQ },
+        { AX3000_GPIO1_BASE, AX3000_GPIO1_IRQ },
+        { AX3000_GPIO2_BASE, AX3000_GPIO2_IRQ },
+        { AX3000_GPIO3_BASE, AX3000_GPIO3_IRQ },
+        { AX3000_GPIO4_BASE, AX3000_GPIO4_IRQ },
+        { AX3000_GPIO5_BASE, AX3000_GPIO5_IRQ },
+        { AX3000_GPIO6_BASE, AX3000_GPIO6_IRQ },
+        { AX3000_GPIO7_BASE, AX3000_GPIO7_IRQ }
+    };
+
+    for (int i = 0; i < AX3000_NUM_GPIOS; i++) {
+        if (!sysbus_realize(SYS_BUS_DEVICE(&s->gpio[i]), errp)) {
+            return;
+        }
+
+        sysbus_mmio_map(SYS_BUS_DEVICE(&s->gpio[i]), 0, gpio_table[i].addr);
+        sysbus_connect_irq(SYS_BUS_DEVICE(&s->gpio[i]), 0,
+                           qdev_get_gpio_in(gic_dev, gpio_table[i].irq));
+    }
+}
+
+static void ax3000_class_init(ObjectClass *oc, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(oc);
+    Ax3000SoCClass *sc = AX3000_SOC_CLASS(oc);
+
+    dc->desc = "Axiado SoC AX3000";
+    dc->realize = ax3000_realize;
+    sc->num_cpus = AX3000_NUM_CPUS;
+}
+
+static const TypeInfo axiado_soc_types[] = {
+    {
+        .name           = TYPE_AX3000_SOC,
+        .parent         = TYPE_SYS_BUS_DEVICE,
+        .instance_size  = sizeof(Ax3000SoCState),
+        .instance_init  = ax3000_init,
+        .class_init     = ax3000_class_init,
+    }
+};
+
+DEFINE_TYPES(axiado_soc_types)
diff --git a/hw/arm/meson.build b/hw/arm/meson.build
index 4233a80..8ee5307 100644
--- a/hw/arm/meson.build
+++ b/hw/arm/meson.build
@@ -110,6 +110,12 @@
 arm_common_ss.add(when: 'CONFIG_VERSATILE', if_true: files('versatilepb.c'))
 arm_common_ss.add(when: 'CONFIG_VEXPRESS', if_true: files('vexpress.c'))
 
+arm_common_ss.add(when: ['CONFIG_AXIADO_SOC', 'TARGET_AARCH64'], if_true: files(
+  'ax3000-soc.c'))
+arm_common_ss.add(when: ['CONFIG_AXIADO_EVK', 'TARGET_AARCH64'], if_true: files(
+  'ax3000-boards.c',
+  'ax3000-evk.c'))
+
 arm_common_ss.add(files('boot.c'))
 
 hw_common_arch += {'arm': arm_common_ss}
diff --git a/hw/arm/mps2-tz.c b/hw/arm/mps2-tz.c
index f101c1b..d6d1786 100644
--- a/hw/arm/mps2-tz.c
+++ b/hw/arm/mps2-tz.c
@@ -1048,6 +1048,19 @@
     const PPCInfo an547_ppcs[] = { {
             .name = "apb_ppcexp0",
             .ports = {
+                { /* port 0 USER MEM APB0 */ },
+                { /* port 1 USER MEM APB0 */ },
+                { /* port 2 reserved */ },
+                { /* port 3 reserved */ },
+                { /* port 4 NPU APB0 */ },
+                { /* port 5 NPU APB1 */ },
+                { /* port 6 reserved */ },
+                { /* port 7 reserved */ },
+                { /* port 8 reserved */ },
+                { /* port 9 reserved */ },
+                { /* port 10 reserved */ },
+                { /* port 11 reserved */ },
+                { /* port 12 reserved */ },
                 { "ssram-mpc", make_mpc, &mms->mpc[0], 0x57000000, 0x1000 },
                 { "qspi-mpc", make_mpc, &mms->mpc[1], 0x57001000, 0x1000 },
                 { "ddr-mpc", make_mpc, &mms->mpc[2], 0x57002000, 0x1000 },
@@ -1100,6 +1113,14 @@
                 { /* port 7 USER AHB interface 3 */ },
                 { "eth-usb", make_eth_usb, NULL, 0x41400000, 0x200000, { 49 } },
             },
+        }, {
+            .name = "ahb_ppcexp1",
+            .ports = {
+                { /* port 0 reserved */ },
+                { "dma1", make_dma, &mms->dma[1], 0x41201000, 0x1000, { 62, 60, 61 } },
+                { "dma2", make_dma, &mms->dma[2], 0x41202000, 0x1000, { 65, 63, 64 } },
+                { "dma3", make_dma, &mms->dma[3], 0x41203000, 0x1000, { 68, 66, 67 } },
+            },
         },
     };
 
diff --git a/hw/arm/raspi4b.c b/hw/arm/raspi4b.c
index 06aeb8d..e1595a8 100644
--- a/hw/arm/raspi4b.c
+++ b/hw/arm/raspi4b.c
@@ -85,7 +85,7 @@
 
     ram_size = board_ram_size(info->board_id);
 
-    if (info->ram_size > UPPER_RAM_BASE) {
+    if (ram_size > UPPER_RAM_BASE) {
         raspi_add_memory_node(fdt, UPPER_RAM_BASE, ram_size - UPPER_RAM_BASE);
     }
 }
diff --git a/hw/arm/virt.c b/hw/arm/virt.c
index 219597c..e7a56e3 100644
--- a/hw/arm/virt.c
+++ b/hw/arm/virt.c
@@ -4441,10 +4441,17 @@
 }
 type_init(machvirt_machine_init);
 
-static void virt_machine_11_1_options(MachineClass *mc)
+static void virt_machine_11_2_options(MachineClass *mc)
 {
 }
-DEFINE_VIRT_MACHINE_AS_LATEST(11, 1)
+DEFINE_VIRT_MACHINE_AS_LATEST(11, 2)
+
+static void virt_machine_11_1_options(MachineClass *mc)
+{
+    virt_machine_11_2_options(mc);
+    compat_props_add(mc->compat_props, hw_compat_11_1, hw_compat_11_1_len);
+}
+DEFINE_VIRT_MACHINE(11, 1)
 
 static void virt_machine_11_0_options(MachineClass *mc)
 {
diff --git a/hw/char/sclpconsole-lm.c b/hw/char/sclpconsole-lm.c
index 9a16896..f6ed282 100644
--- a/hw/char/sclpconsole-lm.c
+++ b/hw/char/sclpconsole-lm.c
@@ -243,7 +243,8 @@
     SCLPConsoleLM *scon = SCLPLM_CONSOLE(event);
 
     len = be16_to_cpu(data->mdb.header.length);
-    if (len < sizeof(data->mdb.header)) {
+    if (len < sizeof(data->mdb.header) ||
+        len > be16_to_cpu(data->header.length) - sizeof(EventBufferHeader)) {
         return SCLP_RC_INCONSISTENT_LENGTHS;
     }
     len -= sizeof(data->mdb.header);
diff --git a/hw/core/machine.c b/hw/core/machine.c
index 73b4d82..4e55119 100644
--- a/hw/core/machine.c
+++ b/hw/core/machine.c
@@ -40,6 +40,9 @@
 #include "qemu/audio.h"
 #include "hw/arm/smmuv3.h"
 
+GlobalProperty hw_compat_11_1[] = {};
+const size_t hw_compat_11_1_len = G_N_ELEMENTS(hw_compat_11_1);
+
 GlobalProperty hw_compat_11_0[] = {
     { "virtio-mmio", VIRTIO_QUEUE_SIZE_OVERRIDE, "1024" },
     { "chardev-vc", "encoding", "cp437" },
@@ -51,6 +54,8 @@
     { TYPE_ARM_SMMUV3, "ssidsize", "0" },
     { TYPE_ARM_SMMUV3, "oas", "44" },
     { "migration", "switchover-ack-legacy", "on" },
+    { "sysbus-ehci-usb", "x-migrate-fetch-addr-64bit", "off" },
+    { "pci-ehci-usb", "x-migrate-fetch-addr-64bit", "off" },
 };
 const size_t hw_compat_11_0_len = G_N_ELEMENTS(hw_compat_11_0);
 
diff --git a/hw/gpio/Kconfig b/hw/gpio/Kconfig
index a209294..fcc7c70 100644
--- a/hw/gpio/Kconfig
+++ b/hw/gpio/Kconfig
@@ -30,3 +30,6 @@
 
 config ZAURUS_SCOOP
     bool
+
+config CADENCE_GPIO
+    bool
diff --git a/hw/gpio/cadence_gpio.c b/hw/gpio/cadence_gpio.c
new file mode 100644
index 0000000..ce8256b
--- /dev/null
+++ b/hw/gpio/cadence_gpio.c
@@ -0,0 +1,292 @@
+/*
+ * Cadence GPIO emulation.
+ *
+ * Author: Kuan-Jui Chiu <kchiu@axiado.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/gpio/cadence_gpio.h"
+#include "hw/core/irq.h"
+#include "migration/vmstate.h"
+#include "qemu/log.h"
+#include "trace.h"
+
+static void cdns_gpio_update_irq(CadenceGPIOState *s)
+{
+    qemu_set_irq(s->irq, s->isr ? 1 : 0);
+}
+
+static void cdns_gpio_update_isr_with_inpvr(CadenceGPIOState *s, uint32_t new)
+{
+    uint32_t new_isr = 0;
+    uint32_t any_edges, rising_edges, falling_edges, deassert_mask;
+
+    /*
+     * If ITR is set, this is level triggered:
+     * set corresponding ISR bits when IVR matches new inpvr value.
+     */
+    new_isr |= s->itr & ~(s->ivr ^ new);
+
+    /*
+     * If ITR is not set, this is edge-triggered:
+     * If IOAR bit is set, trigger on any edge;
+     * otherwise trigger on rising edge if IVR is set,
+     * trigger on falling edge if IVR bit is 0.
+     */
+    any_edges = s->ioar & (s->inpvr ^ new);
+    rising_edges = s->ivr & ~s->inpvr & new;
+    falling_edges = ~s->ivr & s->inpvr & ~new;
+    new_isr |= ~s->itr & (any_edges | rising_edges | falling_edges);
+
+    /*
+     * In bypass mode or if this isn't an input pin, the corresponding ISR
+     * bit is forced to zero.
+     */
+    deassert_mask = s->bmr | ~s->dmr | s->imr;
+
+    new_isr &= ~deassert_mask;
+    s->isr = new_isr;
+
+    cdns_gpio_update_irq(s);
+}
+
+static void cdns_gpio_update_isr(CadenceGPIOState *s)
+{
+    cdns_gpio_update_isr_with_inpvr(s, s->inpvr);
+}
+
+static void cdns_gpio_set(void *opaque, int line, int level)
+{
+    CadenceGPIOState *s = CADENCE_GPIO(opaque);
+    uint32_t new_inpvr = deposit32(s->inpvr, line, 1, level ? 1 : 0);
+
+    trace_cdns_gpio_set(DEVICE(s)->canonical_path, line, level);
+
+    cdns_gpio_update_isr_with_inpvr(s, new_inpvr);
+
+    /* Sync INPVR with new value */
+    s->inpvr = new_inpvr;
+}
+
+static inline void cdns_gpio_update_output_irq(CadenceGPIOState *s)
+{
+    uint32_t is_output = ~s->bmr & ~s->dmr & s->oer;
+
+    for (int i = 0; i < CDNS_GPIO_NUM; i++) {
+        if (extract32(is_output, i, 1)) {
+            /* Forward the output value to corresponding irq */
+            qemu_set_irq(s->output[i], extract32(s->ovr, i, 1));
+        }
+    }
+}
+
+static uint64_t cdns_gpio_read(void *opaque, hwaddr offset, unsigned size)
+{
+    CadenceGPIOState *s = CADENCE_GPIO(opaque);
+    uint32_t reg_value = 0x0;
+
+    switch (offset) {
+    case CDNS_GPIO_BYPASS_MODE:
+        reg_value = s->bmr;
+        break;
+
+    case CDNS_GPIO_DIRECTION_MODE:
+        reg_value = s->dmr;
+        break;
+
+    case CDNS_GPIO_OUTPUT_EN:
+        reg_value = s->oer;
+        break;
+
+    case CDNS_GPIO_OUTPUT_VALUE:
+        reg_value = s->ovr;
+        break;
+
+    case CDNS_GPIO_INPUT_VALUE:
+        reg_value = s->inpvr;
+        break;
+
+    case CDNS_GPIO_IRQ_MASK:
+        reg_value = s->imr;
+        break;
+
+    case CDNS_GPIO_IRQ_STATUS:
+        reg_value = s->isr;
+        break;
+
+    case CDNS_GPIO_IRQ_TYPE:
+        reg_value = s->itr;
+        break;
+
+    case CDNS_GPIO_IRQ_VALUE:
+        reg_value = s->ivr;
+        break;
+
+    case CDNS_GPIO_IRQ_ANY_EDGE:
+        reg_value = s->ioar;
+        break;
+
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Bad register at offset 0x%"
+                      HWADDR_PRIx "\n", TYPE_CADENCE_GPIO, __func__, offset);
+        break;
+    }
+
+    trace_cdns_gpio_read(DEVICE(s)->canonical_path, offset, reg_value);
+
+    return reg_value;
+}
+
+static void cdns_gpio_write(void *opaque, hwaddr offset, uint64_t value,
+                            unsigned size)
+{
+    CadenceGPIOState *s = CADENCE_GPIO(opaque);
+
+    trace_cdns_gpio_write(DEVICE(s)->canonical_path, offset, value);
+
+    switch (offset) {
+    case CDNS_GPIO_BYPASS_MODE:
+        s->bmr = value;
+        cdns_gpio_update_output_irq(s);
+        cdns_gpio_update_isr(s);
+        break;
+
+    case CDNS_GPIO_DIRECTION_MODE:
+        s->dmr = value;
+        cdns_gpio_update_output_irq(s);
+        cdns_gpio_update_isr(s);
+        break;
+
+    case CDNS_GPIO_OUTPUT_EN:
+        s->oer = value;
+        cdns_gpio_update_output_irq(s);
+        break;
+
+    case CDNS_GPIO_OUTPUT_VALUE:
+        s->ovr = value;
+        cdns_gpio_update_output_irq(s);
+        break;
+
+    case CDNS_GPIO_IRQ_EN:
+        s->imr &= ~value;
+        cdns_gpio_update_isr(s);
+        break;
+
+    case CDNS_GPIO_IRQ_DIS:
+        s->imr |= value;
+        cdns_gpio_update_isr(s);
+        break;
+
+    case CDNS_GPIO_IRQ_TYPE:
+        s->itr = value;
+        break;
+
+    case CDNS_GPIO_IRQ_VALUE:
+        s->ivr = value;
+        break;
+
+    case CDNS_GPIO_IRQ_ANY_EDGE:
+        s->ioar = value;
+        break;
+
+    case CDNS_GPIO_INPUT_VALUE:
+    case CDNS_GPIO_IRQ_MASK:
+    case CDNS_GPIO_IRQ_STATUS:
+        /* Read-Only */
+        break;
+
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Bad register at offset 0x%"
+                      HWADDR_PRIx "\n", TYPE_CADENCE_GPIO, __func__, offset);
+        break;
+    }
+}
+
+static const MemoryRegionOps cdns_gpio_ops = {
+    .read = cdns_gpio_read,
+    .write = cdns_gpio_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .impl = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+    },
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+    }
+};
+
+static const VMStateDescription vmstate_cdns_gpio = {
+    .name = TYPE_CADENCE_GPIO,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (const VMStateField[]) {
+        VMSTATE_UINT32(bmr, CadenceGPIOState),
+        VMSTATE_UINT32(dmr, CadenceGPIOState),
+        VMSTATE_UINT32(oer, CadenceGPIOState),
+        VMSTATE_UINT32(ovr, CadenceGPIOState),
+        VMSTATE_UINT32(inpvr, CadenceGPIOState),
+        VMSTATE_UINT32(imr, CadenceGPIOState),
+        VMSTATE_UINT32(isr, CadenceGPIOState),
+        VMSTATE_UINT32(itr, CadenceGPIOState),
+        VMSTATE_UINT32(ivr, CadenceGPIOState),
+        VMSTATE_UINT32(ioar, CadenceGPIOState),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static void cdns_gpio_reset(DeviceState *dev)
+{
+    CadenceGPIOState *s = CADENCE_GPIO(dev);
+
+    s->bmr      = 0;
+    s->dmr      = 0;
+    s->oer      = 0;
+    s->ovr      = 0;
+    s->inpvr    = 0;
+    s->imr      = 0xffffffff;
+    s->isr      = 0;
+    s->itr      = 0;
+    s->ivr      = 0;
+    s->ioar     = 0;
+}
+
+static void cdns_gpio_init(Object *obj)
+{
+    CadenceGPIOState *s = CADENCE_GPIO(obj);
+
+    memory_region_init_io(&s->iomem, obj, &cdns_gpio_ops, s,
+                          TYPE_CADENCE_GPIO, CDNS_GPIO_REG_SIZE);
+
+    qdev_init_gpio_in(DEVICE(s), cdns_gpio_set, CDNS_GPIO_NUM);
+    qdev_init_gpio_out(DEVICE(s), s->output, CDNS_GPIO_NUM);
+
+    sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq);
+    sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
+}
+
+static void cdns_gpio_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    device_class_set_legacy_reset(dc, cdns_gpio_reset);
+    dc->vmsd = &vmstate_cdns_gpio;
+    dc->desc = "Cadence GPIO controller";
+}
+
+static const TypeInfo cdns_gpio_info = {
+    .name = TYPE_CADENCE_GPIO,
+    .parent = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(CadenceGPIOState),
+    .instance_init = cdns_gpio_init,
+    .class_init = cdns_gpio_class_init,
+};
+
+static void cdns_gpio_register_types(void)
+{
+    type_register_static(&cdns_gpio_info);
+}
+
+type_init(cdns_gpio_register_types)
diff --git a/hw/gpio/meson.build b/hw/gpio/meson.build
index 6a67ee9..0555f44 100644
--- a/hw/gpio/meson.build
+++ b/hw/gpio/meson.build
@@ -19,3 +19,4 @@
 system_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files('aspeed_sgpio.c'))
 system_ss.add(when: 'CONFIG_SIFIVE_GPIO', if_true: files('sifive_gpio.c'))
 system_ss.add(when: 'CONFIG_PCF8574', if_true: files('pcf8574.c'))
+system_ss.add(when: 'CONFIG_CADENCE_GPIO', if_true: files('cadence_gpio.c'))
diff --git a/hw/gpio/pca9552.c b/hw/gpio/pca9552.c
index b13ac9f..719149b 100644
--- a/hw/gpio/pca9552.c
+++ b/hw/gpio/pca9552.c
@@ -1,7 +1,10 @@
 /*
- * PCA9552 I2C LED blinker
+ * PCA955X I2C LED blinker and I/O expanders
  *
  *     https://www.nxp.com/docs/en/application-note/AN264.pdf
+ *     https://www.nxp.com/docs/en/data-sheet/PCA9552.pdf
+ *     https://www.nxp.com/docs/en/data-sheet/PCA9555.pdf
+ *     https://www.nxp.com/docs/en/data-sheet/PCA9535_PCA9535C.pdf
  *
  * Copyright (c) 2017-2018, IBM Corporation.
  * Copyright (c) 2020 Philippe Mathieu-Daudé
@@ -12,9 +15,9 @@
 
 #include "qemu/osdep.h"
 #include "qemu/log.h"
-#include "qemu/module.h"
 #include "qemu/bitops.h"
 #include "hw/core/qdev-properties.h"
+#include "hw/i2c/i2c.h"
 #include "hw/gpio/pca9552.h"
 #include "hw/gpio/pca9552_regs.h"
 #include "hw/core/irq.h"
@@ -24,6 +27,25 @@
 #include "trace.h"
 #include "qom/object.h"
 
+#define PCA955X_NR_REGS 10
+#define PCA955X_PIN_COUNT_MAX 16
+
+OBJECT_DECLARE_TYPE(PCA955xState, PCA955xClass, PCA955X)
+
+struct PCA955xState {
+    /*< private >*/
+    I2CSlave parent_obj;
+    /*< public >*/
+
+    uint8_t len;
+    uint8_t pointer;
+
+    uint8_t regs[PCA955X_NR_REGS];
+    qemu_irq gpio_out[PCA955X_PIN_COUNT_MAX];
+    uint8_t ext_state[PCA955X_PIN_COUNT_MAX];
+    char *description; /* For debugging purpose only */
+};
+
 struct PCA955xClass {
     /*< private >*/
     I2CSlaveClass parent_class;
@@ -33,10 +55,7 @@
     uint8_t max_reg;
     bool has_led_support;
 };
-typedef struct PCA955xClass PCA955xClass;
 
-DECLARE_CLASS_CHECKERS(PCA955xClass, PCA955X,
-                       TYPE_PCA955X)
 /*
  * Note:  The LED_ON and LED_OFF configuration values for the PCA955X
  *        chips are the reverse of the PCA953X family of chips.
@@ -49,6 +68,7 @@
 #define PCA9552_PIN_HIZ  0x1
 
 static const char *led_state[] = {"on", "off", "pwm0", "pwm1"};
+static const char *pin_state[] = {"low", "high"};
 
 static uint8_t pca955x_pin_get_config(PCA955xState *s, int pin)
 {
@@ -148,9 +168,12 @@
             /* PCA9535: Simple GPIO behavior */
             uint8_t config_reg = PCA9535_CONFIG0 + (i / 8);
             uint8_t output_reg = PCA9535_OUTPUT0 + (i / 8);
-            uint8_t polarity_reg = PCA9535_POLARITY0 + (i / 8);
 
-            /* Check if pin is configured as input */
+            /*
+             * The input register holds the raw pin logic level; the
+             * polarity inversion register is only applied when the input
+             * port is read (see pca955x_read()).
+             */
             if (s->regs[config_reg] & bit_mask) {
                 /* Input mode - reflect external state */
                 if (s->ext_state[i] == PCA9552_PIN_LOW) {
@@ -160,12 +183,8 @@
                 }
             } else {
                 /* Output mode - reflect output register value */
-                uint8_t output_bit = s->regs[output_reg] & bit_mask;
-                uint8_t polarity_bit = s->regs[polarity_reg] & bit_mask;
-
-                /* Apply polarity inversion if set */
                 s->regs[input_reg] = (s->regs[input_reg] & ~bit_mask) |
-                                    ((output_bit ^ polarity_bit) & bit_mask);
+                                     (s->regs[output_reg] & bit_mask);
             }
         }
 
@@ -187,6 +206,18 @@
         return 0xFF;
     }
 
+    /*
+     * On the GPIO variants, reading an input port returns the raw pin
+     * levels XORed with the polarity inversion register, as specified by
+     * the datasheet.
+     */
+    if (!k->has_led_support &&
+        (reg == PCA9535_INPUT0 || reg == PCA9535_INPUT1)) {
+        uint8_t polarity_reg = PCA9535_POLARITY0 + (reg - PCA9535_INPUT0);
+
+        return s->regs[reg] ^ s->regs[polarity_reg];
+    }
+
     return s->regs[reg];
 }
 
@@ -229,14 +260,26 @@
 }
 
 /*
- * When Auto-Increment is on, the register address is incremented
- * after each byte is sent to or received by the device. The index
- * rollovers to 0 when the maximum register address is reached.
+ * Advance the command pointer after each byte sent to or received from the
+ * device.
+ *
+ * The LED variant auto-increments only when the AI bit (bit 4) is set in the
+ * command byte, rolling over to 0 once the maximum register address is
+ * reached.
+ *
+ * The GPIO variants auto-increment on every access, toggling bit 0 so the
+ * pointer stays within the addressed register pair
+ * (input/output/polarity/config), as specified by their datasheet.
  */
 static void pca955x_autoinc(PCA955xState *s)
 {
     PCA955xClass *k = PCA955X_GET_CLASS(s);
 
+    if (!k->has_led_support) {
+        s->pointer ^= 0x1;
+        return;
+    }
+
     if (s->pointer != 0xFF && s->pointer & PCA9552_AUTOINC) {
         uint8_t reg = s->pointer & 0xf;
 
@@ -245,12 +288,25 @@
     }
 }
 
+/*
+ * The LED variant addresses its registers with a 4-bit command field, while
+ * the GPIO variants only decode 3 bits (the command wraps into the 8-register
+ * window).
+ */
+static inline uint8_t pca955x_cmd_reg(PCA955xState *s)
+{
+    PCA955xClass *k = PCA955X_GET_CLASS(s);
+
+    return s->pointer & (k->has_led_support ? 0xf : 0x7);
+}
+
 static uint8_t pca955x_recv(I2CSlave *i2c)
 {
     PCA955xState *s = PCA955X(i2c);
+    PCA955xClass *k = PCA955X_GET_CLASS(s);
     uint8_t ret;
 
-    ret = pca955x_read(s, s->pointer & 0xf);
+    ret = pca955x_read(s, pca955x_cmd_reg(s));
 
     /*
      * From the Specs:
@@ -262,7 +318,7 @@
      * I don't know what should be done in this case, so throw an
      * error.
      */
-    if (s->pointer == PCA9552_AUTOINC) {
+    if (k->has_led_support && s->pointer == PCA9552_AUTOINC) {
         qemu_log_mask(LOG_GUEST_ERROR,
                       "%s: Autoincrement read starting with register 0\n",
                       __func__);
@@ -282,7 +338,7 @@
         s->pointer = data;
         s->len++;
     } else {
-        pca955x_write(s, s->pointer & 0xf, data);
+        pca955x_write(s, pca955x_cmd_reg(s), data);
 
         pca955x_autoinc(s);
     }
@@ -373,6 +429,79 @@
     pca955x_write(s, reg, val);
 }
 
+static void pca955x_set_ext_state(PCA955xState *s, int pin, int level);
+
+static void pca955x_get_pin(Object *obj, Visitor *v, const char *name,
+                            void *opaque, Error **errp)
+{
+    PCA955xClass *k = PCA955X_GET_CLASS(obj);
+    PCA955xState *s = PCA955X(obj);
+    int pin, rc;
+    uint8_t input_reg, state;
+
+    rc = sscanf(name, "pin%2d", &pin);
+    if (rc != 1) {
+        error_setg(errp, "%s: error reading %s", __func__, name);
+        return;
+    }
+    if (pin < 0 || pin >= k->pin_count) {
+        error_setg(errp, "%s invalid pin %s", __func__, name);
+        return;
+    }
+
+    /*
+     * Report the raw pin logic level; polarity inversion is a read-time
+     * transform applied to the INPUT register, not to the pin state itself.
+     */
+    input_reg = PCA9535_INPUT0 + (pin / 8);
+    state = (s->regs[input_reg] >> (pin % 8)) & 0x1;
+    visit_type_str(v, name, (char **)&pin_state[state], errp);
+}
+
+static void pca955x_set_pin(Object *obj, Visitor *v, const char *name,
+                            void *opaque, Error **errp)
+{
+    PCA955xClass *k = PCA955X_GET_CLASS(obj);
+    PCA955xState *s = PCA955X(obj);
+    int pin, rc;
+    uint8_t state, config_reg;
+    g_autofree char *state_str = NULL;
+
+    if (!visit_type_str(v, name, &state_str, errp)) {
+        return;
+    }
+    rc = sscanf(name, "pin%2d", &pin);
+    if (rc != 1) {
+        error_setg(errp, "%s: error reading %s", __func__, name);
+        return;
+    }
+    if (pin < 0 || pin >= k->pin_count) {
+        error_setg(errp, "%s invalid pin %s", __func__, name);
+        return;
+    }
+
+    for (state = 0; state < ARRAY_SIZE(pin_state); state++) {
+        if (!strcmp(state_str, pin_state[state])) {
+            break;
+        }
+    }
+    if (state >= ARRAY_SIZE(pin_state)) {
+        error_setg(errp, "%s invalid pin state %s", __func__, state_str);
+        return;
+    }
+
+    /* Only input-configured pins can be driven by an external device. */
+    config_reg = PCA9535_CONFIG0 + (pin / 8);
+    if (!((s->regs[config_reg] >> (pin % 8)) & 0x1)) {
+        qemu_log_mask(LOG_UNIMP,
+                      "%s: pin %d is configured as output, ignoring set\n",
+                      s->description, pin);
+        return;
+    }
+
+    pca955x_set_ext_state(s, pin, state != PCA9552_PIN_LOW);
+}
+
 static const VMStateDescription pca9552_vmstate = {
     .name = "PCA9552",
     .version_id = 0,
@@ -382,14 +511,14 @@
         VMSTATE_UINT8(pointer, PCA955xState),
         VMSTATE_UINT8_ARRAY(regs, PCA955xState, PCA955X_NR_REGS),
         VMSTATE_UINT8_ARRAY(ext_state, PCA955xState, PCA955X_PIN_COUNT_MAX),
-        VMSTATE_I2C_SLAVE(i2c, PCA955xState),
+        VMSTATE_I2C_SLAVE(parent_obj, PCA955xState),
         VMSTATE_END_OF_LIST()
     }
 };
 
-static void pca9552_reset(DeviceState *dev)
+static void pca9552_reset_hold(Object *obj, ResetType type)
 {
-    PCA955xState *s = PCA955X(dev);
+    PCA955xState *s = PCA955X(obj);
 
     s->regs[PCA9552_PSC0] = 0xFF;
     s->regs[PCA9552_PWM0] = 0x80;
@@ -407,9 +536,9 @@
     s->len = 0;
 }
 
-static void pca9535_reset(DeviceState *dev)
+static void pca9535_reset_hold(Object *obj, ResetType type)
 {
-    PCA955xState *s = PCA955X(dev);
+    PCA955xState *s = PCA955X(obj);
 
     s->regs[PCA9535_INPUT0] = 0xFF;   /* All inputs high (pull-ups) */
     s->regs[PCA9535_INPUT1] = 0xFF;   /* All inputs high (pull-ups) */
@@ -430,15 +559,22 @@
 static void pca955x_initfn(Object *obj)
 {
     PCA955xClass *k = PCA955X_GET_CLASS(obj);
-    int led;
 
     assert(k->pin_count <= PCA955X_PIN_COUNT_MAX);
-    for (led = 0; led < k->pin_count; led++) {
+    for (int ix = 0; ix < k->pin_count; ix++) {
         char *name;
 
-        name = g_strdup_printf("led%d", led);
-        object_property_add(obj, name, "bool", pca955x_get_led, pca955x_set_led,
-                            NULL, NULL);
+        if (k->has_led_support) {
+            /* LED variant: expose the LED selector state as led%d. */
+            name = g_strdup_printf("led%d", ix);
+            object_property_add(obj, name, "bool",
+                                pca955x_get_led, pca955x_set_led, NULL, NULL);
+        } else {
+            /* GPIO variant: expose the pin logic level as pin%d. */
+            name = g_strdup_printf("pin%d", ix);
+            object_property_add(obj, name, "str",
+                                pca955x_get_pin, pca955x_set_pin, NULL, NULL);
+        }
         g_free(name);
     }
 }
@@ -469,7 +605,7 @@
     PCA955xState *s = PCA955X(dev);
 
     if (!s->description) {
-        s->description = g_strdup("pca-unspecified");
+        s->description = g_strdup(object_get_typename(OBJECT(dev)));
     }
 
     qdev_init_gpio_out(dev, s->gpio_out, k->pin_count);
@@ -492,57 +628,57 @@
     device_class_set_props(dc, pca955x_properties);
 }
 
-static const TypeInfo pca955x_info = {
-    .name          = TYPE_PCA955X,
-    .parent        = TYPE_I2C_SLAVE,
-    .instance_init = pca955x_initfn,
-    .instance_size = sizeof(PCA955xState),
-    .class_init    = pca955x_class_init,
-    .class_size    = sizeof(PCA955xClass),
-    .abstract      = true,
-};
-
 static void pca9552_class_init(ObjectClass *oc, const void *data)
 {
     DeviceClass *dc = DEVICE_CLASS(oc);
+    ResettableClass *rc = RESETTABLE_CLASS(oc);
     PCA955xClass *pc = PCA955X_CLASS(oc);
 
-    device_class_set_legacy_reset(dc, pca9552_reset);
+    rc->phases.hold = pca9552_reset_hold;
     dc->vmsd = &pca9552_vmstate;
     pc->max_reg = PCA9552_LS3;
     pc->pin_count = 16;
     pc->has_led_support = true;
 }
 
-static void pca9535_class_init(ObjectClass *oc, const void *data)
+static void pca95x5_class_init(ObjectClass *oc, const void *data)
 {
     DeviceClass *dc = DEVICE_CLASS(oc);
+    ResettableClass *rc = RESETTABLE_CLASS(oc);
     PCA955xClass *pc = PCA955X_CLASS(oc);
 
-    device_class_set_legacy_reset(dc, pca9535_reset);
+    rc->phases.hold = pca9535_reset_hold;
     dc->vmsd = &pca9552_vmstate;
     pc->max_reg = PCA9535_CONFIG1;
     pc->pin_count = 16;
     pc->has_led_support = false;
 }
 
-static const TypeInfo pca9552_info = {
-    .name          = TYPE_PCA9552,
-    .parent        = TYPE_PCA955X,
-    .class_init    = pca9552_class_init,
+static const TypeInfo pca955x_types[] = {
+    {
+        .name          = TYPE_PCA955X,
+        .parent        = TYPE_I2C_SLAVE,
+        .instance_init = pca955x_initfn,
+        .instance_size = sizeof(PCA955xState),
+        .class_init    = pca955x_class_init,
+        .class_size    = sizeof(PCA955xClass),
+        .abstract      = true,
+    },
+    {
+        .name          = TYPE_PCA9552,
+        .parent        = TYPE_PCA955X,
+        .class_init    = pca9552_class_init,
+    },
+    {
+        .name          = TYPE_PCA9535,
+        .parent        = TYPE_PCA955X,
+        .class_init    = pca95x5_class_init,
+    },
+    {
+        .name          = TYPE_PCA9555,
+        .parent        = TYPE_PCA955X,
+        .class_init    = pca95x5_class_init,
+    }
 };
 
-static const TypeInfo pca9535_info = {
-    .name          = TYPE_PCA9535,
-    .parent        = TYPE_PCA955X,
-    .class_init    = pca9535_class_init,
-};
-
-static void pca955x_register_types(void)
-{
-    type_register_static(&pca955x_info);
-    type_register_static(&pca9552_info);
-    type_register_static(&pca9535_info);
-}
-
-type_init(pca955x_register_types)
+DEFINE_TYPES(pca955x_types)
diff --git a/hw/gpio/pca9554.c b/hw/gpio/pca9554.c
index 8427e01..904698c 100644
--- a/hw/gpio/pca9554.c
+++ b/hw/gpio/pca9554.c
@@ -24,6 +24,8 @@
     /*< private >*/
     I2CSlaveClass parent_class;
     /*< public >*/
+
+    uint8_t pin_count;
 };
 typedef struct PCA9554Class PCA9554Class;
 
@@ -37,46 +39,36 @@
 
 static void pca9554_update_pin_input(PCA9554State *s)
 {
+    PCA9554Class *pc = PCA9554_GET_CLASS(s);
     int i;
     uint8_t config = s->regs[PCA9554_CONFIG];
     uint8_t output = s->regs[PCA9554_OUTPUT];
-    uint8_t internal_state = config | output;
 
-    for (i = 0; i < PCA9554_PIN_COUNT; i++) {
+    for (i = 0; i < pc->pin_count; i++) {
         uint8_t bit_mask = 1 << i;
-        uint8_t internal_pin_state = (internal_state >> i) & 0x1;
         uint8_t old_value = s->regs[PCA9554_INPUT] & bit_mask;
         uint8_t new_value;
 
-        switch (internal_pin_state) {
-        case PCA9554_PIN_LOW:
-            s->regs[PCA9554_INPUT] &= ~bit_mask;
-            break;
-        case PCA9554_PIN_HIZ:
+        if (config & bit_mask) {
             /*
-             * pullup sets it to a logical 1 unless
-             * external device drives it low.
+             * Input: the pin is Hi-Z with a pull-up, so it reads high
+             * unless an external device drives it low.
              */
             if (s->ext_state[i] == PCA9554_PIN_LOW) {
                 s->regs[PCA9554_INPUT] &= ~bit_mask;
             } else {
-                s->regs[PCA9554_INPUT] |=  bit_mask;
+                s->regs[PCA9554_INPUT] |= bit_mask;
             }
-            break;
-        default:
-            break;
+        } else {
+            /* Output: the push-pull stage drives the output register level. */
+            s->regs[PCA9554_INPUT] = (s->regs[PCA9554_INPUT] & ~bit_mask) |
+                                     (output & bit_mask);
         }
 
-        /* update irq state only if pin state changed */
+        /* drive the per-pin GPIO output only if the pin level changed */
         new_value = s->regs[PCA9554_INPUT] & bit_mask;
         if (new_value != old_value) {
-            if (new_value) {
-                /* changed from 0 to 1 */
-                qemu_set_irq(s->gpio_out[i], 1);
-            } else {
-                /* changed from 1 to 0 */
-                qemu_set_irq(s->gpio_out[i], 0);
-            }
+            qemu_set_irq(s->gpio_out[i], !!new_value);
         }
     }
 }
@@ -99,6 +91,12 @@
 
 static void pca9554_write(PCA9554State *s, uint8_t reg, uint8_t data)
 {
+    PCA9554Class *pc = PCA9554_GET_CLASS(s);
+    uint8_t pin_mask = (1 << pc->pin_count) - 1;
+
+    /* Variants narrower than 8 bits ignore the unimplemented upper pins. */
+    data &= pin_mask;
+
     switch (reg) {
     case PCA9554_OUTPUT:
     case PCA9554_CONFIG:
@@ -145,6 +143,14 @@
     return 0;
 }
 
+static void pca9554_set_ext_state(PCA9554State *s, int pin, int level)
+{
+    if (s->ext_state[pin] != level) {
+        s->ext_state[pin] = level;
+        pca9554_update_pin_input(s);
+    }
+}
+
 static void pca9554_get_pin(Object *obj, Visitor *v, const char *name,
                             void *opaque, Error **errp)
 {
@@ -157,14 +163,18 @@
         error_setg(errp, "%s: error reading %s", __func__, name);
         return;
     }
-    if (pin < 0 || pin >= PCA9554_PIN_COUNT) {
+    if (pin < 0 || pin >= PCA9554_GET_CLASS(s)->pin_count) {
         error_setg(errp, "%s invalid pin %s", __func__, name);
         return;
     }
 
-    state = pca9554_read(s, PCA9554_CONFIG);
-    state |= pca9554_read(s, PCA9554_OUTPUT);
-    state = (state >> pin) & 0x1;
+    /*
+     * Report the physical pin level. The input register is kept in sync by
+     * pca9554_update_pin_input(): output pins mirror the OUTPUT register and
+     * input pins reflect the externally driven (or pulled-up) level, so it
+     * holds the wire level regardless of the configured direction.
+     */
+    state = (s->regs[PCA9554_INPUT] >> pin) & 0x1;
     visit_type_str(v, name, (char **)&pin_state[state], errp);
 }
 
@@ -184,7 +194,7 @@
         error_setg(errp, "%s: error reading %s", __func__, name);
         return;
     }
-    if (pin < 0 || pin >= PCA9554_PIN_COUNT) {
+    if (pin < 0 || pin >= PCA9554_GET_CLASS(s)->pin_count) {
         error_setg(errp, "%s invalid pin %s", __func__, name);
         return;
     }
@@ -199,20 +209,34 @@
         return;
     }
 
-    /* First, modify the output register bit */
-    val = pca9554_read(s, PCA9554_OUTPUT);
-    mask = 0x1 << pin;
-    if (state == PCA9554_PIN_LOW) {
-        val &= ~(mask);
+    if (s->hw_dir) {
+        /* Warn and ignore if the guest has configured this pin as output */
+        if (!((s->regs[PCA9554_CONFIG] >> pin) & 0x1)) {
+            qemu_log_mask(LOG_UNIMP,
+                          "%s: pin %d is configured as output, "
+                          "ignoring external set\n",
+                          s->description, pin);
+            return;
+        }
+        /* Drive the external input level */
+        pca9554_set_ext_state(s, pin, state != PCA9554_PIN_LOW);
     } else {
-        val |= mask;
-    }
-    pca9554_write(s, PCA9554_OUTPUT, val);
+        /* Legacy behavior: force output mode and drive */
+        /* First, modify the output register bit */
+        val = pca9554_read(s, PCA9554_OUTPUT);
+        mask = 0x1 << pin;
+        if (state == PCA9554_PIN_LOW) {
+            val &= ~(mask);
+        } else {
+            val |= mask;
+        }
+        pca9554_write(s, PCA9554_OUTPUT, val);
 
-    /* Then, clear the config register bit for output mode */
-    val = pca9554_read(s, PCA9554_CONFIG);
-    val &= ~mask;
-    pca9554_write(s, PCA9554_CONFIG, val);
+        /* Then, clear the config register bit for output mode */
+        val = pca9554_read(s, PCA9554_CONFIG);
+        val &= ~mask;
+        pca9554_write(s, PCA9554_CONFIG, val);
+    }
 }
 
 static const VMStateDescription pca9554_vmstate = {
@@ -232,13 +256,15 @@
 static void pca9554_reset(DeviceState *dev)
 {
     PCA9554State *s = PCA9554(dev);
+    PCA9554Class *pc = PCA9554_GET_CLASS(s);
+    uint8_t pin_mask = (1 << pc->pin_count) - 1;
 
-    s->regs[PCA9554_INPUT] = 0xFF;
-    s->regs[PCA9554_OUTPUT] = 0xFF;
+    s->regs[PCA9554_INPUT] = pin_mask;
+    s->regs[PCA9554_OUTPUT] = pin_mask;
     s->regs[PCA9554_POLARITY] = 0x0; /* No pins are inverted */
-    s->regs[PCA9554_CONFIG] = 0xFF; /* All pins are inputs */
+    s->regs[PCA9554_CONFIG] = pin_mask; /* All pins are inputs */
 
-    memset(s->ext_state, PCA9554_PIN_HIZ, PCA9554_PIN_COUNT);
+    memset(s->ext_state, PCA9554_PIN_HIZ, pc->pin_count);
     pca9554_update_pin_input(s);
 
     s->pointer = 0x0;
@@ -247,55 +273,51 @@
 
 static void pca9554_initfn(Object *obj)
 {
+    PCA9554Class *pc = PCA9554_GET_CLASS(obj);
     int pin;
 
-    for (pin = 0; pin < PCA9554_PIN_COUNT; pin++) {
+    for (pin = 0; pin < pc->pin_count; pin++) {
         char *name;
 
         name = g_strdup_printf("pin%d", pin);
-        object_property_add(obj, name, "bool", pca9554_get_pin, pca9554_set_pin,
+        object_property_add(obj, name, "str", pca9554_get_pin, pca9554_set_pin,
                             NULL, NULL);
         g_free(name);
     }
 }
 
-static void pca9554_set_ext_state(PCA9554State *s, int pin, int level)
-{
-    if (s->ext_state[pin] != level) {
-        s->ext_state[pin] = level;
-        pca9554_update_pin_input(s);
-    }
-}
-
 static void pca9554_gpio_in_handler(void *opaque, int pin, int level)
 {
-
     PCA9554State *s = PCA9554(opaque);
+    PCA9554Class *pc = PCA9554_GET_CLASS(s);
 
-    assert((pin >= 0) && (pin < PCA9554_PIN_COUNT));
+    assert((pin >= 0) && (pin < pc->pin_count));
     pca9554_set_ext_state(s, pin, level);
 }
 
 static void pca9554_realize(DeviceState *dev, Error **errp)
 {
     PCA9554State *s = PCA9554(dev);
+    PCA9554Class *pc = PCA9554_GET_CLASS(s);
 
     if (!s->description) {
-        s->description = g_strdup("pca9554");
+        s->description = g_strdup(object_get_typename(OBJECT(dev)));
     }
 
-    qdev_init_gpio_out(dev, s->gpio_out, PCA9554_PIN_COUNT);
-    qdev_init_gpio_in(dev, pca9554_gpio_in_handler, PCA9554_PIN_COUNT);
+    qdev_init_gpio_out(dev, s->gpio_out, pc->pin_count);
+    qdev_init_gpio_in(dev, pca9554_gpio_in_handler, pc->pin_count);
 }
 
 static const Property pca9554_properties[] = {
     DEFINE_PROP_STRING("description", PCA9554State, description),
+    DEFINE_PROP_BOOL("hw-dir", PCA9554State, hw_dir, false),
 };
 
 static void pca9554_class_init(ObjectClass *klass, const void *data)
 {
     DeviceClass *dc = DEVICE_CLASS(klass);
     I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
+    PCA9554Class *pc = PCA9554_CLASS(klass);
 
     k->event = pca9554_event;
     k->recv = pca9554_recv;
@@ -304,21 +326,32 @@
     device_class_set_legacy_reset(dc, pca9554_reset);
     dc->vmsd = &pca9554_vmstate;
     device_class_set_props(dc, pca9554_properties);
+
+    pc->pin_count = PCA9554_PIN_COUNT;
 }
 
-static const TypeInfo pca9554_info = {
-    .name          = TYPE_PCA9554,
-    .parent        = TYPE_I2C_SLAVE,
-    .instance_init = pca9554_initfn,
-    .instance_size = sizeof(PCA9554State),
-    .class_init    = pca9554_class_init,
-    .class_size    = sizeof(PCA9554Class),
-    .abstract      = false,
+static void pca9536_class_init(ObjectClass *klass, const void *data)
+{
+    PCA9554Class *pc = PCA9554_CLASS(klass);
+
+    pc->pin_count = PCA9536_PIN_COUNT;
+}
+
+static const TypeInfo pca9554_types[] = {
+    {
+        .name          = TYPE_PCA9554,
+        .parent        = TYPE_I2C_SLAVE,
+        .instance_init = pca9554_initfn,
+        .instance_size = sizeof(PCA9554State),
+        .class_init    = pca9554_class_init,
+        .class_size    = sizeof(PCA9554Class),
+        .abstract      = false,
+    },
+    {
+        .name          = TYPE_PCA9536,
+        .parent        = TYPE_PCA9554,
+        .class_init    = pca9536_class_init,
+    }
 };
 
-static void pca9554_register_types(void)
-{
-    type_register_static(&pca9554_info);
-}
-
-type_init(pca9554_register_types)
+DEFINE_TYPES(pca9554_types);
diff --git a/hw/gpio/trace-events b/hw/gpio/trace-events
index cea896b..80ca783 100644
--- a/hw/gpio/trace-events
+++ b/hw/gpio/trace-events
@@ -46,3 +46,8 @@
 stm32l4x5_gpio_write(char *gpio, uint64_t addr, uint64_t data) "GPIO%s addr: 0x%" PRIx64 " val: 0x%" PRIx64 ""
 stm32l4x5_gpio_update_idr(char *gpio, uint32_t old_idr, uint32_t new_idr) "GPIO%s from: 0x%x to: 0x%x"
 stm32l4x5_gpio_pins(char *gpio, uint16_t disconnected, uint16_t high) "GPIO%s disconnected pins: 0x%x levels: 0x%x"
+
+# cadence_gpio.c
+cdns_gpio_read(const char *path, uint64_t offset, uint32_t value) "%s:reg[0x%04" PRIx64 "] -> 0x%" PRIx32
+cdns_gpio_write(const char *path, uint64_t offset, uint64_t value) "%s:reg[0x%04" PRIx64 "] <- 0x%04" PRIx64
+cdns_gpio_set(const char *path, int line, int level) "%s:[%d] <- %d"
diff --git a/hw/hexagon/Kconfig b/hw/hexagon/Kconfig
index 52065ab..83b2763 100644
--- a/hw/hexagon/Kconfig
+++ b/hw/hexagon/Kconfig
@@ -2,6 +2,9 @@
     bool
     default y
     depends on HEXAGON
+    select CPU_CLUSTER
+    select HEX_L2VIC
+    select HEX_QTIMER
 
 config HEX_VIRT
     bool
diff --git a/hw/hexagon/hex-subsys.c b/hw/hexagon/hex-subsys.c
new file mode 100644
index 0000000..4e3a418
--- /dev/null
+++ b/hw/hexagon/hex-subsys.c
@@ -0,0 +1,175 @@
+/*
+ * Hexagon subsystem helpers shared between the machine models.
+ *
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "hw/hexagon/hex-subsys.h"
+#include "hw/hexagon/hexagon_globalreg.h"
+#include "hw/hexagon/hexagon_tlb.h"
+#include "hw/intc/hex-l2vic.h"
+#include "hw/timer/qct-qtimer.h"
+#include "hw/cpu/cluster.h"
+#include "hw/core/loader.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/core/qdev.h"
+#include "hw/core/sysbus.h"
+#include "system/address-spaces.h"
+
+#define HEX_L2VIC_CPU_IRQS 8
+
+/* Number of QTimer frames instantiated for every Hexagon machine. */
+#define HEX_QTIMER_NR_FRAMES 3
+
+#define HEX_QTIMER_L2VIC_IRQ_BASE 2
+
+static DeviceState *l2vic_create(HexagonCommonMachineState *hms,
+                                 const struct hexagon_machine_config *m_cfg)
+{
+    DeviceState *l2vic = qdev_new(TYPE_HEX_L2VIC);
+
+    object_property_add_child(OBJECT(hms), "l2vic", OBJECT(l2vic));
+    sysbus_realize_and_unref(SYS_BUS_DEVICE(l2vic), &error_fatal);
+    sysbus_mmio_map(SYS_BUS_DEVICE(l2vic), 0, m_cfg->l2vic_base);
+    sysbus_mmio_map(SYS_BUS_DEVICE(l2vic), 1,
+                    m_cfg->cfgtable.fastl2vic_base << 16);
+
+    return l2vic;
+}
+
+static void l2vic_connect_cpu(DeviceState *l2vic, DeviceState *cpu)
+{
+    int i;
+
+    for (i = 0; i < HEX_L2VIC_CPU_IRQS; i++) {
+        sysbus_connect_irq(SYS_BUS_DEVICE(l2vic), i, qdev_get_gpio_in(cpu, i));
+    }
+}
+
+static DeviceState *qtimer_create(HexagonCommonMachineState *hms,
+                                  const struct hexagon_machine_config *m_cfg)
+{
+    DeviceState *qtimer = qdev_new(TYPE_QCT_QTIMER);
+
+    object_property_add_child(OBJECT(hms), "qtimer", OBJECT(qtimer));
+    qdev_prop_set_uint32(qtimer, "nr_frames", HEX_QTIMER_NR_FRAMES);
+    sysbus_realize_and_unref(SYS_BUS_DEVICE(qtimer), &error_fatal);
+    sysbus_mmio_map(SYS_BUS_DEVICE(qtimer), 0, m_cfg->csr_base);
+    sysbus_mmio_map(SYS_BUS_DEVICE(qtimer), 1, m_cfg->qtmr_region);
+    for (unsigned int i = 0; i < HEX_QTIMER_NR_FRAMES; i++) {
+        sysbus_connect_irq(SYS_BUS_DEVICE(qtimer), i,
+                           qdev_get_gpio_in(hms->l2vic,
+                                            HEX_QTIMER_L2VIC_IRQ_BASE + i));
+    }
+
+    return qtimer;
+}
+
+static DeviceState *globalreg_create(HexagonCommonMachineState *hms,
+                                     const struct hexagon_machine_config *m_cfg,
+                                     Rev_t rev)
+{
+    DeviceState *glob_regs = qdev_new(TYPE_HEXAGON_GLOBALREG);
+
+    object_property_add_child(OBJECT(hms), "global-regs", OBJECT(glob_regs));
+    qdev_prop_set_uint64(glob_regs, "config-table-addr", m_cfg->cfgbase);
+    qdev_prop_set_uint32(glob_regs, "dsp-rev", rev);
+    object_property_set_link(OBJECT(glob_regs), "l2vic", OBJECT(hms->l2vic),
+                             &error_fatal);
+    object_property_set_link(OBJECT(glob_regs), "qtimer", OBJECT(hms->qtimer),
+                             &error_fatal);
+    sysbus_realize_and_unref(SYS_BUS_DEVICE(glob_regs), &error_fatal);
+
+    return glob_regs;
+}
+
+static DeviceState *tlb_create(HexagonCommonMachineState *hms,
+                               const struct hexagon_machine_config *m_cfg)
+{
+    DeviceState *tlb = qdev_new(TYPE_HEXAGON_TLB);
+
+    object_property_add_child(OBJECT(hms), "tlb", OBJECT(tlb));
+    qdev_prop_set_uint32(tlb, "num-entries", m_cfg->cfgtable.jtlb_size_entries);
+    sysbus_realize_and_unref(SYS_BUS_DEVICE(tlb), &error_fatal);
+
+    return tlb;
+}
+
+static DeviceState *cluster_create(HexagonCommonMachineState *hms)
+{
+    DeviceState *cluster = qdev_new(TYPE_CPU_CLUSTER);
+
+    object_property_add_child(OBJECT(hms), "cluster", OBJECT(cluster));
+    qdev_prop_set_uint32(cluster, "cluster-id", 0);
+
+    return cluster;
+}
+
+void hex_subsys_create(HexagonCommonMachineState *hms,
+                       const struct hexagon_machine_config *m_cfg, Rev_t rev)
+{
+    MachineState *machine = MACHINE(hms);
+    MemoryRegion *sysmem = get_system_memory();
+
+    /* Main DDR at the reset vector. */
+    memory_region_init_ram(&hms->ram, NULL, "ddr.ram", machine->ram_size,
+                           &error_fatal);
+    memory_region_add_subregion(sysmem, 0x0, &hms->ram);
+
+    /* Config-table ROM and the blob that backs it. */
+    memory_region_init_rom(&hms->cfgtable_rom, NULL, "config_table.rom",
+                           sizeof(m_cfg->cfgtable), &error_fatal);
+    memory_region_add_subregion(sysmem, m_cfg->cfgbase, &hms->cfgtable_rom);
+    rom_add_blob_fixed_as("config_table.rom", &m_cfg->cfgtable,
+                          sizeof(m_cfg->cfgtable), m_cfg->cfgbase,
+                          &address_space_memory);
+
+    if (m_cfg->cfgtable.vtcm_size_kb > 0) {
+        memory_region_init_ram(&hms->vtcm, NULL, "vtcm.ram",
+                               m_cfg->cfgtable.vtcm_size_kb * 1024,
+                               &error_fatal);
+        memory_region_add_subregion(sysmem, m_cfg->cfgtable.vtcm_base << 16,
+                                    &hms->vtcm);
+    }
+
+    hms->cluster = cluster_create(hms);
+    hms->l2vic = l2vic_create(hms, m_cfg);
+    hms->qtimer = qtimer_create(hms, m_cfg);
+    hms->glob_regs = globalreg_create(hms, m_cfg, rev);
+    hms->tlb = tlb_create(hms, m_cfg);
+}
+
+void hex_subsys_add_cpu(HexagonCommonMachineState *hms, DeviceState *cpu)
+{
+    object_property_add_child(OBJECT(hms->cluster), "cpu[*]", OBJECT(cpu));
+    object_property_set_link(OBJECT(cpu), "global-regs",
+                             OBJECT(hms->glob_regs), &error_fatal);
+    object_property_set_link(OBJECT(cpu), "tlb", OBJECT(hms->tlb),
+                             &error_fatal);
+    object_property_set_link(OBJECT(cpu), "l2vic", OBJECT(hms->l2vic),
+                             &error_fatal);
+}
+
+void hex_subsys_realize_cluster(HexagonCommonMachineState *hms)
+{
+    /*
+     * The cluster must be realized after its CPUs have been parented into it
+     * (see hex_subsys_add_cpu()) but before any CPU is itself realized, since
+     * qdev_realize_and_unref() on a CPU latches cluster_index into the TCG
+     * cflags at that point.
+     */
+    qdev_realize_and_unref(hms->cluster, NULL, &error_fatal);
+}
+
+void hex_subsys_realize_cpu(HexagonCommonMachineState *hms, DeviceState *cpu,
+                            bool boot_cpu)
+{
+    qdev_realize_and_unref(cpu, NULL, &error_fatal);
+
+    if (boot_cpu) {
+        l2vic_connect_cpu(hms->l2vic, cpu);
+    }
+}
diff --git a/hw/hexagon/hexagon_dsp.c b/hw/hexagon/hexagon_dsp.c
index aa49399..20306c2 100644
--- a/hw/hexagon/hexagon_dsp.c
+++ b/hw/hexagon/hexagon_dsp.c
@@ -14,8 +14,7 @@
 #include "hw/core/boards.h"
 #include "hw/core/qdev-properties.h"
 #include "hw/hexagon/hexagon.h"
-#include "hw/hexagon/hexagon_globalreg.h"
-#include "hw/hexagon/hexagon_tlb.h"
+#include "hw/hexagon/hex-subsys.h"
 #include "hw/core/loader.h"
 #include "qapi/error.h"
 #include "qemu/error-report.h"
@@ -108,9 +107,6 @@
 {
     HexagonCommonMachineState *hms = HEXAGON_COMMON_MACHINE(machine);
     HexagonDspMachineState *dms = HEXAGON_DSP_MACHINE(machine);
-    MemoryRegion *address_space;
-    DeviceState *glob_regs_dev;
-    DeviceState *tlb_dev;
 
     memset(&hexagon_binfo, 0, sizeof(hexagon_binfo));
     if (machine->kernel_filename) {
@@ -120,29 +116,9 @@
 
     machine->enable_graphics = 0;
 
-    address_space = get_system_memory();
+    hex_subsys_create(hms, m_cfg, rev);
 
-    memory_region_init_rom(&hms->cfgtable_rom, NULL, "config_table.rom",
-                           sizeof(m_cfg->cfgtable), &error_fatal);
-    memory_region_add_subregion(address_space, m_cfg->cfgbase,
-                                &hms->cfgtable_rom);
-
-    memory_region_init_ram(&hms->ram, NULL, "ddr.ram",
-                           machine->ram_size, &error_fatal);
-    memory_region_add_subregion(address_space, 0x0, &hms->ram);
-
-    glob_regs_dev = qdev_new(TYPE_HEXAGON_GLOBALREG);
-    object_property_add_child(OBJECT(machine), "global-regs",
-                              OBJECT(glob_regs_dev));
-    qdev_prop_set_uint64(glob_regs_dev, "config-table-addr", m_cfg->cfgbase);
-    qdev_prop_set_uint32(glob_regs_dev, "dsp-rev", rev);
-    sysbus_realize_and_unref(SYS_BUS_DEVICE(glob_regs_dev), &error_fatal);
-
-    tlb_dev = qdev_new(TYPE_HEXAGON_TLB);
-    object_property_add_child(OBJECT(machine), "tlb", OBJECT(tlb_dev));
-    qdev_prop_set_uint32(tlb_dev, "num-entries",
-                         m_cfg->cfgtable.jtlb_size_entries);
-    sysbus_realize_and_unref(SYS_BUS_DEVICE(tlb_dev), &error_fatal);
+    g_autofree HexagonCPU **cpus = g_new(HexagonCPU *, machine->smp.cpus);
 
     for (int i = 0; i < machine->smp.cpus; i++) {
         HexagonCPU *cpu = HEXAGON_CPU(object_new(machine->cpu_type));
@@ -156,16 +132,15 @@
         if (i == 0) {
             hexagon_init_bootstrap(dms, cpu);
         }
-        object_property_set_link(OBJECT(cpu), "global-regs",
-                                 OBJECT(glob_regs_dev), &error_fatal);
-        object_property_set_link(OBJECT(cpu), "tlb",
-                                 OBJECT(tlb_dev), &error_fatal);
-        qdev_realize_and_unref(DEVICE(cpu), NULL, &error_fatal);
+        hex_subsys_add_cpu(hms, DEVICE(cpu));
+        cpus[i] = cpu;
     }
 
-    rom_add_blob_fixed_as("config_table.rom", &m_cfg->cfgtable,
-                          sizeof(m_cfg->cfgtable), m_cfg->cfgbase,
-                          &address_space_memory);
+    hex_subsys_realize_cluster(hms);
+
+    for (int i = 0; i < machine->smp.cpus; i++) {
+        hex_subsys_realize_cpu(hms, DEVICE(cpus[i]), (i == 0));
+    }
 }
 
 static void init_mc(MachineClass *mc)
diff --git a/hw/hexagon/hexagon_globalreg.c b/hw/hexagon/hexagon_globalreg.c
index b5e5913..61621cf 100644
--- a/hw/hexagon/hexagon_globalreg.c
+++ b/hw/hexagon/hexagon_globalreg.c
@@ -11,6 +11,8 @@
 #include "hw/core/qdev-properties.h"
 #include "hw/core/sysbus.h"
 #include "hw/core/resettable.h"
+#include "hw/intc/hex-l2vic.h"
+#include "hw/timer/qct-qtimer.h"
 #include "migration/vmstate.h"
 #include "qom/object.h"
 #include "target/hexagon/cpu.h"
@@ -135,6 +137,38 @@
     return new_val;
 }
 
+static inline bool is_vid_reg(uint32_t reg)
+{
+    return reg == HEX_SREG_VID || reg == HEX_SREG_VID1;
+}
+
+static inline bool is_timer_reg(uint32_t reg)
+{
+    return reg == HEX_SREG_TIMERLO || reg == HEX_SREG_TIMERHI;
+}
+
+static uint32_t get_reg_value(HexagonGlobalRegState *s, uint32_t reg)
+{
+    if (is_vid_reg(reg)) {
+        return l2vic_read_vid(s->l2vic, reg == HEX_SREG_VID ? 0 : 1);
+    }
+    if (is_timer_reg(reg)) {
+        return reg == HEX_SREG_TIMERLO ?
+                qct_qtimer_get_timer_lo(s->qtimer) :
+                qct_qtimer_get_timer_hi(s->qtimer);
+    }
+    return s->regs[reg];
+}
+
+static void set_reg_value(HexagonGlobalRegState *s, uint32_t reg,
+                          uint32_t value)
+{
+    s->regs[reg] = value;
+    if (is_vid_reg(reg)) {
+        l2vic_update_vid(s->l2vic, reg == HEX_SREG_VID ? 0 : 1, value);
+    }
+}
+
 uint32_t hexagon_globalreg_read(HexagonGlobalRegState *s, uint32_t reg,
                                 uint32_t htid)
 {
@@ -146,7 +180,7 @@
     g_assert(reg < NUM_SREGS);
     g_assert(reg >= HEX_SREG_GLB_START);
 
-    value = s->regs[reg];
+    value = get_reg_value(s, reg);
 
     trace_hexagon_globalreg_read(htid, get_sreg_name(reg), value);
     return value;
@@ -160,7 +194,7 @@
     }
     g_assert(reg < NUM_SREGS);
     g_assert(reg >= HEX_SREG_GLB_START);
-    s->regs[reg] = value;
+    set_reg_value(s, reg, value);
     trace_hexagon_globalreg_write(htid, get_sreg_name(reg), value);
 }
 
@@ -168,6 +202,7 @@
                                         uint32_t value)
 {
     uint32_t reg_mask;
+    uint32_t cur_val;
 
     if (!s) {
         return value;
@@ -175,9 +210,10 @@
     g_assert(reg < NUM_SREGS);
     g_assert(reg >= HEX_SREG_GLB_START);
     reg_mask = global_sreg_immut_masks[reg];
+    cur_val = get_reg_value(s, reg);
     return reg_mask == IMMUTABLE ?
-            s->regs[reg] :
-            apply_write_mask(value, s->regs[reg], reg_mask);
+            cur_val :
+            apply_write_mask(value, cur_val, reg_mask);
 }
 
 void hexagon_globalreg_write_masked(HexagonGlobalRegState *s, uint32_t reg,
@@ -186,7 +222,7 @@
     if (!s) {
         return;
     }
-    s->regs[reg] = hexagon_globalreg_masked_value(s, reg, value);
+    set_reg_value(s, reg, hexagon_globalreg_masked_value(s, reg, value));
 }
 
 uint64_t hexagon_globalreg_get_pcycle_base(HexagonGlobalRegState *s)
@@ -256,6 +292,20 @@
     do_hexagon_globalreg_reset(s);
 }
 
+static void hexagon_globalreg_realize(DeviceState *dev, Error **errp)
+{
+    HexagonGlobalRegState *s = HEXAGON_GLOBALREG(dev);
+
+    if (!s->l2vic) {
+        error_setg(errp, "hexagon_globalreg: 'l2vic' link property not set");
+        return;
+    }
+    if (!s->qtimer) {
+        error_setg(errp, "hexagon_globalreg: 'qtimer' link property not set");
+        return;
+    }
+}
+
 static const VMStateDescription vmstate_hexagon_globalreg = {
     .name = "hexagon_globalreg",
     .version_id = 1,
@@ -275,6 +325,10 @@
 };
 
 static const Property hexagon_globalreg_properties[] = {
+    DEFINE_PROP_LINK("l2vic", HexagonGlobalRegState, l2vic,
+                     TYPE_HEX_L2VIC_INTERFACE, HexL2VicInterface *),
+    DEFINE_PROP_LINK("qtimer", HexagonGlobalRegState, qtimer,
+                     TYPE_QCT_QTIMER_INTERFACE, QctQtimerInterface *),
     DEFINE_PROP_UINT32("boot-evb", HexagonGlobalRegState, boot_evb, 0x0),
     DEFINE_PROP_UINT64("config-table-addr", HexagonGlobalRegState,
                        config_table_addr, 0xffffffffULL),
@@ -295,6 +349,7 @@
     ResettableClass *rc = RESETTABLE_CLASS(klass);
 
     rc->phases.hold = hexagon_globalreg_reset_hold;
+    dc->realize = hexagon_globalreg_realize;
     dc->vmsd = &vmstate_hexagon_globalreg;
     dc->user_creatable = false;
     device_class_set_props(dc, hexagon_globalreg_properties);
diff --git a/hw/hexagon/meson.build b/hw/hexagon/meson.build
index bade3a3..720a5d54 100644
--- a/hw/hexagon/meson.build
+++ b/hw/hexagon/meson.build
@@ -1,6 +1,7 @@
 hexagon_ss = ss.source_set()
 hexagon_ss.add(files('hexagon_tlb.c'))
 hexagon_ss.add(files('hexagon_globalreg.c'))
+hexagon_ss.add(when: 'CONFIG_HEX_DSP', if_true: files('hex-subsys.c'))
 hexagon_ss.add(when: 'CONFIG_HEX_DSP', if_true: files('hexagon_dsp.c'))
 hexagon_ss.add(when: 'CONFIG_HEX_VIRT', if_true: files('virt.c'))
 
diff --git a/hw/hexagon/virt.c b/hw/hexagon/virt.c
index b750472..64d8366 100644
--- a/hw/hexagon/virt.c
+++ b/hw/hexagon/virt.c
@@ -13,8 +13,7 @@
 #include "hw/core/clock.h"
 #include "hw/core/sysbus-fdt.h"
 #include "hw/hexagon/hexagon.h"
-#include "hw/hexagon/hexagon_globalreg.h"
-#include "hw/hexagon/hexagon_tlb.h"
+#include "hw/hexagon/hex-subsys.h"
 #include "hw/core/loader.h"
 #include "hw/core/qdev-properties.h"
 #include "hw/core/qdev-clock.h"
@@ -31,11 +30,20 @@
 
 enum {
     VIRT_UART0,
+    VIRT_MMIO,
     VIRT_FDT,
 };
 
+/*
+ * Virtio IRQs run from VIRTIO_IRQ_BASE to
+ * VIRTIO_IRQ_BASE + VIRTIO_DEV_COUNT - 1
+ */
+static const int VIRTIO_IRQ_BASE = 16;
+static const int VIRT_UART0_IRQ = 15;
+
 static const MemMapEntry base_memmap[] = {
     [VIRT_UART0] = { 0x10000000, 0x00000200 },
+    [VIRT_MMIO] = { 0x11000000, 0x00001000 },
     [VIRT_FDT] = { 0x99800000, 0x00400000 },
 };
 
@@ -68,17 +76,36 @@
     qemu_fdt_setprop(fdt, "/chosen", "rng-seed", rng_seed, sizeof(rng_seed));
 }
 
+static int32_t fdt_add_l2vic(HexagonVirtMachineState *vms,
+                             const struct hexagon_machine_config *m_cfg)
+{
+    MachineState *ms = MACHINE(vms);
+    int32_t l2vic_phandle = qemu_fdt_alloc_phandle(ms->fdt);
+    char *nodename = g_strdup_printf("/soc/interrupt-controller@%x",
+                                     m_cfg->l2vic_base);
+    const char compat[] = "qcom,h2-pic\0hvm-pic";
+
+    qemu_fdt_setprop_cell(ms->fdt, "/soc", "interrupt-parent", l2vic_phandle);
+
+    qemu_fdt_add_subnode(ms->fdt, nodename);
+    qemu_fdt_setprop_cell(ms->fdt, nodename, "#address-cells", 0x0);
+    qemu_fdt_setprop_cell(ms->fdt, nodename, "#interrupt-cells", 0x1);
+    qemu_fdt_setprop(ms->fdt, nodename, "compatible", compat, sizeof(compat));
+    qemu_fdt_setprop_cells(ms->fdt, nodename, "reg", 0,
+                           m_cfg->l2vic_base, m_cfg->l2vic_size);
+    qemu_fdt_setprop(ms->fdt, nodename, "interrupt-controller", NULL, 0);
+    qemu_fdt_setprop_cell(ms->fdt, nodename, "phandle", l2vic_phandle);
+
+    g_free(nodename);
+    return l2vic_phandle;
+}
+
 static void fdt_add_hvx(HexagonVirtMachineState *vms,
                         const struct hexagon_machine_config *m_cfg)
 {
     const MachineState *ms = MACHINE(vms);
     uint32_t vtcm_size_bytes = m_cfg->cfgtable.vtcm_size_kb * 1024;
     if (vtcm_size_bytes > 0) {
-        memory_region_init_ram(&vms->vtcm, NULL, "vtcm.ram", vtcm_size_bytes,
-                               &error_fatal);
-        memory_region_add_subregion(vms->sys, m_cfg->cfgtable.vtcm_base << 16,
-                                    &vms->vtcm);
-
         qemu_fdt_add_subnode(ms->fdt, "/soc/vtcm");
         qemu_fdt_setprop_string(ms->fdt, "/soc/vtcm", "compatible",
                                 "qcom,hexagon_vtcm");
@@ -117,7 +144,7 @@
 }
 
 static void fdt_add_uart(const HexagonVirtMachineState *vms, int uart,
-                         int32_t clk_phandle)
+                         int32_t clk_phandle, int32_t l2vic_phandle)
 {
     char *nodename;
     hwaddr base = base_memmap[uart].base;
@@ -135,6 +162,8 @@
     qdev_connect_clock_in(dev, "clk", vms->apb_clk);
     sysbus_realize_and_unref(s, &error_fatal);
     sysbus_mmio_map(s, 0, base);
+    sysbus_connect_irq(s, 0,
+                       qdev_get_gpio_in(vms->parent_obj.l2vic, VIRT_UART0_IRQ));
 
     nodename = g_strdup_printf("/pl011@%" PRIx64, base);
     qemu_fdt_add_subnode(ms->fdt, nodename);
@@ -142,6 +171,9 @@
     /* Note that we can't use setprop_string because of the embedded NUL */
     qemu_fdt_setprop(ms->fdt, nodename, "compatible", compat, sizeof(compat));
     qemu_fdt_setprop_cells(ms->fdt, nodename, "reg", 0, base, size);
+    qemu_fdt_setprop_cell(ms->fdt, nodename, "interrupts", VIRT_UART0_IRQ);
+    qemu_fdt_setprop_cell(ms->fdt, nodename, "interrupt-parent",
+                          l2vic_phandle);
     qemu_fdt_setprop_cells(ms->fdt, nodename, "clocks", clk_phandle,
                            clk_phandle);
     qemu_fdt_setprop(ms->fdt, nodename, "clock-names", clocknames,
@@ -173,7 +205,38 @@
     }
 }
 
+static void create_virtio_devices(HexagonVirtMachineState *vms,
+                                  int32_t l2vic_phandle)
+{
+    MachineState *ms = MACHINE(vms);
+    hwaddr size = base_memmap[VIRT_MMIO].size;
 
+    for (int i = 0; i < VIRTIO_DEV_COUNT; i++) {
+        int irq = VIRTIO_IRQ_BASE + i;
+        hwaddr base = base_memmap[VIRT_MMIO].base + i * size;
+        char *nodename = g_strdup_printf("/soc/virtio_mmio@%" PRIx64, base);
+        DeviceState *dev = qdev_new("virtio-mmio");
+        SysBusDevice *s = SYS_BUS_DEVICE(dev);
+
+        object_property_add_child(OBJECT(MACHINE(vms)), "virtio-mmio[*]",
+                                  OBJECT(dev));
+        sysbus_realize_and_unref(s, &error_fatal);
+        sysbus_mmio_map(s, 0, base);
+        sysbus_connect_irq(s, 0,
+                           qdev_get_gpio_in(vms->parent_obj.l2vic, irq));
+        vms->virtio_mmio[i] = dev;
+
+        qemu_fdt_add_subnode(ms->fdt, nodename);
+        qemu_fdt_setprop_string(ms->fdt, nodename, "compatible",
+                                "virtio,mmio");
+        qemu_fdt_setprop_cells(ms->fdt, nodename, "reg", 0, base, size);
+        qemu_fdt_setprop_cell(ms->fdt, nodename, "interrupts", irq);
+        qemu_fdt_setprop_cell(ms->fdt, nodename, "interrupt-parent",
+                              l2vic_phandle);
+
+        g_free(nodename);
+    }
+}
 
 void hexagon_load_fdt(const HexagonVirtMachineState *vms)
 {
@@ -230,10 +293,8 @@
 {
     HexagonVirtMachineState *vms = HEXAGON_VIRT_MACHINE(ms);
     const struct hexagon_machine_config *m_cfg = &v68n_1024;
-    DeviceState *gsregs_dev;
-    DeviceState *tlb_dev;
-    DeviceState *cpu0;
     int32_t clk_phandle;
+    int32_t l2vic_phandle;
 
     create_fdt(vms);
     qemu_fdt_setprop_string(ms->fdt, "/chosen", "bootargs", ms->kernel_cmdline);
@@ -244,9 +305,7 @@
     vms->apb_clk = clock_new(OBJECT(ms), "apb-pclk");
     clock_set_hz(vms->apb_clk, 24000000);
 
-    memory_region_init_ram(&vms->parent_obj.ram, NULL, "ddr.ram",
-                           ms->ram_size, &error_fatal);
-    memory_region_add_subregion(vms->sys, 0x0, &vms->parent_obj.ram);
+    hex_subsys_create(&vms->parent_obj, m_cfg, v68_rev);
 
     if (m_cfg->l2tcm_size) {
         memory_region_init_ram(&vms->tcm, NULL, "tcm.ram", m_cfg->l2tcm_size,
@@ -255,56 +314,41 @@
                                     &vms->tcm);
     }
 
-    memory_region_init_rom(&vms->parent_obj.cfgtable_rom, NULL,
-                           "config_table.rom", sizeof(m_cfg->cfgtable),
-                           &error_fatal);
-    memory_region_add_subregion(vms->sys, m_cfg->cfgbase,
-                                &vms->parent_obj.cfgtable_rom);
     fdt_add_hvx(vms, m_cfg);
 
-    gsregs_dev = qdev_new(TYPE_HEXAGON_GLOBALREG);
-    object_property_add_child(OBJECT(ms), "global-regs", OBJECT(gsregs_dev));
-    qdev_prop_set_uint64(gsregs_dev, "config-table-addr", m_cfg->cfgbase);
-    qdev_prop_set_uint32(gsregs_dev, "dsp-rev", v68_rev);
-    sysbus_realize_and_unref(SYS_BUS_DEVICE(gsregs_dev), &error_fatal);
+    l2vic_phandle = fdt_add_l2vic(vms, m_cfg);
+    create_virtio_devices(vms, l2vic_phandle);
 
-    tlb_dev = qdev_new(TYPE_HEXAGON_TLB);
-    object_property_add_child(OBJECT(ms), "tlb", OBJECT(tlb_dev));
-    qdev_prop_set_uint32(tlb_dev, "num-entries",
-                         m_cfg->cfgtable.jtlb_size_entries);
-    sysbus_realize_and_unref(SYS_BUS_DEVICE(tlb_dev), &error_fatal);
+    g_autofree HexagonCPU **cpus = g_new(HexagonCPU *, ms->smp.cpus);
 
-    cpu0 = NULL;
     for (int i = 0; i < ms->smp.cpus; i++) {
         HexagonCPU *cpu = HEXAGON_CPU(object_new(ms->cpu_type));
         qemu_register_reset(do_cpu_reset, cpu);
 
         if (i == 0) {
-            cpu0 = DEVICE(cpu);
             if (ms->kernel_filename) {
                 uint64_t entry = load_kernel(vms);
-                qdev_prop_set_uint32(cpu0, "exec-start-addr", entry);
+                qdev_prop_set_uint32(DEVICE(cpu), "exec-start-addr", entry);
             } else if (ms->firmware) {
                 uint64_t entry = load_bios(vms);
-                qdev_prop_set_uint32(cpu0, "exec-start-addr", entry);
+                qdev_prop_set_uint32(DEVICE(cpu), "exec-start-addr", entry);
             }
         }
         qdev_prop_set_uint32(DEVICE(cpu), "htid", i);
         qdev_prop_set_bit(DEVICE(cpu), "start-powered-off", (i != 0));
-        object_property_set_link(OBJECT(cpu), "global-regs",
-                                 OBJECT(gsregs_dev), &error_fatal);
-        object_property_set_link(OBJECT(cpu), "tlb",
-                                 OBJECT(tlb_dev), &error_fatal);
-
-        qdev_realize_and_unref(DEVICE(cpu), NULL, &error_fatal);
+        hex_subsys_add_cpu(&vms->parent_obj, DEVICE(cpu));
+        cpus[i] = cpu;
     }
+
+    hex_subsys_realize_cluster(&vms->parent_obj);
+
+    for (int i = 0; i < ms->smp.cpus; i++) {
+        hex_subsys_realize_cpu(&vms->parent_obj, DEVICE(cpus[i]), (i == 0));
+    }
+
     fdt_add_cpu_nodes(vms);
     clk_phandle = fdt_add_clocks(vms);
-    fdt_add_uart(vms, VIRT_UART0, clk_phandle);
-
-    rom_add_blob_fixed_as("config_table.rom", &m_cfg->cfgtable,
-                          sizeof(m_cfg->cfgtable), m_cfg->cfgbase,
-                          &address_space_memory);
+    fdt_add_uart(vms, VIRT_UART0, clk_phandle, l2vic_phandle);
 
     hexagon_load_fdt(vms);
 }
diff --git a/hw/i2c/aspeed_i2c.c b/hw/i2c/aspeed_i2c.c
index 27afcae..68bdcd0 100644
--- a/hw/i2c/aspeed_i2c.c
+++ b/hw/i2c/aspeed_i2c.c
@@ -159,6 +159,7 @@
     case A_I2CS_INTR_CTRL:
     case A_I2CS_DMA_LEN_STS:
     case A_I2CS_INTR_STS:
+    case A_I2CC_VERSION_CTRL:
         value = bus->regs[offset / sizeof(*bus->regs)];
         break;
     case A_I2CC_DMA_ADDR:
@@ -295,6 +296,65 @@
     return 0;
 }
 
+/*
+ * In AST2700 buffer mode the master DMA command bits (TX/RX_DMA_EN) and the
+ * DMA length registers are reused, but data is moved through the controller
+ * internal SRAM pool at the offset programmed in I2CM_DMA_TX/RX_ADDR instead
+ * of DRAM. FUNC_CFG_DMA_EN selects between the two (set = DRAM).
+ */
+static bool aspeed_i2c_bus_dma_to_pool(AspeedI2CBus *bus)
+{
+    return aspeed_i2c_is_new_mode(bus->controller) &&
+           !ARRAY_FIELD_EX32(bus->regs, I2CC_VERSION_CTRL, FUNC_CFG_DMA_EN);
+}
+
+static int aspeed_i2c_bus_send_dma_pool(AspeedI2CBus *bus)
+{
+    AspeedI2CClass *aic = ASPEED_I2C_GET_CLASS(bus->controller);
+    uint32_t reg_dma_len = aspeed_i2c_bus_dma_len_offset(bus);
+    uint32_t reg_cmd = aspeed_i2c_bus_cmd_offset(bus);
+    uint32_t offset = bus->regs[R_I2CM_DMA_TX_ADDR];
+    uint8_t *pool_base = aic->bus_pool_base(bus);
+    int ret = -1;
+    int i;
+
+    ARRAY_FIELD_DP32(bus->regs, I2CM_DMA_LEN_STS, TX_LEN, 0);
+    for (i = 0; bus->regs[reg_dma_len] &&
+                offset + i < ASPEED_I2C_BUS_POOL_SIZE; i++) {
+        trace_aspeed_i2c_bus_send("BUFF", i + 1, bus->regs[reg_dma_len],
+                                  pool_base[offset + i]);
+        ret = i2c_send(bus->bus, pool_base[offset + i]);
+        bus->regs[reg_dma_len]--;
+        ARRAY_FIELD_DP32(bus->regs, I2CM_DMA_LEN_STS, TX_LEN, i + 1);
+        if (ret) {
+            break;
+        }
+    }
+    SHARED_ARRAY_FIELD_DP32(bus->regs, reg_cmd, TX_DMA_EN, 0);
+    return ret;
+}
+
+static void aspeed_i2c_bus_recv_dma_pool(AspeedI2CBus *bus)
+{
+    AspeedI2CClass *aic = ASPEED_I2C_GET_CLASS(bus->controller);
+    uint32_t reg_dma_len = aspeed_i2c_bus_dma_len_offset(bus);
+    uint32_t reg_cmd = aspeed_i2c_bus_cmd_offset(bus);
+    uint32_t offset = bus->regs[R_I2CM_DMA_RX_ADDR];
+    uint8_t *pool_base = aic->bus_pool_base(bus);
+    int i;
+
+    ARRAY_FIELD_DP32(bus->regs, I2CM_DMA_LEN_STS, RX_LEN, 0);
+    for (i = 0; bus->regs[reg_dma_len] &&
+                offset + i < ASPEED_I2C_BUS_POOL_SIZE; i++) {
+        pool_base[offset + i] = i2c_recv(bus->bus);
+        trace_aspeed_i2c_bus_recv("BUFF", i + 1, bus->regs[reg_dma_len],
+                                  pool_base[offset + i]);
+        bus->regs[reg_dma_len]--;
+        ARRAY_FIELD_DP32(bus->regs, I2CM_DMA_LEN_STS, RX_LEN, i + 1);
+    }
+    SHARED_ARRAY_FIELD_DP32(bus->regs, reg_cmd, RX_DMA_EN, 0);
+}
+
 static int aspeed_i2c_bus_send(AspeedI2CBus *bus)
 {
     AspeedI2CClass *aic = ASPEED_I2C_GET_CLASS(bus->controller);
@@ -320,6 +380,10 @@
         }
         SHARED_ARRAY_FIELD_DP32(bus->regs, reg_cmd, TX_BUFF_EN, 0);
     } else if (SHARED_ARRAY_FIELD_EX32(bus->regs, reg_cmd, TX_DMA_EN)) {
+        /* In buffer mode the DMA moves data through the pool, not DRAM */
+        if (aspeed_i2c_bus_dma_to_pool(bus)) {
+            return aspeed_i2c_bus_send_dma_pool(bus);
+        }
         /* In new mode, clear how many bytes we TXed */
         if (aspeed_i2c_is_new_mode(bus->controller)) {
             ARRAY_FIELD_DP32(bus->regs, I2CM_DMA_LEN_STS, TX_LEN, 0);
@@ -385,6 +449,11 @@
         SHARED_ARRAY_FIELD_DP32(bus->regs, reg_pool_ctrl, RX_COUNT, i & 0xff);
         SHARED_ARRAY_FIELD_DP32(bus->regs, reg_cmd, RX_BUFF_EN, 0);
     } else if (SHARED_ARRAY_FIELD_EX32(bus->regs, reg_cmd, RX_DMA_EN)) {
+        /* In buffer mode the DMA moves data through the pool, not DRAM */
+        if (aspeed_i2c_bus_dma_to_pool(bus)) {
+            aspeed_i2c_bus_recv_dma_pool(bus);
+            return;
+        }
         /* In new mode, clear how many bytes we RXed */
         if (aspeed_i2c_is_new_mode(bus->controller)) {
             ARRAY_FIELD_DP32(bus->regs, I2CM_DMA_LEN_STS, RX_LEN, 0);
@@ -854,6 +923,9 @@
                                                       I2CS_DMA_RX_ADDR_HI,
                                                       ADDR_HI);
         break;
+    case A_I2CC_VERSION_CTRL:
+        bus->regs[R_I2CC_VERSION_CTRL] = value;
+        break;
     default:
         qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n",
                       __func__, offset);
@@ -1497,6 +1569,13 @@
     memset(s->regs, 0, sizeof(s->regs));
     s->pending_intr_sts = 0;
     i2c_end_transfer(s->bus);
+    /*
+     * I2CC_VERSION_CTRL resets to all-ones. FUNC_CFG_DMA_EN is therefore set,
+     * so master DMA targets DRAM unless the guest clears it to select buffer
+     * mode. Guests unaware of buffer mode never touch this register and keep
+     * doing DRAM DMA.
+     */
+    s->regs[R_I2CC_VERSION_CTRL] = 0xffffffff;
 }
 
 static void aspeed_i2c_bus_realize(DeviceState *dev, Error **errp)
diff --git a/hw/i2c/bcm2835_i2c.c b/hw/i2c/bcm2835_i2c.c
index 34de1f3..5f60930 100644
--- a/hw/i2c/bcm2835_i2c.c
+++ b/hw/i2c/bcm2835_i2c.c
@@ -222,7 +222,7 @@
     s->bus = i2c_init_bus(dev, NULL);
 
     memory_region_init_io(&s->iomem, OBJECT(dev), &bcm2835_i2c_ops, s,
-                          TYPE_BCM2835_I2C, 0x24);
+                          TYPE_BCM2835_I2C, 0x20);
     sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem);
     sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq);
 }
diff --git a/hw/i386/pc.c b/hw/i386/pc.c
index f064aa2..e9e4fc2 100644
--- a/hw/i386/pc.c
+++ b/hw/i386/pc.c
@@ -74,6 +74,9 @@
 #include "hw/xen/xen-bus.h"
 #endif
 
+GlobalProperty pc_compat_11_1[] = {};
+const size_t pc_compat_11_1_len = G_N_ELEMENTS(pc_compat_11_1);
+
 GlobalProperty pc_compat_11_0[] = {};
 const size_t pc_compat_11_0_len = G_N_ELEMENTS(pc_compat_11_0);
 
diff --git a/hw/i386/pc_piix.c b/hw/i386/pc_piix.c
index 82457bd..223a19c 100644
--- a/hw/i386/pc_piix.c
+++ b/hw/i386/pc_piix.c
@@ -428,12 +428,21 @@
                      pc_piix_compat_defaults, pc_piix_compat_defaults_len);
 }
 
-static void pc_i440fx_machine_11_1_options(MachineClass *m)
+static void pc_i440fx_machine_11_2_options(MachineClass *m)
 {
     pc_i440fx_machine_options(m);
 }
 
-DEFINE_I440FX_MACHINE_AS_LATEST(11, 1);
+DEFINE_I440FX_MACHINE_AS_LATEST(11, 2);
+
+static void pc_i440fx_machine_11_1_options(MachineClass *m)
+{
+    pc_i440fx_machine_11_2_options(m);
+    compat_props_add(m->compat_props, hw_compat_11_1, hw_compat_11_1_len);
+    compat_props_add(m->compat_props, pc_compat_11_1, pc_compat_11_1_len);
+}
+
+DEFINE_I440FX_MACHINE(11, 1);
 
 static void pc_i440fx_machine_11_0_options(MachineClass *m)
 {
diff --git a/hw/i386/pc_q35.c b/hw/i386/pc_q35.c
index 6c1e4ef..94cd711 100644
--- a/hw/i386/pc_q35.c
+++ b/hw/i386/pc_q35.c
@@ -383,12 +383,21 @@
                      pc_q35_compat_defaults, pc_q35_compat_defaults_len);
 }
 
-static void pc_q35_machine_11_1_options(MachineClass *m)
+static void pc_q35_machine_11_2_options(MachineClass *m)
 {
     pc_q35_machine_options(m);
 }
 
-DEFINE_Q35_MACHINE_AS_LATEST(11, 1);
+DEFINE_Q35_MACHINE_AS_LATEST(11, 2);
+
+static void pc_q35_machine_11_1_options(MachineClass *m)
+{
+    pc_q35_machine_11_2_options(m);
+    compat_props_add(m->compat_props, hw_compat_11_1, hw_compat_11_1_len);
+    compat_props_add(m->compat_props, pc_compat_11_1, pc_compat_11_1_len);
+}
+
+DEFINE_Q35_MACHINE(11, 1);
 
 static void pc_q35_machine_11_0_options(MachineClass *m)
 {
diff --git a/hw/intc/Kconfig b/hw/intc/Kconfig
index 636d00b..097de4e 100644
--- a/hw/intc/Kconfig
+++ b/hw/intc/Kconfig
@@ -8,6 +8,9 @@
 config PL190
     bool
 
+config HEX_L2VIC
+    bool
+
 config IOAPIC
     bool
     select I8259
diff --git a/hw/intc/hex-l2vic.c b/hw/intc/hex-l2vic.c
new file mode 100644
index 0000000..f07ec85
--- /dev/null
+++ b/hw/intc/hex-l2vic.c
@@ -0,0 +1,556 @@
+/*
+ * QEMU L2VIC Interrupt Controller
+ *
+ * Arm PrimeCell PL190 Vector Interrupt Controller was used as a reference.
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/core/irq.h"
+#include "hw/core/sysbus.h"
+#include "migration/vmstate.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qemu/bitmap.h"
+#include "qemu/bitops.h"
+#include "hw/intc/hex-l2vic.h"
+#include "trace.h"
+
+#define L2VIC_VID_GRP_0 0x0 /* Read */
+#define L2VIC_VID_GRP_1 0x4 /* Read */
+#define L2VIC_VID_GRP_2 0x8 /* Read */
+#define L2VIC_VID_GRP_3 0xC /* Read */
+#define L2VIC_INT_ENABLEn 0x100 /* Read/Write */
+#define L2VIC_INT_ENABLE_CLEARn 0x180 /* Write */
+#define L2VIC_INT_ENABLE_SETn 0x200 /* Write */
+#define L2VIC_INT_TYPEn 0x280 /* Read/Write */
+#define L2VIC_INT_STATUSn 0x380 /* Read */
+#define L2VIC_INT_CLEARn 0x400 /* Write */
+#define L2VIC_SOFT_INTn 0x480 /* Write */
+#define L2VIC_INT_PENDINGn 0x500 /* Read */
+#define L2VIC_INT_GRPn_0 0x600 /* Read/Write */
+#define L2VIC_INT_GRPn_1 0x680 /* Read/Write */
+#define L2VIC_INT_GRPn_2 0x700 /* Read/Write */
+#define L2VIC_INT_GRPn_3 0x780 /* Read/Write */
+
+#define L2VIC_INTERRUPT_MAX 1024
+/*
+ * Note about l2vic groups:
+ * Each interrupt to L2VIC can be configured to associate with one of
+ * four groups.
+ * Group 0 interrupts go to IRQ2 via VID 0 (SSR: 0xC2, the default)
+ * Group 1 interrupts go to IRQ3 via VID 1 (SSR: 0xC3)
+ * Group 2 interrupts go to IRQ4 via VID 2 (SSR: 0xC4)
+ * Group 3 interrupts go to IRQ5 via VID 3 (SSR: 0xC5)
+ */
+
+static void bitmap32_write_word(uint32_t *bitmap, int word_offset, uint32_t val)
+{
+    bitmap[word_offset] = val;
+}
+
+static void bitmap32_clear_word(uint32_t *bitmap, int word_offset,
+                                uint32_t mask)
+{
+    bitmap[word_offset] &= ~mask;
+}
+
+static void bitmap32_set_word(uint32_t *bitmap, int word_offset, uint32_t mask)
+{
+    bitmap[word_offset] |= mask;
+}
+
+static uint32_t bitmap32_read_word(uint32_t *bitmap, int word_offset)
+{
+    return bitmap[word_offset];
+}
+
+OBJECT_DECLARE_SIMPLE_TYPE(HexL2VICState, HEX_L2VIC)
+
+#define SLICE_MAX (L2VIC_INTERRUPT_MAX / 32)
+#define L2VIC_REG_RANGE_SIZE 0x80
+
+typedef struct HexL2VICState {
+    SysBusDevice parent_obj;
+
+    MemoryRegion iomem;
+    MemoryRegion fast_iomem;
+    /*
+     * vid_group[i] is readable at L2VIC_VID_GRP_i (offset i*4): the irq
+     * last delivered through VID group i, 0-1023 so only 10 bits are used.
+     */
+    uint32_t vid_group[4];
+    /*
+     * Last irq delivered on any VID group; not specific to group 0.
+     * Used by the ciad path to clear the most-recently-delivered
+     * interrupt from int_status.
+     */
+    uint32_t vid;
+    DECLARE_BITMAP32(int_enable, L2VIC_INTERRUPT_MAX);
+    /* Asserted interrupts awaiting delivery once no VID is active */
+    DECLARE_BITMAP32(int_pending, L2VIC_INTERRUPT_MAX);
+    /* Which enabled interrupt is active */
+    DECLARE_BITMAP32(int_status, L2VIC_INTERRUPT_MAX);
+    /* Edge or Level interrupt */
+    DECLARE_BITMAP32(int_type, L2VIC_INTERRUPT_MAX);
+    DECLARE_BITMAP32(int_group_n[4], L2VIC_INTERRUPT_MAX);
+    qemu_irq irq[8];
+} HexL2VICState;
+
+typedef enum {
+    L2VIC_OP_WRITE,
+    L2VIC_OP_CLEAR,
+    L2VIC_OP_SET,
+    L2VIC_OP_NONE,
+} L2VicWriteOp;
+
+typedef struct {
+    hwaddr base;
+    size_t state_offset;
+    L2VicWriteOp write_op;
+    bool write_only;
+} L2VicRegRange;
+
+static const L2VicRegRange l2vic_reg_ranges[] = {
+    { L2VIC_INT_ENABLEn, offsetof(HexL2VICState, int_enable),
+      L2VIC_OP_WRITE, false },
+    { L2VIC_INT_ENABLE_CLEARn, offsetof(HexL2VICState, int_enable),
+      L2VIC_OP_CLEAR, true },
+    { L2VIC_INT_ENABLE_SETn, offsetof(HexL2VICState, int_enable),
+      L2VIC_OP_SET, true },
+    { L2VIC_INT_TYPEn, offsetof(HexL2VICState, int_type),
+      L2VIC_OP_WRITE, false },
+    { L2VIC_INT_STATUSn, offsetof(HexL2VICState, int_status),
+      L2VIC_OP_NONE, false },
+    { L2VIC_INT_CLEARn, offsetof(HexL2VICState, int_status),
+      L2VIC_OP_CLEAR, true },
+    { L2VIC_SOFT_INTn, offsetof(HexL2VICState, int_pending),
+      L2VIC_OP_NONE, true },
+    { L2VIC_INT_PENDINGn, offsetof(HexL2VICState, int_pending),
+      L2VIC_OP_WRITE, false },
+    { L2VIC_INT_GRPn_0, offsetof(HexL2VICState, int_group_n[0]),
+      L2VIC_OP_WRITE, false },
+    { L2VIC_INT_GRPn_1, offsetof(HexL2VICState, int_group_n[1]),
+      L2VIC_OP_WRITE, false },
+    { L2VIC_INT_GRPn_2, offsetof(HexL2VICState, int_group_n[2]),
+      L2VIC_OP_WRITE, false },
+    { L2VIC_INT_GRPn_3, offsetof(HexL2VICState, int_group_n[3]),
+      L2VIC_OP_WRITE, false },
+};
+
+static uint32_t *l2vic_state_bitmap(HexL2VICState *s, size_t state_offset)
+{
+    return (uint32_t *)((char *)s + state_offset);
+}
+
+static bool l2vic_reg_read_range(HexL2VICState *s, hwaddr offset,
+                                 uint64_t *value)
+{
+    int i;
+
+    for (i = 0; i < ARRAY_SIZE(l2vic_reg_ranges); i++) {
+        const L2VicRegRange *r = &l2vic_reg_ranges[i];
+
+        if (offset >= r->base &&
+            offset < r->base + L2VIC_REG_RANGE_SIZE) {
+            if (r->write_only) {
+                *value = 0;
+            } else {
+                uint32_t *bitmap = l2vic_state_bitmap(s, r->state_offset);
+                *value = bitmap32_read_word(bitmap,
+                                            (offset - r->base) >> 2);
+            }
+            return true;
+        }
+    }
+    return false;
+}
+
+static bool l2vic_reg_write_range(HexL2VICState *s, hwaddr offset,
+                                  uint32_t val)
+{
+    int i;
+
+    for (i = 0; i < ARRAY_SIZE(l2vic_reg_ranges); i++) {
+        const L2VicRegRange *r = &l2vic_reg_ranges[i];
+
+        if (offset >= r->base &&
+            offset < r->base + L2VIC_REG_RANGE_SIZE) {
+            uint32_t *bitmap = l2vic_state_bitmap(s, r->state_offset);
+            int word = (offset - r->base) >> 2;
+
+            switch (r->write_op) {
+            case L2VIC_OP_WRITE:
+                bitmap32_write_word(bitmap, word, val);
+                break;
+            case L2VIC_OP_CLEAR:
+                bitmap32_clear_word(bitmap, word, val);
+                break;
+            case L2VIC_OP_SET:
+                bitmap32_set_word(bitmap, word, val);
+                break;
+            case L2VIC_OP_NONE:
+                /* Read-only or handled elsewhere; ignore the write. */
+                break;
+            default:
+                g_assert_not_reached();
+            }
+            return true;
+        }
+    }
+    return false;
+}
+
+/*
+ * The four INT_GRPn_* register arrays are interleaved across irqs in
+ * blocks of 8: irq 0-7 live in group_n[0], irq 8-15 in group_n[1], irq
+ * 16-23 in group_n[2], irq 24-31 in group_n[3], irq 32-39 back in
+ * group_n[0], and so on.
+ */
+static uint32_t *get_int_group(HexL2VICState *s, int irq)
+{
+    return s->int_group_n[extract32(irq, 3, 2)];
+}
+
+static int find_slice(int irq)
+{
+    return irq / 32;
+}
+
+static int get_vid(HexL2VICState *s, int irq)
+{
+    uint32_t *group = get_int_group(s, irq);
+    uint32_t slice = group[find_slice(irq)];
+    uint32_t vid;
+    /*
+     * Each irq occupies a 4-bit field: bit 3 is the group-enable bit,
+     * bits 0-2 select the VID group.  Shift down to this irq's field.
+     */
+    uint32_t val = slice >> ((irq & 0x7) * 4);
+
+    if (!(val & 0x8)) {
+        return 0;
+    }
+    vid = val & 0x7;
+    if (vid >= ARRAY_SIZE(s->vid_group)) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "L2VIC: irq %d requests invalid vid group %u\n",
+                      irq, vid);
+        return 0;
+    }
+    return vid;
+}
+
+static inline bool vid_active(HexL2VICState *s)
+{
+    const uint32_t size = L2VIC_INTERRUPT_MAX;
+    const uint32_t active_irq = find_first_bit32(s->int_status, size);
+    return active_irq != size;
+}
+
+static bool l2vic_update(HexL2VICState *s, int irq)
+{
+    bool pending;
+    bool enable;
+
+    if (vid_active(s)) {
+        return true;
+    }
+
+    pending = test_bit32(irq, s->int_pending);
+    enable = test_bit32(irq, s->int_enable);
+    if (pending && enable) {
+        int vid = get_vid(s, irq);
+        set_bit32(irq, s->int_status);
+        clear_bit32(irq, s->int_pending);
+        /*
+         * Only auto-disable for edge-triggered interrupts (type=1).
+         * Level-triggered interrupts (type=0, the default) keep their
+         * enable bit set across deliveries -- the firmware enables once
+         * and expects the interrupt to remain enabled.
+         */
+        if (test_bit32(irq, s->int_type)) {
+            clear_bit32(irq, s->int_enable);
+        }
+        s->vid = irq;
+        s->vid_group[vid] = irq;
+
+        qemu_irq_pulse(s->irq[vid + 2]);
+        trace_hex_l2vic_delivered(irq, vid);
+        return true;
+    }
+    return false;
+}
+
+static void l2vic_update_all(HexL2VICState *s)
+{
+    for (int i = 0; i < L2VIC_INTERRUPT_MAX; i++) {
+        if (l2vic_update(s, i)) {
+            /* once vid is active, no-one else can set it until ciad */
+            return;
+        }
+    }
+}
+
+static void l2vic_set_irq(void *opaque, int irq, int level)
+{
+    HexL2VICState *s = (HexL2VICState *)opaque;
+
+    if (level) {
+        set_bit32(irq, s->int_pending);
+    }
+    l2vic_update(s, irq);
+}
+
+static void l2vic_write(void *opaque, hwaddr offset, uint64_t val,
+                        unsigned size)
+{
+    HexL2VICState *s = (HexL2VICState *)opaque;
+
+    trace_hex_l2vic_reg_write((unsigned)offset, (uint32_t)val);
+
+    if (!l2vic_reg_write_range(s, offset, val)) {
+        qemu_log_mask(LOG_UNIMP,
+                      "%s: offset 0x%" HWADDR_PRIx " unimplemented\n",
+                      __func__, offset);
+    }
+
+    /* SOFT_INT also sets pending for edge-triggered interrupts */
+    if (offset >= L2VIC_SOFT_INTn &&
+        offset < L2VIC_SOFT_INTn + L2VIC_REG_RANGE_SIZE && val) {
+        int base_irq = ((offset - L2VIC_SOFT_INTn) >> 2) * 32;
+        uint32_t bits = val;
+        int bit;
+
+        while ((bit = ctz32(bits)) < 32) {
+            int irq = base_irq + bit;
+
+            if (test_bit32(irq, s->int_type)) {
+                set_bit32(irq, s->int_pending);
+            }
+            bits &= ~(1u << bit);
+        }
+    }
+
+    l2vic_update_all(s);
+}
+
+static uint64_t l2vic_read(void *opaque, hwaddr offset, unsigned size)
+{
+    uint64_t value;
+    HexL2VICState *s = (HexL2VICState *)opaque;
+
+    if (offset <= L2VIC_VID_GRP_3) {
+        value = s->vid_group[offset >> 2];
+    } else if (!l2vic_reg_read_range(s, offset, &value)) {
+        value = 0;
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "L2VIC: %s: offset 0x%" HWADDR_PRIx "\n", __func__,
+                      offset);
+    }
+
+    trace_hex_l2vic_reg_read((unsigned)offset, (uint32_t)value);
+    return value;
+}
+
+static const MemoryRegionOps l2vic_ops = {
+    .read = l2vic_read,
+    .write = l2vic_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .valid.min_access_size = 4,
+    .valid.max_access_size = 4,
+    .valid.unaligned = false,
+};
+
+#define FASTL2VIC_ENABLE 0x0
+#define FASTL2VIC_DISABLE 0x1
+#define FASTL2VIC_INT 0x2
+
+static void fastl2vic_write(void *opaque, hwaddr offset, uint64_t val,
+                            unsigned size)
+{
+    if (offset == 0) {
+        uint32_t cmd = (val >> 16) & 0x3;
+        uint32_t irq = val & 0x3ff;
+        uint32_t slice = (irq / 32) * 4;
+        val = 1 << (irq % 32);
+
+        if (cmd == FASTL2VIC_ENABLE) {
+            l2vic_write(opaque, L2VIC_INT_ENABLE_SETn + slice, val, size);
+        } else if (cmd == FASTL2VIC_DISABLE) {
+            l2vic_write(opaque, L2VIC_INT_ENABLE_CLEARn + slice, val, size);
+        } else if (cmd == FASTL2VIC_INT) {
+            l2vic_write(opaque, L2VIC_SOFT_INTn + slice, val, size);
+        } else {
+            qemu_log_mask(LOG_GUEST_ERROR,
+                          "%s: invalid write cmd %" PRId32 "\n",
+                          __func__, cmd);
+        }
+        return;
+    }
+    qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid write offset 0x%08" HWADDR_PRIx
+            "\n", __func__, offset);
+}
+
+static uint64_t fastl2vic_read(void *opaque, hwaddr offset, unsigned size)
+{
+    return 0;
+}
+
+static const MemoryRegionOps fastl2vic_ops = {
+    .read = fastl2vic_read,
+    .write = fastl2vic_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .valid.min_access_size = 4,
+    .valid.max_access_size = 4,
+    .valid.unaligned = false,
+};
+
+static uint32_t l2vic_interface_read_vid_impl(HexL2VicInterface *iface,
+                                               uint32_t group)
+{
+    HexL2VICState *s = HEX_L2VIC(iface);
+    uint32_t result = 0;
+
+    if (group == 0) {
+        /* VID register combines vid_group[0] (VID0) and vid_group[1] (VID1) */
+        result = deposit32(result, 0, 16, s->vid_group[0]);
+        result = deposit32(result, 16, 16, s->vid_group[1]);
+    } else if (group == 1) {
+        /* VID1 register combines vid_group[2] (VID2) and vid_group[3] (VID3) */
+        result = deposit32(result, 0, 16, s->vid_group[2]);
+        result = deposit32(result, 16, 16, s->vid_group[3]);
+    }
+    return result;
+}
+
+static void l2vic_interface_update_vid_impl(HexL2VicInterface *iface,
+                                            uint32_t group, uint32_t value)
+{
+    HexL2VICState *s = HEX_L2VIC(iface);
+
+    if (group == 0) {
+        s->vid_group[0] = extract32(value, 0, 16);
+        s->vid_group[1] = extract32(value, 16, 16);
+    } else if (group == 1) {
+        s->vid_group[2] = extract32(value, 0, 16);
+        s->vid_group[3] = extract32(value, 16, 16);
+    }
+
+    l2vic_update_all(s);
+}
+
+static void l2vic_interface_clear_interrupt_impl(HexL2VicInterface *iface)
+{
+    HexL2VICState *s = HEX_L2VIC(iface);
+
+    if (s->vid < L2VIC_INTERRUPT_MAX) {
+        clear_bit32(s->vid, s->int_status);
+    }
+    l2vic_update_all(s);
+}
+
+static void l2vic_reset_hold(Object *obj, ResetType type G_GNUC_UNUSED)
+{
+    HexL2VICState *s = HEX_L2VIC(obj);
+
+    memset(s->int_enable, 0, sizeof(s->int_enable));
+    memset(s->int_pending, 0, sizeof(s->int_pending));
+    memset(s->int_status, 0, sizeof(s->int_status));
+    memset(s->int_type, 0, sizeof(s->int_type));
+    memset(s->int_group_n, 0, sizeof(s->int_group_n));
+    memset(s->vid_group, 0, sizeof(s->vid_group));
+    s->vid = 0;
+
+    l2vic_update_all(s);
+}
+
+static void reset_irq_handler(void *opaque, int irq, int level)
+{
+    Object *obj = OBJECT(opaque);
+
+    if (level) {
+        l2vic_reset_hold(obj, RESET_TYPE_COLD);
+    }
+}
+
+static void l2vic_init(Object *obj)
+{
+    DeviceState *dev = DEVICE(obj);
+    HexL2VICState *s = HEX_L2VIC(obj);
+    SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+    int i;
+
+    memory_region_init_io(&s->iomem, obj, &l2vic_ops, s, "l2vic", 0x1000);
+    sysbus_init_mmio(sbd, &s->iomem);
+    memory_region_init_io(&s->fast_iomem, obj, &fastl2vic_ops, s, "fast",
+                          0x10000);
+    sysbus_init_mmio(sbd, &s->fast_iomem);
+
+    qdev_init_gpio_in(dev, l2vic_set_irq, L2VIC_INTERRUPT_MAX);
+    qdev_init_gpio_in_named(dev, reset_irq_handler, "reset", 1);
+    for (i = 0; i < 8; i++) {
+        sysbus_init_irq(sbd, &s->irq[i]);
+    }
+}
+
+static const VMStateDescription vmstate_l2vic = {
+    .name = "l2vic",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields =
+        (VMStateField[]){
+            VMSTATE_UINT32_ARRAY(vid_group, HexL2VICState, 4),
+            VMSTATE_UINT32(vid, HexL2VICState),
+            VMSTATE_UINT32_ARRAY(int_enable, HexL2VICState, SLICE_MAX),
+            VMSTATE_UINT32_ARRAY(int_type, HexL2VICState, SLICE_MAX),
+            VMSTATE_UINT32_ARRAY(int_status, HexL2VICState, SLICE_MAX),
+            VMSTATE_UINT32_ARRAY(int_pending, HexL2VICState, SLICE_MAX),
+            VMSTATE_UINT32_2DARRAY(int_group_n, HexL2VICState, 4, SLICE_MAX),
+            VMSTATE_END_OF_LIST() }
+};
+
+static void l2vic_interface_class_init(ObjectClass *klass, const void *data)
+{
+    HexL2VicInterfaceClass *k = HEX_L2VIC_INTERFACE_CLASS(klass);
+
+    k->read_vid = l2vic_interface_read_vid_impl;
+    k->update_vid = l2vic_interface_update_vid_impl;
+    k->clear_interrupt = l2vic_interface_clear_interrupt_impl;
+}
+
+static void l2vic_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+    dc->vmsd = &vmstate_l2vic;
+    rc->phases.hold = l2vic_reset_hold;
+}
+
+static const TypeInfo l2vic_interface_info = {
+    .name = TYPE_HEX_L2VIC_INTERFACE,
+    .parent = TYPE_INTERFACE,
+    .class_size = sizeof(HexL2VicInterfaceClass),
+    .class_init = l2vic_interface_class_init,
+};
+
+static const TypeInfo l2vic_info = {
+    .name = TYPE_HEX_L2VIC,
+    .parent = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(HexL2VICState),
+    .instance_init = l2vic_init,
+    .class_init = l2vic_class_init,
+    .interfaces = (InterfaceInfo[]) {
+        { TYPE_HEX_L2VIC_INTERFACE },
+        { }
+    },
+};
+
+static const TypeInfo l2vic_types[] = {
+    l2vic_interface_info,
+    l2vic_info,
+};
+
+DEFINE_TYPES(l2vic_types)
diff --git a/hw/intc/meson.build b/hw/intc/meson.build
index 377de78..a1401cb 100644
--- a/hw/intc/meson.build
+++ b/hw/intc/meson.build
@@ -74,6 +74,8 @@
 specific_ss.add(when: 'CONFIG_XIVE', if_true: files('xive.c'))
 specific_ss.add(when: ['CONFIG_KVM', 'CONFIG_XIVE'],
 		if_true: files('spapr_xive_kvm.c'))
+
+specific_ss.add(when: 'CONFIG_HEX_L2VIC', if_true: files('hex-l2vic.c'))
 specific_ss.add(when: 'CONFIG_M68K_IRQC', if_true: files('m68k_irqc.c'))
 specific_ss.add(when: 'CONFIG_LOONGSON_IPI_COMMON', if_true: files('loongson_ipi_common.c'))
 specific_ss.add(when: 'CONFIG_LOONGSON_IPI', if_true: files('loongson_ipi.c'))
diff --git a/hw/intc/trace-events b/hw/intc/trace-events
index e7d6c30..7512c6e 100644
--- a/hw/intc/trace-events
+++ b/hw/intc/trace-events
@@ -337,6 +337,10 @@
 sh_intc_read(unsigned size, uint64_t offset, unsigned long val) "size %u 0x%" PRIx64 " -> 0x%lx"
 sh_intc_write(unsigned size, uint64_t offset, unsigned long val) "size %u 0x%" PRIx64 " <- 0x%lx"
 sh_intc_set(int id, int enable) "setting interrupt group %d to %d"
+# hex-l2vic.c
+hex_l2vic_reg_write(unsigned int addr, uint32_t value) "addr: 0x%03x value: 0x%08"PRIx32
+hex_l2vic_reg_read(unsigned int addr, uint32_t value) "addr: 0x%03x value: 0x%08"PRIx32
+hex_l2vic_delivered(int irq, int vid) "l2vic: delivered %d (vid %d)"
 
 # loongson_ipi.c
 loongson_ipi_read(unsigned size, uint64_t addr, uint64_t val) "size: %u addr: 0x%"PRIx64 "val: 0x%"PRIx64
diff --git a/hw/loongarch/virt.c b/hw/loongarch/virt.c
index 6693dea..9cc7992 100644
--- a/hw/loongarch/virt.c
+++ b/hw/loongarch/virt.c
@@ -1465,7 +1465,6 @@
     mc->default_ram_id = "loongarch.ram";
     mc->desc = "QEMU LoongArch Virtual Machine";
     mc->max_cpus = LOONGARCH_MAX_CPUS;
-    mc->is_default = 1;
     mc->default_kernel_irqchip_split = false;
     mc->block_default_type = IF_VIRTIO;
     mc->default_boot_order = "c";
@@ -1546,6 +1545,7 @@
         MACHINE_VER_DEPRECATION(__VA_ARGS__); \
         if (latest) { \
             mc->alias = "virt"; \
+            mc->is_default = true; \
         } \
     } \
     static const TypeInfo MACHINE_VER_SYM(info, virt, __VA_ARGS__) = \
@@ -1600,7 +1600,14 @@
 
 type_init(machvirt_machine_init);
 
-static void virt_machine_11_1_options(MachineClass *mc)
+static void virt_machine_11_2_options(MachineClass *mc)
 {
 }
-DEFINE_VIRT_MACHINE_AS_LATEST(11, 1)
+DEFINE_VIRT_MACHINE_AS_LATEST(11, 2)
+
+static void virt_machine_11_1_options(MachineClass *mc)
+{
+    virt_machine_11_2_options(mc);
+    compat_props_add(mc->compat_props, hw_compat_11_1, hw_compat_11_1_len);
+}
+DEFINE_VIRT_MACHINE(11, 1)
diff --git a/hw/m68k/virt.c b/hw/m68k/virt.c
index 51158ce..2749709 100644
--- a/hw/m68k/virt.c
+++ b/hw/m68k/virt.c
@@ -367,10 +367,17 @@
 #define DEFINE_VIRT_MACHINE(major, minor) \
     DEFINE_VIRT_MACHINE_IMPL(false, major, minor)
 
-static void virt_machine_11_1_options(MachineClass *mc)
+static void virt_machine_11_2_options(MachineClass *mc)
 {
 }
-DEFINE_VIRT_MACHINE_AS_LATEST(11, 1)
+DEFINE_VIRT_MACHINE_AS_LATEST(11, 2)
+
+static void virt_machine_11_1_options(MachineClass *mc)
+{
+    virt_machine_11_2_options(mc);
+    compat_props_add(mc->compat_props, hw_compat_11_1, hw_compat_11_1_len);
+}
+DEFINE_VIRT_MACHINE(11, 1)
 
 static void virt_machine_11_0_options(MachineClass *mc)
 {
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index 1543ee6..b8860dd 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -257,4 +257,7 @@
 config XLNX_ZYNQ_DDRC
     bool
 
+config AXIADO_CLK
+    bool
+
 source macio/Kconfig
diff --git a/hw/misc/aspeed_hace.c b/hw/misc/aspeed_hace.c
index c61efe5..8de05a9 100644
--- a/hw/misc/aspeed_hace.c
+++ b/hw/misc/aspeed_hace.c
@@ -18,11 +18,58 @@
 #include "qapi/error.h"
 #include "migration/vmstate.h"
 #include "crypto/hash.h"
+#include "crypto/cipher.h"
 #include "hw/core/qdev-properties.h"
 #include "hw/core/irq.h"
 #include "trace.h"
 
-#define R_CRYPT_CMD     (0x10 / 4)
+/* Crypto engine registers */
+#define R_CRYPT_SRC         (0x00 / 4)
+#define R_CRYPT_DEST        (0x04 / 4)
+#define R_CRYPT_CONTEXT     (0x08 / 4)
+#define R_CRYPT_DATA_LEN    (0x0c / 4)
+/* HACE0C[27:0] holds the crypto data length */
+#define  CRYPT_DATA_LEN_MASK    0x0FFFFFFF
+#define R_CRYPT_CMD         (0x10 / 4)
+/* AES-GCM associated data length (HACE14) and tag write buffer (HACE18) */
+#define R_CRYPT_GCM_ADD_LEN (0x14 / 4)
+#define R_CRYPT_GCM_TAG     (0x18 / 4)
+/* Crypto engine command register (HACE10) bits */
+#define  CRYPT_CMD_ENCRYPT          BIT(7)
+#define  CRYPT_CMD_ISR_EN           BIT(12)
+#define  CRYPT_CMD_DES_SELECT       BIT(16)
+#define  CRYPT_CMD_TRIPLE_DES       BIT(17)
+#define  CRYPT_CMD_SRC_SG_CTRL      BIT(18)
+/* Operation mode HACE10[6:4] */
+#define  CRYPT_CMD_OP_MODE_MASK     (0x7 << 4)
+#define  CRYPT_CMD_ECB              (0x0 << 4)
+#define  CRYPT_CMD_CBC              (0x1 << 4)
+#define  CRYPT_CMD_CTR              (0x4 << 4)
+#define  CRYPT_CMD_GCM              (0x5 << 4)
+/* AES key length HACE10[3:2] */
+#define  CRYPT_CMD_AES_KEY_LEN_MASK (0x3 << 2)
+#define  CRYPT_CMD_AES256           (0x2 << 2)
+#define  CRYPT_CMD_AES192           (0x1 << 2)
+#define  CRYPT_CMD_AES128           (0x0 << 2)
+
+/*
+ * Crypto context buffer layout (HACE08). The IV is at the start of the buffer
+ * (DES places its 8 byte IV at offset 8) and the cipher key at offset 0x10.
+ */
+#define CRYPT_CTX_IV_OFFSET         0x00
+#define CRYPT_CTX_DES_IV_OFFSET     0x08
+#define CRYPT_CTX_KEY_OFFSET        0x10
+#define CRYPT_CTX_SIZE              0x30
+
+/* AES-GCM uses a 96-bit IV and a 128-bit authentication tag */
+#define CRYPT_GCM_IV_LEN            12
+#define CRYPT_GCM_TAG_LEN           16
+
+/* AST2700 64-bit DMA high address registers for the crypto command */
+#define R_CRYPT_SRC_HI      (0x80 / 4)
+#define R_CRYPT_DEST_HI     (0x84 / 4)
+#define R_CRYPT_CONTEXT_HI  (0x88 / 4)
+#define R_CRYPT_GCM_TAG_HI  (0x8c / 4)
 
 #define R_STATUS        (0x1c / 4)
 #define HASH_IRQ        BIT(9)
@@ -65,7 +112,6 @@
 /* Other cmd bits */
 #define  HASH_IRQ_EN                    BIT(9)
 #define  HASH_SG_EN                     BIT(18)
-#define  CRYPT_IRQ_EN                   BIT(12)
 /* Scatter-gather data list */
 #define SG_LIST_LEN_SIZE                4
 #define SG_LIST_LEN_MASK                0x0FFFFFFF
@@ -501,6 +547,368 @@
     }
 }
 
+static bool crypt_aes_alg(uint32_t cmd, QCryptoCipherAlgo *alg, size_t *keylen)
+{
+    switch (cmd & CRYPT_CMD_AES_KEY_LEN_MASK) {
+    case CRYPT_CMD_AES128:
+        *alg = QCRYPTO_CIPHER_ALGO_AES_128;
+        *keylen = 16;
+        break;
+    case CRYPT_CMD_AES192:
+        *alg = QCRYPTO_CIPHER_ALGO_AES_192;
+        *keylen = 24;
+        break;
+    case CRYPT_CMD_AES256:
+        *alg = QCRYPTO_CIPHER_ALGO_AES_256;
+        *keylen = 32;
+        break;
+    default:
+        return false;
+    }
+
+    return true;
+}
+
+/*
+ * Decode the crypto command register into a libqcrypto algorithm/mode pair
+ * and the block/IV geometry. Returns false for unsupported selections.
+ */
+static bool crypt_decode_cmd(uint32_t cmd, QCryptoCipherAlgo *alg,
+                             QCryptoCipherMode *mode, size_t *keylen,
+                             size_t *blocklen, size_t *iv_offset)
+{
+    if (cmd & CRYPT_CMD_DES_SELECT) {
+        *blocklen = 8;
+        *iv_offset = CRYPT_CTX_DES_IV_OFFSET;
+        if (cmd & CRYPT_CMD_TRIPLE_DES) {
+            *alg = QCRYPTO_CIPHER_ALGO_3DES;
+            *keylen = 24;
+        } else {
+            *alg = QCRYPTO_CIPHER_ALGO_DES;
+            *keylen = 8;
+        }
+    } else {
+        *blocklen = 16;
+        *iv_offset = CRYPT_CTX_IV_OFFSET;
+        if (!crypt_aes_alg(cmd, alg, keylen)) {
+            return false;
+        }
+    }
+
+    switch (cmd & CRYPT_CMD_OP_MODE_MASK) {
+    case CRYPT_CMD_ECB:
+        *mode = QCRYPTO_CIPHER_MODE_ECB;
+        break;
+    case CRYPT_CMD_CBC:
+        *mode = QCRYPTO_CIPHER_MODE_CBC;
+        break;
+    case CRYPT_CMD_CTR:
+        *mode = QCRYPTO_CIPHER_MODE_CTR;
+        break;
+    case CRYPT_CMD_GCM:
+        *mode = QCRYPTO_CIPHER_MODE_GCM;
+        break;
+    default:
+        return false;
+    }
+
+    return true;
+}
+
+/*
+ * Direct access mode: the source/destination register (HACE00/HACE04) points
+ * at a single contiguous buffer in DRAM. Copy @len bytes between it and the
+ * bounce buffer @buf; when @to_dram is true @buf is written out, otherwise it
+ * is read in. Returns true on success.
+ */
+static bool crypt_prepare_direct(AspeedHACEState *s, uint64_t addr,
+                                 uint8_t *buf, uint32_t len, bool to_dram)
+{
+    return !address_space_rw(&s->dram_as, addr, MEMTXATTRS_UNSPECIFIED,
+                             buf, len, to_dram);
+}
+
+/*
+ * Scatter-gather mode: the source/destination register points at an SG list
+ * whose entries are a length word (SG_LIST_LEN_LAST flags the final entry)
+ * followed by a DRAM address, matching the hash engine layout. Gather @len
+ * bytes into @buf, or scatter @buf back out when @to_dram is true.
+ * Returns true on success.
+ */
+static bool crypt_prepare_sg(AspeedHACEState *s, uint64_t addr,
+                             uint8_t *buf, uint32_t len, bool to_dram)
+{
+    uint32_t copied = 0;
+    uint32_t sg_addr;
+    uint32_t sg_len;
+    uint32_t entry;
+    int i;
+
+    for (i = 0; i < ASPEED_HACE_MAX_SG && copied < len; i++) {
+        entry = address_space_ldl_le(&s->dram_as, addr,
+                                     MEMTXATTRS_UNSPECIFIED, NULL);
+        sg_addr = address_space_ldl_le(&s->dram_as, addr + SG_LIST_LEN_SIZE,
+                                       MEMTXATTRS_UNSPECIFIED, NULL);
+        sg_len = entry & SG_LIST_LEN_MASK;
+
+        sg_addr &= SG_LIST_ADDR_MASK;
+        addr += SG_LIST_ENTRY_SIZE;
+
+        if (sg_len > len - copied) {
+            sg_len = len - copied;
+        }
+        if (address_space_rw(&s->dram_as, sg_addr, MEMTXATTRS_UNSPECIFIED,
+                             buf + copied, sg_len, to_dram)) {
+            return false;
+        }
+        copied += sg_len;
+
+        if (entry & SG_LIST_LEN_LAST) {
+            break;
+        }
+    }
+
+    return copied == len;
+}
+
+/*
+ * Add @add to the big-endian counter block @ctr (@len bytes) in place, so the
+ * CTR mode counter can be advanced by the number of blocks just consumed.
+ */
+static void crypt_be_add(uint8_t *ctr, size_t len, uint64_t add)
+{
+    size_t i = len;
+
+    while (i > 0 && add) {
+        i--;
+        add += ctr[i];
+        ctr[i] = add & 0xff;
+        add >>= 8;
+    }
+}
+
+static uint64_t crypt_get_addr(AspeedHACEState *s, int reg, int reg_hi)
+{
+    AspeedHACEClass *ahc = ASPEED_HACE_GET_CLASS(s);
+    uint64_t addr;
+
+    addr = deposit64(0, 0, 32, s->regs[reg]);
+    if (ahc->has_dma64) {
+        addr = deposit64(addr, 32, 32, s->regs[reg_hi]);
+    }
+
+    return addr;
+}
+
+/*
+ * Perform an AES/DES/3DES ECB/CBC/CTR or AES-GCM operation. The source and
+ * destination are either single contiguous buffers (direct access mode) or
+ * scatter-gather lists (HACE10[18]/[19]), addressed by HACE00/HACE04; the
+ * IV/key come from the context buffer (HACE08). For CBC and CTR the resulting
+ * chaining state is written back to the context buffer so the driver can
+ * continue; for GCM the authentication tag is written to the tag buffer.
+ */
+static void do_crypt_operation(AspeedHACEState *s, uint32_t cmd)
+{
+    bool sg_mode = cmd & CRYPT_CMD_SRC_SG_CTRL;
+    uint32_t len = s->regs[R_CRYPT_DATA_LEN];
+    bool encrypt = cmd & CRYPT_CMD_ENCRYPT;
+    g_autoptr(QCryptoCipher) cipher = NULL;
+    g_autofree uint8_t *src_buf = NULL;
+    g_autofree uint8_t *dst_buf = NULL;
+    uint8_t tag[CRYPT_GCM_TAG_LEN];
+    uint8_t ctx[CRYPT_CTX_SIZE];
+    Error *local_err = NULL;
+    QCryptoCipherMode mode;
+    QCryptoCipherAlgo alg;
+    const uint8_t *next_iv;
+    uint64_t ctx_addr;
+    uint64_t src_addr;
+    uint64_t dst_addr;
+    uint64_t tag_addr;
+    uint32_t aad_len;
+    size_t iv_offset;
+    size_t blocklen;
+    size_t buf_len;
+    size_t keylen;
+    size_t ivlen;
+    bool status;
+
+    if (len == 0) {
+        return;
+    }
+
+    if (!crypt_decode_cmd(cmd, &alg, &mode, &keylen, &blocklen, &iv_offset)) {
+        qemu_log_mask(LOG_UNIMP,
+                      "%s: Unsupported crypt command 0x%x\n", __func__, cmd);
+        return;
+    }
+
+    if (!qcrypto_cipher_supports(alg, mode)) {
+        qemu_log_mask(LOG_UNIMP,
+                      "%s: cipher mode not supported by the crypto backend\n",
+                      __func__);
+        return;
+    }
+
+    /* GCM uses a 96-bit IV; the block modes use a full-block IV. */
+    ivlen = (mode == QCRYPTO_CIPHER_MODE_GCM) ? CRYPT_GCM_IV_LEN : blocklen;
+
+    /*
+     * The hardware GCM path is only exercised without associated data (the
+     * driver falls back to software when there is any), so AAD is not modelled.
+     */
+    aad_len = s->regs[R_CRYPT_GCM_ADD_LEN];
+    if (mode == QCRYPTO_CIPHER_MODE_GCM && aad_len != 0) {
+        qemu_log_mask(LOG_UNIMP,
+                      "%s: GCM associated data is not implemented\n", __func__);
+        return;
+    }
+
+    /* Fetch the IV and key from the context buffer in DRAM. */
+    ctx_addr = crypt_get_addr(s, R_CRYPT_CONTEXT, R_CRYPT_CONTEXT_HI);
+    if (address_space_read(&s->dram_as, ctx_addr, MEMTXATTRS_UNSPECIFIED,
+                           ctx, sizeof(ctx))) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: Failed to read context, addr=0x%" HWADDR_PRIx "\n",
+                      __func__, ctx_addr);
+        return;
+    }
+
+    if (trace_event_get_state_backends(TRACE_ASPEED_HACE_HEXDUMP)) {
+        hace_hexdump("context", (char *)ctx, sizeof(ctx));
+    }
+
+    cipher = qcrypto_cipher_new(alg, mode, ctx + CRYPT_CTX_KEY_OFFSET, keylen,
+                                &local_err);
+    if (cipher == NULL) {
+        qemu_log_mask(LOG_GUEST_ERROR, "%s: qcrypto cipher new failed: %s\n",
+                      __func__, error_get_pretty(local_err));
+        error_free(local_err);
+        return;
+    }
+
+    if (mode != QCRYPTO_CIPHER_MODE_ECB &&
+        qcrypto_cipher_setiv(cipher, ctx + iv_offset, ivlen,
+                             &local_err) < 0) {
+        qemu_log_mask(LOG_GUEST_ERROR, "%s: qcrypto cipher setiv failed: %s\n",
+                      __func__, error_get_pretty(local_err));
+        error_free(local_err);
+        return;
+    }
+
+    /*
+     * Round the working buffers up to a whole block. Block modes are already
+     * block-aligned; the stream-like CTR mode may leave a partial final block
+     * that the engine still processes a full block at a time. GCM handles a
+     * partial final block itself, so it operates on the exact length.
+     */
+    buf_len = (mode == QCRYPTO_CIPHER_MODE_GCM) ?
+              len : QEMU_ALIGN_UP(len, blocklen);
+    src_buf = g_malloc0(buf_len);
+    dst_buf = g_malloc0(buf_len);
+
+    /* Gather the source into the bounce buffer, per the selected mode. */
+    src_addr = crypt_get_addr(s, R_CRYPT_SRC, R_CRYPT_SRC_HI);
+    if (sg_mode) {
+        status = crypt_prepare_sg(s, src_addr, src_buf, len, false);
+    } else {
+        status = crypt_prepare_direct(s, src_addr, src_buf, len, false);
+    }
+    if (!status) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: Failed to read src, addr=0x%" HWADDR_PRIx "\n",
+                      __func__, src_addr);
+        return;
+    }
+
+    if (trace_event_get_state_backends(TRACE_ASPEED_HACE_HEXDUMP)) {
+        hace_hexdump("src", (char *)src_buf, len);
+    }
+
+    if (encrypt) {
+        if (qcrypto_cipher_encrypt(cipher, src_buf, dst_buf, buf_len,
+                                   &local_err) < 0) {
+            qemu_log_mask(LOG_GUEST_ERROR, "%s: encrypt failed: %s\n",
+                          __func__, error_get_pretty(local_err));
+            error_free(local_err);
+            return;
+        }
+    } else {
+        if (qcrypto_cipher_decrypt(cipher, src_buf, dst_buf, buf_len,
+                                   &local_err) < 0) {
+            qemu_log_mask(LOG_GUEST_ERROR, "%s: decrypt failed: %s\n",
+                          __func__, error_get_pretty(local_err));
+            error_free(local_err);
+            return;
+        }
+    }
+
+    /* Scatter the result back out, per the selected mode. */
+    dst_addr = crypt_get_addr(s, R_CRYPT_DEST, R_CRYPT_DEST_HI);
+    if (sg_mode) {
+        status = crypt_prepare_sg(s, dst_addr, dst_buf, len, true);
+    } else {
+        status = crypt_prepare_direct(s, dst_addr, dst_buf, len, true);
+    }
+    if (!status) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: Failed to write dst, addr=0x%" HWADDR_PRIx "\n",
+                      __func__, dst_addr);
+        return;
+    }
+
+    if (trace_event_get_state_backends(TRACE_ASPEED_HACE_HEXDUMP)) {
+        hace_hexdump("dst", (char *)dst_buf, len);
+    }
+
+    if (mode == QCRYPTO_CIPHER_MODE_CBC) {
+        /*
+         * CBC chains on the last ciphertext block: the final block of the
+         * output when encrypting, or of the input when decrypting. Write it
+         * back as the IV for the next request.
+         */
+        next_iv = (encrypt ? dst_buf : src_buf) + buf_len - blocklen;
+        if (address_space_write(&s->dram_as, ctx_addr + iv_offset,
+                                MEMTXATTRS_UNSPECIFIED, next_iv, blocklen)) {
+            qemu_log_mask(LOG_GUEST_ERROR,
+                          "%s: Failed to write IV, addr=0x%" HWADDR_PRIx "\n",
+                          __func__, ctx_addr + iv_offset);
+        }
+    } else if (mode == QCRYPTO_CIPHER_MODE_CTR) {
+        /*
+         * CTR chains on the counter, which advances by one per block. Add the
+         * number of blocks processed (buf_len / blocklen) and write it back.
+         */
+        crypt_be_add(ctx + iv_offset, blocklen, buf_len / blocklen);
+        if (address_space_write(&s->dram_as, ctx_addr + iv_offset,
+                                MEMTXATTRS_UNSPECIFIED, ctx + iv_offset,
+                                blocklen)) {
+            qemu_log_mask(LOG_GUEST_ERROR,
+                          "%s: Failed to write IV, addr=0x%" HWADDR_PRIx "\n",
+                          __func__, ctx_addr + iv_offset);
+        }
+    } else if (mode == QCRYPTO_CIPHER_MODE_GCM) {
+        /*
+         * GCM authenticates the message and writes the resulting tag to the
+         * dedicated tag buffer (HACE18/HACE8C).
+         */
+        if (qcrypto_cipher_gettag(cipher, tag, sizeof(tag), &local_err) < 0) {
+            qemu_log_mask(LOG_GUEST_ERROR, "%s: qcrypto cipher gettag failed: "
+                          "%s\n", __func__, error_get_pretty(local_err));
+            error_free(local_err);
+            return;
+        }
+        tag_addr = crypt_get_addr(s, R_CRYPT_GCM_TAG, R_CRYPT_GCM_TAG_HI);
+        if (address_space_write(&s->dram_as, tag_addr, MEMTXATTRS_UNSPECIFIED,
+                                tag, sizeof(tag))) {
+            qemu_log_mask(LOG_GUEST_ERROR,
+                          "%s: Failed to write tag, addr=0x%" HWADDR_PRIx "\n",
+                          __func__, tag_addr);
+        }
+    }
+}
+
 static uint64_t aspeed_hace_read(void *opaque, hwaddr addr, unsigned int size)
 {
     AspeedHACEState *s = ASPEED_HACE(opaque);
@@ -531,16 +939,24 @@
                 qemu_irq_lower(s->irq);
             }
         }
-        if (ahc->raise_crypt_interrupt_workaround) {
-            if (data & CRYPT_IRQ) {
-                data &= ~CRYPT_IRQ;
+        if (data & CRYPT_IRQ) {
+            data &= ~CRYPT_IRQ;
 
-                if (s->regs[addr] & CRYPT_IRQ) {
-                    qemu_irq_lower(s->irq);
-                }
+            if (s->regs[addr] & CRYPT_IRQ) {
+                qemu_irq_lower(s->irq);
             }
         }
         break;
+    case R_CRYPT_SRC:
+    case R_CRYPT_DEST:
+    case R_CRYPT_CONTEXT:
+    case R_CRYPT_GCM_TAG:
+        data &= ahc->src_mask;
+        break;
+    case R_CRYPT_DATA_LEN:
+    case R_CRYPT_GCM_ADD_LEN:
+        data &= CRYPT_DATA_LEN_MASK;
+        break;
     case R_HASH_SRC:
         data &= ahc->src_mask;
         break;
@@ -589,13 +1005,12 @@
         break;
     }
     case R_CRYPT_CMD:
-        qemu_log_mask(LOG_UNIMP, "%s: Crypt commands not implemented\n",
-                       __func__);
-        if (ahc->raise_crypt_interrupt_workaround) {
-            s->regs[R_STATUS] |= CRYPT_IRQ;
-            if (data & CRYPT_IRQ_EN) {
-                qemu_irq_raise(s->irq);
-            }
+        do_crypt_operation(s, data);
+
+        /* Hardware raises the crypt interrupt once the command finishes. */
+        s->regs[R_STATUS] |= CRYPT_IRQ;
+        if (data & CRYPT_CMD_ISR_EN) {
+            qemu_irq_raise(s->irq);
         }
         break;
     case R_HASH_SRC_HI:
@@ -607,6 +1022,16 @@
     case R_HASH_KEY_BUFF_HI:
         data &= ahc->key_hi_mask;
         break;
+    case R_CRYPT_SRC_HI:
+        data &= ahc->src_hi_mask;
+        break;
+    case R_CRYPT_DEST_HI:
+    case R_CRYPT_GCM_TAG_HI:
+        data &= ahc->dest_hi_mask;
+        break;
+    case R_CRYPT_CONTEXT_HI:
+        data &= ahc->key_hi_mask;
+        break;
     default:
         break;
     }
@@ -782,12 +1207,6 @@
     ahc->dest_hi_mask = 0x00000003;
     ahc->key_hi_mask = 0x00000003;
 
-    /*
-     * Currently, it does not support the CRYPT command. Instead, it only
-     * sends an interrupt to notify the firmware that the crypt command
-     * has completed. It is a temporary workaround.
-     */
-    ahc->raise_crypt_interrupt_workaround = true;
     ahc->has_dma64 = true;
 }
 
diff --git a/hw/misc/aspeed_scu.c b/hw/misc/aspeed_scu.c
index 5dbf81c..ca93c36 100644
--- a/hw/misc/aspeed_scu.c
+++ b/hw/misc/aspeed_scu.c
@@ -930,6 +930,11 @@
     s->regs[AST2700_HW_STRAP1] = s->hw_strap1;
 }
 
+static void aspeed_2700_scu_realize(DeviceState *dev, Error **errp)
+{
+    aspeed_scu_realize(dev, errp);
+}
+
 static void aspeed_2700_scu_class_init(ObjectClass *klass, const void *data)
 {
     DeviceClass *dc = DEVICE_CLASS(klass);
@@ -937,6 +942,7 @@
     AspeedSCUClass *asc = ASPEED_SCU_CLASS(klass);
 
     dc->desc = "ASPEED 2700 System Control Unit";
+    dc->realize = aspeed_2700_scu_realize;
     rc->phases.hold = aspeed_ast2700_scu_reset_hold;
     asc->resets = ast2700_a0_resets;
     asc->calc_hpll = aspeed_2600_scu_calc_hpll;
@@ -1063,6 +1069,16 @@
     [AST2700_SCUIO_FREQ_CNT_CTL]        = 0x00000080,
 };
 
+static void aspeed_ast2700_scuio_reset_hold(Object *obj, ResetType type)
+{
+    AspeedSCUState *s = ASPEED_SCU(obj);
+    AspeedSCUClass *asc = ASPEED_SCU_GET_CLASS(obj);
+
+    memcpy(s->regs, asc->resets, asc->nr_regs * 4);
+    s->regs[AST2700_SILICON_REV] = s->silicon_rev;
+    s->regs[AST2700_HW_STRAP1] = s->hw_strap1;
+}
+
 static void aspeed_2700_scuio_class_init(ObjectClass *klass, const void *data)
 {
     DeviceClass *dc = DEVICE_CLASS(klass);
@@ -1070,7 +1086,7 @@
     AspeedSCUClass *asc = ASPEED_SCU_CLASS(klass);
 
     dc->desc = "ASPEED 2700 System Control Unit I/O";
-    rc->phases.hold = aspeed_ast2700_scu_reset_hold;
+    rc->phases.hold = aspeed_ast2700_scuio_reset_hold;
     asc->resets = ast2700_a0_resets_io;
     asc->calc_hpll = aspeed_2600_scu_calc_hpll;
     asc->get_apb = aspeed_2700_scuio_get_apb_freq;
@@ -1161,7 +1177,7 @@
     {
         .name = TYPE_ASPEED_2700_SCU,
         .parent = TYPE_ASPEED_SCU,
-        .instance_size = sizeof(AspeedSCUState),
+        .instance_size = sizeof(Aspeed2700SCUState),
         .class_init = aspeed_2700_scu_class_init,
     },
     {
diff --git a/hw/misc/axiado_clk.c b/hw/misc/axiado_clk.c
new file mode 100644
index 0000000..090beb3
--- /dev/null
+++ b/hw/misc/axiado_clk.c
@@ -0,0 +1,79 @@
+/*
+ * Axiado Clock Control
+ *
+ * Author: Kuan-Jui Chiu <kchiu@axiado.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/misc/axiado_clk.h"
+#include "qemu/log.h"
+
+#define CLKRST_CPU_PLL_POSTDIV_OFFSET   0x0C
+#define CLKRST_CPU_PLL_STS_OFFSET       0x14
+
+static uint64_t pll_read(void *opaque, hwaddr offset, unsigned size)
+{
+    switch (offset) {
+    case CLKRST_CPU_PLL_POSTDIV_OFFSET:
+        return 0x20891b;
+    case CLKRST_CPU_PLL_STS_OFFSET:
+        return 0x01;
+    default:
+        qemu_log_mask(LOG_UNIMP,
+                      "Register 0x%" HWADDR_PRIx " not implemented\n", offset);
+        break;
+    }
+    return 0x00;
+}
+
+static void pll_write(void *opaque, hwaddr offset, uint64_t val, unsigned size)
+{
+    qemu_log_mask(LOG_UNIMP,
+                  "Register 0x%" HWADDR_PRIx " not implemented\n", offset);
+}
+
+static const MemoryRegionOps pll_ops = {
+    .read = pll_read,
+    .write = pll_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .impl = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+    },
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+    }
+};
+
+static void ax3000_clk_init(Object *obj)
+{
+    Ax3000ClkState *s = AX3000_CLK(obj);
+
+    memory_region_init_io(&s->pll_ctrl, obj, &pll_ops, s,
+                          TYPE_AX3000_CLK, AX3000_CLK_PLL_CTRL_SIZE);
+    sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->pll_ctrl);
+}
+
+static void ax3000_clk_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    dc->desc = "Axiado AX3000 Clock Control";
+}
+
+static const TypeInfo ax3000_clk_info = {
+    .parent             = TYPE_SYS_BUS_DEVICE,
+    .name               = TYPE_AX3000_CLK,
+    .instance_size      = sizeof(Ax3000ClkState),
+    .instance_init      = ax3000_clk_init,
+    .class_init         = ax3000_clk_class_init,
+};
+
+static void axiado_clk_register_type(void)
+{
+    type_register_static(&ax3000_clk_info);
+}
+type_init(axiado_clk_register_type);
diff --git a/hw/misc/bcm2835_powermgt.c b/hw/misc/bcm2835_powermgt.c
index 3ec7aba..7b01be4 100644
--- a/hw/misc/bcm2835_powermgt.c
+++ b/hw/misc/bcm2835_powermgt.c
@@ -19,10 +19,40 @@
 #define PASSWORD_MASK 0xff000000
 
 #define R_RSTC 0x1c
-#define V_RSTC_RESET 0x20
+#define V_RSTC_WRCFG_MASK 0x30
+#define V_RSTC_FULL_RESET 0x20
 #define R_RSTS 0x20
 #define V_RSTS_POWEROFF 0x555 /* Linux uses partition 63 to indicate halt. */
 #define R_WDOG 0x24
+#define V_WDOG_TIME_MASK 0xfffff
+#define WDOG_TICKS_PER_SECOND 65536
+
+static void bcm2835_powermgt_expire(void *opaque)
+{
+    BCM2835PowerMgtState *s = opaque;
+
+    if ((s->rsts & 0xfff) == V_RSTS_POWEROFF) {
+        qemu_system_shutdown_request(SHUTDOWN_CAUSE_GUEST_SHUTDOWN);
+    } else {
+        qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
+    }
+}
+
+static void bcm2835_powermgt_update_wdog(BCM2835PowerMgtState *s)
+{
+    uint64_t timeout_ns;
+
+    if ((s->rstc & V_RSTC_WRCFG_MASK) != V_RSTC_FULL_RESET ||
+        s->wdog == 0) {
+        timer_del(s->wdog_timer);
+        return;
+    }
+
+    timeout_ns = muldiv64(s->wdog, NANOSECONDS_PER_SECOND,
+                          WDOG_TICKS_PER_SECOND);
+    timer_mod(s->wdog_timer,
+              qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + timeout_ns);
+}
 
 static uint64_t bcm2835_powermgt_read(void *opaque, hwaddr offset,
                                       unsigned size)
@@ -70,13 +100,7 @@
     switch (offset) {
     case R_RSTC:
         s->rstc = value;
-        if (value & V_RSTC_RESET) {
-            if ((s->rsts & 0xfff) == V_RSTS_POWEROFF) {
-                qemu_system_shutdown_request(SHUTDOWN_CAUSE_GUEST_SHUTDOWN);
-            } else {
-                qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
-            }
-        }
+        bcm2835_powermgt_update_wdog(s);
         break;
     case R_RSTS:
         qemu_log_mask(LOG_UNIMP,
@@ -84,9 +108,8 @@
         s->rsts = value;
         break;
     case R_WDOG:
-        qemu_log_mask(LOG_UNIMP,
-                      "bcm2835_powermgt_write: WDOG\n");
-        s->wdog = value;
+        s->wdog = value & V_WDOG_TIME_MASK;
+        bcm2835_powermgt_update_wdog(s);
         break;
 
     default:
@@ -107,12 +130,13 @@
 
 static const VMStateDescription vmstate_bcm2835_powermgt = {
     .name = TYPE_BCM2835_POWERMGT,
-    .version_id = 1,
-    .minimum_version_id = 1,
+    .version_id = 2,
+    .minimum_version_id = 2,
     .fields = (const VMStateField[]) {
         VMSTATE_UINT32(rstc, BCM2835PowerMgtState),
         VMSTATE_UINT32(rsts, BCM2835PowerMgtState),
         VMSTATE_UINT32(wdog, BCM2835PowerMgtState),
+        VMSTATE_TIMER_PTR(wdog_timer, BCM2835PowerMgtState),
         VMSTATE_END_OF_LIST()
     }
 };
@@ -124,6 +148,8 @@
     memory_region_init_io(&s->iomem, obj, &bcm2835_powermgt_ops, s,
                           TYPE_BCM2835_POWERMGT, 0x200);
     sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem);
+    s->wdog_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
+                                 bcm2835_powermgt_expire, s);
 }
 
 static void bcm2835_powermgt_reset(DeviceState *dev)
@@ -134,6 +160,7 @@
     s->rstc = 0x00000102;
     s->rsts = 0x00001000;
     s->wdog = 0x00000000;
+    timer_del(s->wdog_timer);
 }
 
 static void bcm2835_powermgt_class_init(ObjectClass *klass, const void *data)
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index 23265f6..e86d9ad 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -1,4 +1,5 @@
 system_ss.add(when: 'CONFIG_APPLESMC', if_true: files('applesmc.c'))
+
 system_ss.add(when: 'CONFIG_EDU', if_true: files('edu.c'))
 system_ss.add(when: 'CONFIG_FW_CFG_DMA', if_true: files('vmcoreinfo.c'))
 system_ss.add(when: 'CONFIG_ISA_DEBUG', if_true: files('debugexit.c'))
@@ -168,3 +169,5 @@
 
 # HPPA devices
 system_ss.add(when: 'CONFIG_LASI', if_true: files('lasi.c'))
+
+system_ss.add(when: 'CONFIG_AXIADO_CLK', if_true: files('axiado_clk.c'))
diff --git a/hw/pci-host/Kconfig b/hw/pci-host/Kconfig
index 8cbb830..bda6e16 100644
--- a/hw/pci-host/Kconfig
+++ b/hw/pci-host/Kconfig
@@ -49,6 +49,7 @@
 config PCI_EXPRESS_ASPEED
     bool
     select PCI_EXPRESS
+    select PCIE_PORT
 
 config PCI_EXPRESS_Q35
     bool
diff --git a/hw/ppc/spapr.c b/hw/ppc/spapr.c
index b79828b..7e284ed 100644
--- a/hw/ppc/spapr.c
+++ b/hw/ppc/spapr.c
@@ -4764,14 +4764,25 @@
     DEFINE_SPAPR_MACHINE_IMPL(false, major, minor)
 
 /*
- * pseries-11.1
+ * pseries-11.2
  */
-static void spapr_machine_11_1_class_options(MachineClass *mc)
+static void spapr_machine_11_2_class_options(MachineClass *mc)
 {
     /* Defaults for the latest behaviour inherited from the base class */
 }
 
-DEFINE_SPAPR_MACHINE_AS_LATEST(11, 1);
+DEFINE_SPAPR_MACHINE_AS_LATEST(11, 2);
+
+/*
+ * pseries-11.1
+ */
+static void spapr_machine_11_1_class_options(MachineClass *mc)
+{
+    spapr_machine_11_2_class_options(mc);
+    compat_props_add(mc->compat_props, hw_compat_11_1, hw_compat_11_1_len);
+}
+
+DEFINE_SPAPR_MACHINE(11, 1);
 
 /*
  * pseries-11.0
diff --git a/hw/s390x/ipl.h b/hw/s390x/ipl.h
index fac3076..ef9c063 100644
--- a/hw/s390x/ipl.h
+++ b/hw/s390x/ipl.h
@@ -124,6 +124,12 @@
         return false;
     }
 
+    if (offsetof(IplParameterBlock, pv.components) +
+        ipib_pv->num_comp * sizeof(IPLBlockPVComp) >
+        be32_to_cpu(iplb->len)) {
+        return false;
+    }
+
     for (i = 0; i < ipib_pv->num_comp; i++) {
         /* Addr must be 4k aligned */
         if (ipib_pv->components[i].addr & ~TARGET_PAGE_MASK) {
diff --git a/hw/s390x/s390-virtio-ccw.c b/hw/s390x/s390-virtio-ccw.c
index 25a9fa4..55131c1 100644
--- a/hw/s390x/s390-virtio-ccw.c
+++ b/hw/s390x/s390-virtio-ccw.c
@@ -925,14 +925,26 @@
     DEFINE_CCW_MACHINE_IMPL(false, major, minor)
 
 
+static void ccw_machine_11_2_instance_options(MachineState *machine)
+{
+}
+
+static void ccw_machine_11_2_class_options(MachineClass *mc)
+{
+}
+DEFINE_CCW_MACHINE_AS_LATEST(11, 2);
+
 static void ccw_machine_11_1_instance_options(MachineState *machine)
 {
+    ccw_machine_11_2_instance_options(machine);
 }
 
 static void ccw_machine_11_1_class_options(MachineClass *mc)
 {
+    ccw_machine_11_2_class_options(mc);
+    compat_props_add(mc->compat_props, hw_compat_11_1, hw_compat_11_1_len);
 }
-DEFINE_CCW_MACHINE_AS_LATEST(11, 1);
+DEFINE_CCW_MACHINE(11, 1);
 
 static void ccw_machine_11_0_instance_options(MachineState *machine)
 {
diff --git a/hw/sd/Kconfig b/hw/sd/Kconfig
index 633b9af..c69bf24 100644
--- a/hw/sd/Kconfig
+++ b/hw/sd/Kconfig
@@ -23,3 +23,7 @@
 config CADENCE_SDHCI
     bool
     select SDHCI
+
+config AXIADO_SDHCI
+    bool
+    select SDHCI
diff --git a/hw/sd/axiado_sdhci.c b/hw/sd/axiado_sdhci.c
new file mode 100644
index 0000000..2e88bff
--- /dev/null
+++ b/hw/sd/axiado_sdhci.c
@@ -0,0 +1,119 @@
+/*
+ * Axiado SD Host Controller with embedded PHY
+ *
+ * Author: Kuan-Jui Chiu <kchiu@axiado.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/sd/axiado_sdhci.h"
+#include "sdhci-internal.h"
+#include "qapi/error.h"
+#include "hw/core/qdev-properties.h"
+#include "qemu/log.h"
+
+#define EMMC_PHY_ID     0x00
+#define EMMC_PHY_STATUS 0x50
+
+#define DLL_RDY  (1u << 0)
+#define CAL_DONE (1u << 6)
+
+static uint64_t emmc_phy_read(void *opaque, hwaddr offset, unsigned size)
+{
+    uint32_t val = 0x00;
+
+    switch (offset) {
+    case EMMC_PHY_ID:
+        val = 0x3dff6870;
+        break;
+    case EMMC_PHY_STATUS:
+        val = DLL_RDY | CAL_DONE;
+        break;
+    default:
+        qemu_log_mask(LOG_UNIMP,
+                      "Register 0x%" HWADDR_PRIx " not implemented\n", offset);
+        break;
+    }
+
+    return val;
+}
+
+static void emmc_phy_write(void *opaque, hwaddr offset, uint64_t value,
+                           unsigned size)
+{
+    qemu_log_mask(LOG_UNIMP,
+                  "Register 0x%" HWADDR_PRIx " not implemented\n", offset);
+}
+
+static const MemoryRegionOps emmc_phy_ops = {
+    .read = emmc_phy_read,
+    .write = emmc_phy_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .impl = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+    },
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+    }
+};
+
+static void axiado_sdhci_realize(DeviceState *dev, Error **errp)
+{
+    AxiadoSDHCIState *s = AXIADO_SDHCI(dev);
+    SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+    SysBusDevice *sdhci_sbd;
+
+    qdev_prop_set_uint64(DEVICE(&s->sdhci), "capareg", 0x216737eed0b0);
+    qdev_prop_set_uint64(DEVICE(&s->sdhci), "sd-spec-version", 3);
+
+    sdhci_sbd = SYS_BUS_DEVICE(&s->sdhci);
+    if (!sysbus_realize(sdhci_sbd, errp)) {
+        return;
+    }
+
+    sysbus_init_mmio(sbd, sysbus_mmio_get_region(sdhci_sbd, 0));
+
+    /* Propagate IRQ from SDHCI and SD bus  */
+    sysbus_pass_irq(sbd, sdhci_sbd);
+    s->sd_bus = qdev_get_child_bus(DEVICE(sdhci_sbd), "sd-bus");
+
+    /* Initialize eMMC PHY MMIO */
+    memory_region_init_io(&s->emmc_phy, OBJECT(s), &emmc_phy_ops, s,
+                          "axiado.emmc-phy", 0x1000);
+
+    sysbus_init_mmio(sbd, &s->emmc_phy);
+}
+
+static void axiado_sdhci_instance_init(Object *obj)
+{
+    AxiadoSDHCIState *s = AXIADO_SDHCI(obj);
+
+    object_initialize_child(OBJECT(s), "sdhci", &s->sdhci,
+                            TYPE_SYSBUS_SDHCI);
+}
+
+static void axiado_sdhci_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    dc->realize = axiado_sdhci_realize;
+    dc->desc = "Axiado SD Host Controller with eMMC PHY";
+}
+
+static const TypeInfo axiado_sdhci_info = {
+    .name          = TYPE_AXIADO_SDHCI,
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(AxiadoSDHCIState),
+    .instance_init = axiado_sdhci_instance_init,
+    .class_init    = axiado_sdhci_class_init,
+};
+
+static void axiado_sdhci_register_types(void)
+{
+    type_register_static(&axiado_sdhci_info);
+}
+
+type_init(axiado_sdhci_register_types);
diff --git a/hw/sd/meson.build b/hw/sd/meson.build
index b43d45b..ebf09e3 100644
--- a/hw/sd/meson.build
+++ b/hw/sd/meson.build
@@ -10,3 +10,4 @@
 system_ss.add(when: 'CONFIG_ALLWINNER_H3', if_true: files('allwinner-sdhost.c'))
 system_ss.add(when: 'CONFIG_NPCM7XX', if_true: files('npcm7xx_sdhci.c'))
 system_ss.add(when: 'CONFIG_CADENCE_SDHCI', if_true: files('cadence_sdhci.c'))
+system_ss.add(when: 'CONFIG_AXIADO_SDHCI', if_true: files('axiado_sdhci.c'))
diff --git a/hw/sensor/Kconfig b/hw/sensor/Kconfig
index bc6331b..b459ac2 100644
--- a/hw/sensor/Kconfig
+++ b/hw/sensor/Kconfig
@@ -1,3 +1,7 @@
+config ADC128D818
+    bool
+    depends on I2C
+
 config TMP105
     bool
     depends on I2C
diff --git a/hw/sensor/adc128d818.c b/hw/sensor/adc128d818.c
new file mode 100644
index 0000000..c65508c
--- /dev/null
+++ b/hw/sensor/adc128d818.c
@@ -0,0 +1,696 @@
+/*
+ * Texas Instruments ADC128D818 12-bit 8-channel ADC with I2C interface
+ *
+ * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qapi/error.h"
+#include "qapi/visitor.h"
+#include "qom/object.h"
+#include "hw/sensor/adc128d818.h"
+#include "hw/core/irq.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/i2c/i2c.h"
+#include "migration/vmstate.h"
+#include "trace.h"
+
+
+/* Register addresses */
+#define REG_CONFIG              0x00
+#define REG_INT_STATUS          0x01
+#define REG_INT_MASK            0x03
+#define REG_CONV_RATE           0x07
+#define REG_CH_DISABLE          0x08
+#define REG_ONE_SHOT            0x09
+#define REG_DEEP_SHUTDOWN       0x0a
+#define REG_ADV_CONFIG          0x0b
+#define REG_BUSY_STATUS         0x0c
+
+/* Channel Reading Registers (16-bit, read-only) */
+#define REG_CH_READING_BASE     0x20
+#define REG_CH_READING_LAST     0x27
+
+/* Limit Registers (8-bit, read/write) */
+#define REG_LIMIT_BASE          0x2a
+#define REG_LIMIT_LAST          0x39
+
+/* ID Registers (read-only) */
+#define REG_MANUFACTURER_ID     0x3e
+#define REG_REVISION_ID         0x3f
+
+/* Configuration Register (0x00) bitfields */
+#define CONFIG_START            BIT(0)
+#define CONFIG_INT_ENABLE       BIT(1)
+#define CONFIG_INT_CLEAR        BIT(3)
+#define CONFIG_INITIALIZATION   BIT(7)
+#define CONFIG_WR_MASK \
+    (CONFIG_START | CONFIG_INT_ENABLE | CONFIG_INT_CLEAR)
+
+/* Advanced Configuration Register (0x0B) bitfields */
+#define ADV_CONFIG_EXT_REF_EN   BIT(0)
+#define ADV_CONFIG_MODE_SHIFT   1
+#define ADV_CONFIG_MODE_MASK    (0x3 << ADV_CONFIG_MODE_SHIFT)
+#define ADV_CONFIG_WR_MASK \
+    (ADV_CONFIG_EXT_REF_EN | ADV_CONFIG_MODE_MASK)
+
+/* Busy Status Register (0x0C) bitfields */
+#define BUSY_STATUS_NOT_READY   BIT(1)
+
+/* Conversion Rate Register (0x07) bitfields */
+#define CONV_RATE_MASK          0x01
+
+/* Deep Shutdown Register (0x0A) bitfields */
+#define DEEP_SHUTDOWN_EN        0x01
+
+/* Device constants */
+#define ADC128D818_NUM_CHANNELS         8
+#define ADC128D818_NUM_REGS             0x40
+
+#define ADC128D818_INTERNAL_VREF_MV     2560
+#define ADC128D818_MAX_VDD_MV           5500
+#define ADC128D818_MANUFACTURER_ID_VAL  0x01
+#define ADC128D818_REVISION_ID_VAL      0x09
+
+/* ADC resolution */
+#define ADC128D818_ADC_RESOLUTION       4096
+#define ADC128D818_ADC_MAX              4095
+
+/* Temperature: 0.5 deg C per LSb = 500 milli-degrees per LSb */
+#define ADC128D818_TEMP_LSB_MC          500
+#define ADC128D818_TEMP_RAW_MIN         (-256)
+#define ADC128D818_TEMP_RAW_MAX         255
+
+
+OBJECT_DECLARE_SIMPLE_TYPE(ADC128D818State, ADC128D818)
+
+struct ADC128D818State {
+    I2CSlave parent_obj;
+
+    qemu_irq irq;
+
+    uint8_t len;
+    uint8_t pointer;
+    uint8_t rx_byte;
+
+    uint8_t regs[ADC128D818_NUM_REGS];
+    uint16_t channel[ADC128D818_NUM_CHANNELS];
+
+    int16_t ain[ADC128D818_NUM_CHANNELS]; /* mV */
+    int32_t temperature; /* milli-degrees Celsius */
+    uint16_t ext_vref; /* mV, 0 means not connected */
+    bool temp_alarm; /* temperature high-limit alarm latched */
+
+    char *description;
+};
+
+static uint16_t adc128d818_get_vref(const ADC128D818State *s)
+{
+    if (s->regs[REG_ADV_CONFIG] & ADV_CONFIG_EXT_REF_EN) {
+        if (s->ext_vref > 0u) {
+            return s->ext_vref;
+        }
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: %s: external VREF selected but not"
+                      " connected, falling back to internal\n",
+                      __func__, s->description);
+    }
+
+    return ADC128D818_INTERNAL_VREF_MV;
+}
+
+static uint8_t adc128d818_get_mode(const ADC128D818State *s)
+{
+    return (s->regs[REG_ADV_CONFIG] & ADV_CONFIG_MODE_MASK) >>
+           ADV_CONFIG_MODE_SHIFT;
+}
+
+static bool adc128d818_is_temp_channel(const ADC128D818State *s, unsigned ch)
+{
+    if (ch != 7u) {
+        return false;
+    }
+
+    return adc128d818_get_mode(s) != 1u;
+}
+
+static bool adc128d818_is_reserved_channel(const ADC128D818State *s,
+                                           unsigned ch)
+{
+    switch (adc128d818_get_mode(s)) {
+    case 2u:
+        return ch >= 4u && ch <= 6u;
+    case 3u:
+        return ch == 6u;
+    default:
+        return false;
+    }
+}
+
+static int16_t adc128d818_channel_voltage(const ADC128D818State *s, unsigned ch)
+{
+    switch (adc128d818_get_mode(s)) {
+    case 2u:
+        switch (ch) {
+        case 0u:
+            return (int16_t)(s->ain[0] - s->ain[1]);
+        case 1u:
+            return (int16_t)(s->ain[3] - s->ain[2]);
+        case 2u:
+            return (int16_t)(s->ain[4] - s->ain[5]);
+        case 3u:
+            return (int16_t)(s->ain[7] - s->ain[6]);
+        default:
+            return 0;
+        }
+    case 3u:
+        switch (ch) {
+        case 4u:
+            return (int16_t)(s->ain[4] - s->ain[5]);
+        case 5u:
+            return (int16_t)(s->ain[7] - s->ain[6]);
+        default:
+            return s->ain[ch];
+        }
+    default:
+        return s->ain[ch];
+    }
+}
+
+static void adc128d818_update_irq(ADC128D818State *s)
+{
+    uint8_t cfg = s->regs[REG_CONFIG];
+    uint8_t active;
+    bool level;
+
+    active = s->regs[REG_INT_STATUS] & ~s->regs[REG_INT_MASK];
+
+    /* INT pin is active-low */
+    level = !((cfg & CONFIG_INT_ENABLE) && !(cfg & CONFIG_INT_CLEAR) &&
+              (active != 0u));
+
+    trace_adc128d818_irq(s->description, level);
+    qemu_set_irq(s->irq, level);
+}
+
+static bool adc128d818_monitoring_active(const ADC128D818State *s)
+{
+    if (s->regs[REG_DEEP_SHUTDOWN] & DEEP_SHUTDOWN_EN) {
+        return false;
+    }
+    if (!(s->regs[REG_CONFIG] & CONFIG_START)) {
+        return false;
+    }
+    if (s->regs[REG_CONFIG] & CONFIG_INT_CLEAR) {
+        return false;
+    }
+
+    return true;
+}
+
+static void adc128d818_check_limits(ADC128D818State *s)
+{
+    uint8_t disabled = s->regs[REG_CH_DISABLE];
+    uint8_t int_status = 0u;
+
+    for (unsigned ch = 0u; ch < ADC128D818_NUM_CHANNELS; ch++) {
+        if ((disabled & (1u << ch)) ||
+            adc128d818_is_reserved_channel(s, ch)) {
+            continue;
+        }
+
+        if (adc128d818_is_temp_channel(s, ch)) {
+            int raw = s->temperature / ADC128D818_TEMP_LSB_MC;
+            int thot;
+            int thyst;
+
+            raw = MAX(ADC128D818_TEMP_RAW_MIN,
+                      MIN(ADC128D818_TEMP_RAW_MAX, raw));
+            thot = (int)(int8_t)s->regs[REG_LIMIT_BASE + ch * 2u] * 2;
+            thyst = (int)(int8_t)s->regs[REG_LIMIT_BASE + ch * 2u + 1u] * 2;
+
+            if (raw > thot) {
+                s->temp_alarm = true;
+            } else if (raw <= thyst) {
+                s->temp_alarm = false;
+            }
+            if (s->temp_alarm) {
+                int_status |= (1u << ch);
+            }
+        } else {
+            uint8_t msb = (uint8_t)(s->channel[ch] >> 8u);
+            uint8_t high_lim = s->regs[REG_LIMIT_BASE + ch * 2u];
+            uint8_t low_lim = s->regs[REG_LIMIT_BASE + ch * 2u + 1u];
+
+            if (msb > high_lim || msb <= low_lim) {
+                int_status |= (1u << ch);
+            }
+        }
+    }
+
+    s->regs[REG_INT_STATUS] = int_status;
+    adc128d818_update_irq(s);
+}
+
+static void adc128d818_convert(ADC128D818State *s)
+{
+    uint8_t disabled;
+    uint16_t vref;
+
+    disabled = s->regs[REG_CH_DISABLE];
+    vref = adc128d818_get_vref(s);
+
+    for (unsigned ch = 0u; ch < ADC128D818_NUM_CHANNELS; ch++) {
+        if ((disabled & (1u << ch)) ||
+            adc128d818_is_reserved_channel(s, ch)) {
+            continue;
+        }
+
+        if (adc128d818_is_temp_channel(s, ch)) {
+            int32_t raw = s->temperature / ADC128D818_TEMP_LSB_MC;
+
+            raw =
+                MAX(ADC128D818_TEMP_RAW_MIN, MIN(ADC128D818_TEMP_RAW_MAX, raw));
+            s->channel[ch] = (uint16_t)(((unsigned)raw & 0x1FFu) << 7u);
+        } else {
+            int16_t vin = adc128d818_channel_voltage(s, ch);
+            int32_t dout;
+
+            dout = vin * (int32_t)ADC128D818_ADC_RESOLUTION / vref;
+            dout = MAX(0, MIN((int32_t)ADC128D818_ADC_MAX, dout));
+            s->channel[ch] = (uint16_t)(dout << 4u);
+        }
+
+        trace_adc128d818_convert(s->description, ch, s->channel[ch]);
+    }
+
+    s->regs[REG_BUSY_STATUS] &= ~BUSY_STATUS_NOT_READY;
+
+    adc128d818_check_limits(s);
+}
+
+static uint8_t adc128d818_read_channel(ADC128D818State *s, unsigned ch)
+{
+    uint8_t val;
+
+    if (s->rx_byte == 0u) {
+        val = (uint8_t)(s->channel[ch] >> 8u);
+        trace_adc128d818_read_channel(s->description, ch, s->channel[ch]);
+    } else {
+        val = (uint8_t)(s->channel[ch] & 0xFFu);
+    }
+    s->rx_byte ^= 1u;
+
+    return val;
+}
+
+static uint8_t adc128d818_read_reg(ADC128D818State *s, uint8_t reg)
+{
+    uint8_t val;
+
+    switch (reg) {
+    case REG_INT_STATUS:
+        val = s->regs[REG_INT_STATUS];
+        s->regs[REG_INT_STATUS] = 0x00u;
+        if (adc128d818_monitoring_active(s)) {
+            adc128d818_check_limits(s);
+        } else {
+            adc128d818_update_irq(s);
+        }
+        trace_adc128d818_read(s->description, reg, val);
+        return val;
+    case REG_CONFIG:
+    case REG_INT_MASK:
+    case REG_CONV_RATE:
+    case REG_CH_DISABLE:
+    case REG_ONE_SHOT:
+    case REG_DEEP_SHUTDOWN:
+    case REG_ADV_CONFIG:
+    case REG_BUSY_STATUS:
+    case REG_LIMIT_BASE ... REG_LIMIT_LAST:
+    case REG_MANUFACTURER_ID:
+    case REG_REVISION_ID:
+        trace_adc128d818_read(s->description, reg, s->regs[reg]);
+        return s->regs[reg];
+    case REG_CH_READING_BASE ... REG_CH_READING_LAST:
+        return adc128d818_read_channel(s, reg - REG_CH_READING_BASE);
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: %s: read from undefined register 0x%02x\n",
+                      __func__, s->description, reg);
+        return 0x00u;
+    }
+}
+
+static void adc128d818_write_reg(ADC128D818State *s, uint8_t reg, uint8_t val);
+
+static void adc128d818_reset_regs(ADC128D818State *s)
+{
+    memset(s->regs, 0, sizeof(s->regs));
+    memset(s->channel, 0, sizeof(s->channel));
+    s->temp_alarm = false;
+
+    s->regs[REG_CONFIG] = 0x08u;
+    s->regs[REG_BUSY_STATUS] = 0x02u;
+    s->regs[REG_MANUFACTURER_ID] = ADC128D818_MANUFACTURER_ID_VAL;
+    s->regs[REG_REVISION_ID] = ADC128D818_REVISION_ID_VAL;
+
+    for (unsigned ch = 0u; ch < ADC128D818_NUM_CHANNELS; ch++) {
+        s->regs[REG_LIMIT_BASE + ch * 2u] = 0xFFu;
+    }
+
+    s->pointer = 0x00u;
+    s->len = 0u;
+    s->rx_byte = 0u;
+
+    adc128d818_update_irq(s);
+}
+
+static void adc128d818_write_reg(ADC128D818State *s, uint8_t reg, uint8_t val)
+{
+    trace_adc128d818_write(s->description, reg, val);
+
+    switch (reg) {
+    case REG_CONFIG:
+        if (val & CONFIG_INITIALIZATION) {
+            trace_adc128d818_reset(s->description, "reg");
+            adc128d818_reset_regs(s);
+            break;
+        }
+        s->regs[REG_CONFIG] = val & CONFIG_WR_MASK;
+        if ((val & CONFIG_START) && !(val & CONFIG_INT_CLEAR) &&
+            !(s->regs[REG_DEEP_SHUTDOWN] & DEEP_SHUTDOWN_EN)) {
+            adc128d818_convert(s);
+        }
+        adc128d818_update_irq(s);
+        break;
+    case REG_INT_MASK:
+        s->regs[REG_INT_MASK] = val;
+        adc128d818_update_irq(s);
+        break;
+    case REG_CONV_RATE:
+        if (s->regs[REG_CONFIG] & CONFIG_START) {
+            qemu_log_mask(LOG_GUEST_ERROR,
+                          "%s: %s: CONV_RATE written while running\n",
+                          __func__, s->description);
+            break;
+        }
+        s->regs[REG_CONV_RATE] = val & CONV_RATE_MASK;
+        break;
+    case REG_CH_DISABLE:
+        if (s->regs[REG_CONFIG] & CONFIG_START) {
+            qemu_log_mask(LOG_GUEST_ERROR,
+                          "%s: %s: CH_DISABLE written while running\n",
+                          __func__, s->description);
+            break;
+        }
+        s->regs[REG_CH_DISABLE] = val;
+        memset(s->channel, 0, sizeof(s->channel));
+        s->regs[REG_INT_STATUS] = 0x00u;
+        s->temp_alarm = false;
+        adc128d818_update_irq(s);
+        break;
+    case REG_ONE_SHOT:
+        if (!(s->regs[REG_CONFIG] & CONFIG_START)) {
+            adc128d818_convert(s);
+        }
+        break;
+    case REG_DEEP_SHUTDOWN:
+        if ((val & DEEP_SHUTDOWN_EN) && (s->regs[REG_CONFIG] & CONFIG_START)) {
+            qemu_log_mask(LOG_GUEST_ERROR,
+                          "%s: %s: DEEP_SHUTDOWN set while running\n",
+                          __func__, s->description);
+            break;
+        }
+        s->regs[REG_DEEP_SHUTDOWN] = val & DEEP_SHUTDOWN_EN;
+        break;
+    case REG_ADV_CONFIG:
+        if (s->regs[REG_CONFIG] & CONFIG_START) {
+            qemu_log_mask(LOG_GUEST_ERROR,
+                          "%s: %s: ADV_CONFIG written while running\n",
+                          __func__, s->description);
+            break;
+        }
+        s->regs[REG_ADV_CONFIG] = val & ADV_CONFIG_WR_MASK;
+        memset(s->channel, 0, sizeof(s->channel));
+        s->regs[REG_INT_STATUS] = 0x00u;
+        s->temp_alarm = false;
+        adc128d818_update_irq(s);
+        break;
+    case REG_LIMIT_BASE ... REG_LIMIT_LAST:
+        s->regs[reg] = val;
+        break;
+    case REG_INT_STATUS:
+    case REG_BUSY_STATUS:
+    case REG_MANUFACTURER_ID:
+    case REG_REVISION_ID:
+    case REG_CH_READING_BASE ... REG_CH_READING_LAST:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: %s: write to read-only register 0x%02x\n",
+                      __func__, s->description, reg);
+        break;
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: %s: write to undefined register 0x%02x\n",
+                      __func__, s->description, reg);
+        break;
+    }
+}
+
+static uint8_t adc128d818_recv(I2CSlave *i2c)
+{
+    ADC128D818State *s = ADC128D818(i2c);
+
+    return adc128d818_read_reg(s, s->pointer);
+}
+
+static int adc128d818_send(I2CSlave *i2c, uint8_t data)
+{
+    ADC128D818State *s = ADC128D818(i2c);
+
+    if (s->len == 0u) {
+        s->pointer = data;
+        s->len++;
+    } else {
+        adc128d818_write_reg(s, s->pointer, data);
+    }
+
+    return 0;
+}
+
+static int adc128d818_event(I2CSlave *i2c, enum i2c_event event)
+{
+    ADC128D818State *s = ADC128D818(i2c);
+
+    s->len = 0u;
+    s->rx_byte = 0u;
+
+    return 0;
+}
+
+static void adc128d818_get_ain(Object *obj, Visitor *v, const char *name,
+                               void *opaque, Error **errp)
+{
+    ADC128D818State *s = ADC128D818(obj);
+    int64_t value;
+    int ch_num;
+    int rc;
+
+    rc = sscanf(name, "ain%d", &ch_num);
+    if (rc != 1 || ch_num < 0 || ch_num >= (int)ADC128D818_NUM_CHANNELS) {
+        error_setg(errp, "%s: %s: invalid channel '%s'", __func__,
+                   s->description, name);
+        return;
+    }
+
+    value = s->ain[ch_num];
+    visit_type_int(v, name, &value, errp);
+}
+
+static void adc128d818_set_ain(Object *obj, Visitor *v, const char *name,
+                               void *opaque, Error **errp)
+{
+    ADC128D818State *s = ADC128D818(obj);
+    int64_t value;
+    int ch_num;
+    int rc;
+
+    if (!visit_type_int(v, name, &value, errp)) {
+        return;
+    }
+
+    rc = sscanf(name, "ain%d", &ch_num);
+    if (rc != 1 || ch_num < 0 || ch_num >= (int)ADC128D818_NUM_CHANNELS) {
+        error_setg(errp, "%s: %s: invalid channel '%s'", __func__,
+                   s->description, name);
+        return;
+    }
+
+    if (value < INT16_MIN || value > INT16_MAX) {
+        error_setg(errp, "%s: %s: value %" PRId64 " out of range for '%s'",
+                   __func__, s->description, value, name);
+        return;
+    }
+
+    s->ain[ch_num] = (int16_t)value;
+
+    if (adc128d818_monitoring_active(s)) {
+        adc128d818_convert(s);
+    }
+}
+
+static void adc128d818_get_temperature(
+    Object *obj, Visitor *v, const char *name, void *opaque, Error **errp)
+{
+    ADC128D818State *s = ADC128D818(obj);
+    int64_t value = s->temperature;
+
+    visit_type_int(v, name, &value, errp);
+}
+
+static void adc128d818_set_temperature(
+    Object *obj, Visitor *v, const char *name, void *opaque, Error **errp)
+{
+    ADC128D818State *s = ADC128D818(obj);
+    int64_t value;
+
+    if (!visit_type_int(v, name, &value, errp)) {
+        return;
+    }
+
+    if (value < INT32_MIN || value > INT32_MAX) {
+        error_setg(errp, "%s: %s: value %" PRId64 " out of range", __func__,
+                   s->description, value);
+        return;
+    }
+
+    s->temperature = (int32_t)value;
+
+    if (adc128d818_monitoring_active(s)) {
+        adc128d818_convert(s);
+    }
+}
+
+static const VMStateDescription adc128d818_vmstate = {
+    .name = "ADC128D818",
+    .version_id = 0,
+    .minimum_version_id = 0,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT8(len, ADC128D818State),
+        VMSTATE_UINT8(pointer, ADC128D818State),
+        VMSTATE_UINT8(rx_byte, ADC128D818State),
+        VMSTATE_UINT8_ARRAY(regs, ADC128D818State,
+                            ADC128D818_NUM_REGS),
+        VMSTATE_UINT16_ARRAY(channel, ADC128D818State,
+                             ADC128D818_NUM_CHANNELS),
+        VMSTATE_INT16_ARRAY(ain, ADC128D818State,
+                            ADC128D818_NUM_CHANNELS),
+        VMSTATE_INT32(temperature, ADC128D818State),
+        VMSTATE_UINT16(ext_vref, ADC128D818State),
+        VMSTATE_BOOL(temp_alarm, ADC128D818State),
+        VMSTATE_I2C_SLAVE(parent_obj, ADC128D818State),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static void adc128d818_reset_hold(Object *obj, ResetType type)
+{
+    ADC128D818State *s = ADC128D818(obj);
+
+    trace_adc128d818_reset(s->description, "hw");
+    adc128d818_reset_regs(s);
+}
+
+static void adc128d818_get_ext_vref(
+    Object *obj, Visitor *v, const char *name, void *opaque, Error **errp)
+{
+    ADC128D818State *s = ADC128D818(obj);
+    int64_t value = (int64_t)s->ext_vref;
+
+    visit_type_int(v, name, &value, errp);
+}
+
+static void adc128d818_set_ext_vref(
+    Object *obj, Visitor *v, const char *name, void *opaque, Error **errp)
+{
+    ADC128D818State *s = ADC128D818(obj);
+    int64_t value;
+
+    if (!visit_type_int(v, name, &value, errp)) {
+        return;
+    }
+
+    if (value < 0 || value > ADC128D818_MAX_VDD_MV) {
+        error_setg(errp,
+                   "%s: %s: ext-vref-mv %" PRId64 " out of range (0..%u mV)",
+                   __func__, s->description, value, ADC128D818_MAX_VDD_MV);
+        return;
+    }
+
+    s->ext_vref = (uint16_t)value;
+
+    if (adc128d818_monitoring_active(s)) {
+        adc128d818_convert(s);
+    }
+}
+
+static void adc128d818_initfn(Object *obj)
+{
+    for (unsigned ch = 0u; ch < ADC128D818_NUM_CHANNELS; ch++) {
+        char *name = g_strdup_printf("ain%u", ch);
+
+        object_property_add(obj, name, "int", adc128d818_get_ain,
+                            adc128d818_set_ain, NULL, NULL);
+        g_free(name);
+    }
+
+    object_property_add(obj, "temperature", "int", adc128d818_get_temperature,
+                        adc128d818_set_temperature, NULL, NULL);
+    object_property_add(obj, "ext-vref-mv", "int", adc128d818_get_ext_vref,
+                        adc128d818_set_ext_vref, NULL, NULL);
+}
+
+static void adc128d818_realize(DeviceState *dev, Error **errp)
+{
+    ADC128D818State *s = ADC128D818(dev);
+
+    if (!s->description) {
+        s->description = g_strdup(object_get_typename(OBJECT(dev)));
+    }
+
+    qdev_init_gpio_out(dev, &s->irq, 1u);
+}
+
+static const Property adc128d818_properties[] = {
+    DEFINE_PROP_STRING("description", ADC128D818State, description),
+};
+
+static void adc128d818_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    I2CSlaveClass *ic = I2C_SLAVE_CLASS(klass);
+    ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+    ic->event = adc128d818_event;
+    ic->recv = adc128d818_recv;
+    ic->send = adc128d818_send;
+    dc->realize = adc128d818_realize;
+    rc->phases.hold = adc128d818_reset_hold;
+    dc->vmsd = &adc128d818_vmstate;
+    device_class_set_props(dc, adc128d818_properties);
+}
+
+static const TypeInfo adc128d818_types[] = {
+    {
+        .name          = TYPE_ADC128D818,
+        .parent        = TYPE_I2C_SLAVE,
+        .instance_init = adc128d818_initfn,
+        .instance_size = sizeof(ADC128D818State),
+        .class_init    = adc128d818_class_init,
+    },
+};
+
+DEFINE_TYPES(adc128d818_types)
diff --git a/hw/sensor/meson.build b/hw/sensor/meson.build
index 420fdc3..fe36c9e 100644
--- a/hw/sensor/meson.build
+++ b/hw/sensor/meson.build
@@ -1,3 +1,4 @@
+system_ss.add(when: 'CONFIG_ADC128D818', if_true: files('adc128d818.c'))
 system_ss.add(when: 'CONFIG_TMP105', if_true: files('tmp105.c'))
 system_ss.add(when: 'CONFIG_TMP421', if_true: files('tmp421.c'))
 system_ss.add(when: 'CONFIG_DPS310', if_true: files('dps310.c'))
diff --git a/hw/sensor/trace-events b/hw/sensor/trace-events
index a3fe54f..5a3630f 100644
--- a/hw/sensor/trace-events
+++ b/hw/sensor/trace-events
@@ -1,5 +1,13 @@
 # See docs/devel/tracing.rst for syntax documentation.
 
+# adc128d818.c
+adc128d818_read(const char *id, uint8_t reg, uint8_t value) "%s reg 0x%02x val 0x%02x"
+adc128d818_read_channel(const char *id, uint8_t channel, uint16_t value) "%s ch %u val 0x%04x"
+adc128d818_write(const char *id, uint8_t reg, uint8_t value) "%s reg 0x%02x val 0x%02x"
+adc128d818_convert(const char *id, uint8_t channel, uint16_t value) "%s ch %u val 0x%04x"
+adc128d818_irq(const char *id, bool level) "%s level %u"
+adc128d818_reset(const char *id, const char *source) "%s %s"
+
 # tmp105.c
 tmp105_read(uint8_t dev, uint8_t addr) "device: 0x%02x, addr: 0x%02x"
 tmp105_write(uint8_t dev, uint8_t addr) "device: 0x%02x, addr 0x%02x"
diff --git a/hw/ssi/aspeed_smc.c b/hw/ssi/aspeed_smc.c
index c8cc6cf..bf596f7 100644
--- a/hw/ssi/aspeed_smc.c
+++ b/hw/ssi/aspeed_smc.c
@@ -163,6 +163,9 @@
 /* Read Timing Compensation Register */
 #define R_TIMINGS         (0x94 / 4)
 
+/* Data fifo */
+#define R_DATA_FIFO       (0x200 / 4)
+
 /* SPI controller registers and bits (AST2400) */
 #define R_SPI_CONF        (0x00 / 4)
 #define   SPI_CONF_ENABLE_W0   0
@@ -209,6 +212,7 @@
 #define ASPEED_SMC_FEATURE_DMA_GRANT 0x2
 #define ASPEED_SMC_FEATURE_WDT_CONTROL 0x4
 #define ASPEED_SMC_FEATURE_DMA_DRAM_ADDR_HIGH 0x08
+#define ASPEED_SMC_FEATURE_DATA_FIFO 0x10
 
 static inline bool aspeed_smc_has_dma(const AspeedSMCClass *asc)
 {
@@ -225,6 +229,11 @@
     return !!(asc->features & ASPEED_SMC_FEATURE_DMA_DRAM_ADDR_HIGH);
 }
 
+static inline bool aspeed_smc_has_data_fifo(const AspeedSMCClass *asc)
+{
+    return !!(asc->features & ASPEED_SMC_FEATURE_DATA_FIFO);
+}
+
 #define aspeed_smc_error(fmt, ...)                                      \
     qemu_log_mask(LOG_GUEST_ERROR, "%s: " fmt "\n", __func__, ## __VA_ARGS__)
 
@@ -664,6 +673,7 @@
 {
     AspeedSMCState *s = ASPEED_SMC(opaque);
     AspeedSMCClass *asc = ASPEED_SMC_GET_CLASS(opaque);
+    int cs;
 
     addr >>= 2;
 
@@ -689,6 +699,18 @@
         trace_aspeed_smc_read(addr << 2, size, s->regs[addr]);
 
         *data = s->regs[addr];
+    } else if (aspeed_smc_has_data_fifo(asc) && addr >= R_DATA_FIFO) {
+        cs = asc->data_fifo_offset_to_cs(s, addr << 2);
+        if (cs >= 0) {
+            /*
+             * Data fifo mode only supports SPI user mode.
+             * The flash address is provided by the SPI command/address cycles,
+             * the MMIO addr parameter is ignored.
+             */
+            return aspeed_smc_flash_read(&s->flashes[cs], 0, data, size, attrs);
+        }
+        aspeed_smc_error("Invalid data fifo offset %" HWADDR_PRIx, addr << 2);
+        return MEMTX_ERROR;
     } else {
         qemu_log_mask(LOG_UNIMP, "%s: not implemented: 0x%" HWADDR_PRIx "\n",
                       __func__, addr);
@@ -1063,6 +1085,19 @@
     } else if (aspeed_smc_has_dma(asc) && aspeed_smc_has_dma64(asc) &&
                addr == R_DMA_DRAM_ADDR_HIGH) {
         s->regs[addr] = DMA_DRAM_ADDR_HIGH(value);
+    } else if (aspeed_smc_has_data_fifo(asc) && addr >= R_DATA_FIFO) {
+        int cs = asc->data_fifo_offset_to_cs(s, addr << 2);
+        if (cs >= 0) {
+            /*
+             * Data fifo mode only supports SPI user mode.
+             * The flash address is provided by the SPI command/address cycles,
+             * the MMIO addr parameter is ignored.
+             */
+            return aspeed_smc_flash_write(&s->flashes[cs], 0, data, size,
+                                          attrs);
+        }
+        aspeed_smc_error("Invalid data fifo offset %" HWADDR_PRIx, addr << 2);
+        return MEMTX_ERROR;
     } else {
         qemu_log_mask(LOG_UNIMP, "%s: not implemented: 0x%" HWADDR_PRIx "\n",
                       __func__, addr);
@@ -1183,8 +1218,8 @@
 
 static const VMStateDescription vmstate_aspeed_smc = {
     .name = "aspeed.smc",
-    .version_id = 3,
-    .minimum_version_id = 1,
+    .version_id = 4,
+    .minimum_version_id = 2,
     .fields = (const VMStateField[]) {
         VMSTATE_UINT32_ARRAY(regs, AspeedSMCState, ASPEED_SMC_R_MAX),
         VMSTATE_UNUSED_V(2, 2), /* was snoop_index/snoop_dummies */
@@ -1808,6 +1843,39 @@
     }
 }
 
+/*
+ * Convert a data fifo offset to a chip select (CS).
+ *
+ * Data fifo access starts at 0x200. The data fifo offset index is
+ * calculated by subtracting the data fifo base offset from the MMIO address.
+ *
+ * The data fifo offset index increments by 1 for every 16MB of flash address
+ * space. Each offset step therefore represents a 16MB address decode range.
+ *
+ * The CS is determined by matching the data fifo offset index against the
+ * segment start address of each CS.
+ *
+ * Returns the CS index on success, or -1 if the offset is invalid.
+ */
+static int aspeed_2700_smc_data_fifo_offset_to_cs(const AspeedSMCState *s,
+                                                  uint32_t offset)
+{
+    AspeedSMCClass *asc = ASPEED_SMC_GET_CLASS(s);
+    uint32_t start_offset;
+    uint32_t fifo_offset;
+    int i;
+
+    for (i = 0; i < asc->cs_num_max; i++) {
+        start_offset = (s->regs[R_SEG_ADDR0 + i] & 0x0000ffff) << 16;
+        fifo_offset = start_offset / 0x1000000;
+        if (fifo_offset == offset - (R_DATA_FIFO << 2)) {
+            return i;
+        }
+    }
+
+    return -1;
+}
+
 static const uint32_t aspeed_2700_fmc_resets[ASPEED_SMC_R_MAX] = {
     [R_CONF] = (CONF_FLASH_TYPE_SPI << CONF_FLASH_TYPE0 |
             CONF_FLASH_TYPE_SPI << CONF_FLASH_TYPE1),
@@ -1842,6 +1910,27 @@
     { 0x0, 0 }, /* disabled */
 };
 
+/*
+ * AST2700 supports data fifo mode with a base data fifo start offset of 0x200.
+ *
+ * The data fifo start offset increments by 1 for every 16MB of flash address
+ * space. Each offset step therefore represents a 16MB address decode range.
+ *
+ * Assuming each chip select (CS) can use the maximum flash size of 256MB:
+ *   256MB / 16MB = 0x10 offset steps per CS.
+ *
+ * Data fifo start offset for CSn:
+ *   0x200 + (n * 0x10)
+ *
+ * Examples:
+ *   CS0: 0x200
+ *   CS1: 0x210
+ *   CS2: 0x220
+ *   CS3: 0x230
+ *
+ * asc->nregs should be set to: 0x200 + (asc->cs_num_max * 0x10)
+ * to cover all possible data fifo regions.
+ */
 static void aspeed_2700_fmc_class_init(ObjectClass *klass, const void *data)
 {
     DeviceClass *dc = DEVICE_CLASS(klass);
@@ -1861,14 +1950,16 @@
     asc->flash_window_base = 0x100000000;
     asc->flash_window_size = 1 * GiB;
     asc->features          = ASPEED_SMC_FEATURE_DMA |
-                             ASPEED_SMC_FEATURE_DMA_DRAM_ADDR_HIGH;
+                             ASPEED_SMC_FEATURE_DMA_DRAM_ADDR_HIGH |
+                             ASPEED_SMC_FEATURE_DATA_FIFO;
     asc->dma_flash_mask    = 0x2FFFFFFC;
     asc->dma_dram_mask     = 0xFFFFFFFC;
     asc->dma_start_length  = 1;
-    asc->nregs             = ASPEED_SMC_R_MAX;
+    asc->nregs             = (0x200 + (asc->cs_num_max * 0x10)) >> 2;
     asc->segment_to_reg    = aspeed_2700_smc_segment_to_reg;
     asc->reg_to_segment    = aspeed_2700_smc_reg_to_segment;
     asc->dma_ctrl          = aspeed_2600_smc_dma_ctrl;
+    asc->data_fifo_offset_to_cs = aspeed_2700_smc_data_fifo_offset_to_cs;
     asc->reg_ops           = &aspeed_2700_smc_flash_ops;
 }
 
@@ -1896,14 +1987,16 @@
     asc->flash_window_base = 0x180000000;
     asc->flash_window_size = 1 * GiB;
     asc->features          = ASPEED_SMC_FEATURE_DMA |
-                             ASPEED_SMC_FEATURE_DMA_DRAM_ADDR_HIGH;
+                             ASPEED_SMC_FEATURE_DMA_DRAM_ADDR_HIGH |
+                             ASPEED_SMC_FEATURE_DATA_FIFO;
     asc->dma_flash_mask    = 0x2FFFFFFC;
     asc->dma_dram_mask     = 0xFFFFFFFC;
     asc->dma_start_length  = 1;
-    asc->nregs             = ASPEED_SMC_R_MAX;
+    asc->nregs             = (0x200 + (asc->cs_num_max * 0x10)) >> 2;
     asc->segment_to_reg    = aspeed_2700_smc_segment_to_reg;
     asc->reg_to_segment    = aspeed_2700_smc_reg_to_segment;
     asc->dma_ctrl          = aspeed_2600_smc_dma_ctrl;
+    asc->data_fifo_offset_to_cs = aspeed_2700_smc_data_fifo_offset_to_cs;
     asc->reg_ops           = &aspeed_2700_smc_flash_ops;
 }
 
@@ -1930,14 +2023,16 @@
     asc->flash_window_base = 0x200000000;
     asc->flash_window_size = 1 * GiB;
     asc->features          = ASPEED_SMC_FEATURE_DMA |
-                             ASPEED_SMC_FEATURE_DMA_DRAM_ADDR_HIGH;
+                             ASPEED_SMC_FEATURE_DMA_DRAM_ADDR_HIGH |
+                             ASPEED_SMC_FEATURE_DATA_FIFO;
     asc->dma_flash_mask    = 0x2FFFFFFC;
     asc->dma_dram_mask     = 0xFFFFFFFC;
     asc->dma_start_length  = 1;
-    asc->nregs             = ASPEED_SMC_R_MAX;
+    asc->nregs             = (0x200 + (asc->cs_num_max * 0x10)) >> 2;
     asc->segment_to_reg    = aspeed_2700_smc_segment_to_reg;
     asc->reg_to_segment    = aspeed_2700_smc_reg_to_segment;
     asc->dma_ctrl          = aspeed_2600_smc_dma_ctrl;
+    asc->data_fifo_offset_to_cs = aspeed_2700_smc_data_fifo_offset_to_cs;
     asc->reg_ops           = &aspeed_2700_smc_flash_ops;
 }
 
@@ -1964,14 +2059,16 @@
     asc->flash_window_base = 0x280000000;
     asc->flash_window_size = 1 * GiB;
     asc->features          = ASPEED_SMC_FEATURE_DMA |
-                             ASPEED_SMC_FEATURE_DMA_DRAM_ADDR_HIGH;
+                             ASPEED_SMC_FEATURE_DMA_DRAM_ADDR_HIGH |
+                             ASPEED_SMC_FEATURE_DATA_FIFO;
     asc->dma_flash_mask    = 0x0FFFFFFC;
     asc->dma_dram_mask     = 0xFFFFFFFC;
     asc->dma_start_length  = 1;
-    asc->nregs             = ASPEED_SMC_R_MAX;
+    asc->nregs             = (0x200 + (asc->cs_num_max * 0x10)) >> 2;
     asc->segment_to_reg    = aspeed_2700_smc_segment_to_reg;
     asc->reg_to_segment    = aspeed_2700_smc_reg_to_segment;
     asc->dma_ctrl          = aspeed_2600_smc_dma_ctrl;
+    asc->data_fifo_offset_to_cs = aspeed_2700_smc_data_fifo_offset_to_cs;
     asc->reg_ops           = &aspeed_2700_smc_flash_ops;
 }
 
diff --git a/hw/timer/Kconfig b/hw/timer/Kconfig
index b3d823c..e1b751a 100644
--- a/hw/timer/Kconfig
+++ b/hw/timer/Kconfig
@@ -65,3 +65,6 @@
 
 config AVR_TIMER16
     bool
+
+config HEX_QTIMER
+    bool
diff --git a/hw/timer/meson.build b/hw/timer/meson.build
index 201b5d8..8323efa 100644
--- a/hw/timer/meson.build
+++ b/hw/timer/meson.build
@@ -34,3 +34,5 @@
 system_ss.add(when: 'CONFIG_SIFIVE_PWM', if_true: files('sifive_pwm.c'))
 
 system_ss.add(when: 'CONFIG_AVR_TIMER16', if_true: files('avr_timer16.c'))
+
+system_ss.add(when: 'CONFIG_HEX_QTIMER', if_true: files('qct-qtimer.c'))
diff --git a/hw/timer/qct-qtimer.c b/hw/timer/qct-qtimer.c
new file mode 100644
index 0000000..f628e8d
--- /dev/null
+++ b/hw/timer/qct-qtimer.c
@@ -0,0 +1,667 @@
+/*
+ * Qualcomm QCT QTimer
+ *
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/core/irq.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/core/sysbus.h"
+#include "hw/timer/qct-qtimer.h"
+#include "migration/vmstate.h"
+#include "qemu/bitops.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qemu/timer.h"
+#include "qapi/error.h"
+#include "trace.h"
+
+#define QTIMER_MEM_SIZE_BYTES 0x1000
+#define QTIMER_DEFAULT_FREQ_HZ 19200000ULL
+
+#define QCT_QTIMER_TIMER_FRAME_ELTS (16)
+#define QCT_QTIMER_TIMER_VIEW_ELTS (2)
+
+#define QCT_QTIMER_AC_CNTFRQ (0x000)
+#define QCT_QTIMER_AC_CNTSR (0x004)
+#define QCT_QTIMER_AC_CNTTID_0 (0x08)
+#define QCT_QTIMER_AC_CNTACR_START (0x40)
+#define QCT_QTIMER_AC_CNTACR_END (0x5c)
+#define QCT_QTIMER_AC_CNTTID_1 (0x108)
+#define QCT_QTIMER_AC_CNTACR_RWPT (1 << 5) /* R/W of CNTP_* regs */
+#define QCT_QTIMER_AC_CNTACR_RWVT (1 << 4) /* R/W of CNTV_* regs */
+#define QCT_QTIMER_AC_CNTACR_RVOFF (1 << 3) /* R/W of CNTVOFF register */
+#define QCT_QTIMER_AC_CNTACR_RFRQ (1 << 2) /* R/W of CNTFRQ register */
+#define QCT_QTIMER_AC_CNTACR_RPVCT (1 << 1) /* R/W of CNTVCT register */
+#define QCT_QTIMER_AC_CNTACR_RPCT (1 << 0) /* R/W of CNTPCT register */
+#define QCT_QTIMER_VERSION (0x0fd0)
+
+#define QCT_QTIMER_CNTPCT_LO (0x000)
+#define QCT_QTIMER_CNTPCT_HI (0x004)
+#define QCT_QTIMER_CNT_FREQ (0x010)
+#define QCT_QTIMER_CNTPL0ACR (0x014)
+#define QCT_QTIMER_CNTPL0ACR_PL0CTEN (1 << 9)
+#define QCT_QTIMER_CNTPL0ACR_PL0TVEN (1 << 8)
+#define QCT_QTIMER_CNTPL0ACR_PL0VCTEN (1 << 1)
+#define QCT_QTIMER_CNTPL0ACR_PL0PCTEN (1 << 0)
+#define QCT_QTIMER_CNTP_CVAL_LO (0x020)
+#define QCT_QTIMER_CNTP_CVAL_HI (0x024)
+#define QCT_QTIMER_CNT_MASK 0x00ffffffffffffffULL
+#define QCT_QTIMER_CNT_HI_BITS 24
+#define QCT_QTIMER_CNTP_TVAL (0x028)
+#define QCT_QTIMER_CNTP_CTL (0x02c)
+#define QCT_QTIMER_CNTP_CTL_ENABLE (1 << 0)
+#define QCT_QTIMER_CNTP_CTL_INTEN (1 << 1)
+#define QCT_QTIMER_CNTP_CTL_ISTAT (1 << 2)
+
+OBJECT_DECLARE_SIMPLE_TYPE(QCTQtimerState, QCT_QTIMER)
+
+typedef struct QCTHextimerState {
+    QCTQtimerState *qtimer;
+    QEMUTimer *timer;       /* one-shot deadline timer */
+    int64_t offset_ns;      /* QEMU_CLOCK_VIRTUAL ns at which cntpct == 0 */
+    uint64_t cntval;        /* 64-bit physical timer compare value */
+    uint32_t control;
+    uint32_t cnt_ctrl;
+    uint32_t cntpl0acr;
+    uint32_t int_level;
+    qemu_irq irq;
+} QCTHextimerState;
+
+struct QCTQtimerState {
+    SysBusDevice parent_obj;
+
+    MemoryRegion iomem;
+    MemoryRegion view_iomem;
+    uint32_t secure;
+    QCTHextimerState timer[QCT_QTIMER_TIMER_FRAME_ELTS];
+    uint32_t freq_hz;
+    uint32_t nr_frames;
+    uint32_t nr_views;
+    uint32_t frame_stride;
+    uint32_t freq_scale;
+};
+
+/*
+ * QTimer version register:
+ *
+ *    3                   2                   1
+ *  1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Major |         Minor         |           Step                |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+#define QCT_QTIMER_VERSION_VALUE 0x20020000
+
+static uint32_t qct_qtimer_cnttid(QCTQtimerState *s, unsigned int half)
+{
+    uint32_t nibble = 0x1 | (s->nr_views > 1 ? 0x4 : 0x0);
+    uint32_t base = half * 8;
+    uint32_t cnttid = 0;
+    unsigned int i;
+
+    for (i = 0; i < 8; i++) {
+        if (base + i < s->nr_frames) {
+            cnttid |= nibble << (i * 4);
+        }
+    }
+    return cnttid;
+}
+
+/* Counter value derived on-demand from QEMU_CLOCK_VIRTUAL. */
+static uint64_t hex_timer_now(QCTHextimerState *s)
+{
+    int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+    uint32_t scale;
+    uint64_t scaled_elapsed;
+
+    if (now <= s->offset_ns) {
+        return 0;
+    }
+    scale = MAX(s->qtimer->freq_scale, 1u);
+    scaled_elapsed = (uint64_t)(now - s->offset_ns) / scale;
+    return muldiv64(scaled_elapsed, s->qtimer->freq_hz,
+                    NANOSECONDS_PER_SECOND) &
+           QCT_QTIMER_CNT_MASK;
+}
+
+/* Arm (or disarm) the one-shot deadline timer. */
+static void hex_timer_rearm(QCTHextimerState *s)
+{
+    uint32_t scale;
+    uint64_t base_ns;
+    int64_t deadline_ns;
+
+    if (!(s->control & QCT_QTIMER_CNTP_CTL_ENABLE)) {
+        timer_del(s->timer);
+        return;
+    }
+
+    scale = MAX(s->qtimer->freq_scale, 1u);
+    /*
+     * Round the ticks-to-ns conversion up so that hex_timer_now(), which
+     * truncates when it divides elapsed ns by scale, is guaranteed to
+     * report >= cntval once this deadline fires. A truncating conversion
+     * here could re-arm at the same deadline forever when scale > 1.
+     */
+    base_ns = muldiv64_round_up(s->cntval, NANOSECONDS_PER_SECOND,
+                                s->qtimer->freq_hz);
+    if (base_ns >
+        ((uint64_t)INT64_MAX - (uint64_t)s->offset_ns) / scale) {
+        timer_del(s->timer);
+        return;
+    }
+    deadline_ns = s->offset_ns + (int64_t)(base_ns * scale);
+    timer_mod(s->timer, deadline_ns);
+}
+
+static void hex_timer_update(QCTHextimerState *s)
+{
+    int level = s->int_level &&
+                (s->control & QCT_QTIMER_CNTP_CTL_ENABLE) &&
+                !(s->control & QCT_QTIMER_CNTP_CTL_INTEN);
+
+    trace_qtimer_interrupt();
+    qemu_set_irq(s->irq, level);
+}
+
+/*
+ * Access-control (AC) region: offsets below 0x1000, gates CNTFRQ/CNTSR/
+ * CNTTID/CNTACR per frame plus the shared VERSION register.
+ */
+static uint64_t qct_qtimer_ac_read(void *opaque, hwaddr offset, unsigned size)
+{
+    QCTQtimerState *s = opaque;
+    uint32_t frame;
+
+    switch (offset) {
+    case QCT_QTIMER_AC_CNTFRQ:
+        return s->freq_hz;
+    case QCT_QTIMER_AC_CNTSR:
+        return s->secure;
+    case QCT_QTIMER_AC_CNTTID_0:
+        return qct_qtimer_cnttid(s, 0);
+    case QCT_QTIMER_AC_CNTTID_1:
+        return qct_qtimer_cnttid(s, 1);
+    case QCT_QTIMER_AC_CNTACR_START ... QCT_QTIMER_AC_CNTACR_END:
+        frame = (offset - QCT_QTIMER_AC_CNTACR_START) / 4;
+        if (frame >= s->nr_frames) {
+            qemu_log_mask(LOG_GUEST_ERROR, "%s: bad CNTACR offset 0x%x\n",
+                          __func__, (int)offset);
+            return 0;
+        }
+        return s->timer[frame].cnt_ctrl;
+    case QCT_QTIMER_VERSION:
+        return QCT_QTIMER_VERSION_VALUE;
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR, "%s: bad offset 0x%x\n", __func__,
+                      (int)offset);
+        return 0;
+    }
+}
+
+static void qct_qtimer_ac_write(void *opaque, hwaddr offset, uint64_t value,
+                                unsigned size)
+{
+    QCTQtimerState *s = opaque;
+    uint32_t frame;
+
+    switch (offset) {
+    case QCT_QTIMER_AC_CNTFRQ:
+        if (value == 0) {
+            qemu_log_mask(LOG_GUEST_ERROR, "%s: bad CNTFRQ value 0\n",
+                          __func__);
+            return;
+        }
+        s->freq_hz = value;
+        return;
+    case QCT_QTIMER_AC_CNTSR:
+        if (value > 0xff) {
+            qemu_log_mask(LOG_GUEST_ERROR, "%s: bad CNTSR value 0x%x\n",
+                          __func__, (int)value);
+            return;
+        }
+        s->secure = value;
+        return;
+    case QCT_QTIMER_AC_CNTACR_START ... QCT_QTIMER_AC_CNTACR_END:
+        frame = (offset - QCT_QTIMER_AC_CNTACR_START) / 4;
+        if (frame >= s->nr_frames) {
+            qemu_log_mask(LOG_GUEST_ERROR, "%s: bad CNTACR offset 0x%x\n",
+                          __func__, (int)offset);
+            return;
+        }
+        s->timer[frame].cnt_ctrl = value;
+        return;
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR, "%s: bad offset 0x%x\n", __func__,
+                      (int)offset);
+        return;
+    }
+}
+
+static const MemoryRegionOps qct_qtimer_ac_ops = {
+    .read = qct_qtimer_ac_read,
+    .write = qct_qtimer_ac_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+        .unaligned = false,
+    },
+    .impl = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+    },
+};
+
+/*
+ * View region: a flat array of (frame, view) slots, each frame_stride
+ * bytes wide, holding the per-frame CNTPCT/CNTP_CVAL/CNTP_TVAL/CNTP_CTL
+ * register set.
+ */
+static QCTHextimerState *qct_qtimer_demux(QCTQtimerState *s, hwaddr offset,
+                                          uint32_t *reg_offset,
+                                          uint32_t *view)
+{
+    uint32_t stride = s->frame_stride;
+    uint32_t stride_shift = ctz32(stride);
+    uint32_t slot_nr = offset >> stride_shift;
+    uint32_t frame = slot_nr / s->nr_views;
+
+    *reg_offset = offset & (stride - 1);
+    *view = slot_nr % s->nr_views;
+    if (frame >= s->nr_frames) {
+        return NULL;
+    }
+    return &s->timer[frame];
+}
+
+/* Frames 8+ are described by CNTTID_1; each frame's 2nd view is a gated bit. */
+static bool qct_qtimer_view_visible(QCTQtimerState *s, uint32_t frame,
+                                    uint32_t view)
+{
+    uint32_t cnttid = qct_qtimer_cnttid(s, frame < 8 ? 0 : 1);
+    uint32_t frame_idx = frame < 8 ? frame : frame - 8;
+
+    return !view || (cnttid & (0x4 << (frame_idx * 4)));
+}
+
+static bool access_ok(QCTHextimerState *s, uint32_t reg_offset, uint32_t view)
+{
+    uint32_t acr;
+    uint32_t pl0acr;
+
+    switch (reg_offset) {
+    case QCT_QTIMER_CNT_FREQ:
+        acr = QCT_QTIMER_AC_CNTACR_RFRQ;
+        pl0acr = QCT_QTIMER_CNTPL0ACR_PL0PCTEN |
+                 QCT_QTIMER_CNTPL0ACR_PL0VCTEN;
+        break;
+    case QCT_QTIMER_CNTPCT_LO:
+    case QCT_QTIMER_CNTPCT_HI:
+        acr = QCT_QTIMER_AC_CNTACR_RPCT;
+        pl0acr = QCT_QTIMER_CNTPL0ACR_PL0PCTEN;
+        break;
+    case QCT_QTIMER_CNTP_CVAL_LO:
+    case QCT_QTIMER_CNTP_CVAL_HI:
+    case QCT_QTIMER_CNTP_TVAL:
+    case QCT_QTIMER_CNTP_CTL:
+        acr = QCT_QTIMER_AC_CNTACR_RWPT;
+        pl0acr = QCT_QTIMER_CNTPL0ACR_PL0CTEN;
+        break;
+    default:
+        /* CNTPL0ACR and VERSION are ungated. */
+        return true;
+    }
+
+    if (!(s->cnt_ctrl & acr)) {
+        return false;
+    }
+    return !view || (s->cntpl0acr & pl0acr);
+}
+
+static MemTxResult hex_timer_read(void *opaque, hwaddr offset, uint64_t *data,
+                                  unsigned size, MemTxAttrs attrs)
+{
+    QCTQtimerState *qs = opaque;
+    uint32_t reg_offset;
+    uint32_t view;
+    QCTHextimerState *s = qct_qtimer_demux(qs, offset, &reg_offset, &view);
+    uint32_t frame;
+
+    if (!s) {
+        *data = 0;
+        return MEMTX_ACCESS_ERROR;
+    }
+    frame = s - qs->timer;
+
+    trace_qtimer_read(offset);
+
+    if (!qct_qtimer_view_visible(qs, frame, view)) {
+        *data = 0;
+        return MEMTX_OK;
+    }
+
+    if (!access_ok(s, reg_offset, view)) {
+        return MEMTX_ACCESS_ERROR;
+    }
+
+    switch (reg_offset) {
+    case QCT_QTIMER_CNT_FREQ:
+        *data = s->qtimer->freq_hz;
+        return MEMTX_OK;
+    case QCT_QTIMER_CNTP_CVAL_LO:
+        *data = extract64(s->cntval, 0, 32);
+        return MEMTX_OK;
+    case QCT_QTIMER_CNTP_CVAL_HI:
+        /* HI half is 24-bit per TRM; bits [31:24] are reserved. */
+        *data = extract64(s->cntval, 32, QCT_QTIMER_CNT_HI_BITS);
+        return MEMTX_OK;
+    case QCT_QTIMER_CNTPCT_LO:
+        *data = extract64(hex_timer_now(s), 0, 32);
+        return MEMTX_OK;
+    case QCT_QTIMER_CNTPCT_HI:
+        *data = extract64(hex_timer_now(s), 32, QCT_QTIMER_CNT_HI_BITS);
+        return MEMTX_OK;
+    case QCT_QTIMER_CNTP_TVAL:
+        *data = (uint32_t)(int32_t)(int64_t)(s->cntval - hex_timer_now(s));
+        return MEMTX_OK;
+    case QCT_QTIMER_CNTP_CTL:
+        /*
+         * CNTP_CTL: bit 0 EN, bit 1 IMASK, bit 2 ISTAT (interrupt
+         * pending). ISTAT tracks int_level and is read-only.
+         */
+        *data = s->control | ((s->int_level & 0x1) << 2);
+        return MEMTX_OK;
+    case QCT_QTIMER_CNTPL0ACR:
+        *data = view ? 0 : s->cntpl0acr;
+        return MEMTX_OK;
+    case QCT_QTIMER_VERSION:
+        *data = QCT_QTIMER_VERSION_VALUE;
+        return MEMTX_OK;
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR, "%s: bad offset 0x%x\n", __func__,
+                      (int)offset);
+        *data = 0;
+        return MEMTX_ACCESS_ERROR;
+    }
+}
+
+static MemTxResult hex_timer_write(void *opaque, hwaddr offset,
+                                   uint64_t value, unsigned size,
+                                   MemTxAttrs attrs)
+{
+    QCTQtimerState *qs = opaque;
+    uint32_t reg_offset;
+    uint32_t view;
+    QCTHextimerState *s = qct_qtimer_demux(qs, offset, &reg_offset, &view);
+    uint32_t frame;
+
+    if (!s) {
+        return MEMTX_ACCESS_ERROR;
+    }
+    frame = s - qs->timer;
+
+    trace_qtimer_write(offset, value);
+
+    if (!qct_qtimer_view_visible(qs, frame, view)) {
+        return MEMTX_OK;
+    }
+
+    if (!access_ok(s, reg_offset, view)) {
+        return MEMTX_ACCESS_ERROR;
+    }
+
+    switch (reg_offset) {
+    case QCT_QTIMER_CNTP_CVAL_LO:
+        s->int_level = 0;
+        s->cntval = deposit64(s->cntval, 0, 32, value);
+        hex_timer_rearm(s);
+        break;
+    case QCT_QTIMER_CNTP_CVAL_HI:
+        s->int_level = 0;
+        /* HI half is 24-bit per TRM; bits [31:24] are reserved. */
+        s->cntval = deposit64(s->cntval, 32, QCT_QTIMER_CNT_HI_BITS, value) &
+                    QCT_QTIMER_CNT_MASK;
+        hex_timer_rearm(s);
+        break;
+    case QCT_QTIMER_CNTP_CTL:
+        /* ISTAT (bit 2) is read-only; keep SW writes from polluting it. */
+        s->control = value & ~QCT_QTIMER_CNTP_CTL_ISTAT;
+        hex_timer_rearm(s);
+        break;
+    case QCT_QTIMER_CNTP_TVAL:
+        /* TVAL write: CVAL = CNTPCT + TVAL (TVAL is signed 32-bit). */
+        s->int_level = 0;
+        s->cntval = (hex_timer_now(s) + (int64_t)(int32_t)value) &
+                    QCT_QTIMER_CNT_MASK;
+        hex_timer_rearm(s);
+        break;
+    case QCT_QTIMER_CNTPL0ACR:
+        if (!view) {
+            s->cntpl0acr = value;
+        }
+        break;
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR, "%s: bad offset 0x%x\n", __func__,
+                      (int)offset);
+        return MEMTX_ACCESS_ERROR;
+    }
+    hex_timer_update(s);
+    return MEMTX_OK;
+}
+
+static void hex_timer_tick(void *opaque)
+{
+    QCTHextimerState *s = opaque;
+    uint64_t now = hex_timer_now(s);
+    uint64_t diff56 = (now - s->cntval) & QCT_QTIMER_CNT_MASK;
+    int64_t signed_diff = (int64_t)(diff56 << 8) >> 8;
+
+    if (signed_diff >= 0) {
+        s->int_level = 1;
+        hex_timer_update(s);
+    } else {
+        hex_timer_rearm(s);
+    }
+}
+
+static const MemoryRegionOps hex_timer_ops = {
+    .read_with_attrs = hex_timer_read,
+    .write_with_attrs = hex_timer_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 8,
+        .unaligned = false,
+    },
+    .impl = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+    },
+};
+
+static const VMStateDescription vmstate_qct_hextimer = {
+    .name = "qct-hextimer",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (const VMStateField[]) {
+        VMSTATE_UINT32(control, QCTHextimerState),
+        VMSTATE_UINT32(cnt_ctrl, QCTHextimerState),
+        VMSTATE_INT64(offset_ns, QCTHextimerState),
+        VMSTATE_UINT64(cntval, QCTHextimerState),
+        VMSTATE_UINT32(cntpl0acr, QCTHextimerState),
+        VMSTATE_UINT32(int_level, QCTHextimerState),
+        VMSTATE_TIMER_PTR(timer, QCTHextimerState),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static const VMStateDescription vmstate_qct_qtimer = {
+    .name = "qct-qtimer",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (const VMStateField[]) {
+        VMSTATE_UINT32(freq_hz, QCTQtimerState),
+        VMSTATE_UINT32(secure, QCTQtimerState),
+        VMSTATE_STRUCT_VARRAY_UINT32(timer, QCTQtimerState, nr_frames,
+                                    1, vmstate_qct_hextimer, QCTHextimerState),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static void qct_qtimer_realize(DeviceState *dev, Error **errp)
+{
+    SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+    QCTQtimerState *s = QCT_QTIMER(dev);
+    unsigned int i;
+
+    if (s->nr_frames > QCT_QTIMER_TIMER_FRAME_ELTS) {
+        error_setg(errp, "nr_frames too high");
+        return;
+    }
+    if (s->nr_views > QCT_QTIMER_TIMER_VIEW_ELTS) {
+        error_setg(errp, "nr_views too high");
+        return;
+    }
+    if (s->freq_hz == 0) {
+        error_setg(errp, "freq-hz must be nonzero");
+        return;
+    }
+    if (s->frame_stride == 0 || !is_power_of_2(s->frame_stride)) {
+        error_setg(errp, "frame_stride must be a nonzero power of two");
+        return;
+    }
+
+    memory_region_init_io(&s->iomem, OBJECT(s), &qct_qtimer_ac_ops, s,
+                          "qct-qtimer-ac", QTIMER_MEM_SIZE_BYTES);
+    sysbus_init_mmio(sbd, &s->iomem);
+
+    memory_region_init_io(&s->view_iomem, OBJECT(s), &hex_timer_ops, s,
+                          "qct-qtimer-view",
+                          (uint64_t)s->frame_stride * s->nr_frames *
+                          s->nr_views);
+    sysbus_init_mmio(sbd, &s->view_iomem);
+
+    for (i = 0; i < s->nr_frames; i++) {
+        QCTHextimerState *t = &s->timer[i];
+
+        t->qtimer = s;
+        s->secure |= (1 << i);
+
+        sysbus_init_irq(sbd, &t->irq);
+        t->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, hex_timer_tick, t);
+    }
+}
+
+static void qct_qtimer_unrealize(DeviceState *dev)
+{
+    QCTQtimerState *s = QCT_QTIMER(dev);
+    unsigned int i;
+
+    for (i = 0; i < s->nr_frames; i++) {
+        QCTHextimerState *t = &s->timer[i];
+
+        if (t->timer) {
+            timer_free(t->timer);
+            t->timer = NULL;
+        }
+    }
+}
+
+static void qct_qtimer_reset_hold(Object *obj, ResetType type)
+{
+    QCTQtimerState *s = QCT_QTIMER(obj);
+    unsigned int i;
+
+    for (i = 0; i < s->nr_frames; i++) {
+        QCTHextimerState *t = &s->timer[i];
+
+        /*
+         * Per TRM: CTL = 0 (EN=0, IMASK=0, ISTAT=0), CVAL = 0 so that
+         * TVAL (= CVAL - CNTPCT) also reads 0 at reset. The QEMUTimer is
+         * only armed when SW sets CTL.EN=1, so cntval=0 does not cause a
+         * spurious fire before SW programs the compare value.
+         */
+        t->control = 0;
+        t->cnt_ctrl = QCT_QTIMER_AC_CNTACR_RWPT | QCT_QTIMER_AC_CNTACR_RWVT |
+                      QCT_QTIMER_AC_CNTACR_RVOFF | QCT_QTIMER_AC_CNTACR_RFRQ |
+                      QCT_QTIMER_AC_CNTACR_RPVCT | QCT_QTIMER_AC_CNTACR_RPCT;
+        t->cntval = 0;
+        t->cntpl0acr = 0;
+        t->int_level = 0;
+        t->offset_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+        timer_del(t->timer);
+        qemu_set_irq(t->irq, 0);
+    }
+}
+
+static const Property qct_qtimer_properties[] = {
+    DEFINE_PROP_UINT32("freq-hz", QCTQtimerState, freq_hz,
+                       QTIMER_DEFAULT_FREQ_HZ),
+    DEFINE_PROP_UINT32("freq-scale", QCTQtimerState, freq_scale, 1),
+    DEFINE_PROP_UINT32("nr_frames", QCTQtimerState, nr_frames, 2),
+    DEFINE_PROP_UINT32("nr_views", QCTQtimerState, nr_views, 1),
+    DEFINE_PROP_UINT32("frame_stride", QCTQtimerState, frame_stride, 0x1000),
+};
+
+static void qct_qtimer_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+    device_class_set_props(dc, qct_qtimer_properties);
+    dc->realize = qct_qtimer_realize;
+    dc->unrealize = qct_qtimer_unrealize;
+    dc->vmsd = &vmstate_qct_qtimer;
+    rc->phases.hold = qct_qtimer_reset_hold;
+}
+
+/* QTimer interface implementation, backing HEX_SREG_TIMERLO/TIMERHI */
+static uint32_t qct_qtimer_get_timer_lo_impl(const QctQtimerInterface *obj)
+{
+    QCTQtimerState *s = QCT_QTIMER((QctQtimerInterface *)obj);
+
+    return s->nr_frames > 0 ? extract64(hex_timer_now(&s->timer[0]), 0, 32)
+                            : 0;
+}
+
+static uint32_t qct_qtimer_get_timer_hi_impl(const QctQtimerInterface *obj)
+{
+    QCTQtimerState *s = QCT_QTIMER((QctQtimerInterface *)obj);
+
+    return s->nr_frames > 0 ? extract64(hex_timer_now(&s->timer[0]), 32, 32)
+                            : 0;
+}
+
+static void qct_qtimer_interface_class_init(ObjectClass *klass,
+                                            const void *data)
+{
+    QctQtimerInterfaceClass *k = QCT_QTIMER_INTERFACE_CLASS(klass);
+
+    k->get_timer_lo = qct_qtimer_get_timer_lo_impl;
+    k->get_timer_hi = qct_qtimer_get_timer_hi_impl;
+}
+
+static const TypeInfo qct_qtimer_types[] = {
+    {
+        .name = TYPE_QCT_QTIMER_INTERFACE,
+        .parent = TYPE_INTERFACE,
+        .class_size = sizeof(QctQtimerInterfaceClass),
+        .class_init = qct_qtimer_interface_class_init,
+    },
+    {
+        .name = TYPE_QCT_QTIMER,
+        .parent = TYPE_SYS_BUS_DEVICE,
+        .instance_size = sizeof(QCTQtimerState),
+        .class_init = qct_qtimer_class_init,
+        .interfaces = (InterfaceInfo[]) {
+            { TYPE_QCT_QTIMER_INTERFACE },
+            { }
+        },
+    },
+};
+
+DEFINE_TYPES(qct_qtimer_types)
diff --git a/hw/timer/trace-events b/hw/timer/trace-events
index 634ba1d..636310f 100644
--- a/hw/timer/trace-events
+++ b/hw/timer/trace-events
@@ -128,3 +128,8 @@
 imx_epit_read(const char *name, uint32_t value) "(%s) = 0x%08x"
 imx_epit_write(const char *name, uint64_t value) "(%s, value = 0x%08" PRIx64 ")"
 imx_epit_cmp(uint32_t sr) "sr was %d"
+
+# qct-qtimer.c
+qtimer_interrupt(void) "qtimer interrupt line updated"
+qtimer_read(uint64_t offset) "offset 0x%" PRIx64
+qtimer_write(uint64_t offset, uint64_t value) "offset 0x%" PRIx64 " value 0x%" PRIx64
diff --git a/hw/usb/hcd-ehci.c b/hw/usb/hcd-ehci.c
index 28a60e4..451a918 100644
--- a/hw/usb/hcd-ehci.c
+++ b/hw/usb/hcd-ehci.c
@@ -72,7 +72,7 @@
 } EHCI_STATES;
 
 /* macros for accessing fields within next link pointer entry */
-#define NLPTR_GET(x)             ((x) & 0xffffffe0)
+#define NLPTR_GET(x)             ((x) & ~0x1fULL)
 #define NLPTR_TYPE_GET(x)        (((x) >> 1) & 3)
 #define NLPTR_TBIT(x)            ((x) & 1)  /* 1=invalid, 0=valid */
 
@@ -96,6 +96,17 @@
     *data = val; \
     } while (0)
 
+/*
+ * EHCIqh / EHCIqtd / EHCIitd are sized to always include the extended
+ * high buffer pointer fields from EHCI 1.0 Appendix B. When 64-bit
+ * addressing capability is not advertised to the guest, the descriptors
+ * in guest memory only have the classic 32-bit layout, so DMA transfers
+ * must not read or write past that boundary.
+ */
+#define EHCI_QH_DWORDS_32   (offsetof(EHCIqh, bufptr_hi) / sizeof(uint32_t))
+#define EHCI_QTD_DWORDS_32  (offsetof(EHCIqtd, bufptr_hi) / sizeof(uint32_t))
+#define EHCI_ITD_DWORDS_32  (offsetof(EHCIitd, bufptr_hi) / sizeof(uint32_t))
+
 static const char *ehci_state_names[] = {
     [EST_INACTIVE]     = "INACTIVE",
     [EST_ACTIVE]       = "ACTIVE",
@@ -147,6 +158,38 @@
     return nr2str(ehci_mmio_names, ARRAY_SIZE(ehci_mmio_names), addr);
 }
 
+static uint64_t ehci_get_buf_addr(const EHCIState *s, uint32_t hi,
+                                  uint32_t lo, uint32_t lo_mask)
+{
+    uint64_t addr = lo & lo_mask;
+
+    if (s->caps_64bit_addr) {
+        addr = deposit64(addr, 32, 32, hi);
+    }
+
+    return addr;
+}
+
+static uint64_t ehci_get_desc_addr(const EHCIState *s, uint32_t lo)
+{
+    return ehci_get_buf_addr(s, s->ctrldssegment, lo, UINT32_MAX);
+}
+
+static uint32_t ehci_qh_dwords(const EHCIState *s)
+{
+    return s->caps_64bit_addr ? (sizeof(EHCIqh) >> 2) : EHCI_QH_DWORDS_32;
+}
+
+static uint32_t ehci_qtd_dwords(const EHCIState *s)
+{
+    return s->caps_64bit_addr ? (sizeof(EHCIqtd) >> 2) : EHCI_QTD_DWORDS_32;
+}
+
+static uint32_t ehci_itd_dwords(const EHCIState *s)
+{
+    return s->caps_64bit_addr ? (sizeof(EHCIitd) >> 2) : EHCI_ITD_DWORDS_32;
+}
+
 static void ehci_trace_usbsts(uint32_t mask, int state)
 {
     /* interrupts */
@@ -287,7 +330,7 @@
     return async ? s->astate : s->pstate;
 }
 
-static void ehci_set_fetch_addr(EHCIState *s, int async, uint32_t addr)
+static void ehci_set_fetch_addr(EHCIState *s, int async, uint64_t addr)
 {
     if (async) {
         s->a_fetch_addr = addr;
@@ -296,7 +339,7 @@
     }
 }
 
-static int ehci_get_fetch_addr(EHCIState *s, int async)
+static uint64_t ehci_get_fetch_addr(EHCIState *s, int async)
 {
     return async ? s->a_fetch_addr : s->p_fetch_addr;
 }
@@ -373,7 +416,7 @@
 }
 
 /* Get an array of dwords from main memory */
-static inline int get_dwords(EHCIState *ehci, uint32_t addr,
+static inline int get_dwords(EHCIState *ehci, uint64_t addr,
                              uint32_t *buf, int num)
 {
     int i;
@@ -395,7 +438,7 @@
 }
 
 /* Put an array of dwords in to main memory */
-static inline int put_dwords(EHCIState *ehci, uint32_t addr,
+static inline int put_dwords(EHCIState *ehci, uint64_t addr,
                              uint32_t *buf, int num)
 {
     int i;
@@ -440,7 +483,7 @@
         (qh->current_qtd != q->qh.current_qtd) ||
         (q->async && qh->next_qtd != q->qh.next_qtd) ||
         (memcmp(&qh->altnext_qtd, &q->qh.altnext_qtd,
-                                 7 * sizeof(uint32_t)) != 0) ||
+                EHCI_QH_OVERLAY_COUNT * sizeof(uint32_t)) != 0) ||
         (q->dev != NULL && q->dev->addr != devaddr)) {
         return false;
     } else {
@@ -455,7 +498,8 @@
             (p->qtd.next != qtd->next)) ||
         (!NLPTR_TBIT(p->qtd.altnext) && (p->qtd.altnext != qtd->altnext)) ||
         p->qtd.token != qtd->token ||
-        p->qtd.bufptr[0] != qtd->bufptr[0]) {
+        p->qtd.bufptr[0] != qtd->bufptr[0] ||
+        p->qtd.bufptr_hi[0] != qtd->bufptr_hi[0]) {
         return false;
     } else {
         return true;
@@ -487,10 +531,12 @@
     int state;
 
     /* Verify the qh + qtd, like we do when going through fetchqh & fetchqtd */
+    memset(&qh, 0, sizeof(qh));
+    memset(&qtd, 0, sizeof(qtd));
     get_dwords(q->ehci, NLPTR_GET(q->qhaddr),
-               (uint32_t *) &qh, sizeof(EHCIqh) >> 2);
+               (uint32_t *) &qh, ehci_qh_dwords(q->ehci));
     get_dwords(q->ehci, NLPTR_GET(q->qtdaddr),
-               (uint32_t *) &qtd, sizeof(EHCIqtd) >> 2);
+               (uint32_t *) &qtd, ehci_qtd_dwords(q->ehci));
     if (!ehci_verify_qh(q, &qh) || !ehci_verify_qtd(p, &qtd)) {
         p->async = EHCI_ASYNC_INITIALIZED;
         ehci_free_packet(p);
@@ -549,7 +595,7 @@
 
 /* queue management */
 
-static EHCIQueue *ehci_alloc_queue(EHCIState *ehci, uint32_t addr, int async)
+static EHCIQueue *ehci_alloc_queue(EHCIState *ehci, uint64_t addr, int async)
 {
     EHCIQueueHead *head = async ? &ehci->aqueues : &ehci->pqueues;
     EHCIQueue *q;
@@ -622,7 +668,7 @@
     g_free(q);
 }
 
-static EHCIQueue *ehci_find_queue_by_qh(EHCIState *ehci, uint32_t addr,
+static EHCIQueue *ehci_find_queue_by_qh(EHCIState *ehci, uint64_t addr,
                                         int async)
 {
     EHCIQueueHead *head = async ? &ehci->aqueues : &ehci->pqueues;
@@ -1109,6 +1155,16 @@
         }
         break;
 
+    case CTRLDSSEGMENT:
+        if (!s->caps_64bit_addr) {
+            qemu_log_mask(LOG_GUEST_ERROR,
+                          "ehci: write to CTRLDSSEGMENT while "
+                          "64-bit addressing capability is disabled\n");
+            return;
+        }
+        val |= s->ctrldssegment_default;
+        break;
+
     case ASYNCLISTADDR:
         if (ehci_async_enabled(s)) {
             qemu_log_mask(LOG_GUEST_ERROR,
@@ -1134,8 +1190,8 @@
 static void ehci_flush_qh(EHCIQueue *q)
 {
     uint32_t *qh = (uint32_t *) &q->qh;
-    uint32_t dwords = sizeof(EHCIqh) >> 2;
-    uint32_t addr = NLPTR_GET(q->qhaddr);
+    uint32_t dwords = ehci_qh_dwords(q->ehci);
+    uint64_t addr = NLPTR_GET(q->qhaddr);
 
     put_dwords(q->ehci, addr + 3 * sizeof(uint32_t), qh + 3, dwords - 3);
 }
@@ -1174,6 +1230,7 @@
 
     for (i = 0; i < 5; i++) {
         q->qh.bufptr[i] = p->qtd.bufptr[i];
+        q->qh.bufptr_hi[i] = p->qtd.bufptr_hi[i];
     }
 
     if (!(q->qh.epchar & QH_EPCHAR_DTC)) {
@@ -1207,7 +1264,8 @@
             return -1;
         }
 
-        page  = p->qtd.bufptr[cpage] & QTD_BUFPTR_MASK;
+        page = ehci_get_buf_addr(p->queue->ehci, p->qtd.bufptr_hi[cpage],
+                                 p->qtd.bufptr[cpage], QTD_BUFPTR_MASK);
         page += offset;
         plen  = bytes;
         if (plen > 4096 - offset) {
@@ -1406,12 +1464,13 @@
 /* 4.7.2 */
 static int ehci_process_itd(EHCIState *ehci,
                             EHCIitd *itd,
-                            uint32_t addr)
+                            uint64_t addr)
 {
     USBDevice *dev;
     USBEndpoint *ep;
     uint32_t i, len, pid, dir, devaddr, endp;
-    uint32_t pg, off, ptr1, ptr2, max, mult;
+    uint32_t pg, off, max, mult;
+    uint64_t ptr1, ptr2;
 
     ehci->periodic_sched_active = PERIODIC_ACTIVE;
 
@@ -1434,7 +1493,8 @@
                 return -1;
             }
 
-            ptr1 = (itd->bufptr[pg] & ITD_BUFPTR_MASK);
+            ptr1 = ehci_get_buf_addr(ehci, itd->bufptr_hi[pg],
+                                     itd->bufptr[pg], ITD_BUFPTR_MASK);
             qemu_sglist_init(&ehci->isgl, ehci->device, 2, ehci->as);
             if (off + len > 4096) {
                 /* transfer crosses page border */
@@ -1442,7 +1502,9 @@
                     qemu_sglist_destroy(&ehci->isgl);
                     return -1;  /* avoid page pg + 1 */
                 }
-                ptr2 = (itd->bufptr[pg + 1] & ITD_BUFPTR_MASK);
+                ptr2 = ehci_get_buf_addr(ehci, itd->bufptr_hi[pg + 1],
+                                         itd->bufptr[pg + 1],
+                                         ITD_BUFPTR_MASK);
                 uint32_t len2 = off + len - 4096;
                 uint32_t len1 = len - len2;
                 qemu_sglist_add(&ehci->isgl, ptr1 + off, len1);
@@ -1528,7 +1590,9 @@
     EHCIqh qh;
     int i = 0;
     int again = 0;
-    uint32_t entry = ehci->asynclistaddr;
+    uint64_t entry = 0;
+
+    entry = ehci_get_desc_addr(ehci, ehci->asynclistaddr);
 
     /* set reclamation flag at start event (4.8.6) */
     if (async) {
@@ -1538,9 +1602,10 @@
     ehci_queues_rip_unused(ehci, async);
 
     /*  Find the head of the list (4.9.1.1) */
+    memset(&qh, 0, sizeof(qh));
     for (i = 0; i < MAX_QH; i++) {
         if (get_dwords(ehci, NLPTR_GET(entry), (uint32_t *) &qh,
-                       sizeof(EHCIqh) >> 2) < 0) {
+                       ehci_qh_dwords(ehci)) < 0) {
             return 0;
         }
         ehci_trace_qh(NULL, NLPTR_GET(entry), &qh);
@@ -1556,8 +1621,8 @@
             goto out;
         }
 
-        entry = qh.next;
-        if (entry == ehci->asynclistaddr) {
+        entry = ehci_get_desc_addr(ehci, qh.next);
+        if (entry == ehci_get_desc_addr(ehci, ehci->asynclistaddr)) {
             break;
         }
     }
@@ -1578,7 +1643,7 @@
 static int ehci_state_fetchentry(EHCIState *ehci, int async)
 {
     int again = 0;
-    uint32_t entry = ehci_get_fetch_addr(ehci, async);
+    uint64_t entry = ehci_get_fetch_addr(ehci, async);
 
     if (NLPTR_TBIT(entry)) {
         ehci_set_state(ehci, async, EST_ACTIVE);
@@ -1611,8 +1676,8 @@
     default:
         /* TODO: handle FSTN type */
         qemu_log_mask(LOG_GUEST_ERROR,
-                      "FETCHENTRY: entry at 0x%x is of type %u "
-                      "which is not supported yet\n",
+                      "FETCHENTRY: entry at %" PRIx64 " is of type %" PRIu64
+                      " which is not supported yet\n",
                       entry, NLPTR_TYPE_GET(entry));
         return -1;
     }
@@ -1623,7 +1688,7 @@
 
 static EHCIQueue *ehci_state_fetchqh(EHCIState *ehci, int async)
 {
-    uint32_t entry;
+    uint64_t entry;
     EHCIQueue *q;
     EHCIqh qh;
 
@@ -1641,8 +1706,9 @@
         goto out;
     }
 
+    memset(&qh, 0, sizeof(qh));
     if (get_dwords(ehci, NLPTR_GET(q->qhaddr),
-                   (uint32_t *) &qh, sizeof(EHCIqh) >> 2) < 0) {
+                   (uint32_t *) &qh, ehci_qh_dwords(ehci)) < 0) {
         q = NULL;
         goto out;
     }
@@ -1683,7 +1749,7 @@
     }
 
     if (trace_event_get_state_backends(TRACE_USB_EHCI_FETCHQH_DBG)) {
-        if (q->qhaddr != q->qh.next) {
+        if (q->qhaddr != ehci_get_desc_addr(ehci, q->qh.next)) {
             trace_usb_ehci_fetchqh_dbg(q->qhaddr,
                                        q->qh.epchar & QH_EPCHAR_H,
                                        q->qh.token & QTD_TOKEN_HALT,
@@ -1698,7 +1764,7 @@
     } else if ((q->qh.token & QTD_TOKEN_ACTIVE) &&
                (NLPTR_TBIT(q->qh.current_qtd) == 0) &&
                (q->qh.current_qtd != 0)) {
-        q->qtdaddr = q->qh.current_qtd;
+        q->qtdaddr = ehci_get_desc_addr(ehci, q->qh.current_qtd);
         ehci_set_state(ehci, async, EST_FETCHQTD);
 
     } else {
@@ -1712,14 +1778,15 @@
 
 static int ehci_state_fetchitd(EHCIState *ehci, int async)
 {
-    uint32_t entry;
+    uint64_t entry;
     EHCIitd itd;
 
     assert(!async);
     entry = ehci_get_fetch_addr(ehci, async);
 
+    memset(&itd, 0, sizeof(itd));
     if (get_dwords(ehci, NLPTR_GET(entry), (uint32_t *) &itd,
-                   sizeof(EHCIitd) >> 2) < 0) {
+                   ehci_itd_dwords(ehci)) < 0) {
         return -1;
     }
     ehci_trace_itd(ehci, entry, &itd);
@@ -1729,8 +1796,8 @@
     }
 
     put_dwords(ehci, NLPTR_GET(entry), (uint32_t *) &itd,
-               sizeof(EHCIitd) >> 2);
-    ehci_set_fetch_addr(ehci, async, itd.next);
+               ehci_itd_dwords(ehci));
+    ehci_set_fetch_addr(ehci, async, ehci_get_desc_addr(ehci, itd.next));
     ehci_set_state(ehci, async, EST_FETCHENTRY);
 
     return 1;
@@ -1738,7 +1805,7 @@
 
 static int ehci_state_fetchsitd(EHCIState *ehci, int async)
 {
-    uint32_t entry;
+    uint64_t entry;
     EHCIsitd sitd;
 
     assert(!async);
@@ -1757,7 +1824,7 @@
         warn_report("Skipping active siTD");
     }
 
-    ehci_set_fetch_addr(ehci, async, sitd.next);
+    ehci_set_fetch_addr(ehci, async, ehci_get_desc_addr(ehci, sitd.next));
     ehci_set_state(ehci, async, EST_FETCHENTRY);
     return 1;
 }
@@ -1776,14 +1843,14 @@
      */
     if (((q->qh.token & QTD_TOKEN_TBYTES_MASK) != 0) &&
         (NLPTR_TBIT(q->qh.altnext_qtd) == 0)) {
-        q->qtdaddr = q->qh.altnext_qtd;
+        q->qtdaddr = ehci_get_desc_addr(q->ehci, q->qh.altnext_qtd);
         ehci_set_state(q->ehci, q->async, EST_FETCHQTD);
 
     /*
      *  next qTD is valid
      */
     } else if (NLPTR_TBIT(q->qh.next_qtd) == 0) {
-        q->qtdaddr = q->qh.next_qtd;
+        q->qtdaddr = ehci_get_desc_addr(q->ehci, q->qh.next_qtd);
         ehci_set_state(q->ehci, q->async, EST_FETCHQTD);
 
     /*
@@ -1802,17 +1869,21 @@
     EHCIqtd qtd;
     EHCIPacket *p;
     int again = 1;
-    uint32_t addr;
+    uint64_t addr;
 
     addr = NLPTR_GET(q->qtdaddr);
     if (get_dwords(q->ehci, addr +  8, &qtd.token,   1) < 0) {
         return 0;
     }
     barrier();
+    memset(qtd.bufptr_hi, 0, sizeof(qtd.bufptr_hi));
     if (get_dwords(q->ehci, addr +  0, &qtd.next,    1) < 0 ||
         get_dwords(q->ehci, addr +  4, &qtd.altnext, 1) < 0 ||
         get_dwords(q->ehci, addr + 12, qtd.bufptr,
-                   ARRAY_SIZE(qtd.bufptr)) < 0) {
+                   ARRAY_SIZE(qtd.bufptr)) < 0 ||
+        (q->ehci->caps_64bit_addr &&
+         get_dwords(q->ehci, addr + offsetof(EHCIqtd, bufptr_hi),
+                    qtd.bufptr_hi, ARRAY_SIZE(qtd.bufptr_hi)) < 0)) {
         return 0;
     }
     ehci_trace_qtd(q, NLPTR_GET(q->qtdaddr), &qtd);
@@ -1866,10 +1937,12 @@
 
 static int ehci_state_horizqh(EHCIQueue *q)
 {
+    uint64_t addr;
     int again = 0;
 
-    if (ehci_get_fetch_addr(q->ehci, q->async) != q->qh.next) {
-        ehci_set_fetch_addr(q->ehci, q->async, q->qh.next);
+    addr = ehci_get_desc_addr(q->ehci, q->qh.next);
+    if (ehci_get_fetch_addr(q->ehci, q->async) != addr) {
+        ehci_set_fetch_addr(q->ehci, q->async, addr);
         ehci_set_state(q->ehci, q->async, EST_FETCHENTRY);
         again = 1;
     } else {
@@ -1885,13 +1958,13 @@
     USBEndpoint *ep = p->packet.ep;
     EHCIQueue *q = p->queue;
     EHCIqtd qtd = p->qtd;
-    uint32_t qtdaddr;
+    uint64_t qtdaddr;
 
     for (;;) {
         if (NLPTR_TBIT(qtd.next) != 0) {
             break;
         }
-        qtdaddr = qtd.next;
+        qtdaddr = ehci_get_desc_addr(q->ehci, qtd.next);
         /*
          * Detect circular td lists, Windows creates these, counting on the
          * active bit going low after execution to make the queue stop.
@@ -1901,8 +1974,9 @@
                 goto leave;
             }
         }
+        memset(qtd.bufptr_hi, 0, sizeof(qtd.bufptr_hi));
         if (get_dwords(q->ehci, NLPTR_GET(qtdaddr),
-                       (uint32_t *) &qtd, sizeof(EHCIqtd) >> 2) < 0) {
+                       (uint32_t *) &qtd, ehci_qtd_dwords(q->ehci)) < 0) {
             return -1;
         }
         ehci_trace_qtd(q, NLPTR_GET(qtdaddr), &qtd);
@@ -2008,7 +2082,8 @@
 static int ehci_state_writeback(EHCIQueue *q)
 {
     EHCIPacket *p = QTAILQ_FIRST(&q->packets);
-    uint32_t *qtd, addr;
+    uint32_t *qtd;
+    uint64_t addr;
     int again = 0;
 
     /*  Write back the QTD from the QH area */
@@ -2194,6 +2269,8 @@
     uint32_t entry;
     uint32_t list;
     const int async = 0;
+    uint64_t entry64;
+    uint64_t list64;
 
     /* 4.6 */
 
@@ -2218,12 +2295,14 @@
             break;
         }
         list |= ((ehci->frindex & 0x1ff8) >> 1);
-
-        if (get_dwords(ehci, list, &entry, 1) < 0) {
+        list64 = ehci_get_desc_addr(ehci, list);
+        if (get_dwords(ehci, list64, &entry, 1) < 0) {
             break;
         }
-        trace_usb_ehci_periodic_state_advance(ehci->frindex / 8, list, entry);
-        ehci_set_fetch_addr(ehci, async, entry);
+        entry64 = ehci_get_desc_addr(ehci, entry);
+        trace_usb_ehci_periodic_state_advance(ehci->frindex / 8,
+                                              list64, entry64);
+        ehci_set_fetch_addr(ehci, async, entry64);
         ehci_set_state(ehci, async, EST_FETCHENTRY);
         ehci_advance_state(ehci, async);
         ehci_queues_rip_unused(ehci, async);
@@ -2414,6 +2493,18 @@
     .wakeup_endpoint = ehci_wakeup_endpoint,
 };
 
+static bool ehci_fetch_addr_64_needed(void *opaque, int version_id)
+{
+    EHCIState *s = opaque;
+
+    return s->migrate_fetch_addr_64bit;
+}
+
+static bool ehci_fetch_addr_32_needed(void *opaque, int version_id)
+{
+    return !ehci_fetch_addr_64_needed(opaque, version_id);
+}
+
 static int usb_ehci_pre_save(void *opaque)
 {
     EHCIState *ehci = opaque;
@@ -2424,6 +2515,11 @@
     ehci->last_run_ns -= (ehci->frindex - new_frindex) * UFRAME_TIMER_NS;
     ehci->frindex = new_frindex;
 
+    if (!ehci->migrate_fetch_addr_64bit) {
+        ehci->migrate_a_fetch_addr = ehci->a_fetch_addr;
+        ehci->migrate_p_fetch_addr = ehci->p_fetch_addr;
+    }
+
     return 0;
 }
 
@@ -2444,6 +2540,11 @@
         }
     }
 
+    if (!s->migrate_fetch_addr_64bit) {
+        s->a_fetch_addr = s->migrate_a_fetch_addr;
+        s->p_fetch_addr = s->migrate_p_fetch_addr;
+    }
+
     return 0;
 }
 
@@ -2504,8 +2605,14 @@
         /* schedule state */
         VMSTATE_UINT32(astate, EHCIState),
         VMSTATE_UINT32(pstate, EHCIState),
-        VMSTATE_UINT32(a_fetch_addr, EHCIState),
-        VMSTATE_UINT32(p_fetch_addr, EHCIState),
+        VMSTATE_UINT32_TEST(migrate_a_fetch_addr, EHCIState,
+                            ehci_fetch_addr_32_needed),
+        VMSTATE_UINT32_TEST(migrate_p_fetch_addr, EHCIState,
+                            ehci_fetch_addr_32_needed),
+        VMSTATE_UINT64_TEST(a_fetch_addr, EHCIState,
+                            ehci_fetch_addr_64_needed),
+        VMSTATE_UINT64_TEST(p_fetch_addr, EHCIState,
+                            ehci_fetch_addr_64_needed),
         VMSTATE_END_OF_LIST()
     }
 };
@@ -2524,6 +2631,9 @@
                    s->maxframes);
         return;
     }
+    if (s->caps_64bit_addr) {
+        s->caps[0x08] |= BIT(0);
+    }
 
     memory_region_add_subregion(&s->mem, s->capsbase, &s->mem_caps);
     memory_region_add_subregion(&s->mem, s->opregbase, &s->mem_opreg);
@@ -2583,7 +2693,7 @@
     s->caps[0x05] = 0x00;        /* No companion ports at present */
     s->caps[0x06] = 0x00;
     s->caps[0x07] = 0x00;
-    s->caps[0x08] = 0x80;        /* We can cache whole frame, no 64-bit */
+    s->caps[0x08] = 0x80;        /* We can cache whole frame */
     s->caps[0x0a] = 0x00;
     s->caps[0x0b] = 0x00;
 
diff --git a/hw/usb/hcd-ehci.h b/hw/usb/hcd-ehci.h
index d038ee1..b5ac9c8 100644
--- a/hw/usb/hcd-ehci.h
+++ b/hw/usb/hcd-ehci.h
@@ -63,6 +63,7 @@
 #define ITD_BUFPTR_MAXPKT_SH     0
 #define ITD_BUFPTR_MULT_MASK     0x00000003
 #define ITD_BUFPTR_MULT_SH       0
+    uint32_t bufptr_hi[7];
 } EHCIitd;
 
 /*
@@ -139,8 +140,12 @@
     uint32_t bufptr[5];               /* Standard buffer pointer */
 #define QTD_BUFPTR_MASK               0xfffff000
 #define QTD_BUFPTR_SH                 12
+    uint32_t bufptr_hi[5];
 } EHCIqtd;
 
+/* QH overlay: altnext_qtd, token, bufptr[5], bufptr_hi[5] */
+#define EHCI_QH_OVERLAY_COUNT 12
+
 /*
  * EHCI spec version 1.0 Section 3.6
  */
@@ -194,6 +199,7 @@
 #define BUFPTR_FRAMETAG_MASK          0x0000001f
 #define BUFPTR_SBYTES_MASK            0x00000fe0
 #define BUFPTR_SBYTES_SH              5
+    uint32_t bufptr_hi[5];
 } EHCIqh;
 
 enum async_state {
@@ -208,7 +214,7 @@
     QTAILQ_ENTRY(EHCIPacket) next;
 
     EHCIqtd qtd;           /* copy of current QTD (being worked on) */
-    uint32_t qtdaddr;      /* address QTD read from                 */
+    uint64_t qtdaddr;      /* address QTD read from                 */
 
     USBPacket packet;
     QEMUSGList sgl;
@@ -229,8 +235,8 @@
      * when guest removes an entry (doorbell, handshake sequence)
      */
     EHCIqh qh;             /* copy of current QH (being worked on) */
-    uint32_t qhaddr;       /* address QH read from                 */
-    uint32_t qtdaddr;      /* address QTD read from                */
+    uint64_t qhaddr;       /* address QH read from                 */
+    uint64_t qtdaddr;      /* address QTD read from                */
     int last_pid;          /* pid of last packet executed          */
     USBDevice *dev;
     QTAILQ_HEAD(, EHCIPacket) packets;
@@ -256,6 +262,13 @@
 
     /* properties */
     uint32_t maxframes;
+    /*
+     * Controls migration stream compatibility for old machine types.
+     * Old machine types only transfer 32-bit fetch addresses.
+     */
+    bool migrate_fetch_addr_64bit;
+    bool caps_64bit_addr;
+    uint32_t ctrldssegment_default;
 
     /*
      *  EHCI spec version 1.0 Section 2.3
@@ -293,9 +306,18 @@
     EHCIQueueHead aqueues;
     EHCIQueueHead pqueues;
 
-    /* which address to look at next */
-    uint32_t a_fetch_addr;
-    uint32_t p_fetch_addr;
+    /*
+     * which address to look at next
+     *
+     * Migration compatibility fields for old machine types that only
+     * support 32-bit fetch addresses in the migration stream.
+     *
+     * New machine types migrate the full 64-bit runtime fetch address.
+     */
+    uint32_t migrate_a_fetch_addr;
+    uint32_t migrate_p_fetch_addr;
+    uint64_t a_fetch_addr;
+    uint64_t p_fetch_addr;
 
     USBPacket ipacket;
     QEMUSGList isgl;
@@ -308,7 +330,13 @@
 };
 
 #define DEFINE_EHCI_COMMON_PROPERTIES(_state) \
-    DEFINE_PROP_UINT32("maxframes", _state, ehci.maxframes, 128)
+    DEFINE_PROP_UINT32("maxframes", _state, ehci.maxframes, 128), \
+    DEFINE_PROP_BOOL("x-migrate-fetch-addr-64bit", _state, \
+                     ehci.migrate_fetch_addr_64bit, true), \
+    DEFINE_PROP_BOOL("caps-64bit-addr", _state, \
+                     ehci.caps_64bit_addr, false), \
+    DEFINE_PROP_UINT32("ctrldssegment-default", _state, \
+                       ehci.ctrldssegment_default, 0)
 
 extern const VMStateDescription vmstate_ehci;
 
diff --git a/hw/usb/trace-events b/hw/usb/trace-events
index 0d4318d..67249d6 100644
--- a/hw/usb/trace-events
+++ b/hw/usb/trace-events
@@ -86,15 +86,15 @@
 usb_ehci_portsc_change(uint32_t addr, uint32_t port, uint32_t new, uint32_t old) "ch mmio 0x%04x [port %d] = 0x%x (old: 0x%x)"
 usb_ehci_usbsts(const char *sts, int state) "usbsts %s %d"
 usb_ehci_state(const char *schedule, const char *state) "%s schedule %s"
-usb_ehci_qh_ptrs(void *q, uint32_t addr, uint32_t nxt, uint32_t c_qtd, uint32_t n_qtd, uint32_t a_qtd) "q %p - QH @ 0x%08x: next 0x%08x qtds 0x%08x,0x%08x,0x%08x"
-usb_ehci_qh_fields(uint32_t addr, int rl, int mplen, int eps, int ep, int devaddr) "QH @ 0x%08x - rl %d, mplen %d, eps %d, ep %d, dev %d"
-usb_ehci_qh_bits(uint32_t addr, int c, int h, int dtc, int i) "QH @ 0x%08x - c %d, h %d, dtc %d, i %d"
+usb_ehci_qh_ptrs(void *q, uint64_t addr, uint32_t nxt, uint32_t c_qtd, uint32_t n_qtd, uint32_t a_qtd) "q %p - QH @ 0x%" PRIx64 ": next 0x%08x qtds 0x%08x,0x%08x,0x%08x"
+usb_ehci_qh_fields(uint64_t addr, int rl, int mplen, int eps, int ep, int devaddr) "QH @ 0x%" PRIx64 " - rl %d, mplen %d, eps %d, ep %d, dev %d"
+usb_ehci_qh_bits(uint64_t addr, int c, int h, int dtc, int i) "QH @ 0x%" PRIx64 " - c %d, h %d, dtc %d, i %d"
 usb_ehci_qh_tbytes(uint32_t tbytes) "updating tbytes to %d"
-usb_ehci_qtd_ptrs(void *q, uint32_t addr, uint32_t nxt, uint32_t altnext) "q %p - QTD @ 0x%08x: next 0x%08x altnext 0x%08x"
-usb_ehci_qtd_fields(uint32_t addr, int tbytes, int cpage, int cerr, int pid) "QTD @ 0x%08x - tbytes %d, cpage %d, cerr %d, pid %d"
-usb_ehci_qtd_bits(uint32_t addr, int ioc, int active, int halt, int babble, int xacterr) "QTD @ 0x%08x - ioc %d, active %d, halt %d, babble %d, xacterr %d"
-usb_ehci_itd(uint32_t addr, uint32_t nxt, uint32_t mplen, uint32_t mult, uint32_t ep, uint32_t devaddr) "ITD @ 0x%08x: next 0x%08x - mplen %d, mult %d, ep %d, dev %d"
-usb_ehci_sitd(uint32_t addr, uint32_t nxt, uint32_t active) "ITD @ 0x%08x: next 0x%08x - active %d"
+usb_ehci_qtd_ptrs(void *q, uint64_t addr, uint32_t nxt, uint32_t altnext) "q %p - QTD @ 0x%" PRIx64 ": next 0x%08x altnext 0x%08x"
+usb_ehci_qtd_fields(uint64_t addr, int tbytes, int cpage, int cerr, int pid) "QTD @ 0x%" PRIx64 " - tbytes %d, cpage %d, cerr %d, pid %d"
+usb_ehci_qtd_bits(uint64_t addr, int ioc, int active, int halt, int babble, int xacterr) "QTD @ 0x%" PRIx64 " - ioc %d, active %d, halt %d, babble %d, xacterr %d"
+usb_ehci_itd(uint64_t addr, uint32_t nxt, uint32_t mplen, uint32_t mult, uint32_t ep, uint32_t devaddr) "ITD @ 0x%" PRIx64 ": next 0x%08x - mplen %d, mult %d, ep %d, dev %d"
+usb_ehci_sitd(uint64_t addr, uint32_t nxt, uint32_t active) "SITD @ 0x%" PRIx64 ": next 0x%08x - active %d"
 usb_ehci_port_attach(uint32_t port, const char *owner, const char *device) "attach port #%d, owner %s, device %s"
 usb_ehci_port_detach(uint32_t port, const char *owner) "detach port #%d, owner %s"
 usb_ehci_port_reset(uint32_t port, int enable) "reset port #%d - %d"
@@ -104,16 +104,16 @@
 usb_ehci_port_disable(uint32_t port) "port #%d"
 usb_ehci_queue_action(void *q, const char *action) "q %p: %s"
 usb_ehci_packet_action(void *q, void *p, const char *action) "q %p p %p: %s"
-usb_ehci_packet_submit(uint32_t qhaddr, uint32_t next, uint32_t qtdaddr, int pid, size_t len, int endp, int status, int actual_length) "qh=0x%x, next=0x%x, qtd=0x%x, pid=0x%x, len=%zd, endp=0x%x, status=%d, actual_length=%d"
+usb_ehci_packet_submit(uint64_t qhaddr, uint32_t next, uint64_t qtdaddr, int pid, size_t len, int endp, int status, int actual_length) "qh=0x%" PRIx64 ", next=0x%x, qtd=0x%" PRIx64 ", pid=0x%x, len=%zd, endp=0x%x, status=%d, actual_length=%d"
 usb_ehci_irq(uint32_t level, uint32_t frindex, uint32_t sts, uint32_t mask) "level %d, frindex 0x%04x, sts 0x%x, mask 0x%x"
 usb_ehci_guest_bug(const char *reason) "%s"
 usb_ehci_doorbell_ring(void) ""
 usb_ehci_doorbell_ack(void) ""
 usb_ehci_dma_error(void) ""
-usb_ehci_execute_complete(uint32_t qhaddr, uint32_t next, uint32_t qtdaddr, int status, int actual_length) "qhaddr=0x%x, next=0x%x, qtdaddr=0x%x, status=%d, actual_length=%d"
-usb_ehci_fetchqh_reclaim_done(uint32_t qhaddr) "QH 0x%08x H-bit set, reclamation status reset - done processing"
-usb_ehci_fetchqh_dbg(uint32_t qhaddr, uint32_t h, uint32_t halt, uint32_t active, uint32_t next) "QH 0x%08x (h 0x%x halt 0x%x active 0x%x) next 0x%08x"
-usb_ehci_periodic_state_advance(uint32_t frame, uint32_t list, uint32_t entry) "frame=%d, list=0x%x, entry=0x%x"
+usb_ehci_execute_complete(uint64_t qhaddr, uint32_t next, uint64_t qtdaddr, int status, int actual_length) "qhaddr=0x%" PRIx64 ", next=0x%x, qtdaddr=0x%" PRIx64 ", status=%d, actual_length=%d"
+usb_ehci_fetchqh_reclaim_done(uint64_t qhaddr) "QH 0x%" PRIx64 " H-bit set, reclamation status reset - done processing"
+usb_ehci_fetchqh_dbg(uint64_t qhaddr, uint32_t h, uint32_t halt, uint32_t active, uint32_t next) "QH 0x%" PRIx64 " (h 0x%x halt 0x%x active 0x%x) next 0x%08x"
+usb_ehci_periodic_state_advance(uint32_t frame, uint64_t list, uint64_t entry) "frame=%d, list=0x%" PRIx64 ", entry=0x%" PRIx64
 usb_ehci_skipped_uframes(uint64_t skipped_uframes) "skipped %" PRIu64 " uframes"
 usb_ehci_log(const char *msg) "%s"
 
diff --git a/include/crypto/cipher.h b/include/crypto/cipher.h
index 9293931..2e36141 100644
--- a/include/crypto/cipher.h
+++ b/include/crypto/cipher.h
@@ -235,4 +235,40 @@
                          const uint8_t *iv, size_t niv,
                          Error **errp);
 
+/**
+ * qcrypto_cipher_setaad:
+ * @cipher: the cipher object
+ * @aad: the associated data to authenticate
+ * @len: the length of @aad
+ * @errp: pointer to a NULL-initialized error object
+ *
+ * For AEAD modes such as GCM, feed the associated data (AAD) that is
+ * authenticated but not encrypted. It must be called after
+ * qcrypto_cipher_setiv() and before the first encrypt/decrypt call. It is
+ * an error to call this on a mode that is not an AEAD mode.
+ *
+ * Returns: 0 on success, -1 on error
+ */
+int qcrypto_cipher_setaad(QCryptoCipher *cipher,
+                          const uint8_t *aad, size_t len,
+                          Error **errp);
+
+/**
+ * qcrypto_cipher_gettag:
+ * @cipher: the cipher object
+ * @tag: buffer to receive the authentication tag
+ * @len: the length of @tag
+ * @errp: pointer to a NULL-initialized error object
+ *
+ * For AEAD modes such as GCM, read back the authentication tag computed
+ * over the associated data and the message. It must be called after the
+ * encrypt/decrypt operation. It is an error to call this on a mode that is
+ * not an AEAD mode.
+ *
+ * Returns: 0 on success, -1 on error
+ */
+int qcrypto_cipher_gettag(QCryptoCipher *cipher,
+                          uint8_t *tag, size_t len,
+                          Error **errp);
+
 #endif /* QCRYPTO_CIPHER_H */
diff --git a/include/hw/arm/aspeed_ast1700.h b/include/hw/arm/aspeed_ast1700.h
index f7bd4e8..39c5977 100644
--- a/include/hw/arm/aspeed_ast1700.h
+++ b/include/hw/arm/aspeed_ast1700.h
@@ -41,7 +41,7 @@
     MemoryRegion sram;
     AspeedSMCState spi;
     AspeedADCState adc;
-    AspeedSCUState scu;
+    Aspeed2700SCUState scu;
     AspeedGPIOState gpio;
     AspeedSGPIOState sgpiom[AST1700_SGPIO_NUM];
     AspeedI2CState i2c;
diff --git a/include/hw/arm/aspeed_coprocessor.h b/include/hw/arm/aspeed_coprocessor.h
index ac58a5f..acb8b06 100644
--- a/include/hw/arm/aspeed_coprocessor.h
+++ b/include/hw/arm/aspeed_coprocessor.h
@@ -20,11 +20,8 @@
     MemoryRegion *sram;
     MemoryRegion sram_alias;
     MemoryRegion uart_alias;
-    MemoryRegion scu_alias;
     Clock *sysclk;
 
-    AspeedSCUState *scu;
-    AspeedSCUState scuio;
     AspeedTimerCtrlState timerctrl;
     SerialMM *uart;
     int uart_dev;
@@ -47,11 +44,22 @@
     AspeedCoprocessorState parent;
     AspeedINTCState intc[2];
     UnimplementedDeviceState ipc[2];
-    UnimplementedDeviceState scuio;
     UnimplementedDeviceState pric[2];
     UnimplementedDeviceState otp;
 
     ARMv7MState armv7m;
+
+    /*
+     * SCU, SCUIO and FMC are not owned by this coprocessor: they are
+     * shared with the main PSP SoC, and only aliased into this
+     * coprocessor's own address space here.
+     */
+    MemoryRegion scu_alias;
+    MemoryRegion scuio_alias;
+    MemoryRegion fmc_alias;
+    Aspeed2700SCUState *scu;
+    AspeedSCUState *scuio;
+    AspeedSMCState *fmc;
 };
 
 #define TYPE_ASPEED27X0SSP_COPROCESSOR "aspeed27x0ssp-coprocessor"
diff --git a/include/hw/arm/aspeed_soc.h b/include/hw/arm/aspeed_soc.h
index 41dc04e..cd68c7f 100644
--- a/include/hw/arm/aspeed_soc.h
+++ b/include/hw/arm/aspeed_soc.h
@@ -151,6 +151,7 @@
     AspeedINTCState intcioexp[ASPEED_IOEXP_NUM];
     GICv3State gic;
     MemoryRegion dram_empty;
+    Aspeed2700SCUState scu;
 };
 
 #define TYPE_ASPEED27X0_SOC "aspeed27x0-soc"
diff --git a/include/hw/arm/ax3000-boards.h b/include/hw/arm/ax3000-boards.h
new file mode 100644
index 0000000..4a63266
--- /dev/null
+++ b/include/hw/arm/ax3000-boards.h
@@ -0,0 +1,28 @@
+/*
+ * Axiado Boards
+ *
+ * Author: Kuan-Jui Chiu <kchiu@axiado.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef AXIADO_BOARD_H
+#define AXIADO_BOARD_H
+
+#include "hw/core/boards.h"
+#include "hw/arm/ax3000-soc.h"
+
+#define TYPE_AX3000_MACHINE       MACHINE_TYPE_NAME("ax3000")
+OBJECT_DECLARE_TYPE(Ax3000MachineState, Ax3000MachineClass, AX3000_MACHINE)
+
+typedef struct Ax3000MachineState {
+    MachineState parent;
+
+    Ax3000SoCState *soc;
+} Ax3000MachineState;
+
+typedef struct Ax3000MachineClass {
+    MachineClass parent;
+
+} Ax3000MachineClass;
+#endif
diff --git a/include/hw/arm/ax3000-soc.h b/include/hw/arm/ax3000-soc.h
new file mode 100644
index 0000000..344fcb6
--- /dev/null
+++ b/include/hw/arm/ax3000-soc.h
@@ -0,0 +1,98 @@
+/*
+ * Axiado SoC AX3000
+ *
+ * Author: Kuan-Jui Chiu <kchiu@axiado.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef AXIADO_AX3000_H
+#define AXIADO_AX3000_H
+
+#include "cpu.h"
+#include "hw/intc/arm_gicv3_common.h"
+#include "hw/char/cadence_uart.h"
+#include "hw/misc/axiado_clk.h"
+#include "hw/gpio/cadence_gpio.h"
+#include "hw/sd/axiado_sdhci.h"
+#include "hw/core/sysbus.h"
+#include "qemu/units.h"
+
+#define TYPE_AX3000_SOC "ax3000"
+OBJECT_DECLARE_TYPE(Ax3000SoCState, Ax3000SoCClass, AX3000_SOC)
+
+#define AX3000_DRAM0_BASE       0x3C000000
+#define AX3000_DRAM0_SIZE       (1088 * MiB)
+#define AX3000_DRAM1_BASE       0x400000000
+#define AX3000_DRAM1_SIZE       (2 * GiB)
+
+#define AX3000_GIC_DIST_BASE    0x80300000
+#define AX3000_GIC_DIST_SIZE    (64 * KiB)
+#define AX3000_GIC_REDIST_BASE  0x80380000
+#define AX3000_GIC_REDIST_SIZE  (512 * KiB)
+
+#define AX3000_UART0_BASE       0x80520000
+#define AX3000_UART1_BASE       0x805a0000
+#define AX3000_UART2_BASE       0x80620000
+#define AX3000_UART3_BASE       0x80520800
+
+#define AX3000_SDHCI0_BASE      0x86000000
+#define AX3000_EMMC_PHY_BASE    0x80801C00
+
+#define AX3000_GPIO0_BASE       0x80500000
+#define AX3000_GPIO1_BASE       0x80580000
+#define AX3000_GPIO2_BASE       0x80600000
+#define AX3000_GPIO3_BASE       0x80680000
+#define AX3000_GPIO4_BASE       0x80700000
+#define AX3000_GPIO5_BASE       0x80780000
+#define AX3000_GPIO6_BASE       0x80800000
+#define AX3000_GPIO7_BASE       0x80880000
+
+#define AX3000_TIMER_CTRL       0x8A020000
+#define AX3000_PLL_BASE         0x80000000
+
+enum Ax3000Configuration {
+    AX3000_NUM_CPUS     = 4,
+    AX3000_NUM_IRQS     = 224,
+    AX3000_NUM_BANKS    = 2,
+    AX3000_NUM_UARTS    = 4,
+    AX3000_NUM_GPIOS    = 8,
+};
+
+typedef struct Ax3000SoCState {
+    SysBusDevice        parent;
+
+    ARMCPU              cpu[AX3000_NUM_CPUS];
+    GICv3State          gic;
+    MemoryRegion        dram[AX3000_NUM_BANKS];
+    Ax3000ClkState      ax3000_clk;
+    CadenceUARTState    uart[AX3000_NUM_UARTS];
+    CadenceGPIOState    gpio[AX3000_NUM_GPIOS];
+    AxiadoSDHCIState    sdhci0;
+} Ax3000SoCState;
+
+typedef struct Ax3000SoCClass {
+    SysBusDeviceClass   parent;
+
+    uint32_t            num_cpus;
+} Ax3000SoCClass;
+
+enum Ax3000Irqs {
+    AX3000_UART0_IRQ    = 112,
+    AX3000_UART1_IRQ    = 113,
+    AX3000_UART2_IRQ    = 114,
+    AX3000_UART3_IRQ    = 170,
+
+    AX3000_SDHCI0_IRQ   = 123,
+
+    AX3000_GPIO0_IRQ    = 183,
+    AX3000_GPIO1_IRQ    = 184,
+    AX3000_GPIO2_IRQ    = 185,
+    AX3000_GPIO3_IRQ    = 186,
+    AX3000_GPIO4_IRQ    = 187,
+    AX3000_GPIO5_IRQ    = 188,
+    AX3000_GPIO6_IRQ    = 189,
+    AX3000_GPIO7_IRQ    = 190,
+};
+
+#endif /* AXIADO_AX3000_H */
diff --git a/include/hw/core/boards.h b/include/hw/core/boards.h
index 29c6893..a436d48 100644
--- a/include/hw/core/boards.h
+++ b/include/hw/core/boards.h
@@ -815,6 +815,9 @@
     }
 }
 
+extern GlobalProperty hw_compat_11_1[];
+extern const size_t hw_compat_11_1_len;
+
 extern GlobalProperty hw_compat_11_0[];
 extern const size_t hw_compat_11_0_len;
 
diff --git a/include/hw/gpio/cadence_gpio.h b/include/hw/gpio/cadence_gpio.h
new file mode 100644
index 0000000..69646ba
--- /dev/null
+++ b/include/hw/gpio/cadence_gpio.h
@@ -0,0 +1,53 @@
+/*
+ * Cadence GPIO registers definition.
+ *
+ * Author: Kuan-Jui Chiu <kchiu@axiado.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef CADENCE_GPIO_H
+#define CADENCE_GPIO_H
+
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+
+#define TYPE_CADENCE_GPIO "cadence_gpio"
+OBJECT_DECLARE_SIMPLE_TYPE(CadenceGPIOState, CADENCE_GPIO)
+
+#define CDNS_GPIO_REG_SIZE      0x400
+#define CDNS_GPIO_NUM           32
+
+#define CDNS_GPIO_BYPASS_MODE           0x00
+#define CDNS_GPIO_DIRECTION_MODE        0x04
+#define CDNS_GPIO_OUTPUT_EN             0x08
+#define CDNS_GPIO_OUTPUT_VALUE          0x0c
+#define CDNS_GPIO_INPUT_VALUE           0x10
+#define CDNS_GPIO_IRQ_MASK              0x14
+#define CDNS_GPIO_IRQ_EN                0x18
+#define CDNS_GPIO_IRQ_DIS               0x1c
+#define CDNS_GPIO_IRQ_STATUS            0x20
+#define CDNS_GPIO_IRQ_TYPE              0x24
+#define CDNS_GPIO_IRQ_VALUE             0x28
+#define CDNS_GPIO_IRQ_ANY_EDGE          0x2c
+
+struct CadenceGPIOState {
+    SysBusDevice parent_obj;
+
+    MemoryRegion iomem;
+
+    uint32_t bmr;
+    uint32_t dmr;
+    uint32_t oer;
+    uint32_t ovr;
+    uint32_t inpvr;
+    uint32_t imr;
+    uint32_t isr;
+    uint32_t itr;
+    uint32_t ivr;
+    uint32_t ioar;
+    qemu_irq irq;
+    qemu_irq output[CDNS_GPIO_NUM];
+};
+
+#endif /* CADENCE_GPIO_H */
diff --git a/include/hw/gpio/pca9552.h b/include/hw/gpio/pca9552.h
index 43b1752..5299c13 100644
--- a/include/hw/gpio/pca9552.h
+++ b/include/hw/gpio/pca9552.h
@@ -1,39 +1,18 @@
 /*
- * PCA9552 I2C LED blinker
+ * PCA955X I2C LED blinker and I/O expanders
  *
  * Copyright (c) 2017-2018, IBM Corporation.
  *
  * This work is licensed under the terms of the GNU GPL, version 2 or
  * later. See the COPYING file in the top-level directory.
  */
-#ifndef PCA9552_H
-#define PCA9552_H
 
-#include "hw/i2c/i2c.h"
-#include "qom/object.h"
+#ifndef HW_GPIO_PCA9552_H
+#define HW_GPIO_PCA9552_H
 
-#define TYPE_PCA9552 "pca9552"
 #define TYPE_PCA955X "pca955x"
+#define TYPE_PCA9552 "pca9552"
 #define TYPE_PCA9535 "pca9535"
-typedef struct PCA955xState PCA955xState;
-DECLARE_INSTANCE_CHECKER(PCA955xState, PCA955X,
-                         TYPE_PCA955X)
-
-#define PCA955X_NR_REGS 10
-#define PCA955X_PIN_COUNT_MAX 16
-
-struct PCA955xState {
-    /*< private >*/
-    I2CSlave i2c;
-    /*< public >*/
-
-    uint8_t len;
-    uint8_t pointer;
-
-    uint8_t regs[PCA955X_NR_REGS];
-    qemu_irq gpio_out[PCA955X_PIN_COUNT_MAX];
-    uint8_t ext_state[PCA955X_PIN_COUNT_MAX];
-    char *description; /* For debugging purpose only */
-};
+#define TYPE_PCA9555 "pca9555"
 
 #endif
diff --git a/include/hw/gpio/pca9554.h b/include/hw/gpio/pca9554.h
index 54bfc4c..ac83537 100644
--- a/include/hw/gpio/pca9554.h
+++ b/include/hw/gpio/pca9554.h
@@ -12,12 +12,14 @@
 #include "qom/object.h"
 
 #define TYPE_PCA9554 "pca9554"
+#define TYPE_PCA9536 "pca9536"
 typedef struct PCA9554State PCA9554State;
 DECLARE_INSTANCE_CHECKER(PCA9554State, PCA9554,
                          TYPE_PCA9554)
 
 #define PCA9554_NR_REGS 4
 #define PCA9554_PIN_COUNT 8
+#define PCA9536_PIN_COUNT 4
 
 struct PCA9554State {
     /*< private >*/
@@ -31,6 +33,7 @@
     qemu_irq gpio_out[PCA9554_PIN_COUNT];
     uint8_t ext_state[PCA9554_PIN_COUNT];
     char *description; /* For debugging purpose only */
+    bool hw_dir; /* Honor pin direction */
 };
 
 #endif
diff --git a/include/hw/hexagon/hex-subsys.h b/include/hw/hexagon/hex-subsys.h
new file mode 100644
index 0000000..5792f7b
--- /dev/null
+++ b/include/hw/hexagon/hex-subsys.h
@@ -0,0 +1,32 @@
+/*
+ * Hexagon subsystem helpers shared between the machine models.
+ *
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_HEXAGON_HEX_SUBSYS_H
+#define HW_HEXAGON_HEX_SUBSYS_H
+
+#include "hw/hexagon/hexagon.h"
+#include "hw/core/qdev.h"
+
+/* Create the subsystem shared by every Hexagon machine. */
+void hex_subsys_create(HexagonCommonMachineState *hms,
+                       const struct hexagon_machine_config *m_cfg, Rev_t rev);
+
+/*
+ * Parent a CPU into the subsystem's cluster and wire its links.  Call for
+ * every CPU before hex_subsys_realize_cluster(), then realize each CPU with
+ * hex_subsys_realize_cpu().  CPU[0] receives the L2VIC outputs.
+ */
+void hex_subsys_add_cpu(HexagonCommonMachineState *hms, DeviceState *cpu);
+
+/* Realize the CPU cluster, once all CPUs have been parented into it. */
+void hex_subsys_realize_cluster(HexagonCommonMachineState *hms);
+
+/* Realize a CPU previously parented via hex_subsys_add_cpu(). */
+void hex_subsys_realize_cpu(HexagonCommonMachineState *hms, DeviceState *cpu,
+                            bool boot_cpu);
+
+#endif /* HW_HEXAGON_HEX_SUBSYS_H */
diff --git a/include/hw/hexagon/hexagon.h b/include/hw/hexagon/hexagon.h
index 1034b09..3d7b3cb 100644
--- a/include/hw/hexagon/hexagon.h
+++ b/include/hw/hexagon/hexagon.h
@@ -156,6 +156,12 @@
 
     MemoryRegion ram;
     MemoryRegion cfgtable_rom;
+    MemoryRegion vtcm;
+    DeviceState *cluster;
+    DeviceState *l2vic;
+    DeviceState *qtimer;
+    DeviceState *glob_regs;
+    DeviceState *tlb;
 };
 
 #endif
diff --git a/include/hw/hexagon/hexagon_globalreg.h b/include/hw/hexagon/hexagon_globalreg.h
index 9500998..07437df 100644
--- a/include/hw/hexagon/hexagon_globalreg.h
+++ b/include/hw/hexagon/hexagon_globalreg.h
@@ -10,6 +10,8 @@
 
 #include "hw/core/qdev.h"
 #include "hw/core/sysbus.h"
+#include "hw/intc/hex-l2vic.h"
+#include "hw/timer/qct-qtimer.h"
 #include "qom/object.h"
 #include "target/hexagon/cpu.h"
 
@@ -22,6 +24,12 @@
     /* Array of system registers */
     uint32_t regs[NUM_SREGS];
 
+    /* L2VIC interface used to back the VID/VID1 registers */
+    HexL2VicInterface *l2vic;
+
+    /* QTimer interface used to back the TIMERLO/TIMERHI registers */
+    QctQtimerInterface *qtimer;
+
     /* Global performance cycle counter base */
     uint64_t g_pcycle_base;
 
diff --git a/include/hw/hexagon/virt.h b/include/hw/hexagon/virt.h
index fcb4776..4ca2f1e 100644
--- a/include/hw/hexagon/virt.h
+++ b/include/hw/hexagon/virt.h
@@ -11,15 +11,17 @@
 #include "hw/hexagon/hexagon.h"
 #include "target/hexagon/cpu.h"
 
+#define VIRTIO_DEV_COUNT 8
+
 struct HexagonVirtMachineState {
     HexagonCommonMachineState parent_obj;
 
     int fdt_size;
     MemoryRegion *sys;
     MemoryRegion tcm;
-    MemoryRegion vtcm;
     MemoryRegion bios;
     Clock *apb_clk;
+    DeviceState *virtio_mmio[VIRTIO_DEV_COUNT];
 };
 
 void hexagon_load_fdt(const struct HexagonVirtMachineState *vms);
diff --git a/include/hw/i2c/aspeed_i2c.h b/include/hw/i2c/aspeed_i2c.h
index 156998e..05937a7 100644
--- a/include/hw/i2c/aspeed_i2c.h
+++ b/include/hw/i2c/aspeed_i2c.h
@@ -231,6 +231,8 @@
     FIELD(I2CS_DMA_TX_ADDR_HI, ADDR_HI, 0, 7)
 REG32(I2CS_DMA_RX_ADDR_HI, 0x6c)
     FIELD(I2CS_DMA_RX_ADDR_HI, ADDR_HI, 0, 7)
+REG32(I2CC_VERSION_CTRL, 0x94)
+    FIELD(I2CC_VERSION_CTRL, FUNC_CFG_DMA_EN, 2, 1)
 
 struct AspeedI2CState;
 
diff --git a/include/hw/i386/pc.h b/include/hw/i386/pc.h
index d4b6d3e..ac03da9 100644
--- a/include/hw/i386/pc.h
+++ b/include/hw/i386/pc.h
@@ -209,6 +209,9 @@
 /* sgx.c */
 void pc_machine_init_sgx_epc(PCMachineState *pcms);
 
+extern GlobalProperty pc_compat_11_1[];
+extern const size_t pc_compat_11_1_len;
+
 extern GlobalProperty pc_compat_11_0[];
 extern const size_t pc_compat_11_0_len;
 
diff --git a/include/hw/intc/hex-l2vic.h b/include/hw/intc/hex-l2vic.h
new file mode 100644
index 0000000..edc2782
--- /dev/null
+++ b/include/hw/intc/hex-l2vic.h
@@ -0,0 +1,61 @@
+/*
+ * QEMU L2VIC Interrupt Controller
+ *
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_INTC_HEX_L2VIC_H
+#define HW_INTC_HEX_L2VIC_H
+
+#include "qom/object.h"
+
+#define TYPE_HEX_L2VIC "hex-l2vic"
+/*
+ * L2VIC Interface for CPU/GlobalReg interaction
+ */
+#define TYPE_HEX_L2VIC_INTERFACE "hex-l2vic-if"
+
+typedef struct HexL2VicInterface HexL2VicInterface;
+
+typedef struct HexL2VicInterfaceClass {
+    InterfaceClass parent_class;
+
+    uint32_t (*read_vid)(HexL2VicInterface *l2vic, uint32_t group);
+
+    /*
+     * Write the VID: unpack the fields into per-group VIDs.  This does
+     * not deliver or clear any interrupt; a pending interrupt stays
+     * gated until ciad.
+     */
+    void (*update_vid)(HexL2VicInterface *l2vic, uint32_t group,
+                        uint32_t value);
+
+    /* Clear interrupt using CIAD instruction */
+    void (*clear_interrupt)(HexL2VicInterface *l2vic);
+} HexL2VicInterfaceClass;
+
+DECLARE_OBJ_CHECKERS(HexL2VicInterface, HexL2VicInterfaceClass,
+                     HEX_L2VIC_INTERFACE, TYPE_HEX_L2VIC_INTERFACE);
+
+static inline uint32_t l2vic_read_vid(HexL2VicInterface *l2vic,
+                                       uint32_t group)
+{
+    HexL2VicInterfaceClass *k = HEX_L2VIC_INTERFACE_GET_CLASS(l2vic);
+    return k->read_vid(l2vic, group);
+}
+
+static inline void l2vic_update_vid(HexL2VicInterface *l2vic, uint32_t group,
+                                     uint32_t value)
+{
+    HexL2VicInterfaceClass *k = HEX_L2VIC_INTERFACE_GET_CLASS(l2vic);
+    k->update_vid(l2vic, group, value);
+}
+
+static inline void l2vic_clear_interrupt(HexL2VicInterface *l2vic)
+{
+    HexL2VicInterfaceClass *k = HEX_L2VIC_INTERFACE_GET_CLASS(l2vic);
+    k->clear_interrupt(l2vic);
+}
+
+#endif /* HW_INTC_HEX_L2VIC_H */
diff --git a/include/hw/misc/aspeed_hace.h b/include/hw/misc/aspeed_hace.h
index b5416b0..9b0e768 100644
--- a/include/hw/misc/aspeed_hace.h
+++ b/include/hw/misc/aspeed_hace.h
@@ -49,7 +49,6 @@
     uint32_t key_mask;
     uint32_t hash_mask;
     uint64_t nr_regs;
-    bool raise_crypt_interrupt_workaround;
     uint32_t src_hi_mask;
     uint32_t dest_hi_mask;
     uint32_t key_hi_mask;
diff --git a/include/hw/misc/aspeed_scu.h b/include/hw/misc/aspeed_scu.h
index c30940a..9045494 100644
--- a/include/hw/misc/aspeed_scu.h
+++ b/include/hw/misc/aspeed_scu.h
@@ -20,6 +20,7 @@
 #define TYPE_ASPEED_2500_SCU TYPE_ASPEED_SCU "-ast2500"
 #define TYPE_ASPEED_2600_SCU TYPE_ASPEED_SCU "-ast2600"
 #define TYPE_ASPEED_2700_SCU TYPE_ASPEED_SCU "-ast2700"
+OBJECT_DECLARE_SIMPLE_TYPE(Aspeed2700SCUState, ASPEED_2700_SCU)
 #define TYPE_ASPEED_2700_SCUIO TYPE_ASPEED_SCU "io" "-ast2700"
 #define TYPE_ASPEED_1030_SCU TYPE_ASPEED_SCU "-ast1030"
 
@@ -41,6 +42,10 @@
     uint32_t hw_prot_key;
 };
 
+struct Aspeed2700SCUState {
+    AspeedSCUState parent_obj;
+};
+
 #define AST2400_A1_SILICON_REV   0x02010303U
 #define AST2500_A1_SILICON_REV   0x04010303U
 #define AST2600_A3_SILICON_REV   0x05030303U
diff --git a/include/hw/misc/axiado_clk.h b/include/hw/misc/axiado_clk.h
new file mode 100644
index 0000000..6e12a50
--- /dev/null
+++ b/include/hw/misc/axiado_clk.h
@@ -0,0 +1,26 @@
+/*
+ * Axiado AX3000 Clock Control
+ *
+ * Author: Kuan-Jui Chiu <kchiu@axiado.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef AXIADO_AX3000_CLK_H
+#define AXIADO_AX3000_CLK_H
+
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+
+#define TYPE_AX3000_CLK "ax3000-clk"
+OBJECT_DECLARE_SIMPLE_TYPE(Ax3000ClkState, AX3000_CLK)
+
+#define AX3000_CLK_PLL_CTRL_SIZE    0x1000
+
+typedef struct Ax3000ClkState {
+    SysBusDevice        parent;
+
+    MemoryRegion        pll_ctrl;
+} Ax3000ClkState;
+
+#endif /* AXIADO_AX3000_CLK_H */
diff --git a/include/hw/misc/bcm2835_powermgt.h b/include/hw/misc/bcm2835_powermgt.h
index fb0740c..d1903a9 100644
--- a/include/hw/misc/bcm2835_powermgt.h
+++ b/include/hw/misc/bcm2835_powermgt.h
@@ -12,6 +12,7 @@
 #define BCM2835_POWERMGT_H
 
 #include "hw/core/sysbus.h"
+#include "qemu/timer.h"
 #include "qom/object.h"
 
 #define TYPE_BCM2835_POWERMGT "bcm2835-powermgt"
@@ -24,6 +25,7 @@
     uint32_t rstc;
     uint32_t rsts;
     uint32_t wdog;
+    QEMUTimer *wdog_timer;
 };
 
 #endif
diff --git a/include/hw/s390x/ipl/qipl.h b/include/hw/s390x/ipl/qipl.h
index 8d3c83a..b390f2f 100644
--- a/include/hw/s390x/ipl/qipl.h
+++ b/include/hw/s390x/ipl/qipl.h
@@ -20,6 +20,8 @@
 #define LOADPARM_LEN    8
 #define NO_LOADPARM "\0\0\0\0\0\0\0\0"
 
+#define MAX_BOOT_ENTRIES  32
+
 enum S390IplType {
     S390_IPL_TYPE_FCP = 0x00,
     S390_IPL_TYPE_CCW = 0x02,
diff --git a/include/hw/sd/axiado_sdhci.h b/include/hw/sd/axiado_sdhci.h
new file mode 100644
index 0000000..85afeba
--- /dev/null
+++ b/include/hw/sd/axiado_sdhci.h
@@ -0,0 +1,21 @@
+/*
+ * Axiado SD Host Controller
+ *
+ * Author: Kuan-Jui Chiu <kchiu@axiado.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "hw/sd/sdhci.h"
+#include "qom/object.h"
+
+#define TYPE_AXIADO_SDHCI "axiado-sdhci"
+OBJECT_DECLARE_SIMPLE_TYPE(AxiadoSDHCIState, AXIADO_SDHCI)
+
+typedef struct AxiadoSDHCIState {
+    SysBusDevice parent;
+
+    SDHCIState sdhci;
+    MemoryRegion emmc_phy;
+    BusState *sd_bus;
+} AxiadoSDHCIState;
diff --git a/include/hw/sensor/adc128d818.h b/include/hw/sensor/adc128d818.h
new file mode 100644
index 0000000..10c34b9
--- /dev/null
+++ b/include/hw/sensor/adc128d818.h
@@ -0,0 +1,14 @@
+/*
+ * Texas Instruments ADC128D818 12-bit 8-channel ADC with I2C interface
+ *
+ * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_SENSOR_ADC128D818_H
+#define HW_SENSOR_ADC128D818_H
+
+#define TYPE_ADC128D818 "adc128d818"
+
+#endif
diff --git a/include/hw/ssi/aspeed_smc.h b/include/hw/ssi/aspeed_smc.h
index a273365..5f391fc 100644
--- a/include/hw/ssi/aspeed_smc.h
+++ b/include/hw/ssi/aspeed_smc.h
@@ -47,7 +47,7 @@
 #define TYPE_ASPEED_SMC "aspeed.smc"
 OBJECT_DECLARE_TYPE(AspeedSMCState, AspeedSMCClass, ASPEED_SMC)
 
-#define ASPEED_SMC_R_MAX        (0x100 / 4)
+#define ASPEED_SMC_R_MAX        (0x300 / 4)
 #define ASPEED_SMC_CS_MAX       5
 
 struct AspeedSMCState {
@@ -114,6 +114,7 @@
                            AspeedSegments *seg);
     void (*dma_ctrl)(AspeedSMCState *s, uint32_t value);
     int (*addr_width)(const AspeedSMCState *s);
+    int (*data_fifo_offset_to_cs)(const AspeedSMCState *s, uint32_t offset);
     const MemoryRegionOps *reg_ops;
 };
 
diff --git a/include/hw/timer/qct-qtimer.h b/include/hw/timer/qct-qtimer.h
new file mode 100644
index 0000000..53d8291
--- /dev/null
+++ b/include/hw/timer/qct-qtimer.h
@@ -0,0 +1,43 @@
+/*
+ * Qualcomm QCT QTimer
+ *
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_TIMER_QCT_QTIMER_H
+#define HW_TIMER_QCT_QTIMER_H
+
+#include "qom/object.h"
+
+#define TYPE_QCT_QTIMER "qct-qtimer"
+
+/* QTimer interface for external access from hexagon_globalreg */
+#define TYPE_QCT_QTIMER_INTERFACE "qct-qtimer-if"
+
+typedef struct QctQtimerInterface QctQtimerInterface;
+
+typedef struct QctQtimerInterfaceClass {
+    InterfaceClass parent_class;
+
+    /* Read the live physical counter, backing HEX_SREG_TIMERLO/TIMERHI */
+    uint32_t (*get_timer_lo)(const QctQtimerInterface *qtimer);
+    uint32_t (*get_timer_hi)(const QctQtimerInterface *qtimer);
+} QctQtimerInterfaceClass;
+
+DECLARE_OBJ_CHECKERS(QctQtimerInterface, QctQtimerInterfaceClass,
+                     QCT_QTIMER_INTERFACE, TYPE_QCT_QTIMER_INTERFACE);
+
+static inline uint32_t qct_qtimer_get_timer_lo(const QctQtimerInterface *qtimer)
+{
+    QctQtimerInterfaceClass *k = QCT_QTIMER_INTERFACE_GET_CLASS(qtimer);
+    return k->get_timer_lo(qtimer);
+}
+
+static inline uint32_t qct_qtimer_get_timer_hi(const QctQtimerInterface *qtimer)
+{
+    QctQtimerInterfaceClass *k = QCT_QTIMER_INTERFACE_GET_CLASS(qtimer);
+    return k->get_timer_hi(qtimer);
+}
+
+#endif /* HW_TIMER_QCT_QTIMER_H */
diff --git a/include/qemu/bitops.h b/include/qemu/bitops.h
index c7b838a..f7363a5 100644
--- a/include/qemu/bitops.h
+++ b/include/qemu/bitops.h
@@ -43,10 +43,9 @@
  * be some guest-visible register view of the bit array.
  *
  * We do not currently implement uint32_t versions of find_last_bit(),
- * find_next_bit(), find_next_zero_bit(), find_first_bit() or
- * find_first_zero_bit(), because we haven't yet needed them. If you
- * need them you should implement them similarly to the 'unsigned long'
- * versions.
+ * find_next_bit(), find_next_zero_bit() or find_first_zero_bit(),
+ * because we haven't yet needed them. If you need them you should
+ * implement them similarly to the 'unsigned long' versions.
  *
  * You can declare a bitmap to be used with these functions via the
  * DECLARE_BITMAP and DECLARE_BITMAP32 macros in bitmap.h.
@@ -383,6 +382,29 @@
 }
 
 /**
+ * find_first_bit32 - find the first set bit in a memory region
+ * @addr: The address to start the search at
+ * @size: The maximum size to search
+ *
+ * Returns the bit number of the first set bit,
+ * or @size if there is no set bit in the bitmap.
+ */
+static inline uint32_t find_first_bit32(const uint32_t *addr, uint32_t size)
+{
+    uint32_t result;
+
+    for (result = 0; result < size; result += 32) {
+        uint32_t tmp = *addr++;
+        if (tmp) {
+            result += ctz32(tmp);
+            return result < size ? result : size;
+        }
+    }
+    /* Not found */
+    return size;
+}
+
+/**
  * DOC: Miscellaneous bit operations on single values
  *
  * These functions are a collection of useful operations
diff --git a/include/qemu/host-utils.h b/include/qemu/host-utils.h
index 2e8da7f..1db9dbb 100644
--- a/include/qemu/host-utils.h
+++ b/include/qemu/host-utils.h
@@ -607,10 +607,10 @@
 }
 
 /**
- * sadd32_saturate - addition with saturation
+ * sadd32_saturate - 32-bit signed addition with saturation
  * @x, @y: addends
  *
- * Computes @x + @y, and saturates rathern than truncating the result.
+ * Computes @x + @y, and saturates rather than truncating the result.
  */
 static inline int32_t sadd32_saturate(int32_t x, int32_t y)
 {
@@ -622,10 +622,10 @@
 }
 
 /**
- * sadd64_saturate - addition with saturation
+ * sadd64_saturate - 64-bit signed addition with saturation
  * @x, @y: addends
  *
- * Computes @x + @y, and saturates rathern than truncating the result.
+ * Computes @x + @y, and saturates rather than truncating the result.
  */
 static inline int64_t sadd64_saturate(int64_t x, int64_t y)
 {
@@ -637,31 +637,31 @@
 }
 
 /**
- * ssub32_saturate - subtraction with saturation
+ * ssub32_saturate - 32-bit signed subtraction with saturation
  * @x, @y: addends
  *
- * Computes @x + @y, and saturates rathern than truncating the result.
+ * Computes @x - @y, and saturates rather than truncating the result.
  */
-static inline bool ssub32_saturate(int32_t x, int32_t y)
+static inline int32_t ssub32_saturate(int32_t x, int32_t y)
 {
     int32_t ret;
     if (ssub32_overflow(x, y, &ret)) {
-        ret = x < 0 ? INT32_MAX : INT32_MIN;
+        ret = x < 0 ? INT32_MIN : INT32_MAX;
     }
     return ret;
 }
 
 /**
- * ssub64_saturate - subtraction with saturation
+ * ssub64_saturate - 64-bit signed subtraction with saturation
  * @x, @y: addends
  *
- * Computes @x + @y, and saturates rathern than truncating the result.
+ * Computes @x - @y, and saturates rather than truncating the result.
  */
-static inline bool ssub64_saturate(int64_t x, int64_t y)
+static inline int64_t ssub64_saturate(int64_t x, int64_t y)
 {
     int64_t ret;
     if (ssub64_overflow(x, y, &ret)) {
-        ret = x < 0 ? INT64_MAX : INT64_MIN;
+        ret = x < 0 ? INT64_MIN : INT64_MAX;
     }
     return ret;
 }
diff --git a/linux-user/aarch64/elfload.c b/linux-user/aarch64/elfload.c
index 64e25a0..48be495 100644
--- a/linux-user/aarch64/elfload.c
+++ b/linux-user/aarch64/elfload.c
@@ -177,6 +177,7 @@
     GET_FEATURE_ID(aa64_ssve_fexpa, ARM_HWCAP_A64_SME_SFEXPA);
     GET_FEATURE_ID(aa64_fprcvt, ARM_HWCAP_A64_FPRCVT);
     GET_FEATURE_ID(aa64_sme_mop4, ARM_HWCAP_A64_SME_SMOP4);
+    GET_FEATURE_ID(aa64_sme_tmop, ARM_HWCAP_A64_SME_STMOP);
 
     return hwcaps;
 }
diff --git a/linux-user/alpha/elfload.c b/linux-user/alpha/elfload.c
index 7be9e46..c251751 100644
--- a/linux-user/alpha/elfload.c
+++ b/linux-user/alpha/elfload.c
@@ -32,3 +32,13 @@
 {
     return "ev67";
 }
+
+void elf_core_copy_fpregs(target_elf_fpregset_t *r, const CPUAlphaState *env)
+{
+    int i;
+
+    for (i = 0; i < 31; i++) {
+        r->fpr[i] = tswap64(env->fir[i]);
+    }
+    r->fpcr = tswap64(cpu_alpha_load_fpcr((CPUAlphaState *)env));
+}
diff --git a/linux-user/alpha/target_elf.h b/linux-user/alpha/target_elf.h
index dd90c6f..5efe7f3 100644
--- a/linux-user/alpha/target_elf.h
+++ b/linux-user/alpha/target_elf.h
@@ -19,6 +19,17 @@
  *   r0-r30 at indices 0-30, pc at 31, ps at 32.
  * r31 (hardwired zero) is not stored; pc occupies index 31.
  */
+/*
+ * The floating-point note holds $f0 through $f30 and then the control
+ * register in the slot $f31 would occupy; $f31 reads as zero.
+ */
+#define HAVE_ELF_CORE_FPREGS    1
+
+typedef struct target_elf_fpregset_t {
+    uint64_t fpr[31];    /* $f0-$f30 */
+    uint64_t fpcr;       /* the slot for $f31 */
+} target_elf_fpregset_t;
+
 typedef struct target_elf_gregset_t {
     abi_ulong regs[31];  /* integer registers r0-r30  [0..30] */
     abi_ulong pc;        /* program counter           [31]    */
diff --git a/linux-user/elfload.c b/linux-user/elfload.c
index e7c56af..88508de 100644
--- a/linux-user/elfload.c
+++ b/linux-user/elfload.c
@@ -1887,6 +1887,17 @@
     bswap_phdr(phdr, 1);
 }
 
+#ifdef HAVE_ELF_CORE_FPREGS
+static void fill_fpregset_note(void *data, CPUState *cpu)
+{
+    /* Fill locally and copy: note memory is only aligned to 4. */
+    target_elf_fpregset_t fpregs = {};
+
+    elf_core_copy_fpregs(&fpregs, cpu_env(cpu));
+    memcpy(data, &fpregs, sizeof(fpregs));
+}
+#endif
+
 static void fill_prstatus_note(void *data, CPUState *cpu, int signr)
 {
     /*
@@ -2166,6 +2177,9 @@
     offset += size_note("CORE", ts->info->auxv_len);
     offset += size_note("CORE", sizeof(struct target_elf_prpsinfo));
     offset += size_note("CORE", sizeof(struct target_elf_prstatus)) * cpus;
+#ifdef HAVE_ELF_CORE_FPREGS
+    offset += size_note("CORE", sizeof(target_elf_fpregset_t)) * cpus;
+#endif
     note_size = offset - note_offset;
     data_offset = TARGET_PAGE_ALIGN(offset);
 
@@ -2222,6 +2236,11 @@
             dptr = fill_note(&hptr, NT_PRSTATUS, "CORE",
                              sizeof(struct target_elf_prstatus));
             fill_prstatus_note(dptr, cpu_iter, cpu_iter == cpu ? signr : 0);
+#ifdef HAVE_ELF_CORE_FPREGS
+            dptr = fill_note(&hptr, NT_FPREGSET, "CORE",
+                             sizeof(target_elf_fpregset_t));
+            fill_fpregset_note(dptr, cpu_iter);
+#endif
         }
 
         if (dump_write(fd, header, data_offset) < 0) {
diff --git a/linux-user/hppa/elfload.c b/linux-user/hppa/elfload.c
index ff4301b..dd5b0b3 100644
--- a/linux-user/hppa/elfload.c
+++ b/linux-user/hppa/elfload.c
@@ -17,6 +17,13 @@
     return "PARISC";
 }
 
+void elf_core_copy_fpregs(target_elf_fpregset_t *r, const CPUArchState *env)
+{
+    for (int i = 0; i < 32; i++) {
+        r->fpr[i] = tswap64(env->fr[i]);
+    }
+}
+
 void elf_core_copy_regs(target_elf_gregset_t *r, const CPUArchState *env)
 {
     int i;
diff --git a/linux-user/hppa/target_elf.h b/linux-user/hppa/target_elf.h
index 22547b1..4357873 100644
--- a/linux-user/hppa/target_elf.h
+++ b/linux-user/hppa/target_elf.h
@@ -39,4 +39,13 @@
 #define STACK_ALIGNMENT         64
 #define VDSO_HEADER             "vdso.c.inc"
 
+#define HAVE_ELF_CORE_FPREGS    1
+
+/*
+ * Matches the kernel's elf_fpregset_t (ELF_NFPREG = 32): fr0-fr31.
+ */
+typedef struct target_elf_fpregset_t {
+    uint64_t fpr[32];
+} target_elf_fpregset_t;
+
 #endif
diff --git a/linux-user/loader.h b/linux-user/loader.h
index da9ad28..5ddd140 100644
--- a/linux-user/loader.h
+++ b/linux-user/loader.h
@@ -109,6 +109,9 @@
 
 struct target_elf_gregset_t;
 void elf_core_copy_regs(struct target_elf_gregset_t *, const CPUArchState *);
+/* Only defined by a target whose target_elf.h sets HAVE_ELF_CORE_FPREGS. */
+struct target_elf_fpregset_t;
+void elf_core_copy_fpregs(struct target_elf_fpregset_t *, const CPUArchState *);
 
 typedef struct {
     const uint8_t *image;
diff --git a/linux-user/mips/elfload.c b/linux-user/mips/elfload.c
index ce2c451..1d62ae0 100644
--- a/linux-user/mips/elfload.c
+++ b/linux-user/mips/elfload.c
@@ -130,6 +130,14 @@
 
 #undef MATCH_PLATFORM_INSN
 
+void elf_core_copy_fpregs(target_elf_fpregset_t *r, const CPUMIPSState *env)
+{
+    for (int i = 0; i < 32; i++) {
+        r->fpr[i] = tswap64(env->active_fpu.fpr[i].d);
+    }
+    r->fcsr = tswap32(env->active_fpu.fcr31);
+}
+
 /* See linux kernel: arch/mips/kernel/process.c:elf_dump_regs.  */
 #ifndef TARGET_MIPS64
 void elf_core_copy_regs(target_elf_gregset_t *r, const CPUMIPSState *env)
diff --git a/linux-user/mips/target_elf.h b/linux-user/mips/target_elf.h
index 157306f..426f905 100644
--- a/linux-user/mips/target_elf.h
+++ b/linux-user/mips/target_elf.h
@@ -26,4 +26,17 @@
     };
 } target_elf_gregset_t;
 
+#define HAVE_ELF_CORE_FPREGS    1
+
+/*
+ * Matches the kernel's elf_fpregset_t (ELF_NFPREG = 33):
+ *   fpr[0..31] hold f0-f31; fcsr occupies the low 32 bits of slot 32.
+ *   pad rounds the struct to 33 × 8 bytes = 264 bytes.
+ */
+typedef struct target_elf_fpregset_t {
+    uint64_t fpr[32];
+    uint32_t fcsr;
+    uint32_t pad;
+} target_elf_fpregset_t;
+
 #endif
diff --git a/linux-user/mips64/target_elf.h b/linux-user/mips64/target_elf.h
index 061471a..efb5d97 100644
--- a/linux-user/mips64/target_elf.h
+++ b/linux-user/mips64/target_elf.h
@@ -32,4 +32,17 @@
     };
 } target_elf_gregset_t;
 
+#define HAVE_ELF_CORE_FPREGS    1
+
+/*
+ * Matches the kernel's elf_fpregset_t (ELF_NFPREG = 33):
+ *   fpr[0..31] hold f0-f31; fcsr occupies the low 32 bits of slot 32.
+ *   pad rounds the struct to 33 × 8 bytes = 264 bytes.
+ */
+typedef struct target_elf_fpregset_t {
+    uint64_t fpr[32];
+    uint32_t fcsr;
+    uint32_t pad;
+} target_elf_fpregset_t;
+
 #endif
diff --git a/linux-user/riscv/elfload.c b/linux-user/riscv/elfload.c
index afe103a..1bc8bea 100644
--- a/linux-user/riscv/elfload.c
+++ b/linux-user/riscv/elfload.c
@@ -11,6 +11,15 @@
     return "max";
 }
 
+void elf_core_copy_fpregs(target_elf_fpregset_t *r, const CPURISCVState *env)
+{
+    for (int i = 0; i < 32; i++) {
+        r->fpr[i] = tswap64(env->fpr[i]);
+    }
+    r->fcsr = tswap32(((uint32_t)env->frm << FSR_RD_SHIFT) |
+                      (riscv_cpu_get_fflags((CPURISCVState *)env) << FSR_AEXC_SHIFT));
+}
+
 void elf_core_copy_regs(target_elf_gregset_t *r, const CPURISCVState *env)
 {
     r->pc = tswapal(env->pc);
diff --git a/linux-user/riscv/target_elf.h b/linux-user/riscv/target_elf.h
index 859f726..185b81a 100644
--- a/linux-user/riscv/target_elf.h
+++ b/linux-user/riscv/target_elf.h
@@ -27,4 +27,15 @@
     abi_ulong regs[31];
 } target_elf_gregset_t;
 
+#define HAVE_ELF_CORE_FPREGS    1
+
+/*
+ * Matches struct __riscv_d_ext_state from uapi/asm/ptrace.h:
+ *   f0-f31 as 64-bit values followed by fcsr.
+ */
+typedef struct target_elf_fpregset_t {
+    uint64_t fpr[32];
+    uint32_t fcsr;
+} target_elf_fpregset_t;
+
 #endif
diff --git a/linux-user/sh4/cpu_loop.c b/linux-user/sh4/cpu_loop.c
index ee2958d..0815b9c 100644
--- a/linux-user/sh4/cpu_loop.c
+++ b/linux-user/sh4/cpu_loop.c
@@ -64,6 +64,13 @@
             cpu_exec_step_atomic(cs);
             arch_interrupt = false;
             break;
+        case 0x180:
+            /* Illegal instruction */
+            /* fallthrough */
+        case 0x1a0:
+            /* Illegal instruction in delay slot */
+            force_sig_fault(TARGET_SIGILL, TARGET_ILL_ILLOPC, env->pc);
+            break;
         default:
             fprintf(stderr, "Unhandled trap: 0x%x\n", trapnr);
             cpu_dump_state(cs, stderr, 0);
diff --git a/linux-user/sh4/elfload.c b/linux-user/sh4/elfload.c
index f03ce49..4160162 100644
--- a/linux-user/sh4/elfload.c
+++ b/linux-user/sh4/elfload.c
@@ -52,6 +52,16 @@
     return hwcap;
 }
 
+void elf_core_copy_fpregs(target_elf_fpregset_t *r, const CPUSH4State *env)
+{
+    for (int i = 0; i < 16; i++) {
+        r->fpregs[i] = tswap32(env->fregs[i]);
+        r->xfpregs[i] = tswap32(env->fregs[16 + i]);
+    }
+    r->fpscr = tswap32(env->fpscr);
+    r->fpul = tswap32(env->fpul);
+}
+
 void elf_core_copy_regs(target_elf_gregset_t *r, const CPUSH4State *env)
 {
     for (int i = 0; i < 16; i++) {
diff --git a/linux-user/sh4/signal.c b/linux-user/sh4/signal.c
index 00290d6..047174a 100644
--- a/linux-user/sh4/signal.c
+++ b/linux-user/sh4/signal.c
@@ -109,7 +109,7 @@
            the SP, otherwise we would be pushing the signal context to
            invalid memory.  */
         regs->gregs[15] = regs->gregs[1];
-    } else if (regs->flags & TB_FLAG_DELAY_SLOT) {
+    } else if (regs->flags & (TB_FLAG_DELAY_SLOT | TB_FLAG_DELAY_SLOT_COND)) {
         /* If we are in a delay slot, push the previous instruction.  */
         regs->pc -= 2;
     }
@@ -206,6 +206,8 @@
         __put_user(set->sig[i + 1], &frame->extramask[i]);
     }
 
+    regs->fpscr = FPSCR_PR;
+
     /* Set up to return from userspace.  If provided, use a stub
        already in userspace.  */
     if (ka->sa_flags & TARGET_SA_RESTORER) {
@@ -258,6 +260,8 @@
         __put_user(set->sig[i], &frame->uc.tuc_sigmask.sig[i]);
     }
 
+    regs->fpscr = FPSCR_PR;
+
     /* Set up to return from userspace.  If provided, use a stub
        already in userspace.  */
     if (ka->sa_flags & TARGET_SA_RESTORER) {
diff --git a/linux-user/sh4/target_elf.h b/linux-user/sh4/target_elf.h
index 3fcb63d..5923022 100644
--- a/linux-user/sh4/target_elf.h
+++ b/linux-user/sh4/target_elf.h
@@ -25,4 +25,16 @@
     struct target_pt_regs pt;
 } target_elf_gregset_t;
 
+#define HAVE_ELF_CORE_FPREGS    1
+
+/*
+ * Matches struct user_fpu_struct from arch/sh/include/asm/user.h.
+ */
+typedef struct target_elf_fpregset_t {
+    uint32_t fpregs[16];
+    uint32_t xfpregs[16];
+    uint32_t fpscr;
+    uint32_t fpul;
+} target_elf_fpregset_t;
+
 #endif
diff --git a/linux-user/strace.list b/linux-user/strace.list
index 25ace05..e952f15 100644
--- a/linux-user/strace.list
+++ b/linux-user/strace.list
@@ -1737,3 +1737,6 @@
 #ifdef TARGET_NR_fspick
 { TARGET_NR_fspick, "fspick", "%s(%d,%s,%d)", NULL, NULL },
 #endif
+#ifdef TARGET_NR_mount_setattr
+{ TARGET_NR_mount_setattr, "mount_setattr", "%s(%d,%s,%d,%p,%d)", NULL, NULL },
+#endif
diff --git a/linux-user/syscall.c b/linux-user/syscall.c
index dc02868..cfa68df 100644
--- a/linux-user/syscall.c
+++ b/linux-user/syscall.c
@@ -9735,6 +9735,13 @@
            int, __to_dfd, const char *, __to_pathname, unsigned int, flag)
 #endif
 
+#if defined(TARGET_NR_mount_setattr) && defined(__NR_mount_setattr)
+#define __NR_sys_mount_setattr __NR_mount_setattr
+_syscall5(int, sys_mount_setattr, int, dfd, const char *, path,
+          unsigned int, flags, struct mount_attr_ver0 *, uattr,
+          size_t, usize)
+#endif
+
 #if defined(TARGET_NR_fsopen) && defined(__NR_fsopen)
 #define __NR_sys_fsopen __NR_fsopen
 _syscall2(int, sys_fsopen, const char *, fs_name, unsigned int, flags);
@@ -14480,6 +14487,43 @@
         return do_map_shadow_stack(cpu_env, arg1, arg2, arg3);
 #endif
 
+#if defined(TARGET_NR_mount_setattr) && defined(__NR_mount_setattr)
+    case TARGET_NR_mount_setattr:
+        {
+            struct mount_attr_ver0 attr = {};
+            abi_ulong usize = arg5;
+
+            if (usize < sizeof(struct target_mount_attr_ver0)) {
+                return -TARGET_EINVAL;
+            }
+            ret = copy_struct_from_user(&attr, sizeof(attr), arg4, usize);
+            if (ret) {
+                if (ret == -TARGET_E2BIG) {
+                    qemu_log_mask(LOG_UNIMP,
+                                  "Unimplemented mount_setattr mount_attr "
+                                  "size: " TARGET_ABI_FMT_lu "\n", usize);
+                }
+                return ret;
+            }
+            /*
+             * MOUNT_ATTR_* and the MS_* propagation flags have the same
+             * values on all targets, so only byte order needs fixing up.
+             */
+            attr.attr_set = tswap64(attr.attr_set);
+            attr.attr_clr = tswap64(attr.attr_clr);
+            attr.propagation = tswap64(attr.propagation);
+            attr.userns_fd = tswap64(attr.userns_fd);
+
+            p = lock_user_string(arg2);
+            if (!p) {
+                return -TARGET_EFAULT;
+            }
+            ret = get_errno(sys_mount_setattr(arg1, p, arg3, &attr,
+                                              sizeof(attr)));
+            unlock_user(p, arg2, 0);
+        }
+        return ret;
+#endif
 #if defined(TARGET_NR_fsopen) && defined(__NR_fsopen)
     case TARGET_NR_fsopen:
         {
diff --git a/linux-user/syscall_defs.h b/linux-user/syscall_defs.h
index e033c7d..e28853c 100644
--- a/linux-user/syscall_defs.h
+++ b/linux-user/syscall_defs.h
@@ -2770,6 +2770,19 @@
     abi_ullong mode;
     abi_ullong resolve;
 };
+/* from kernel's include/uapi/linux/mount.h */
+struct mount_attr_ver0 {
+    uint64_t attr_set;
+    uint64_t attr_clr;
+    uint64_t propagation;
+    uint64_t userns_fd;
+};
+struct target_mount_attr_ver0 {
+    abi_ullong attr_set;
+    abi_ullong attr_clr;
+    abi_ullong propagation;
+    abi_ullong userns_fd;
+};
 #ifndef RESOLVE_NO_MAGICLINKS
 #define RESOLVE_NO_MAGICLINKS   0x02
 #endif
diff --git a/pc-bios/s390-ccw.img b/pc-bios/s390-ccw.img
index e24a93e1..291cd77 100644
--- a/pc-bios/s390-ccw.img
+++ b/pc-bios/s390-ccw.img
Binary files differ
diff --git a/pc-bios/s390-ccw/bootmap.c b/pc-bios/s390-ccw/bootmap.c
index 420ee32..8151226 100644
--- a/pc-bios/s390-ccw/bootmap.c
+++ b/pc-bios/s390-ccw/bootmap.c
@@ -61,6 +61,7 @@
 static void *s2_prev_blk = _s2;
 static void *s2_cur_blk = _s2 + MAX_SECTOR_SIZE;
 static void *s2_next_blk = _s2 + MAX_SECTOR_SIZE * 2;
+static void *s2_end = _s2 + sizeof(_s2);
 
 static inline int verify_boot_info(BootInfo *bip)
 {
@@ -308,7 +309,8 @@
                 }
             }
 
-            return menu_get_zipl_boot_index(s2_cur_blk + banner_offset);
+            return menu_get_zipl_boot_index(s2_cur_blk + banner_offset,
+                                            s2_end);
         }
 
         prev_block_nr = cur_block_nr;
@@ -902,7 +904,7 @@
         if (dir_rem[level] == 0) {
             /* Nothing remaining */
             level--;
-            if (virtio_read(sec_loc[level], temp)) {
+            if (level >= 0 && virtio_read(sec_loc[level], temp)) {
                 puts("Failed to read ISO directory");
                 return -EIO;
             }
diff --git a/pc-bios/s390-ccw/helper.h b/pc-bios/s390-ccw/helper.h
index 8e3dfcb..d9b7da4 100644
--- a/pc-bios/s390-ccw/helper.h
+++ b/pc-bios/s390-ccw/helper.h
@@ -45,4 +45,14 @@
     }
 }
 
+static inline size_t strnlen(const char *s, size_t maxlen)
+{
+    size_t len = 0;
+
+    while (len < maxlen && s[len]) {
+        len++;
+    }
+    return len;
+}
+
 #endif
diff --git a/pc-bios/s390-ccw/menu.c b/pc-bios/s390-ccw/menu.c
index eeaff78..9b81154 100644
--- a/pc-bios/s390-ccw/menu.c
+++ b/pc-bios/s390-ccw/menu.c
@@ -16,6 +16,7 @@
 #include "s390-ccw.h"
 #include "sclp.h"
 #include "s390-time.h"
+#include "helper.h"
 
 #define KEYCODE_NO_INP '\0'
 #define KEYCODE_ESCAPE '\033'
@@ -26,6 +27,9 @@
 #define ZIPL_TIMEOUT_OFFSET 138
 #define ZIPL_FLAG_OFFSET    140
 
+/* Max printable chars for a zipl boot menu entry */
+#define ZIPL_ENTRY_MAX 80
+
 #define TOD_CLOCK_MILLISECOND   0x3e8000
 
 #define LOW_CORE_EXTERNAL_INT_ADDR   0x86
@@ -176,21 +180,31 @@
     return boot_index;
 }
 
-/* Returns the entry number that was printed */
+/* Returns the entry number that was printed, or -1 on invalid entry */
 static int zipl_print_entry(const char *data, size_t len)
 {
-    char buf[len + 2];
+    char buf[ZIPL_ENTRY_MAX + 2];
+    const char *p;
+
+    if (len > ZIPL_ENTRY_MAX) {
+        len = ZIPL_ENTRY_MAX;
+    }
 
     ebcdic_to_ascii(data, buf, len);
     buf[len] = '\n';
     buf[len + 1] = '\0';
 
+    p = (buf[0] == ' ') ? buf + 1 : buf;
+    if (!isdigit((unsigned char)*p)) {
+        return -1;
+    }
+
     printf("%s", buf);
 
-    return buf[0] == ' ' ? atoi(buf + 1) : atoi(buf);
+    return atoi(p);
 }
 
-int menu_get_zipl_boot_index(const char *menu_data)
+int menu_get_zipl_boot_index(const char *menu_data, const char *menu_data_end)
 {
     size_t len;
     int entry;
@@ -206,16 +220,28 @@
         timeout = zipl_timeout * 1000;
     }
 
-    /* Print banner */
+    if (menu_data >= menu_data_end) {
+        return 0; /* Boot default */
+    }
+
+    /* Skip banner */
+    len = strnlen(menu_data, menu_data_end - menu_data);
+    menu_data += len + 1;
+    if (menu_data >= menu_data_end || !(*menu_data)) {
+        return 0; /* No entries, boot default */
+    }
+
     puts("s390-ccw zIPL Boot Menu\n");
-    menu_data += strlen(menu_data) + 1;
 
     /* Print entries */
-    while (*menu_data) {
-        len = strlen(menu_data);
+    while (menu_data < menu_data_end && *menu_data) {
+        len = strnlen(menu_data, menu_data_end - menu_data);
         entry = zipl_print_entry(menu_data, len);
         menu_data += len + 1;
 
+        if (entry < 0 || entry >= MAX_BOOT_ENTRIES) {
+            continue;
+        }
         valid_entries[entry] = true;
 
         if (entry == 0) {
diff --git a/pc-bios/s390-ccw/netmain.c b/pc-bios/s390-ccw/netmain.c
index 651cedf..791854f 100644
--- a/pc-bios/s390-ccw/netmain.c
+++ b/pc-bios/s390-ccw/netmain.c
@@ -40,6 +40,9 @@
 #define DEFAULT_BOOT_RETRIES 10
 #define DEFAULT_TFTP_RETRIES 20
 
+/* Index 0 is reserved for default alias, start PXE cfg indices at 1 */
+#define PXECFG_MAX              (MAX_BOOT_ENTRIES - 1)
+
 extern char _start[];
 
 #define KERNEL_ADDR             ((void *)0L)
@@ -381,13 +384,13 @@
 
 static int net_try_pxelinux_cfg(filename_ip_t *fn_ip)
 {
-    struct pl_cfg_entry entries[MAX_BOOT_ENTRIES];
+    struct pl_cfg_entry entries[PXECFG_MAX];
     int num_ent, def_ent = 0;
 
     num_ent = pxelinux_load_parse_cfg(fn_ip, mac, get_uuid(),
                                       DEFAULT_TFTP_RETRIES,
                                       cfgbuf, sizeof(cfgbuf),
-                                      entries, MAX_BOOT_ENTRIES, &def_ent);
+                                      entries, PXECFG_MAX, &def_ent);
 
     return net_select_and_load_kernel(fn_ip, num_ent, def_ent, entries);
 }
@@ -470,11 +473,11 @@
          * a magic comment string.
          */
         if (!strncasecmp("# pxelinux", cfgbuf, 10)) {
-            struct pl_cfg_entry entries[MAX_BOOT_ENTRIES];
+            struct pl_cfg_entry entries[PXECFG_MAX];
             int num_ent, def_ent = 0;
 
             num_ent = pxelinux_parse_cfg(cfgbuf, sizeof(cfgbuf), entries,
-                                         MAX_BOOT_ENTRIES, &def_ent);
+                                         PXECFG_MAX, &def_ent);
             return net_select_and_load_kernel(fn_ip, num_ent, def_ent,
                                               entries);
         }
diff --git a/pc-bios/s390-ccw/s390-ccw.h b/pc-bios/s390-ccw/s390-ccw.h
index 1e1f717..25aac91 100644
--- a/pc-bios/s390-ccw/s390-ccw.h
+++ b/pc-bios/s390-ccw/s390-ccw.h
@@ -76,14 +76,12 @@
 
 /* menu.c */
 void menu_set_parms(uint8_t boot_menu_flag, uint32_t boot_menu_timeout);
-int menu_get_zipl_boot_index(const char *menu_data);
+int menu_get_zipl_boot_index(const char *menu_data, const char *menu_data_end);
 bool menu_is_enabled_zipl(void);
 int menu_get_enum_boot_index(bool *valid_entries);
 bool menu_is_enabled_enum(void);
 int menu_get_boot_index(bool *valid_entries);
 
-#define MAX_BOOT_ENTRIES  31
-
 __attribute__ ((__noreturn__))
 static inline void panic(const char *string)
 {
diff --git a/qapi/crypto.json b/qapi/crypto.json
index 2b55bef..6e3a98f 100644
--- a/qapi/crypto.json
+++ b/qapi/crypto.json
@@ -121,10 +121,12 @@
 #
 # @ctr: Counter (Since 2.8)
 #
+# @gcm: Galois/Counter Mode (Since 11.2)
+#
 # Since: 2.6
 ##
 { 'enum': 'QCryptoCipherMode',
-  'data': ['ecb', 'cbc', 'xts', 'ctr']}
+  'data': ['ecb', 'cbc', 'xts', 'ctr', 'gcm']}
 
 ##
 # @QCryptoIVGenAlgo:
diff --git a/target/arm/cpu-features.h b/target/arm/cpu-features.h
index fb5ed25..280ead6 100644
--- a/target/arm/cpu-features.h
+++ b/target/arm/cpu-features.h
@@ -1590,6 +1590,11 @@
     return FIELD_EX64_IDREG(id, ID_AA64SMFR0, SMOP4);
 }
 
+static inline bool isar_feature_aa64_sme_tmop(const ARMISARegisters *id)
+{
+    return FIELD_EX64_IDREG(id, ID_AA64SMFR0, STMOP);
+}
+
 static inline bool isar_feature_aa64_ssve_aes(const ARMISARegisters *id)
 {
     return FIELD_EX64_IDREG(id, ID_AA64SMFR0, AES);
@@ -1837,6 +1842,26 @@
     return isar_feature_aa64_sme_mop4(id) && isar_feature_aa64_sme_i16i64(id);
 }
 
+static inline bool isar_feature_aa64_sme_tmop_b16b16(const ARMISARegisters *id)
+{
+    return isar_feature_aa64_sme_tmop(id) && isar_feature_aa64_sme_b16b16(id);
+}
+
+static inline bool isar_feature_aa64_sme_tmop_f16f16(const ARMISARegisters *id)
+{
+    return isar_feature_aa64_sme_tmop(id) && isar_feature_aa64_sme_f16f16(id);
+}
+
+static inline bool isar_feature_aa64_sme_tmop_f8f16(const ARMISARegisters *id)
+{
+    return isar_feature_aa64_sme_tmop(id) && isar_feature_aa64_sme_f8f16(id);
+}
+
+static inline bool isar_feature_aa64_sme_tmop_f8f32(const ARMISARegisters *id)
+{
+    return isar_feature_aa64_sme_tmop(id) && isar_feature_aa64_sme_f8f32(id);
+}
+
 /*
  * Feature tests for "does this exist in either 32-bit or 64-bit?"
  */
diff --git a/target/arm/tcg/cpu64.c b/target/arm/tcg/cpu64.c
index 8a3ebf6..24f3494 100644
--- a/target/arm/tcg/cpu64.c
+++ b/target/arm/tcg/cpu64.c
@@ -1384,8 +1384,9 @@
     SET_IDREG(isar, ID_AA64DFR0, t);
 
     t = GET_IDREG(isar, ID_AA64SMFR0);
-    t = FIELD_DP64(t, ID_AA64SMFR0, SFEXPA, 1);   /* FEAT_SSVE_FEXPA */
     t = FIELD_DP64(t, ID_AA64SMFR0, SMOP4, 1);    /* FEAT_SME_MOP4 */
+    t = FIELD_DP64(t, ID_AA64SMFR0, STMOP, 1);    /* FEAT_SME_TMOP */
+    t = FIELD_DP64(t, ID_AA64SMFR0, SFEXPA, 1);   /* FEAT_SSVE_FEXPA */
     t = FIELD_DP64(t, ID_AA64SMFR0, AES, 1);      /* FEAT_SSVE_AES */
     t = FIELD_DP64(t, ID_AA64SMFR0, SF8DP2, 1);   /* FEAT_SSVE_FP8DOT2 */
     t = FIELD_DP64(t, ID_AA64SMFR0, SF8DP4, 1);   /* FEAT_SSVE_FP8DOT4 */
diff --git a/target/arm/tcg/fp8_helper.c b/target/arm/tcg/fp8_helper.c
index 4b046f5..0be811e 100644
--- a/target/arm/tcg/fp8_helper.c
+++ b/target/arm/tcg/fp8_helper.c
@@ -1005,3 +1005,53 @@
     FP8MulContext ctx = fp8_mul_start(env, 0xf);
     sme_mop4(vza, vzn, vzm, &ctx, desc, sizeof(float16), inner_fmop4a_hb);
 }
+
+void HELPER(sme_ftmopa_hb)(void *vza, void *vzn, void *vzm, void *vzk,
+                           CPUArchState *env, uint32_t desc)
+{
+    FP8MulContext ctx = fp8_mul_start(env, 0xf);
+    intptr_t oprsz = simd_maxsz(desc);
+    intptr_t dim = oprsz >> MO_16;
+    intptr_t index = simd_data(desc);
+    intptr_t ctrl_base = (index * oprsz) >> 1;
+    uint8_t *zn0 = vzn, *zn1 = vzn + sizeof(ARMVectorReg);
+    uint16_t *za = vza, *zm = vzm;
+    uint64_t *zk = vzk;
+
+    for (intptr_t row = 0; row < dim; row++) {
+        uint16_t *za_row = za + tile_vslice_offset(row);
+
+        for (intptr_t col = 0; col < dim; col++) {
+            uint16_t e2 = zm[H2(col)];
+            uint16_t *e3 = za_row + H2(col);
+            uint16_t e1 = 0;
+
+            /*
+             * Four control bits select two elements.  The two elements
+             * may be non-contiguous, so assemble them locally into e1.
+             * Pseudo-code has a double loop running forward, with a
+             * test for (i < 2) to limit construction to 2 elements.
+             * Easier to run a single loop backward, shifting extra
+             * elements off the top of our uint16_t.
+             */
+            uint64_t this_ctrl = extractn(zk, ctrl_base + col * 4, 4);
+            for (int i = 3; i >= 0; i--) {
+                if (this_ctrl & (1 << i)) {
+                    bool e = i & 1;
+                    bool r = i & 2;
+                    uint8_t *p = (r ? zn1 : zn0) + H1(2 * row + e);
+                    e1 = (e1 << 8) | *p;
+                }
+            }
+
+            *e3 = f8dotadd_h(e1, e2, 2, *e3, &ctx);
+        }
+    }
+}
+
+void HELPER(sme_ftmopa_sb)(void *vza, void *vzn, void *vzm, void *vzk,
+                           CPUArchState *env, uint32_t desc)
+{
+    FP8MulContext ctx = fp8_mul_start(env, 0xf);
+    sme_tmop_4way_sb(vza, vzn, vzm, vzk, &ctx, desc, inner_fmop4a_sb);
+}
diff --git a/target/arm/tcg/helper-fp8-defs.h b/target/arm/tcg/helper-fp8-defs.h
index dedbd85..e2cf2ba 100644
--- a/target/arm/tcg/helper-fp8-defs.h
+++ b/target/arm/tcg/helper-fp8-defs.h
@@ -47,3 +47,6 @@
 
 DEF_HELPER_FLAGS_5(sme_fmop4a_sb, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, env, i32)
 DEF_HELPER_FLAGS_5(sme_fmop4a_hb, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, env, i32)
+
+DEF_HELPER_FLAGS_6(sme_ftmopa_hb, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, ptr, env, i32)
+DEF_HELPER_FLAGS_6(sme_ftmopa_sb, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, ptr, env, i32)
diff --git a/target/arm/tcg/helper-sme-defs.h b/target/arm/tcg/helper-sme-defs.h
index 7fc7129..b8cb643 100644
--- a/target/arm/tcg/helper-sme-defs.h
+++ b/target/arm/tcg/helper-sme-defs.h
@@ -404,3 +404,18 @@
 DEF_HELPER_FLAGS_4(sme_usmop4s_sb, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
 DEF_HELPER_FLAGS_4(sme_usmop4a_dh, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
 DEF_HELPER_FLAGS_4(sme_usmop4s_dh, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
+
+DEF_HELPER_FLAGS_6(sme_bftmopa_hh, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, ptr, fpst, i32)
+DEF_HELPER_FLAGS_6(sme_ftmopa_hh, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, ptr, fpst, i32)
+DEF_HELPER_FLAGS_6(sme_ftmopa_ss, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, ptr, fpst, i32)
+
+DEF_HELPER_FLAGS_6(sme_bftmopa_sh, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, ptr, env, i32)
+DEF_HELPER_FLAGS_6(sme_ftmopa_sh, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, ptr, env, i32)
+
+DEF_HELPER_FLAGS_5(sme_stmopa_sh, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, ptr, i32)
+DEF_HELPER_FLAGS_5(sme_utmopa_sh, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, ptr, i32)
+
+DEF_HELPER_FLAGS_5(sme_stmopa_sb, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, ptr, i32)
+DEF_HELPER_FLAGS_5(sme_sutmopa_sb, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, ptr, i32)
+DEF_HELPER_FLAGS_5(sme_utmopa_sb, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, ptr, i32)
+DEF_HELPER_FLAGS_5(sme_ustmopa_sb, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, ptr, i32)
diff --git a/target/arm/tcg/sme.decode b/target/arm/tcg/sme.decode
index 53e7e67..e8176ff 100644
--- a/target/arm/tcg/sme.decode
+++ b/target/arm/tcg/sme.decode
@@ -1131,3 +1131,31 @@
 
 USMOP4_sb       1000 0001 000. ...0 1000 00.. ..0. 00..         @mop4_o2
 USMOP4_dh       1010 0001 110. ...0 0000 00.. ..0. 1...         @mop4_o3
+
+# SME TMOP Sparse outer products
+
+&tmop           zad zn zm zk idx
+%tmop_zk        10:3 !function=expand_tmop_zk
+
+@tmop_o1        .... .... ... zm:5 ... ... .... idx:2 ... zad:1 \
+                &tmop zn=%zn_ax2 zk=%tmop_zk
+@tmop_o2        .... .... ... zm:5 ... ... .... idx:2 .. zad:2 \
+                &tmop zn=%zn_ax2 zk=%tmop_zk
+
+BFTMOPA_hh      1000 0001 011 ..... 000 ... .... .. 100 .       @tmop_o1
+FTMOPA_hh       1000 0001 010 ..... 000 ... .... .. 100 .       @tmop_o1
+FTMOPA_ss       1000 0000 010 ..... 000 ... .... .. 00 ..       @tmop_o2
+
+BFTMOPA_sh      1000 0001 010 ..... 000 ... .... .. 00 ..       @tmop_o2
+FTMOPA_sh       1000 0001 011 ..... 000 ... .... .. 00 ..       @tmop_o2
+
+FTMOPA_hb       1000 0000 011 ..... 000 ... .... .. 100 .       @tmop_o1
+FTMOPA_sb       1000 0000 011 ..... 000 ... .... .. 00 ..       @tmop_o2
+
+STMOPA_sh       1000 0000 010 ..... 100 ... .... .. 10 ..       @tmop_o2
+UTMOPA_sh       1000 0001 010 ..... 100 ... .... .. 10 ..       @tmop_o2
+
+STMOPA_sb       1000 0000 010 ..... 100 ... .... .. 00 ..       @tmop_o2
+SUTMOPA_sb      1000 0000 011 ..... 100 ... .... .. 00 ..       @tmop_o2
+USTMOPA_sb      1000 0001 010 ..... 100 ... .... .. 00 ..       @tmop_o2
+UTMOPA_sb       1000 0001 011 ..... 100 ... .... .. 00 ..       @tmop_o2
diff --git a/target/arm/tcg/sme_helper.c b/target/arm/tcg/sme_helper.c
index 23bb816..9de9542 100644
--- a/target/arm/tcg/sme_helper.c
+++ b/target/arm/tcg/sme_helper.c
@@ -2636,6 +2636,137 @@
     }
 }
 
+/*
+ * Sparse outer product, non-widening.  ESZ in {16, 32}.
+ */
+static void sme_tmop(void *vza, void *vzn, void *vzm, uint64_t *zk,
+                     void *fn_opaque, uint32_t desc, MemOp esz,
+                     void (*fn)(void *, void *, void *, void *))
+{
+    intptr_t oprsz = simd_maxsz(desc);
+    intptr_t index = simd_data(desc);
+    intptr_t esize = 1 << esz;
+    intptr_t host_adj = HOST_BIG_ENDIAN ? 8 - esize : 0;
+    /* Base in bits for op3[index*:csize], csize = (VL * 2) / esize. */
+    intptr_t ctrl_base = index * oprsz * 2;
+    /* Create a zero for use with the largest esz. */
+    uint32_t zero = 0;
+
+    for (intptr_t row = 0; row < oprsz; row += esize) {
+        void *vza_row = vza + tile_vslice_offset(row);
+
+        for (intptr_t col = 0; col < oprsz; col += esize) {
+            void *e2 = vzm + (col ^ host_adj);
+            void *e3 = vza_row + (col ^ host_adj);
+
+            /*
+             * Two control bits select one element:
+             *   Zn[row], if [0] is set,
+             *   Zn+1[row], if [1] is set,
+             *   0, otherwise.
+             * Compute the address of that element.
+             */
+            void *e1 = &zero;
+            uint64_t this_ctrl = extractn(zk, (ctrl_base + 2 * col) >> esz, 2);
+            if (this_ctrl) {
+                e1 = vzn + (row ^ host_adj);
+                if (!(this_ctrl & 1)) {
+                    e1 += sizeof(ARMVectorReg);
+                }
+            }
+            fn(e3, e1, e2, fn_opaque);
+        }
+    }
+}
+
+/*
+ * Sparse outer product, widening 2-way, 16 to 32-bit.
+ */
+static void sme_tmop_2way_sh(uint32_t *za, uint16_t *zn0, uint32_t *zm,
+                             uint64_t *zk, void *fn_opaque, uint32_t desc,
+                             void (*fn)(void *, void *, void *, void *))
+{
+    intptr_t oprsz = simd_maxsz(desc);
+    intptr_t dim = oprsz >> MO_32;
+    intptr_t index = simd_data(desc);
+    intptr_t ctrl_base = (index * oprsz) >> 1;
+    uint16_t *zn1 = zn0 + sizeof(ARMVectorReg) / 2;
+
+    for (intptr_t row = 0; row < dim; row++) {
+        uint32_t *za_row = za + tile_vslice_offset(row);
+
+        for (intptr_t col = 0; col < dim; col++) {
+            uint32_t *e2 = zm + H4(col);
+            uint32_t *e3 = za_row + H4(col);
+            uint32_t e1 = 0;
+
+            /*
+             * Four control bits select two elements.  The two elements
+             * may be non-contiguous, so assemble them locally into e1.
+             * Pseudo-code has a double loop running forward, with a
+             * test for (i < 2) to limit construction to 2 elements.
+             * Easier to run a single loop backward, shifting extra
+             * elements off the top of our uint32_t.
+             */
+            uint64_t this_ctrl = extractn(zk, ctrl_base + col * 4, 4);
+            for (int i = 3; i >= 0; i--) {
+                if (this_ctrl & (1 << i)) {
+                    bool e = i & 1;
+                    bool r = i & 2;
+                    uint16_t *p = (r ? zn1 : zn0) + H2(2 * row + e);
+                    e1 = (e1 << 16) | *p;
+                }
+            }
+
+            fn(e3, &e1, e2, fn_opaque);
+        }
+    }
+}
+
+void sme_tmop_4way_sb(uint32_t *za, uint8_t *zn0, uint32_t *zm,
+                      uint64_t *zk, void *fn_opaque, uint32_t desc,
+                      void (*fn)(void *, void *, void *, void *))
+{
+    intptr_t oprsz = simd_maxsz(desc);
+    intptr_t dim = oprsz >> MO_32;
+    intptr_t index = simd_data(desc);
+    intptr_t ctrl_base = (index * oprsz) >> 1;
+    uint8_t *zn1 = zn0 + sizeof(ARMVectorReg);
+
+    for (intptr_t row = 0; row < dim; row++) {
+        uint32_t *za_row = za + tile_vslice_offset(row);
+
+        for (intptr_t col = 0; col < dim; col++) {
+            uint32_t *e2 = zm + H4(col);
+            uint32_t *e3 = za_row + H4(col);
+            uint16_t e1l = 0, e1h = 0;
+            uint32_t e1;
+
+            /*
+             * Eight control bits select two elements from each row.
+             * The elements may be non-contiguous, so assemble them
+             * locally into e1.
+             * Pseudo-code has a triple loop running forward, with a
+             * test for (i < 2) to limit construction to 2 elements.
+             * Easier to run a single loop backward, shifting extra
+             * elements off the top.
+             */
+            uint64_t this_ctrl = extractn(zk, ctrl_base + col * 8, 8);
+            for (int e = 3; e >= 0; e--) {
+                if (this_ctrl & (0x01 << e)) {
+                    e1l = (e1l << 8) | zn0[H1(4 * row + e)];
+                }
+                if (this_ctrl & (0x10 << e)) {
+                    e1h = (e1h << 8) | zn1[H1(4 * row + e)];
+                }
+            }
+            e1 = (e1h << 16) | e1l;
+
+            fn(e3, &e1, e2, fn_opaque);
+        }
+    }
+}
+
 static void inner_fmop4a_hh(void *vd, void *vn, void *vm, void *vinfo)
 {
     float16 *d = vd, *n = vn, *m = vm;
@@ -2650,6 +2781,12 @@
     sme_mop4(vza, vzn, vzm, fpst, desc, sizeof(float16), inner_fmop4a_hh);
 }
 
+void HELPER(sme_ftmopa_hh)(void *vza, void *vzn, void *vzm, void *vzk,
+                           float_status *fpst, uint32_t desc)
+{
+    sme_tmop(vza, vzn, vzm, vzk, fpst, desc, MO_16, inner_fmop4a_hh);
+}
+
 static void inner_fmop4s_hh(void *vd, void *vn, void *vm, void *vinfo)
 {
     float16 *d = vd, *n = vn, *m = vm;
@@ -2692,6 +2829,12 @@
     sme_mop4(vza, vzn, vzm, fpst, desc, sizeof(float32), inner_fmop4a_ss);
 }
 
+void HELPER(sme_ftmopa_ss)(void *vza, void *vzn, void *vzm, void *vzk,
+                           float_status *fpst, uint32_t desc)
+{
+    sme_tmop(vza, vzn, vzm, vzk, fpst, desc, MO_32, inner_fmop4a_ss);
+}
+
 static void inner_fmop4s_ss(void *vd, void *vn, void *vm, void *vinfo)
 {
     float32 *d = vd, *n = vn, *m = vm;
@@ -2776,6 +2919,12 @@
     sme_mop4(vza, vzn, vzm, fpst, desc, sizeof(bfloat16), inner_bfmop4a_hh);
 }
 
+void HELPER(sme_bftmopa_hh)(void *vza, void *vzn, void *vzm, void *vzk,
+                            float_status *fpst, uint32_t desc)
+{
+    sme_tmop(vza, vzn, vzm, vzk, fpst, desc, MO_16, inner_bfmop4a_hh);
+}
+
 static void inner_bfmop4s_hh(void *vd, void *vn, void *vm, void *vinfo)
 {
     bfloat16 *d = vd, *n = vn, *m = vm;
@@ -2832,6 +2981,16 @@
                                 : inner_bfmop4a_sh);
 }
 
+void HELPER(sme_bftmopa_sh)(void *vza, void *vzn, void *vzm, void *vzk,
+                            CPUArchState *env, uint32_t desc)
+{
+    float_status fpst;
+
+    sme_tmop_2way_sh(vza, vzn, vzm, vzk, &fpst, desc,
+                     is_ebf(env, &fpst) ? inner_ebf_bfmop4a_sh
+                                        : inner_bfmop4a_sh);
+}
+
 static void inner_bfmop4s_sh(void *vd, void *vn, void *vm, void *vinfo)
 {
     float32 *d = vd;
@@ -2905,6 +3064,12 @@
     sme_mop4(vza, vzn, vzm, env, desc, sizeof(float32), inner_fmop4a_sh);
 }
 
+void HELPER(sme_ftmopa_sh)(void *vza, void *vzn, void *vzm, void *vzk,
+                           CPUArchState *env, uint32_t desc)
+{
+    sme_tmop_2way_sh(vza, vzn, vzm, vzk, env, desc, inner_fmop4a_sh);
+}
+
 static void inner_fmop4s_sh(void *vd, void *vn, void *vm, void *vinfo)
 {
     float32 *d = vd;
@@ -2958,6 +3123,18 @@
 
 #undef IMOP4_2WAY
 
+#define ITMOP_2WAY(TNAME, MNAME)                                        \
+void HELPER(sme_##TNAME)(void *vza, void *vzn, void *vzm,               \
+                         void *vzk, uint32_t desc)                      \
+{                                                                       \
+    sme_tmop_2way_sh(vza, vzn, vzm, vzk, NULL, desc, inner_##MNAME);    \
+}
+
+ITMOP_2WAY(stmopa_sh, smop4a_sh)
+ITMOP_2WAY(utmopa_sh, umop4a_sh)
+
+#undef ITMOP_2WAY
+
 #define IMOP4_4WAY(NAME, OP, TYPED, TYPEN, TYPEM)                       \
 static void inner_##NAME(void *vd, void *vn, void *vm, void *vinfo)     \
 {                                                                       \
@@ -2991,3 +3168,17 @@
 IMOP4_4WAY(usmop4s_dh, -, int64_t, uint16_t, int16_t)
 
 #undef IMOP4_4WAY
+
+#define ITMOP_4WAY(TNAME, MNAME)                                        \
+void HELPER(sme_##TNAME)(void *vza, void *vzn, void *vzm,               \
+                         void *vzk, uint32_t desc)                      \
+{                                                                       \
+    sme_tmop_4way_sb(vza, vzn, vzm, vzk, NULL, desc, inner_##MNAME);    \
+}
+
+ITMOP_4WAY(stmopa_sb, smop4a_sb)
+ITMOP_4WAY(utmopa_sb, umop4a_sb)
+ITMOP_4WAY(sutmopa_sb, sumop4a_sb)
+ITMOP_4WAY(ustmopa_sb, usmop4a_sb)
+
+#undef ITMOP_4WAY
diff --git a/target/arm/tcg/translate-sme.c b/target/arm/tcg/translate-sme.c
index 0794794..768b76f 100644
--- a/target/arm/tcg/translate-sme.c
+++ b/target/arm/tcg/translate-sme.c
@@ -31,6 +31,12 @@
  * Include the generated decoder.
  */
 
+static int expand_tmop_zk(DisasContext *s, int x)
+{
+    /* Pseudocode for 1:K:1:zk. */
+    return 0b10100 | ((x & 4) << 1) | (x & 3);
+}
+
 #include "decode-sme.c.inc"
 
 static bool sme2_zt0_enabled_check(DisasContext *s)
@@ -2132,3 +2138,76 @@
            a->s ? gen_helper_sme_usmop4s_sb : gen_helper_sme_usmop4a_sb)
 TRANS_FEAT(USMOP4_dh, aa64_sme_mop4_i16i64, do_mop4_int, a, MO_64,
            a->s ? gen_helper_sme_usmop4s_dh : gen_helper_sme_usmop4a_dh)
+
+static bool do_tmop_fp(DisasContext *s, arg_tmop *a, MemOp esz,
+                       int e_fpst, gen_helper_gvec_4_ptr *fn)
+{
+    if (sme_smza_enabled_check(s)) {
+        int svl = streaming_vec_reg_size(s);
+        uint32_t desc = simd_desc(svl, svl, a->idx);
+        TCGv_ptr za = get_tile(s, esz, a->zad);
+        TCGv_ptr zn = vec_full_reg_ptr(s, a->zn);
+        TCGv_ptr zm = vec_full_reg_ptr(s, a->zm);
+        TCGv_ptr zk = vec_full_reg_ptr(s, a->zm);
+        TCGv_ptr fpst = (e_fpst >= 0 ? fpstatus_ptr(e_fpst) : tcg_env);
+
+        fn(za, zn, zm, zk, fpst, tcg_constant_i32(desc));
+    }
+    return true;
+}
+
+TRANS_FEAT(BFTMOPA_hh, aa64_sme_tmop_b16b16, do_tmop_fp,
+           a, MO_16, FPST_ZA, gen_helper_sme_bftmopa_hh)
+TRANS_FEAT(FTMOPA_hh, aa64_sme_tmop_f16f16, do_tmop_fp,
+           a, MO_16, FPST_ZA_F16, gen_helper_sme_ftmopa_hh)
+TRANS_FEAT(FTMOPA_ss, aa64_sme_tmop, do_tmop_fp,
+           a, MO_32, FPST_ZA, gen_helper_sme_ftmopa_ss)
+
+TRANS_FEAT(BFTMOPA_sh, aa64_sme_tmop, do_tmop_fp,
+           a, MO_32, FPST_ENV, gen_helper_sme_bftmopa_sh)
+TRANS_FEAT(FTMOPA_sh, aa64_sme_tmop, do_tmop_fp,
+           a, MO_32, FPST_ENV, gen_helper_sme_ftmopa_sh)
+
+static bool do_tmop_fp8(DisasContext *s, arg_tmop *a, MemOp esz,
+                        gen_helper_gvec_4_ptr *fn)
+{
+    if (!fpmr_access_check(s)) {
+        return true;
+    }
+    return do_tmop_fp(s, a, esz, FPST_ENV, fn);
+}
+
+TRANS_FEAT(FTMOPA_hb, aa64_sme_tmop_f8f16, do_tmop_fp8,
+           a, MO_16, gen_helper_sme_ftmopa_hb)
+TRANS_FEAT(FTMOPA_sb, aa64_sme_tmop_f8f32, do_tmop_fp8,
+           a, MO_32, gen_helper_sme_ftmopa_sb)
+
+static bool do_tmop_int(DisasContext *s, arg_tmop *a, MemOp esz,
+                        gen_helper_gvec_4 *fn)
+{
+    if (sme_smza_enabled_check(s)) {
+        int svl = streaming_vec_reg_size(s);
+        uint32_t desc = simd_desc(svl, svl, a->idx);
+        TCGv_ptr za = get_tile(s, esz, a->zad);
+        TCGv_ptr zn = vec_full_reg_ptr(s, a->zn);
+        TCGv_ptr zm = vec_full_reg_ptr(s, a->zm);
+        TCGv_ptr zk = vec_full_reg_ptr(s, a->zm);
+
+        fn(za, zn, zm, zk, tcg_constant_i32(desc));
+    }
+    return true;
+}
+
+TRANS_FEAT(STMOPA_sh, aa64_sme_tmop, do_tmop_int,
+           a, MO_32, gen_helper_sme_stmopa_sh)
+TRANS_FEAT(UTMOPA_sh, aa64_sme_tmop, do_tmop_int,
+           a, MO_32, gen_helper_sme_utmopa_sh)
+
+TRANS_FEAT(STMOPA_sb, aa64_sme_tmop, do_tmop_int,
+           a, MO_32, gen_helper_sme_stmopa_sb)
+TRANS_FEAT(SUTMOPA_sb, aa64_sme_tmop, do_tmop_int,
+           a, MO_32, gen_helper_sme_sutmopa_sb)
+TRANS_FEAT(USTMOPA_sb, aa64_sme_tmop, do_tmop_int,
+           a, MO_32, gen_helper_sme_ustmopa_sb)
+TRANS_FEAT(UTMOPA_sb, aa64_sme_tmop, do_tmop_int,
+           a, MO_32, gen_helper_sme_utmopa_sb)
diff --git a/target/arm/tcg/vec_internal.h b/target/arm/tcg/vec_internal.h
index 038a2a3..8f6d8a9 100644
--- a/target/arm/tcg/vec_internal.h
+++ b/target/arm/tcg/vec_internal.h
@@ -555,4 +555,11 @@
               uint32_t desc, size_t esize,
               void (*fn)(void *, void *, void *, void *));
 
+/*
+ * Perform SME sparse outer product, 4-way, 8 to 32-bit.
+ */
+void sme_tmop_4way_sb(uint32_t *za, uint8_t *zn0, uint32_t *zm,
+                      uint64_t *zk, void *fn_opaque, uint32_t desc,
+                      void (*fn)(void *, void *, void *, void *));
+
 #endif /* TARGET_ARM_VEC_INTERNAL_H */
diff --git a/target/hexagon/arch.c b/target/hexagon/arch.c
index 0a400bf..3c41735 100644
--- a/target/hexagon/arch.c
+++ b/target/hexagon/arch.c
@@ -199,6 +199,10 @@
     set_float_rounding_mode(
         softfloat_roundingmodes[fREAD_REG_FIELD(USR, USR_FPRND)],
         &env->fp_status);
+    /*
+     * No need to check env->hvx_fp_status, these instructions don't
+     * raise exceptions nor interact with usr fields.
+     */
 }
 
 #ifdef CONFIG_USER_ONLY
@@ -237,6 +241,10 @@
         SOFTFLOAT_TEST_FLAG(float_flag_overflow, FPOVFF, FPOVFE);
         SOFTFLOAT_TEST_FLAG(float_flag_underflow, FPUNFF, FPUNFE);
     }
+    /*
+     * No need to check env->hvx_fp_status, these instructions don't
+     * raise exceptions nor interact with usr fields.
+     */
 }
 
 int arch_sf_recip_common(float32 *Rs, float32 *Rt, float32 *Rd, int *adjust,
diff --git a/target/hexagon/attribs_def.h.inc b/target/hexagon/attribs_def.h.inc
index 6c55063..d12f6ac4 100644
--- a/target/hexagon/attribs_def.h.inc
+++ b/target/hexagon/attribs_def.h.inc
@@ -84,6 +84,7 @@
 DEF_ATTRIB(CVI_SCATTER_RELEASE, "CVI Store Release for scatter", "", "")
 DEF_ATTRIB(CVI_TMP_DST, "CVI instruction that doesn't write a register", "", "")
 DEF_ATTRIB(CVI_SLOT23, "Can execute in slot 2 or slot 3 (HVX)", "", "")
+DEF_ATTRIB(CVI_VA_2SRC, "Execs on multimedia vector engine; requires two srcs", "", "")
 
 DEF_ATTRIB(VTCM_ALLBANK_ACCESS, "Allocates in all VTCM schedulers.", "", "")
 
@@ -196,5 +197,13 @@
 DEF_ATTRIB(RESTRICT_NOSLOT1_STORE, "Packet must not have slot 1 store", "", "")
 DEF_ATTRIB(RESTRICT_LATEPRED, "Predicate can not be used as a .new.", "", "")
 
+/* HVX IEEE FP extension attributes */
+DEF_ATTRIB(HVX_IEEE_FP, "HVX IEEE FP extension instruction", "", "")
+DEF_ATTRIB(HVX_IEEE_FP_ACC, "HVX IEEE FP accumulate instruction", "", "")
+DEF_ATTRIB(HVX_IEEE_FP_OUT_16, "HVX IEEE FP 16-bit output", "", "")
+DEF_ATTRIB(HVX_IEEE_FP_OUT_32, "HVX IEEE FP 32-bit output", "", "")
+DEF_ATTRIB(CVI_VX_NO_TMP_LD, "HVX multiply without tmp load", "", "")
+DEF_ATTRIB(HVX_FLT, "This a floating point HVX instruction.", "", "")
+
 /* Keep this as the last attribute: */
 DEF_ATTRIB(ZZ_LASTATTRIB, "Last attribute in the file", "", "")
diff --git a/target/hexagon/cpu.c b/target/hexagon/cpu.c
index 12d27ce..0ddd898e 100644
--- a/target/hexagon/cpu.c
+++ b/target/hexagon/cpu.c
@@ -65,14 +65,17 @@
     DEFINE_PROP_LINK("tlb", HexagonCPU, tlb, TYPE_HEXAGON_TLB,
                      HexagonTLBState *),
     DEFINE_PROP_UINT32("exec-start-addr", HexagonCPU, boot_addr, 0xffffffff),
+    DEFINE_PROP_LINK("l2vic", HexagonCPU, l2vic,
+                     TYPE_HEX_L2VIC_INTERFACE, HexL2VicInterface *),
     DEFINE_PROP_LINK("global-regs", HexagonCPU, globalregs,
         TYPE_HEXAGON_GLOBALREG, HexagonGlobalRegState *),
     DEFINE_PROP_UINT32("htid", HexagonCPU, htid, 0),
 #endif
-    DEFINE_PROP_BOOL("lldb-compat", HexagonCPU, lldb_compat, false),
-    DEFINE_PROP_UNSIGNED("lldb-stack-adjust", HexagonCPU, lldb_stack_adjust, 0,
-                         qdev_prop_uint32, target_ulong),
-    DEFINE_PROP_BOOL("short-circuit", HexagonCPU, short_circuit, true),
+    DEFINE_PROP_BOOL("lldb-compat", HexagonCPU, cfg.lldb_compat, false),
+    DEFINE_PROP_UNSIGNED("lldb-stack-adjust", HexagonCPU, cfg.lldb_stack_adjust,
+                         0, qdev_prop_uint32, target_ulong),
+    DEFINE_PROP_BOOL("short-circuit", HexagonCPU, cfg.short_circuit, true),
+    DEFINE_PROP_BOOL("ieee-fp", HexagonCPU, cfg.ieee_fp_extension, true),
 };
 
 const char * const hexagon_regnames[TOTAL_PER_THREAD_REGS] = {
@@ -124,7 +127,7 @@
 static target_ulong adjust_stack_ptrs(CPUHexagonState *env, target_ulong addr)
 {
     HexagonCPU *cpu = env_archcpu(env);
-    target_ulong stack_adjust = cpu->lldb_stack_adjust;
+    target_ulong stack_adjust = cpu->cfg.lldb_stack_adjust;
     target_ulong stack_start = env->stack_start;
     target_ulong stack_size = 0x10000;
 
@@ -236,7 +239,7 @@
 {
     HexagonCPU *cpu = env_archcpu(env);
 
-    if (cpu->lldb_compat) {
+    if (cpu->cfg.lldb_compat) {
         /*
          * When comparing with LLDB, it doesn't step through single-cycle
          * hardware loops the same way.  So, we just skip them here
@@ -417,6 +420,9 @@
     set_float_detect_tininess(float_tininess_before_rounding, &env->fp_status);
     /* Default NaN value: sign bit set, all frac bits set */
     set_float_default_nan_pattern(0b11111111, &env->fp_status);
+
+    set_default_nan_mode(1, &env->hvx_fp_status);
+    set_float_default_nan_pattern(0b01111111, &env->hvx_fp_status);
 #ifndef CONFIG_USER_ONLY
     memset(env->t_sreg, 0, sizeof(uint32_t) * NUM_SREGS);
     memset(env->greg, 0, sizeof(uint32_t) * NUM_GREGS);
@@ -441,12 +447,13 @@
     const HexagonCPU *cpu = HEXAGON_CPU(cs);
     info->print_insn = print_insn_hexagon;
     info->endian = BFD_ENDIAN_LITTLE;
-    info->target_info = HEXAGON_CPU_GET_CLASS(cpu)->hex_def;
+    info->target_info = &cpu->cfg;
 }
 
 static void hexagon_cpu_realize(DeviceState *dev, Error **errp)
 {
     CPUState *cs = CPU(dev);
+    HexagonCPU *cpu = HEXAGON_CPU(dev);
     HexagonCPUClass *mcc = HEXAGON_CPU_GET_CLASS(dev);
     Error *local_err = NULL;
 
@@ -456,6 +463,8 @@
         return;
     }
 
+    cpu->cfg.hex_def = mcc->hex_def;
+
     gdb_register_coprocessor(cs, hexagon_hvx_gdb_read_register,
                              hexagon_hvx_gdb_write_register,
                              gdb_find_static_feature("hexagon-hvx.xml"));
diff --git a/target/hexagon/cpu.h b/target/hexagon/cpu.h
index 7694fd9..c50fbb3 100644
--- a/target/hexagon/cpu.h
+++ b/target/hexagon/cpu.h
@@ -39,6 +39,7 @@
 #include "qemu/bitmap.h"
 
 #include "target/hexagon/reg_fields.h"
+#include "hw/intc/hex-l2vic.h"
 
 #define NUM_PREGS 4
 #define TOTAL_PER_THREAD_REGS 64
@@ -151,6 +152,7 @@
     MemLog mem_log_stores[STORES_MAX];
 
     float_status fp_status;
+    float_status hvx_fp_status;
 
     target_ulong llsc_addr;
     target_ulong llsc_val;
@@ -185,24 +187,22 @@
     const HexagonCPUDef *hex_def;
 } HexagonCPUClass;
 
+#include "cpu_bits.h"
+
 struct ArchCPU {
     CPUState parent_obj;
 
     CPUHexagonState env;
-
-    bool lldb_compat;
-    target_ulong lldb_stack_adjust;
-    bool short_circuit;
+    HexagonCPUConfig cfg;
 #ifndef CONFIG_USER_ONLY
     HexagonTLBState *tlb;
     uint32_t boot_addr;
     HexagonGlobalRegState *globalregs;
     uint32_t htid;
+    HexL2VicInterface *l2vic;
 #endif
 };
 
-#include "cpu_bits.h"
-
 FIELD(TB_FLAGS, IS_TIGHT_LOOP, 0, 1)
 FIELD(TB_FLAGS, MMU_INDEX, 1, 3)
 FIELD(TB_FLAGS, PCYCLE_ENABLED, 4, 1)
diff --git a/target/hexagon/cpu_bits.h b/target/hexagon/cpu_bits.h
index 164e74c..a8fba4a 100644
--- a/target/hexagon/cpu_bits.h
+++ b/target/hexagon/cpu_bits.h
@@ -21,6 +21,14 @@
 #include "qemu/bitops.h"
 #include "cpu-qom.h"
 
+typedef struct HexagonCPUConfig {
+    bool lldb_compat;
+    uint32_t lldb_stack_adjust;
+    bool short_circuit;
+    bool ieee_fp_extension;
+    const HexagonCPUDef *hex_def;
+} HexagonCPUConfig;
+
 #define PCALIGN 4
 #define PCALIGN_MASK (PCALIGN - 1)
 
@@ -123,7 +131,7 @@
     return ((bits == 0x3) || (bits == 0x0));
 }
 
-int disassemble_hexagon(uint32_t *words, int nwords, bfd_vma pc, GString *buf,
-                        const HexagonCPUDef *hex_def);
+int disassemble_hexagon(uint32_t *words, int nwords, bfd_vma pc,
+                        GString *buf, const HexagonCPUConfig *cfg);
 
 #endif
diff --git a/target/hexagon/decode.c b/target/hexagon/decode.c
index 6eddcca..b12e91f 100644
--- a/target/hexagon/decode.c
+++ b/target/hexagon/decode.c
@@ -549,21 +549,35 @@
     return bits == 0x2;
 }
 
+/*
+ * Check that the packet's instructions can be grouped into slots: walk them
+ * in encoding order handing out slots in strictly decreasing order, and fail
+ * if an instruction has no valid slot at or below the running slot.  Two
+ * instructions may legally share a slot, so this does not require unique
+ * slots, only that every instruction fits.
+ */
 static bool has_valid_slot_assignment(Packet *pkt)
 {
-    int used_slots = 0;
-    for (int i = 0; i < pkt->num_insns; i++) {
-        int slot_mask;
-        Insn *insn = &pkt->insn[i];
-        if (decode_opcode_ends_loop(insn->opcode)) {
+    int i;
+    int slot = 3;
+
+    for (i = 0; i < pkt->num_insns; i++) {
+        SlotMask valid_slots;
+        if (decode_opcode_ends_loop(pkt->insn[i].opcode)) {
             /* We overload slot 0 for endloop. */
             continue;
         }
-        slot_mask = 1 << insn->slot;
-        if (used_slots & slot_mask) {
+        if (slot < 0) {
             return false;
         }
-        used_slots |= slot_mask;
+        valid_slots = get_valid_slots(pkt, i);
+        while (!(valid_slots & (1 << slot))) {
+            if (slot <= 0) {
+                return false;
+            }
+            slot--;
+        }
+        slot--;
     }
     return true;
 }
@@ -842,7 +856,7 @@
 
 /* Used for "-d in_asm" logging */
 int disassemble_hexagon(uint32_t *words, int nwords, bfd_vma pc,
-                        GString *buf, const HexagonCPUDef  *hex_def)
+                        GString *buf, const HexagonCPUConfig *cfg)
 {
     HexagonCPUDef any_def = {
         .hex_version = HEX_VER_ANY,  /* Allow decode to accept anything */
@@ -853,7 +867,7 @@
     ctx.hex_def = &any_def;
 
     if (decode_packet(&ctx, nwords, words, &ctx.pkt, true) > 0) {
-        snprint_a_pkt_disas(buf, &ctx.pkt, words, pc, hex_def);
+        snprint_a_pkt_disas(buf, &ctx.pkt, words, pc, cfg);
         return ctx.pkt.encod_pkt_size_in_bytes;
     } else {
         for (int i = 0; i < nwords; i++) {
diff --git a/target/hexagon/gen_printinsn.py b/target/hexagon/gen_printinsn.py
index d5f9699..cf1a12a 100755
--- a/target/hexagon/gen_printinsn.py
+++ b/target/hexagon/gen_printinsn.py
@@ -28,13 +28,14 @@
 ##     Generate data for printing each instruction (format string + operands)
 ##
 def regprinter(m):
-    str = m.group(1)
-    str += ":".join(["%d"] * len(m.group(2)))
-    str += m.group(3)
     if ("S" in m.group(1)) and (len(m.group(2)) == 1):
-        str += "/%s"
+        str = "%s"
     elif ("C" in m.group(1)) and (len(m.group(2)) == 1):
-        str += "/%s"
+        str = "%s"
+    else:
+        str = m.group(1)
+        str += ":".join(["%d"] * len(m.group(2)))
+        str += m.group(3)
     return str
 
 
@@ -142,11 +143,12 @@ def main():
                     else:
                         regno = ri
                     if len(b) == 1:
-                        f.write(f", insn->regno[{regno}]")
                         if "S" in a:
                             f.write(f", sreg2str(insn->regno[{regno}])")
                         elif "C" in a:
                             f.write(f", creg2str(insn->regno[{regno}])")
+                        else:
+                            f.write(f", insn->regno[{regno}]")
                     elif len(b) == 2:
                         f.write(f", insn->regno[{regno}] + 1" f", insn->regno[{regno}]")
                     else:
diff --git a/target/hexagon/gen_tcg_funcs.py b/target/hexagon/gen_tcg_funcs.py
index 6d5d99c..2592acf 100755
--- a/target/hexagon/gen_tcg_funcs.py
+++ b/target/hexagon/gen_tcg_funcs.py
@@ -23,6 +23,15 @@
 import hex_common
 from textwrap import dedent
 
+def gen_disabled_ieee_insn(f, tag, regs):
+    f.write("    if (!ctx->ieee_fp_extension) {\n")
+    for regtype, regid in regs:
+        reg = hex_common.get_register(tag, regtype, regid)
+        if reg.is_hvx_reg() and reg.is_written():
+            reg.gen_zero(f)
+    f.write("        return;\n")
+    f.write("    }\n")
+
 ##
 ## Generate the TCG code to call the helper
 ##     For A2_add: Rd32=add(Rs32,Rt32), { RdV=RsV+RtV;}
@@ -74,7 +83,25 @@ def gen_tcg_func(f, tag, regs, imms):
         i = 1 if immlett.isupper() else 0
         f.write(f"    int {hex_common.imm_name(immlett)} = insn->immed[{i}];\n")
 
+    if "A_HVX_IEEE_FP" in hex_common.attribdict[tag]:
+        gen_disabled_ieee_insn(f, tag, regs)
+
     if hex_common.is_idef_parser_enabled(tag):
+        gpr_operands = [
+            hex_common.get_register(tag, regtype, regid)
+            for regtype, regid in regs
+            if hex_common.get_register(tag, regtype, regid).may_alias_gpr()
+        ]
+        dests = [reg for reg in gpr_operands if reg.is_written()]
+        for reg in gpr_operands:
+            if reg.is_written() or not reg.is_read():
+                continue
+            src = reg.reg_tcg()
+            for dest in dests:
+                f.write(hex_common.code_fmt(f"""\
+                    {src} = gen_unalias_gpr_src({src}, {dest.reg_tcg()});
+                """))
+
         declared = []
         ## Handle registers
         for regtype, regid in regs:
diff --git a/target/hexagon/gen_tcg_hvx.h b/target/hexagon/gen_tcg_hvx.h
index 0da64d4..2a342cd 100644
--- a/target/hexagon/gen_tcg_hvx.h
+++ b/target/hexagon/gen_tcg_hvx.h
@@ -234,6 +234,176 @@
     tcg_gen_gvec_sub(MO_32, VddV_off, VuuV_off, VvvV_off, \
                      sizeof(MMVector) * 2, sizeof(MMVector) * 2)
 
+#define fGEN_TCG_V6_vaddbsat(SHORTCODE) \
+    tcg_gen_gvec_ssadd(MO_8, VdV_off, VuV_off, VvV_off, \
+                       VECTOR_SIZE_BYTE, VECTOR_SIZE_BYTE)
+
+#define fGEN_TCG_V6_vaddhsat(SHORTCODE) \
+    tcg_gen_gvec_ssadd(MO_16, VdV_off, VuV_off, VvV_off, \
+                       VECTOR_SIZE_BYTE, VECTOR_SIZE_BYTE)
+
+#define fGEN_TCG_V6_vaddwsat(SHORTCODE) \
+    tcg_gen_gvec_ssadd(MO_32, VdV_off, VuV_off, VvV_off, \
+                       VECTOR_SIZE_BYTE, VECTOR_SIZE_BYTE)
+
+#define fGEN_TCG_V6_vaddubsat(SHORTCODE) \
+    tcg_gen_gvec_usadd(MO_8, VdV_off, VuV_off, VvV_off, \
+                       VECTOR_SIZE_BYTE, VECTOR_SIZE_BYTE)
+
+#define fGEN_TCG_V6_vadduhsat(SHORTCODE) \
+    tcg_gen_gvec_usadd(MO_16, VdV_off, VuV_off, VvV_off, \
+                       VECTOR_SIZE_BYTE, VECTOR_SIZE_BYTE)
+
+#define fGEN_TCG_V6_vadduwsat(SHORTCODE) \
+    tcg_gen_gvec_usadd(MO_32, VdV_off, VuV_off, VvV_off, \
+                       VECTOR_SIZE_BYTE, VECTOR_SIZE_BYTE)
+
+#define fGEN_TCG_PAIR_ADDSUB(OP, VECE, DST, SRC_A, SRC_B) \
+    tcg_gen_gvec_##OP(VECE, DST, SRC_A, SRC_B, \
+                     2 * VECTOR_SIZE_BYTE, 2 * VECTOR_SIZE_BYTE)
+
+#define fGEN_TCG_V6_vaddbsat_dv(SHORTCODE) \
+    fGEN_TCG_PAIR_ADDSUB(ssadd, MO_8, VddV_off, VuuV_off, VvvV_off)
+
+#define fGEN_TCG_V6_vaddhsat_dv(SHORTCODE) \
+    fGEN_TCG_PAIR_ADDSUB(ssadd, MO_16, VddV_off, VuuV_off, VvvV_off)
+
+#define fGEN_TCG_V6_vaddwsat_dv(SHORTCODE) \
+    fGEN_TCG_PAIR_ADDSUB(ssadd, MO_32, VddV_off, VuuV_off, VvvV_off)
+
+#define fGEN_TCG_V6_vaddubsat_dv(SHORTCODE) \
+    fGEN_TCG_PAIR_ADDSUB(usadd, MO_8, VddV_off, VuuV_off, VvvV_off)
+
+#define fGEN_TCG_V6_vadduhsat_dv(SHORTCODE) \
+    fGEN_TCG_PAIR_ADDSUB(usadd, MO_16, VddV_off, VuuV_off, VvvV_off)
+
+#define fGEN_TCG_V6_vadduwsat_dv(SHORTCODE) \
+    fGEN_TCG_PAIR_ADDSUB(usadd, MO_32, VddV_off, VuuV_off, VvvV_off)
+
+#define fGEN_TCG_V6_vsubbsat(SHORTCODE) \
+    tcg_gen_gvec_sssub(MO_8, VdV_off, VuV_off, VvV_off, \
+                       VECTOR_SIZE_BYTE, VECTOR_SIZE_BYTE)
+
+#define fGEN_TCG_V6_vsubhsat(SHORTCODE) \
+    tcg_gen_gvec_sssub(MO_16, VdV_off, VuV_off, VvV_off, \
+                       VECTOR_SIZE_BYTE, VECTOR_SIZE_BYTE)
+
+#define fGEN_TCG_V6_vsubwsat(SHORTCODE) \
+    tcg_gen_gvec_sssub(MO_32, VdV_off, VuV_off, VvV_off, \
+                       VECTOR_SIZE_BYTE, VECTOR_SIZE_BYTE)
+
+#define fGEN_TCG_V6_vsububsat(SHORTCODE) \
+    tcg_gen_gvec_ussub(MO_8, VdV_off, VuV_off, VvV_off, \
+                       VECTOR_SIZE_BYTE, VECTOR_SIZE_BYTE)
+
+#define fGEN_TCG_V6_vsubuhsat(SHORTCODE) \
+    tcg_gen_gvec_ussub(MO_16, VdV_off, VuV_off, VvV_off, \
+                       VECTOR_SIZE_BYTE, VECTOR_SIZE_BYTE)
+
+#define fGEN_TCG_V6_vsubuwsat(SHORTCODE) \
+    tcg_gen_gvec_ussub(MO_32, VdV_off, VuV_off, VvV_off, \
+                       VECTOR_SIZE_BYTE, VECTOR_SIZE_BYTE)
+
+#define fGEN_TCG_V6_vsubbsat_dv(SHORTCODE) \
+    fGEN_TCG_PAIR_ADDSUB(sssub, MO_8, VddV_off, VuuV_off, VvvV_off)
+
+#define fGEN_TCG_V6_vsubhsat_dv(SHORTCODE) \
+    fGEN_TCG_PAIR_ADDSUB(sssub, MO_16, VddV_off, VuuV_off, VvvV_off)
+
+#define fGEN_TCG_V6_vsubwsat_dv(SHORTCODE) \
+    fGEN_TCG_PAIR_ADDSUB(sssub, MO_32, VddV_off, VuuV_off, VvvV_off)
+
+#define fGEN_TCG_V6_vsububsat_dv(SHORTCODE) \
+    fGEN_TCG_PAIR_ADDSUB(ussub, MO_8, VddV_off, VuuV_off, VvvV_off)
+
+#define fGEN_TCG_V6_vsubuhsat_dv(SHORTCODE) \
+    fGEN_TCG_PAIR_ADDSUB(ussub, MO_16, VddV_off, VuuV_off, VvvV_off)
+
+#define fGEN_TCG_V6_vsubuwsat_dv(SHORTCODE) \
+    fGEN_TCG_PAIR_ADDSUB(ussub, MO_32, VddV_off, VuuV_off, VvvV_off)
+
+#define fGEN_TCG_V6_vmpyih(SHORTCODE) \
+    tcg_gen_gvec_mul(MO_16, VdV_off, VuV_off, VvV_off, \
+                     VECTOR_SIZE_BYTE, VECTOR_SIZE_BYTE)
+
+#define fGEN_TCG_V6_vabsdiffub(SHORTCODE) \
+    gen_gvec_uabsdiff(MO_8, VdV_off, VuV_off, VvV_off, \
+                     VECTOR_SIZE_BYTE, VECTOR_SIZE_BYTE)
+
+#define fGEN_TCG_V6_vabsdiffuh(SHORTCODE) \
+    gen_gvec_uabsdiff(MO_16, VdV_off, VuV_off, VvV_off, \
+                     VECTOR_SIZE_BYTE, VECTOR_SIZE_BYTE)
+
+#define fGEN_TCG_V6_vabsdiffh(SHORTCODE) \
+    gen_gvec_sabsdiff(MO_16, VdV_off, VuV_off, VvV_off, \
+                     VECTOR_SIZE_BYTE, VECTOR_SIZE_BYTE)
+
+#define fGEN_TCG_V6_vabsdiffw(SHORTCODE) \
+    gen_gvec_sabsdiff(MO_32, VdV_off, VuV_off, VvV_off, \
+                     VECTOR_SIZE_BYTE, VECTOR_SIZE_BYTE)
+
+#define fGEN_TCG_VEC_AVG(VECE, SHIFT_FN) \
+    do { \
+        intptr_t tmpoff = offsetof(CPUHexagonState, vtmp); \
+        tcg_gen_gvec_and(MO_64, tmpoff, VuV_off, VvV_off, \
+                         VECTOR_SIZE_BYTE, VECTOR_SIZE_BYTE); \
+        tcg_gen_gvec_xor(MO_64, VdV_off, VuV_off, VvV_off, \
+                         VECTOR_SIZE_BYTE, VECTOR_SIZE_BYTE); \
+        SHIFT_FN(VECE, VdV_off, VdV_off, 1, \
+                 VECTOR_SIZE_BYTE, VECTOR_SIZE_BYTE); \
+        tcg_gen_gvec_add(VECE, VdV_off, tmpoff, VdV_off, \
+                         VECTOR_SIZE_BYTE, VECTOR_SIZE_BYTE); \
+    } while (0)
+
+#define fGEN_TCG_V6_vavgub(SHORTCODE) \
+    fGEN_TCG_VEC_AVG(MO_8, tcg_gen_gvec_shri)
+
+#define fGEN_TCG_V6_vavguh(SHORTCODE) \
+    fGEN_TCG_VEC_AVG(MO_16, tcg_gen_gvec_shri)
+
+#define fGEN_TCG_V6_vavguw(SHORTCODE) \
+    fGEN_TCG_VEC_AVG(MO_32, tcg_gen_gvec_shri)
+
+#define fGEN_TCG_V6_vavgb(SHORTCODE) \
+    fGEN_TCG_VEC_AVG(MO_8, tcg_gen_gvec_sari)
+
+#define fGEN_TCG_V6_vavgh(SHORTCODE) \
+    fGEN_TCG_VEC_AVG(MO_16, tcg_gen_gvec_sari)
+
+#define fGEN_TCG_V6_vavgw(SHORTCODE) \
+    fGEN_TCG_VEC_AVG(MO_32, tcg_gen_gvec_sari)
+
+#define fGEN_TCG_VEC_AVGRND(VECE, SHIFT_FN) \
+    do { \
+        intptr_t tmpoff = offsetof(CPUHexagonState, vtmp); \
+        tcg_gen_gvec_or(MO_64, tmpoff, VuV_off, VvV_off, \
+                        VECTOR_SIZE_BYTE, VECTOR_SIZE_BYTE); \
+        tcg_gen_gvec_xor(MO_64, VdV_off, VuV_off, VvV_off, \
+                         VECTOR_SIZE_BYTE, VECTOR_SIZE_BYTE); \
+        SHIFT_FN(VECE, VdV_off, VdV_off, 1, \
+                 VECTOR_SIZE_BYTE, VECTOR_SIZE_BYTE); \
+        tcg_gen_gvec_sub(VECE, VdV_off, tmpoff, VdV_off, \
+                         VECTOR_SIZE_BYTE, VECTOR_SIZE_BYTE); \
+    } while (0)
+
+#define fGEN_TCG_V6_vavgubrnd(SHORTCODE) \
+    fGEN_TCG_VEC_AVGRND(MO_8, tcg_gen_gvec_shri)
+
+#define fGEN_TCG_V6_vavguhrnd(SHORTCODE) \
+    fGEN_TCG_VEC_AVGRND(MO_16, tcg_gen_gvec_shri)
+
+#define fGEN_TCG_V6_vavguwrnd(SHORTCODE) \
+    fGEN_TCG_VEC_AVGRND(MO_32, tcg_gen_gvec_shri)
+
+#define fGEN_TCG_V6_vavgbrnd(SHORTCODE) \
+    fGEN_TCG_VEC_AVGRND(MO_8, tcg_gen_gvec_sari)
+
+#define fGEN_TCG_V6_vavghrnd(SHORTCODE) \
+    fGEN_TCG_VEC_AVGRND(MO_16, tcg_gen_gvec_sari)
+
+#define fGEN_TCG_V6_vavgwrnd(SHORTCODE) \
+    fGEN_TCG_VEC_AVGRND(MO_32, tcg_gen_gvec_sari)
+
 /* Vector shift right - various forms */
 #define fGEN_TCG_V6_vasrh(SHORTCODE) \
     do { \
diff --git a/target/hexagon/genptr.c b/target/hexagon/genptr.c
index 3f31037..2a98b13 100644
--- a/target/hexagon/genptr.c
+++ b/target/hexagon/genptr.c
@@ -37,6 +37,76 @@
 
 #include "genptr.h"
 
+static void gen_sabsdiff_i32(TCGv_i32 d, TCGv_i32 a, TCGv_i32 b)
+{
+    TCGv_i32 t = tcg_temp_new_i32();
+
+    tcg_gen_sub_i32(t, a, b);
+    tcg_gen_sub_i32(d, b, a);
+    tcg_gen_movcond_i32(TCG_COND_LT, d, a, b, d, t);
+}
+
+static void gen_sabsdiff_vec(unsigned vece, TCGv_vec d, TCGv_vec a, TCGv_vec b)
+{
+    TCGv_vec t = tcg_temp_new_vec_matching(d);
+
+    tcg_gen_smin_vec(vece, t, a, b);
+    tcg_gen_smax_vec(vece, d, a, b);
+    tcg_gen_sub_vec(vece, d, d, t);
+}
+
+void gen_gvec_sabsdiff(unsigned vece, uint32_t dofs, uint32_t aofs,
+                       uint32_t bofs, uint32_t oprsz, uint32_t maxsz)
+{
+    static const TCGOpcode vecop_list[] = {
+        INDEX_op_sub_vec, INDEX_op_smin_vec, INDEX_op_smax_vec, 0
+    };
+    static const GVecGen3 ops[4] = {
+        [MO_16] = { .fniv = gen_sabsdiff_vec,
+                    .fno = gen_helper_gvec_sabsdiff_h,
+                    .opt_opc = vecop_list,
+                    .vece = MO_16 },
+        [MO_32] = { .fni4 = gen_sabsdiff_i32,
+                    .fniv = gen_sabsdiff_vec,
+                    .fno = gen_helper_gvec_sabsdiff_w,
+                    .opt_opc = vecop_list,
+                    .vece = MO_32 },
+    };
+
+    tcg_debug_assert(vece == MO_16 || vece == MO_32);
+    tcg_gen_gvec_3(dofs, aofs, bofs, oprsz, maxsz, &ops[vece]);
+}
+
+static void gen_uabsdiff_vec(unsigned vece, TCGv_vec d, TCGv_vec a, TCGv_vec b)
+{
+    TCGv_vec t = tcg_temp_new_vec_matching(d);
+
+    tcg_gen_umin_vec(vece, t, a, b);
+    tcg_gen_umax_vec(vece, d, a, b);
+    tcg_gen_sub_vec(vece, d, d, t);
+}
+
+void gen_gvec_uabsdiff(unsigned vece, uint32_t dofs, uint32_t aofs,
+                       uint32_t bofs, uint32_t oprsz, uint32_t maxsz)
+{
+    static const TCGOpcode vecop_list[] = {
+        INDEX_op_sub_vec, INDEX_op_umin_vec, INDEX_op_umax_vec, 0
+    };
+    static const GVecGen3 ops[4] = {
+        [MO_8] = { .fniv = gen_uabsdiff_vec,
+                   .fno = gen_helper_gvec_uabsdiff_b,
+                   .opt_opc = vecop_list,
+                   .vece = MO_8 },
+        [MO_16] = { .fniv = gen_uabsdiff_vec,
+                    .fno = gen_helper_gvec_uabsdiff_h,
+                    .opt_opc = vecop_list,
+                    .vece = MO_16 },
+    };
+
+    tcg_debug_assert(vece == MO_8 || vece == MO_16);
+    tcg_gen_gvec_3(dofs, aofs, bofs, oprsz, maxsz, &ops[vece]);
+}
+
 TCGv gen_read_reg(TCGv result, int num)
 {
     tcg_gen_mov_tl(result, hex_gpr[num]);
@@ -91,6 +161,17 @@
     }
 }
 
+TCGv gen_unalias_gpr_src(TCGv src, TCGv dst)
+{
+    if (src != dst) {
+        return src;
+    }
+
+    TCGv tmp = tcg_temp_new();
+    tcg_gen_mov_tl(tmp, src);
+    return tmp;
+}
+
 static TCGv_i64 get_result_gpr_pair(DisasContext *ctx, int rnum)
 {
     TCGv_i64 result = tcg_temp_new_i64();
diff --git a/target/hexagon/genptr.h b/target/hexagon/genptr.h
index 45ee038..f498398 100644
--- a/target/hexagon/genptr.h
+++ b/target/hexagon/genptr.h
@@ -36,6 +36,7 @@
 TCGv gen_read_reg(TCGv result, int num);
 TCGv gen_read_preg(TCGv pred, uint8_t num);
 TCGv get_result_gpr(DisasContext *ctx, int rnum);
+TCGv gen_unalias_gpr_src(TCGv src, TCGv dst);
 TCGv get_result_pred(DisasContext *ctx, int pnum);
 void gen_pred_write(DisasContext *ctx, int pnum, TCGv val);
 void gen_set_usr_field(DisasContext *ctx, int field, TCGv val);
@@ -58,6 +59,10 @@
 void gen_set_half(int N, TCGv result, TCGv src);
 void gen_set_half_i64(int N, TCGv_i64 result, TCGv src);
 void probe_noshuf_load(TCGv va, int s, int mi);
+void gen_gvec_sabsdiff(unsigned vece, uint32_t dofs, uint32_t aofs,
+                       uint32_t bofs, uint32_t oprsz, uint32_t maxsz);
+void gen_gvec_uabsdiff(unsigned vece, uint32_t dofs, uint32_t aofs,
+                       uint32_t bofs, uint32_t oprsz, uint32_t maxsz);
 
 extern const target_ulong reg_immut_masks[TOTAL_PER_THREAD_REGS];
 
diff --git a/target/hexagon/helper.h b/target/hexagon/helper.h
index 033e561..78dc28c 100644
--- a/target/hexagon/helper.h
+++ b/target/hexagon/helper.h
@@ -108,6 +108,11 @@
 DEF_HELPER_2(probe_hvx_stores, void, env, int)
 DEF_HELPER_2(probe_pkt_scalar_hvx_stores, void, env, int)
 
+DEF_HELPER_FLAGS_4(gvec_sabsdiff_h, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
+DEF_HELPER_FLAGS_4(gvec_sabsdiff_w, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
+DEF_HELPER_FLAGS_4(gvec_uabsdiff_b, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
+DEF_HELPER_FLAGS_4(gvec_uabsdiff_h, TCG_CALL_NO_RWG, void, ptr, ptr, ptr, i32)
+
 #if !defined(CONFIG_USER_ONLY)
 DEF_HELPER_3(raise_stack_overflow, void, env, i32, i32)
 DEF_HELPER_2(swi, void, env, i32)
diff --git a/target/hexagon/hex_common.py b/target/hexagon/hex_common.py
index d91a653..e33d43e 100755
--- a/target/hexagon/hex_common.py
+++ b/target/hexagon/hex_common.py
@@ -253,6 +253,8 @@ def need_env(tag):
             "A_LOAD" in attribdict[tag] or
             "A_CVI_GATHER" in attribdict[tag] or
             "A_CVI_SCATTER" in attribdict[tag] or
+            "A_HVX_IEEE_FP" in attribdict[tag] or
+            "A_HVX_FLT" in attribdict[tag] or
             "A_IMPLICIT_WRITES_USR" in attribdict[tag] or
             "A_PRIV" in attribdict[tag] or
             "J2_trap" in tag)
@@ -388,6 +390,8 @@ def decl_reg_num(self, f, regno):
         """))
     def idef_arg(self, declared):
         declared.append(self.reg_tcg())
+    def may_alias_gpr(self):
+        return False
     def helper_arg(self):
         return HelperArg(
             self.helper_proto_type(),
@@ -495,6 +499,8 @@ def is_new(self):
         return False
 
 class GprDest(Register, Single, Dest):
+    def may_alias_gpr(self):
+        return True
     def decl_tcg(self, f, tag, regno):
         self.decl_reg_num(f, regno)
         f.write(code_fmt(f"""\
@@ -510,6 +516,8 @@ def analyze_write(self, f, tag, regno):
         """))
 
 class GprSource(Register, Single, OldSource):
+    def may_alias_gpr(self):
+        return True
     def decl_tcg(self, f, tag, regno):
         self.decl_reg_num(f, regno)
         f.write(code_fmt(f"""\
@@ -531,6 +539,8 @@ def analyze_read(self, f, regno):
         """))
 
 class GprReadWrite(Register, Single, ReadWrite):
+    def may_alias_gpr(self):
+        return True
     def decl_tcg(self, f, tag, regno):
         self.decl_reg_num(f, regno)
         f.write(code_fmt(f"""\
@@ -557,6 +567,8 @@ def analyze_write(self, f, tag, regno):
         """))
 
 class ControlDest(Register, Single, Dest):
+    def may_alias_gpr(self):
+        return True
     def decl_reg_num(self, f, regno):
         f.write(code_fmt(f"""\
             const int {self.reg_num} = insn->regno[{regno}]  + HEX_REG_SA0;
@@ -593,6 +605,8 @@ def analyze_read(self, f, regno):
         """))
 
 class ModifierSource(Register, Single, OldSource):
+    def may_alias_gpr(self):
+        return True
     def decl_reg_num(self, f, regno):
         f.write(code_fmt(f"""\
             const int {self.reg_num} = insn->regno[{regno}] + HEX_REG_M0;
@@ -772,6 +786,11 @@ def decl_tcg(self, f, tag, regno):
                 TCGv_ptr {self.reg_tcg()} = tcg_temp_new_ptr();
                 tcg_gen_addi_ptr({self.reg_tcg()}, tcg_env, {self.hvx_off()});
             """))
+    def gen_zero(self, f):
+        f.write(code_fmt(f"""\
+                tcg_gen_gvec_dup_imm(MO_64, {self.hvx_off()},
+                    sizeof(MMVector), sizeof(MMVector), 0);
+            """))
     def gen_write(self, f, tag):
         pass
     def helper_hvx_desc(self, f):
@@ -838,6 +857,11 @@ def decl_tcg(self, f, tag, regno):
                 TCGv_ptr {self.reg_tcg()} = tcg_temp_new_ptr();
                 tcg_gen_addi_ptr({self.reg_tcg()}, tcg_env, {self.hvx_off()});
             """))
+    def gen_zero(self, f):
+        f.write(code_fmt(f"""\
+                tcg_gen_gvec_dup_imm(MO_64, {self.hvx_off()},
+                    sizeof(MMVector), sizeof(MMVector), 0);
+            """))
     def gen_write(self, f, tag):
         pass
     def helper_hvx_desc(self, f):
@@ -870,6 +894,11 @@ def decl_tcg(self, f, tag, regno):
                                  vreg_src_off(ctx, {self.reg_num}),
                                  sizeof(MMVector), sizeof(MMVector));
             """))
+    def gen_zero(self, f):
+        f.write(code_fmt(f"""\
+                tcg_gen_gvec_dup_imm(MO_64, {self.hvx_off()},
+                    sizeof(MMVector), sizeof(MMVector), 0);
+            """))
     def gen_write(self, f, tag):
         f.write(code_fmt(f"""\
             gen_vreg_write(ctx, {self.hvx_off()}, {self.reg_num},
@@ -903,6 +932,11 @@ def decl_tcg(self, f, tag, regno):
                 TCGv_ptr {self.reg_tcg()} = tcg_temp_new_ptr();
                 tcg_gen_addi_ptr({self.reg_tcg()}, tcg_env, {self.hvx_off()});
             """))
+    def gen_zero(self, f):
+        f.write(code_fmt(f"""\
+            tcg_gen_gvec_dup_imm(MO_64, {self.hvx_off()},
+                sizeof(MMVectorPair), sizeof(MMVectorPair), 0);
+        """))
     def gen_write(self, f, tag):
         pass
     def helper_hvx_desc(self, f):
@@ -962,6 +996,11 @@ def decl_tcg(self, f, tag, regno):
                 TCGv_ptr {self.reg_tcg()} = tcg_temp_new_ptr();
                 tcg_gen_addi_ptr({self.reg_tcg()}, tcg_env, {self.hvx_off()});
             """))
+    def gen_zero(self, f):
+        f.write(code_fmt(f"""\
+            tcg_gen_gvec_dup_imm(MO_64, {self.hvx_off()},
+                sizeof(MMVectorPair), sizeof(MMVectorPair), 0);
+        """))
     def gen_write(self, f, tag):
         f.write(code_fmt(f"""\
             gen_vreg_write_pair(ctx, {self.hvx_off()}, {self.reg_num},
diff --git a/target/hexagon/imported/mmvec/encode_ext.def b/target/hexagon/imported/mmvec/encode_ext.def
index 402438f..16f043b 100644
--- a/target/hexagon/imported/mmvec/encode_ext.def
+++ b/target/hexagon/imported/mmvec/encode_ext.def
@@ -647,36 +647,36 @@
 DEF_ENC(V6_vmpyewuh_64,        ICLASS_CJ" 1 110 101 vvvvv PP 0 uuuuu 110 ddddd")
 
 DEF_FIELDROW_DESC32(        ICLASS_CJ" 1 110 --0 ----- PP 1 ----- ----- ---","Vx32=Vu32")
-DEF_ENC(V6_vunpackob,         ICLASS_CJ" 1 110 --0 ---00 PP 1 uuuuu 000 xxxxx") //
-DEF_ENC(V6_vunpackoh,         ICLASS_CJ" 1 110 --0 ---00 PP 1 uuuuu 001 xxxxx") //
+DEF_ENC(V6_vunpackob,         ICLASS_CJ" 1 110 --0 --000 PP 1 uuuuu 000 xxxxx") //
+DEF_ENC(V6_vunpackoh,         ICLASS_CJ" 1 110 --0 --000 PP 1 uuuuu 001 xxxxx") //
 //DEF_ENC(V6_vunpackow,     ICLASS_CJ" 1 110 --0 ---00 PP 1 uuuuu 010 xxxxx") //
 
-DEF_ENC(V6_vhist,            ICLASS_CJ" 1 110 --0 ---00 PP 1 -000- 100 -----")
-DEF_ENC(V6_vwhist256,        ICLASS_CJ" 1 110 --0 ---00 PP 1 -0010 100 -----")
-DEF_ENC(V6_vwhist256_sat,    ICLASS_CJ" 1 110 --0 ---00 PP 1 -0011 100 -----")
-DEF_ENC(V6_vwhist128,        ICLASS_CJ" 1 110 --0 ---00 PP 1 -010- 100 -----")
-DEF_ENC(V6_vwhist128m,        ICLASS_CJ" 1 110 --0 ---00 PP 1 -011i 100 -----")
+DEF_ENC(V6_vhist,            ICLASS_CJ" 1 110 --0 --000 PP 1 -000- 100 -----")
+DEF_ENC(V6_vwhist256,        ICLASS_CJ" 1 110 --0 --000 PP 1 -0010 100 -----")
+DEF_ENC(V6_vwhist256_sat,    ICLASS_CJ" 1 110 --0 --000 PP 1 -0011 100 -----")
+DEF_ENC(V6_vwhist128,        ICLASS_CJ" 1 110 --0 --000 PP 1 -010- 100 -----")
+DEF_ENC(V6_vwhist128m,        ICLASS_CJ" 1 110 --0 --000 PP 1 -011i 100 -----")
 
 DEF_FIELDROW_DESC32(        ICLASS_CJ" 1 110 --0 ----- PP 1 ----- ----- ---","if (Qv4) Vx32=Vu32")
-DEF_ENC(V6_vaddbq,             ICLASS_CJ" 1 110 vv0 ---01 PP 1 uuuuu 000 xxxxx") //
-DEF_ENC(V6_vaddhq,             ICLASS_CJ" 1 110 vv0 ---01 PP 1 uuuuu 001 xxxxx") //
-DEF_ENC(V6_vaddwq,             ICLASS_CJ" 1 110 vv0 ---01 PP 1 uuuuu 010 xxxxx") //
-DEF_ENC(V6_vaddbnq,         ICLASS_CJ" 1 110 vv0 ---01 PP 1 uuuuu 011 xxxxx") //
-DEF_ENC(V6_vaddhnq,         ICLASS_CJ" 1 110 vv0 ---01 PP 1 uuuuu 100 xxxxx") //
-DEF_ENC(V6_vaddwnq,         ICLASS_CJ" 1 110 vv0 ---01 PP 1 uuuuu 101 xxxxx") //
-DEF_ENC(V6_vsubbq,             ICLASS_CJ" 1 110 vv0 ---01 PP 1 uuuuu 110 xxxxx") //
-DEF_ENC(V6_vsubhq,             ICLASS_CJ" 1 110 vv0 ---01 PP 1 uuuuu 111 xxxxx") //
+DEF_ENC(V6_vaddbq,             ICLASS_CJ" 1 110 vv0 --001 PP 1 uuuuu 000 xxxxx") //
+DEF_ENC(V6_vaddhq,             ICLASS_CJ" 1 110 vv0 --001 PP 1 uuuuu 001 xxxxx") //
+DEF_ENC(V6_vaddwq,             ICLASS_CJ" 1 110 vv0 --001 PP 1 uuuuu 010 xxxxx") //
+DEF_ENC(V6_vaddbnq,         ICLASS_CJ" 1 110 vv0 --001 PP 1 uuuuu 011 xxxxx") //
+DEF_ENC(V6_vaddhnq,         ICLASS_CJ" 1 110 vv0 --001 PP 1 uuuuu 100 xxxxx") //
+DEF_ENC(V6_vaddwnq,         ICLASS_CJ" 1 110 vv0 --001 PP 1 uuuuu 101 xxxxx") //
+DEF_ENC(V6_vsubbq,             ICLASS_CJ" 1 110 vv0 --001 PP 1 uuuuu 110 xxxxx") //
+DEF_ENC(V6_vsubhq,             ICLASS_CJ" 1 110 vv0 --001 PP 1 uuuuu 111 xxxxx") //
 
-DEF_ENC(V6_vsubwq,             ICLASS_CJ" 1 110 vv0 ---10 PP 1 uuuuu 000 xxxxx") //
-DEF_ENC(V6_vsubbnq,         ICLASS_CJ" 1 110 vv0 ---10 PP 1 uuuuu 001 xxxxx") //
-DEF_ENC(V6_vsubhnq,         ICLASS_CJ" 1 110 vv0 ---10 PP 1 uuuuu 010 xxxxx") //
-DEF_ENC(V6_vsubwnq,         ICLASS_CJ" 1 110 vv0 ---10 PP 1 uuuuu 011 xxxxx") //
+DEF_ENC(V6_vsubwq,             ICLASS_CJ" 1 110 vv0 --010 PP 1 uuuuu 000 xxxxx") //
+DEF_ENC(V6_vsubbnq,         ICLASS_CJ" 1 110 vv0 --010 PP 1 uuuuu 001 xxxxx") //
+DEF_ENC(V6_vsubhnq,         ICLASS_CJ" 1 110 vv0 --010 PP 1 uuuuu 010 xxxxx") //
+DEF_ENC(V6_vsubwnq,         ICLASS_CJ" 1 110 vv0 --010 PP 1 uuuuu 011 xxxxx") //
 
-DEF_ENC(V6_vhistq,            ICLASS_CJ" 1 110 vv0 ---10 PP 1 --00- 100 -----")
-DEF_ENC(V6_vwhist256q,        ICLASS_CJ" 1 110 vv0 ---10 PP 1 --010 100 -----")
-DEF_ENC(V6_vwhist256q_sat,    ICLASS_CJ" 1 110 vv0 ---10 PP 1 --011 100 -----")
-DEF_ENC(V6_vwhist128q,        ICLASS_CJ" 1 110 vv0 ---10 PP 1 --10- 100 -----")
-DEF_ENC(V6_vwhist128qm,        ICLASS_CJ" 1 110 vv0 ---10 PP 1 --11i 100 -----")
+DEF_ENC(V6_vhistq,            ICLASS_CJ" 1 110 vv0 --010 PP 1 --00- 100 -----")
+DEF_ENC(V6_vwhist256q,        ICLASS_CJ" 1 110 vv0 --010 PP 1 --010 100 -----")
+DEF_ENC(V6_vwhist256q_sat,    ICLASS_CJ" 1 110 vv0 --010 PP 1 --011 100 -----")
+DEF_ENC(V6_vwhist128q,        ICLASS_CJ" 1 110 vv0 --010 PP 1 --10- 100 -----")
+DEF_ENC(V6_vwhist128qm,        ICLASS_CJ" 1 110 vv0 --010 PP 1 --11i 100 -----")
 
 
 DEF_ENC(V6_vandvqv,            ICLASS_CJ" 1 110 vv0 ---11 PP 1 uuuuu 000 ddddd")
@@ -804,5 +804,83 @@
 DEF_ENC(V6_vmpyowh,        ICLASS_CJ" 1 111 111 vvvvv PP 0 uuuuu 111 ddddd")
 DEF_ENC(V6_vmpyuhvs,"00011111110vvvvvPP1uuuuu111ddddd")
 
+/* IEEE FP multiply instructions */
+DEF_ENC(V6_vmpy_sf_sf,"00011111100vvvvvPP1uuuuu001ddddd")
+DEF_ENC(V6_vmpy_sf_hf,"00011111100vvvvvPP1uuuuu010ddddd")
+DEF_ENC(V6_vmpy_hf_hf,"00011111100vvvvvPP1uuuuu011ddddd")
+DEF_ENC(V6_vdmpy_sf_hf,"00011111101vvvvvPP1uuuuu110ddddd")
+
+/* IEEE FP multiply-accumulate instructions */
+DEF_ENC(V6_vmpy_sf_hf_acc,"00011100010vvvvvPP1uuuuu001xxxxx")
+DEF_ENC(V6_vmpy_hf_hf_acc,"00011100010vvvvvPP1uuuuu010xxxxx")
+DEF_ENC(V6_vdmpy_sf_hf_acc,"00011100010vvvvvPP1uuuuu011xxxxx")
+
+/* IEEE FP add/sub instructions */
+DEF_ENC(V6_vadd_sf_sf,"00011111100vvvvvPP1uuuuu110ddddd")
+DEF_ENC(V6_vsub_sf_sf,"00011111100vvvvvPP1uuuuu111ddddd")
+DEF_ENC(V6_vadd_sf_hf,"00011111100vvvvvPP1uuuuu100ddddd")
+DEF_ENC(V6_vsub_sf_hf,"00011111100vvvvvPP1uuuuu101ddddd")
+DEF_ENC(V6_vadd_hf_hf,"00011111101vvvvvPP1uuuuu111ddddd")
+DEF_ENC(V6_vsub_hf_hf,"00011111011vvvvvPP1uuuuu000ddddd")
+
+/* IEEE FP min/max instructions */
+DEF_ENC(V6_vfmin_hf,"00011100011vvvvvPP1uuuuu000ddddd")
+DEF_ENC(V6_vfmin_sf,"00011100011vvvvvPP1uuuuu001ddddd")
+DEF_ENC(V6_vfmax_hf,"00011100011vvvvvPP1uuuuu010ddddd")
+DEF_ENC(V6_vfmax_sf,"00011100011vvvvvPP1uuuuu011ddddd")
+DEF_ENC(V6_vmax_sf,"00011111110vvvvvPP1uuuuu001ddddd")
+DEF_ENC(V6_vmin_sf,"00011111110vvvvvPP1uuuuu010ddddd")
+DEF_ENC(V6_vmax_hf,"00011111110vvvvvPP1uuuuu011ddddd")
+DEF_ENC(V6_vmin_hf,"00011111110vvvvvPP1uuuuu100ddddd")
+
+/* IEEE FP move, negate, abs instructions */
+DEF_ENC(V6_vassign_fp,"00011110--0-0110PP1uuuuu001ddddd")
+DEF_ENC(V6_vfneg_hf,"00011110--0-0110PP1uuuuu010ddddd")
+DEF_ENC(V6_vfneg_sf,"00011110--0-0110PP1uuuuu011ddddd")
+DEF_ENC(V6_vabs_hf,"00011110--0-0110PP1uuuuu100ddddd")
+DEF_ENC(V6_vabs_sf,"00011110--0-0110PP1uuuuu101ddddd")
+
+/* IEEE FP vcvt instructions */
+DEF_ENC(V6_vcvt_sf_hf,"00011110--0-0100PP1uuuuu100ddddd")
+DEF_ENC(V6_vcvt_hf_sf,"00011111011vvvvvPP1uuuuu001ddddd")
+DEF_ENC(V6_vcvt_hf_ub,"00011110--0-0100PP1uuuuu001ddddd")
+DEF_ENC(V6_vcvt_hf_b,"00011110--0-0100PP1uuuuu010ddddd")
+DEF_ENC(V6_vcvt_hf_uh,"00011110--0-0100PP1uuuuu101ddddd")
+DEF_ENC(V6_vcvt_hf_h,"00011110--0-0100PP1uuuuu111ddddd")
+DEF_ENC(V6_vcvt_uh_hf,"00011110--0--101PP1uuuuu000ddddd")
+DEF_ENC(V6_vcvt_h_hf,"00011110--0-0110PP1uuuuu000ddddd")
+DEF_ENC(V6_vcvt_ub_hf,"00011111110vvvvvPP1uuuuu101ddddd")
+DEF_ENC(V6_vcvt_b_hf,"00011111110vvvvvPP1uuuuu110ddddd")
+
+/* IEEE FP vconv instructions */
+DEF_ENC(V6_vconv_sf_w,"00011110--0--101PP1uuuuu011ddddd")
+DEF_ENC(V6_vconv_w_sf,"00011110--0--101PP1uuuuu001ddddd")
+DEF_ENC(V6_vconv_hf_h,"00011110--0--101PP1uuuuu100ddddd")
+DEF_ENC(V6_vconv_h_hf,"00011110--0--101PP1uuuuu010ddddd")
+
+/* IEEE FP compare instructions */
+DEF_ENC(V6_vgtsf,"00011100100vvvvvPP1uuuuu011100dd")
+DEF_ENC(V6_vgthf,"00011100100vvvvvPP1uuuuu011101dd")
+DEF_ENC(V6_vgtsf_and,"00011100100vvvvvPP1uuuuu110010xx")
+DEF_ENC(V6_vgthf_and,"00011100100vvvvvPP1uuuuu110011xx")
+DEF_ENC(V6_vgtsf_or,"00011100100vvvvvPP1uuuuu001100xx")
+DEF_ENC(V6_vgthf_or,"00011100100vvvvvPP1uuuuu001101xx")
+DEF_ENC(V6_vgtsf_xor,"00011100100vvvvvPP1uuuuu111010xx")
+DEF_ENC(V6_vgthf_xor,"00011100100vvvvvPP1uuuuu111011xx")
+
+/* BFLOAT instructions */
+DEF_ENC(V6_vmpy_sf_bf,"00011101010vvvvvPP1uuuuu100ddddd")
+DEF_ENC(V6_vmpy_sf_bf_acc,"00011101000vvvvvPP1uuuuu000xxxxx")
+DEF_ENC(V6_vadd_sf_bf,"00011101010vvvvvPP1uuuuu110ddddd")
+DEF_ENC(V6_vsub_sf_bf,"00011101010vvvvvPP1uuuuu101ddddd")
+DEF_ENC(V6_vmax_bf,"00011101010vvvvvPP1uuuuu111ddddd")
+DEF_ENC(V6_vmin_bf,"00011101010vvvvvPP1uuuuu000ddddd")
+DEF_ENC(V6_vcvt_bf_sf,"00011101010vvvvvPP1uuuuu011ddddd")
+
+/* BFLOAT compare instructions */
+DEF_ENC(V6_vgtbf,"00011100100vvvvvPP1uuuuu011110dd")
+DEF_ENC(V6_vgtbf_and,"00011100100vvvvvPP1uuuuu110100xx")
+DEF_ENC(V6_vgtbf_or,"00011100100vvvvvPP1uuuuu001110xx")
+DEF_ENC(V6_vgtbf_xor,"00011100100vvvvvPP1uuuuu111100xx")
 
 #endif /* NO MMVEC */
diff --git a/target/hexagon/imported/mmvec/ext.idef b/target/hexagon/imported/mmvec/ext.idef
index 03d31f6..857aa61 100644
--- a/target/hexagon/imported/mmvec/ext.idef
+++ b/target/hexagon/imported/mmvec/ext.idef
@@ -43,7 +43,9 @@
 EXTINSN(V6_##TAG, SYNTAX, ATTRIBS(A_EXTENSION,A_CVI,A_CVI_VA),  \
 DESCR, DO_FOR_EACH_CODE(WIDTH, CODE))
 
-
+#define ITERATOR_INSN_ANY_SLOT_2SRC(WIDTH,TAG,SYNTAX,DESCR,CODE) \
+EXTINSN(V6_##TAG, SYNTAX, ATTRIBS(A_EXTENSION,A_CVI,A_CVI_VA,A_CVI_VA_2SRC,A_HVX_FLT),  \
+DESCR, DO_FOR_EACH_CODE(WIDTH, CODE))
 
 #define ITERATOR_INSN2_ANY_SLOT(WIDTH,TAG,SYNTAX,SYNTAX2,DESCR,CODE) \
 ITERATOR_INSN_ANY_SLOT(WIDTH,TAG,SYNTAX2,DESCR,CODE)
@@ -61,6 +63,9 @@
 EXTINSN(V6_##TAG, SYNTAX, ATTRIBS(A_EXTENSION,A_CVI,A_CVI_VS),  \
 DESCR, DO_FOR_EACH_CODE(WIDTH, CODE))
 
+#define ITERATOR_INSN_SHIFT_SLOT_FLT(WIDTH,TAG,SYNTAX,DESCR,CODE) \
+EXTINSN(V6_##TAG, SYNTAX, ATTRIBS(A_EXTENSION,A_CVI,A_CVI_VS,A_HVX_FLT),  \
+DESCR, DO_FOR_EACH_CODE(WIDTH, CODE))
 
 #define ITERATOR_INSN_SHIFT3_SLOT(WIDTH,TAG,SYNTAX,DESCR,CODE) \
 EXTINSN(V6_##TAG, SYNTAX, ATTRIBS(A_EXTENSION,A_CVI,A_CVI_VS,A_CVI_VS_3SRC,A_NOTE_SHIFT_RESOURCE,A_NOTE_NOVP,A_NOTE_VA_UNARY),  \
@@ -2895,9 +2900,371 @@
     }
     } )
 
+/* KVX - IEEE FP Instructions */
 
+/* Single pipe, 32-bit output */
+#define ITERATOR_INSN_IEEE_FP_32(WIDTH,TAG,SYNTAX,DESCR,CODE) \
+EXTINSN(V6_##TAG, SYNTAX, \
+ATTRIBS(A_EXTENSION,A_HVX_IEEE_FP,A_CVI,A_CVI_VX,A_HVX_IEEE_FP_OUT_32), \
+DESCR, DO_FOR_EACH_CODE(WIDTH, CODE))
 
+/* Single pipe, 16-bit output */
+#define ITERATOR_INSN_IEEE_FP_16(WIDTH,TAG,SYNTAX,DESCR,CODE) \
+EXTINSN(V6_##TAG, SYNTAX, \
+ATTRIBS(A_EXTENSION,A_HVX_IEEE_FP,A_CVI,A_CVI_VX,A_HVX_IEEE_FP_OUT_16), \
+DESCR, DO_FOR_EACH_CODE(WIDTH, CODE))
 
+/* Two pipes: P2 & P3, single output: P2, 32-bit output */
+#define ITERATOR_INSN_IEEE_FP_DOUBLE_SINGLE_32(WIDTH,TAG,SYNTAX,DESCR,CODE) \
+EXTINSN(V6_##TAG, SYNTAX, \
+ATTRIBS(A_EXTENSION,A_HVX_IEEE_FP,A_CVI,A_CVI_VX_DV,A_HVX_IEEE_FP_OUT_32), \
+DESCR, DO_FOR_EACH_CODE(WIDTH, CODE))
+
+/* Two pipes: P2 & P3, two outputs, 32-bit output */
+#define ITERATOR_INSN_IEEE_FP_DOUBLE_32(WIDTH,TAG,SYNTAX,DESCR,CODE) \
+EXTINSN(V6_##TAG, SYNTAX, \
+ATTRIBS(A_EXTENSION,A_HVX_IEEE_FP,A_CVI,A_CVI_VX_DV,A_HVX_IEEE_FP_OUT_32), \
+DESCR, DO_FOR_EACH_CODE(WIDTH, CODE))
+
+/*
+ * single pipe, accumulate instruction, produces 16-bit output, requires 16-bit
+ * accumulate input
+ */
+#define ITERATOR_INSN_IEEE_FP_ACC_16(WIDTH,TAG,SYNTAX,DESCR,CODE) \
+EXTINSN(V6_##TAG, SYNTAX, \
+ATTRIBS(A_EXTENSION,A_HVX_IEEE_FP,A_CVI,A_CVI_VX,A_HVX_IEEE_FP_ACC,A_HVX_IEEE_FP_OUT_16,A_CVI_VX_NO_TMP_LD), \
+DESCR, DO_FOR_EACH_CODE(WIDTH, CODE))
+
+/*
+ * single pipe, accumulate instruction, produces 32-bit output, requires 32-bit
+ * accumulate input
+ */
+#define ITERATOR_INSN_IEEE_FP_ACC_32(WIDTH,TAG,SYNTAX,DESCR,CODE) \
+EXTINSN(V6_##TAG, SYNTAX, \
+ATTRIBS(A_EXTENSION,A_HVX_IEEE_FP,A_CVI,A_CVI_VX,A_HVX_IEEE_FP_ACC,A_HVX_IEEE_FP_OUT_32,A_CVI_VX_NO_TMP_LD), \
+DESCR, DO_FOR_EACH_CODE(WIDTH, CODE))
+
+/* IEEE FP multiply instructions */
+ITERATOR_INSN_IEEE_FP_DOUBLE_SINGLE_32(32, vmpy_sf_sf,
+    "Vd32.sf=vmpy(Vu32.sf,Vv32.sf)", "Vector IEEE mul: sf",
+    VdV.sf[i] = float32_mul(VuV.sf[i], VvV.sf[i], &env->hvx_fp_status))
+ITERATOR_INSN_IEEE_FP_DOUBLE_32(32, vmpy_sf_hf,
+    "Vdd32.sf=vmpy(Vu32.hf,Vv32.hf)", "Vector IEEE mul: hf widen to sf",
+    VddV.v[0].sf[i] = fp_mult_sf_hf(VuV.hf[2*i], VvV.hf[2*i], &env->hvx_fp_status);
+    VddV.v[1].sf[i] = fp_mult_sf_hf(VuV.hf[2*i+1], VvV.hf[2*i+1], &env->hvx_fp_status))
+ITERATOR_INSN_IEEE_FP_16(16, vmpy_hf_hf,     "Vd32.hf=vmpy(Vu32.hf,Vv32.hf)",
+    "Vector IEEE mul: hf",
+    VdV.hf[i] = float16_mul(VuV.hf[i], VvV.hf[i], &env->hvx_fp_status))
+ITERATOR_INSN_IEEE_FP_32(32, vdmpy_sf_hf,     "Vd32.sf=vdmpy(Vu32.hf,Vv32.hf)",
+    "Vector IEEE mul reduction: hf widen to sf",
+    VdV.sf[i] = fp_vdmpy(VuV.hf[2*i+1], VuV.hf[2*i], VvV.hf[2*i+1],
+        VvV.hf[2*i], &env->hvx_fp_status))
+
+/* IEEE FP multiply-accumulate instructions */
+ITERATOR_INSN_IEEE_FP_DOUBLE_32(32, vmpy_sf_hf_acc,
+    "Vxx32.sf+=vmpy(Vu32.hf,Vv32.hf)", "Vector IEEE fma: hf widen to sf",
+    VxxV.v[0].sf[i] = float32_muladd(f16_to_f32(VuV.hf[2*i]),
+                                     f16_to_f32(VvV.hf[2*i]),
+                                     VxxV.v[0].sf[i], 0, &env->hvx_fp_status);
+    VxxV.v[1].sf[i] = float32_muladd(f16_to_f32(VuV.hf[2*i+1]),
+                                     f16_to_f32(VvV.hf[2*i+1]),
+                                     VxxV.v[1].sf[i], 0, &env->hvx_fp_status))
+ITERATOR_INSN_IEEE_FP_ACC_16(16, vmpy_hf_hf_acc,
+    "Vx32.hf+=vmpy(Vu32.hf,Vv32.hf)", "Vector IEEE fma: hf",
+    VxV.hf[i] = float16_muladd(VuV.hf[i], VvV.hf[i], VxV.hf[i], 0, &env->hvx_fp_status))
+ITERATOR_INSN_IEEE_FP_ACC_32(32, vdmpy_sf_hf_acc,
+    "Vx32.sf+=vdmpy(Vu32.hf,Vv32.hf)", "Vector IEEE fma reduce: hf widen to sf",
+    VxV.sf[i] = float32_add(fp_vdmpy(VuV.hf[2*i+1], VuV.hf[2*i],
+                                     VvV.hf[2*i+1], VvV.hf[2*i],
+                                     &env->hvx_fp_status),
+                            VxV.sf[i], &env->hvx_fp_status))
+
+/* IEEE FP add/sub instructions */
+ITERATOR_INSN_IEEE_FP_32(32, vadd_sf_sf, "Vd32.sf=vadd(Vu32.sf,Vv32.sf)",
+    "Vector IEEE add: sf",
+    VdV.sf[i] = float32_add(VuV.sf[i], VvV.sf[i], &env->hvx_fp_status))
+ITERATOR_INSN_IEEE_FP_32(32, vsub_sf_sf, "Vd32.sf=vsub(Vu32.sf,Vv32.sf)",
+    "Vector IEEE sub: sf",
+    VdV.sf[i] = float32_sub(VuV.sf[i], VvV.sf[i], &env->hvx_fp_status))
+ITERATOR_INSN_IEEE_FP_16(16, vadd_hf_hf, "Vd32.hf=vadd(Vu32.hf,Vv32.hf)",
+    "Vector IEEE add: hf",
+    VdV.hf[i] = float16_add(VuV.hf[i], VvV.hf[i], &env->hvx_fp_status))
+ITERATOR_INSN_IEEE_FP_16(16, vsub_hf_hf, "Vd32.hf=vsub(Vu32.hf,Vv32.hf)",
+    "Vector IEEE sub: hf",
+    VdV.hf[i] = float16_sub(VuV.hf[i], VvV.hf[i], &env->hvx_fp_status))
+ITERATOR_INSN_IEEE_FP_DOUBLE_32(32, vadd_sf_hf,
+    "Vdd32.sf=vadd(Vu32.hf,Vv32.hf)",  "Vector IEEE add: hf widen to sf",
+    VddV.v[0].sf[i] = float32_add(f16_to_f32(VuV.hf[2*i]),
+                                  f16_to_f32(VvV.hf[2*i]), &env->hvx_fp_status);
+    VddV.v[1].sf[i] = float32_add(f16_to_f32(VuV.hf[2*i+1]),
+                                  f16_to_f32(VvV.hf[2*i+1]), &env->hvx_fp_status))
+ITERATOR_INSN_IEEE_FP_DOUBLE_32(32, vsub_sf_hf,
+    "Vdd32.sf=vsub(Vu32.hf,Vv32.hf)",  "Vector IEEE sub: hf widen to sf",
+    VddV.v[0].sf[i] = float32_sub(f16_to_f32(VuV.hf[2*i]),
+                                  f16_to_f32(VvV.hf[2*i]), &env->hvx_fp_status);
+    VddV.v[1].sf[i] = float32_sub(f16_to_f32(VuV.hf[2*i+1]),
+                                  f16_to_f32(VvV.hf[2*i+1]), &env->hvx_fp_status))
+
+#define ITERATOR_INSN_IEEE_FP_16_32_LATE(WIDTH,TAG,SYNTAX,DESCR,CODE) \
+EXTINSN(V6_##TAG, SYNTAX, \
+        ATTRIBS(A_EXTENSION,A_HVX_IEEE_FP,A_CVI,A_CVI_VX,A_HVX_IEEE_FP_OUT_16,A_HVX_IEEE_FP_OUT_32), \
+        DESCR, DO_FOR_EACH_CODE(WIDTH, CODE))
+
+/* IEEE FP min/max instructions */
+ITERATOR_INSN_IEEE_FP_16_32_LATE(16, vfmin_hf, "Vd32.hf=vfmin(Vu32.hf,Vv32.hf)", \
+    "Vector IEEE min: hf",  VdV.hf[i] = float16_min(VuV.hf[i], VvV.hf[i], \
+	&env->hvx_fp_status))
+ITERATOR_INSN_IEEE_FP_16_32_LATE(32, vfmin_sf, "Vd32.sf=vfmin(Vu32.sf,Vv32.sf)", \
+    "Vector IEEE min: sf",  VdV.sf[i] = float32_min(VuV.sf[i], VvV.sf[i], \
+	&env->hvx_fp_status))
+ITERATOR_INSN_IEEE_FP_16_32_LATE(16, vfmax_hf,  "Vd32.hf=vfmax(Vu32.hf,Vv32.hf)", \
+    "Vector IEEE max: hf", VdV.hf[i] = float16_max(VuV.hf[i], VvV.hf[i], \
+	&env->hvx_fp_status))
+ITERATOR_INSN_IEEE_FP_16_32_LATE(32, vfmax_sf,  "Vd32.sf=vfmax(Vu32.sf,Vv32.sf)", \
+    "Vector IEEE max: sf", VdV.sf[i] = float32_max(VuV.sf[i], VvV.sf[i], \
+	&env->hvx_fp_status))
+
+ITERATOR_INSN_ANY_SLOT_2SRC(32,vmax_sf,"Vd32.sf=vmax(Vu32.sf,Vv32.sf)", \
+    "Vector max of sf input", VdV.sf[i] = qf_max_sf(VuV.sf[i], VvV.sf[i], \
+	&env->hvx_fp_status))
+ITERATOR_INSN_ANY_SLOT_2SRC(32,vmin_sf,"Vd32.sf=vmin(Vu32.sf,Vv32.sf)", \
+    "Vector min of sf input", VdV.sf[i] = qf_min_sf(VuV.sf[i], VvV.sf[i], \
+	&env->hvx_fp_status))
+ITERATOR_INSN_ANY_SLOT_2SRC(16,vmax_hf,"Vd32.hf=vmax(Vu32.hf,Vv32.hf)", \
+    "Vector max of hf input", VdV.hf[i] = qf_max_hf(VuV.hf[i], VvV.hf[i], \
+	&env->hvx_fp_status))
+ITERATOR_INSN_ANY_SLOT_2SRC(16,vmin_hf,"Vd32.hf=vmin(Vu32.hf,Vv32.hf)", \
+    "Vector min of hf input", VdV.hf[i] = qf_min_hf(VuV.hf[i], VvV.hf[i], \
+	&env->hvx_fp_status))
+
+/* IEEE FP move, negate, abs instructions */
+ITERATOR_INSN_IEEE_FP_16_32_LATE(32, vassign_fp, "Vd32.w=vfmv(Vu32.w)", \
+    "Vector IEEE move", VdV.w[i]  = VuV.w[i])
+ITERATOR_INSN_IEEE_FP_16_32_LATE(16, vfneg_hf, "Vd32.hf=vfneg(Vu32.hf)", \
+    "Vector IEEE neg: hf", VdV.hf[i] = float16_chs(VuV.hf[i]))
+ITERATOR_INSN_IEEE_FP_16_32_LATE(32, vfneg_sf, "Vd32.sf=vfneg(Vu32.sf)", \
+    "Vector IEEE neg: sf", VdV.sf[i] = float32_chs(VuV.sf[i]))
+ITERATOR_INSN_IEEE_FP_16_32_LATE(16, vabs_hf,  "Vd32.hf=vabs(Vu32.hf)", \
+    "Vector IEEE abs: hf", VdV.hf[i] = float16_abs(VuV.hf[i]))
+ITERATOR_INSN_IEEE_FP_16_32_LATE(32, vabs_sf,  "Vd32.sf=vabs(Vu32.sf)", \
+    "Vector IEEE abs: sf", VdV.sf[i] = float32_abs(VuV.sf[i]))
+
+/* Two pipes: P2 & P3, two outputs, 16-bit */
+#define ITERATOR_INSN_IEEE_FP_DOUBLE_16(WIDTH,TAG,SYNTAX,DESCR,CODE) \
+EXTINSN(V6_##TAG, SYNTAX, \
+ATTRIBS(A_EXTENSION,A_HVX_IEEE_FP,A_CVI,A_CVI_VX_DV,A_HVX_IEEE_FP_OUT_16), \
+DESCR, DO_FOR_EACH_CODE(WIDTH, CODE))
+
+/* Two pipes: P2 & P3, two outputs, 32-bit output */
+#define ITERATOR_INSN_IEEE_FP_DOUBLE_32(WIDTH,TAG,SYNTAX,DESCR,CODE) \
+EXTINSN(V6_##TAG, SYNTAX, \
+    ATTRIBS(A_EXTENSION,A_HVX_IEEE_FP,A_CVI,A_CVI_VX_DV,A_HVX_IEEE_FP_OUT_32), \
+    DESCR, DO_FOR_EACH_CODE(WIDTH, CODE))
+
+/* Single pipe, 16-bit output */
+#define ITERATOR_INSN_IEEE_FP_16(WIDTH,TAG,SYNTAX,DESCR,CODE) \
+EXTINSN(V6_##TAG, SYNTAX, \
+    ATTRIBS(A_EXTENSION,A_HVX_IEEE_FP,A_CVI,A_CVI_VX,A_HVX_IEEE_FP_OUT_16), \
+    DESCR, DO_FOR_EACH_CODE(WIDTH, CODE))
+
+/* single pipe, output can feed 16- or 32-bit accumulate */
+#define ITERATOR_INSN_IEEE_FP_16_32(WIDTH,TAG,SYNTAX,DESCR,CODE) \
+EXTINSN(V6_##TAG, SYNTAX, \
+    ATTRIBS(A_EXTENSION,A_HVX_IEEE_FP,A_CVI,A_CVI_VX,A_HVX_IEEE_FP_OUT_16,A_HVX_IEEE_FP_OUT_32), \
+    DESCR, DO_FOR_EACH_CODE(WIDTH, CODE))
+
+/******************************************************************************
+ * IEEE FP convert instructions
+ ******************************************************************************/
+
+ITERATOR_INSN_IEEE_FP_DOUBLE_16(32,  vcvt_hf_ub, "Vdd32.hf=vcvt(Vu32.ub)",
+    "Vector IEEE cvt from int: ub widen to hf",
+    VddV.v[0].hf[2*i]   = uint64_to_float16_scalbn(VuV.ub[4*i], float_round_nearest_even, 0);
+    VddV.v[0].hf[2*i+1] = uint64_to_float16_scalbn(VuV.ub[4*i+1], float_round_nearest_even, 0);
+    VddV.v[1].hf[2*i]   = uint64_to_float16_scalbn(VuV.ub[4*i+2], float_round_nearest_even, 0);
+    VddV.v[1].hf[2*i+1] = uint64_to_float16_scalbn(VuV.ub[4*i+3], float_round_nearest_even, 0))
+
+ITERATOR_INSN_IEEE_FP_DOUBLE_16(32,  vcvt_hf_b,  "Vdd32.hf=vcvt(Vu32.b)",
+    "Vector IEEE cvt from int: b widen to hf",
+    VddV.v[0].hf[2*i]   = int64_to_float16_scalbn(VuV.b[4*i], float_round_nearest_even, 0);
+    VddV.v[0].hf[2*i+1] = int64_to_float16_scalbn(VuV.b[4*i+1], float_round_nearest_even, 0);
+    VddV.v[1].hf[2*i]   = int64_to_float16_scalbn(VuV.b[4*i+2], float_round_nearest_even, 0);
+    VddV.v[1].hf[2*i+1] = int64_to_float16_scalbn(VuV.b[4*i+3], float_round_nearest_even, 0))
+
+ITERATOR_INSN_IEEE_FP_DOUBLE_32(32, vcvt_sf_hf, "Vdd32.sf=vcvt(Vu32.hf)",
+    "Vector IEEE cvt: hf widen to sf",
+    VddV.v[0].sf[i] = f16_to_f32(VuV.hf[2*i]);
+    VddV.v[1].sf[i] = f16_to_f32(VuV.hf[2*i+1]))
+
+ITERATOR_INSN_IEEE_FP_16(16,    vcvt_hf_uh, "Vd32.hf=vcvt(Vu32.uh)",
+    "Vector IEEE cvt from int: uh to hf",
+    VdV.hf[i] = uint64_to_float16_scalbn(VuV.uh[i], float_round_nearest_even, 0))
+ITERATOR_INSN_IEEE_FP_16(16,    vcvt_hf_h,  "Vd32.hf=vcvt(Vu32.h)",
+    "Vector IEEE cvt from int: h to hf",
+    VdV.hf[i] = int64_to_float16_scalbn(VuV.h[i], float_round_nearest_even, 0))
+ITERATOR_INSN_IEEE_FP_16_32(16, vcvt_uh_hf, "Vd32.uh=vcvt(Vu32.hf)",
+    "Vector IEEE cvt to int: hf to uh",
+    VdV.uh[i] = float16_to_uint16_scalbn(VuV.hf[i], float_round_nearest_even, 0, &env->hvx_fp_status))
+ITERATOR_INSN_IEEE_FP_16_32(16, vcvt_h_hf,  "Vd32.h=vcvt(Vu32.hf)",
+    "Vector IEEE cvt to int: hf to h",
+    VdV.h[i]  = float16_to_int16_scalbn(VuV.hf[i], float_round_nearest_even, 0, &env->hvx_fp_status))
+
+ITERATOR_INSN_IEEE_FP_16(32, vcvt_hf_sf, "Vd32.hf=vcvt(Vu32.sf,Vv32.sf)",
+    "Vector IEEE cvt: sf to hf",
+    VdV.hf[2*i]   = f32_to_f16(VuV.sf[i]);
+    VdV.hf[2*i+1] = f32_to_f16(VvV.sf[i]))
+
+ITERATOR_INSN_IEEE_FP_16_32(32, vcvt_ub_hf, "Vd32.ub=vcvt(Vu32.hf,Vv32.hf)", "Vector cvt to int: hf narrow to ub",
+    VdV.ub[4*i]   = float16_to_uint8_scalbn(VuV.hf[2*i], float_round_nearest_even, 0, &env->hvx_fp_status);
+    VdV.ub[4*i+1] = float16_to_uint8_scalbn(VuV.hf[2*i+1], float_round_nearest_even, 0, &env->hvx_fp_status);
+    VdV.ub[4*i+2] = float16_to_uint8_scalbn(VvV.hf[2*i], float_round_nearest_even, 0, &env->hvx_fp_status);
+    VdV.ub[4*i+3] = float16_to_uint8_scalbn(VvV.hf[2*i+1], float_round_nearest_even, 0, &env->hvx_fp_status))
+
+ITERATOR_INSN_IEEE_FP_16_32(32, vcvt_b_hf,  "Vd32.b=vcvt(Vu32.hf,Vv32.hf)",
+    "Vector cvt to int: hf narrow to b",
+    VdV.b[4*i]   = float16_to_int8_scalbn(VuV.hf[2*i], float_round_nearest_even, 0, &env->hvx_fp_status);
+    VdV.b[4*i+1] = float16_to_int8_scalbn(VuV.hf[2*i+1], float_round_nearest_even, 0, &env->hvx_fp_status);
+    VdV.b[4*i+2] = float16_to_int8_scalbn(VvV.hf[2*i], float_round_nearest_even, 0, &env->hvx_fp_status);
+    VdV.b[4*i+3] = float16_to_int8_scalbn(VvV.hf[2*i+1], float_round_nearest_even, 0, &env->hvx_fp_status))
+
+ITERATOR_INSN_SHIFT_SLOT_FLT(32, vconv_w_sf,"Vd32.w=Vu32.sf",
+    "Vector conversion of sf32 format to int w",
+    VdV.w[i] = conv_w_sf(VuV.sf[i], &env->hvx_fp_status))
+
+ITERATOR_INSN_SHIFT_SLOT_FLT(16, vconv_h_hf,"Vd32.h=Vu32.hf",
+    "Vector conversion of hf16 format to int hw",
+    VdV.h[i] = conv_h_hf(VuV.hf[i], &env->hvx_fp_status))
+
+ITERATOR_INSN_SHIFT_SLOT_FLT(32, vconv_sf_w,"Vd32.sf=Vu32.w",
+    "Vector conversion of int w format to sf32",
+    VdV.sf[i] = int32_to_float32(VuV.w[i], &env->hvx_fp_status))
+
+ITERATOR_INSN_SHIFT_SLOT_FLT(16, vconv_hf_h,"Vd32.hf=Vu32.h",
+    "Vector conversion of int hw format to hf16",
+    VdV.hf[i] = float16_val(int16_to_float16(VuV.h[i], &env->hvx_fp_status)))
+
+/******************************************************************************
+ * IEEE FP compare instructions
+ ******************************************************************************/
+
+#define VCMPGT_SF(DEST, ASRC, ASRCOP, CMP, N, SRC, MASK, WIDTH) \
+{ \
+    for (fHIDE(int) i = 0; i < fVBYTES(); i += WIDTH) { \
+        fHIDE(int) VAL = fCMPGT_SF(VuV.SRC[i/WIDTH],VvV.SRC[i/WIDTH]) ? MASK : 0; \
+        fSETQBITS(DEST,WIDTH,MASK,i,ASRC ASRCOP VAL); \
+    } \
+}
+
+#define VCMPGT_HF(DEST, ASRC, ASRCOP, CMP, N, SRC, MASK, WIDTH) \
+{ \
+    for (fHIDE(int) i = 0; i < fVBYTES(); i += WIDTH) { \
+        fHIDE(int) VAL = fCMPGT_HF(VuV.SRC[i/WIDTH],VvV.SRC[i/WIDTH]) ? MASK : 0; \
+        fSETQBITS(DEST,WIDTH,MASK,i,ASRC ASRCOP VAL); \
+    } \
+}
+
+#define VCMPGT_BF(DEST, ASRC, ASRCOP, CMP, N, SRC, MASK, WIDTH) \
+{ \
+    fBFLOAT(); \
+    for (fHIDE(int) i = 0; i < fVBYTES(); i += WIDTH) { \
+        fHIDE(int) VAL = fCMPGT_BF(VuV.SRC[i/WIDTH],VvV.SRC[i/WIDTH]) ? MASK : 0; \
+        fSETQBITS(DEST,WIDTH,MASK,i,ASRC ASRCOP VAL); \
+    } \
+}
+
+/* Vector SF compare */
+#define MMVEC_CMPGT_SF(TYPE,TYPE2,DESCR,N,MASK,WIDTH,SRC) \
+    EXTINSN(V6_vgt##TYPE##_and, "Qx4&=vcmp.gt(Vu32." TYPE2 ",Vv32." TYPE2 ")", \
+        ATTRIBS(A_EXTENSION,A_CVI,A_CVI_VA,A_CVI_VA_2SRC,A_HVX_FLT), \
+        DESCR" greater than with predicate-and", \
+        VCMPGT_SF(QxV, fGETQBITS(QxV,WIDTH,MASK,i), &, ">", N, SRC, MASK, WIDTH)) \
+    EXTINSN(V6_vgt##TYPE##_xor, "Qx4^=vcmp.gt(Vu32." TYPE2 ",Vv32." TYPE2 ")", \
+        ATTRIBS(A_EXTENSION,A_CVI,A_CVI_VA,A_CVI_VA_2SRC,A_HVX_FLT), \
+        DESCR" greater than with predicate-xor", \
+        VCMPGT_SF(QxV, fGETQBITS(QxV,WIDTH,MASK,i), ^, ">", N, SRC, MASK, WIDTH)) \
+    EXTINSN(V6_vgt##TYPE##_or, "Qx4|=vcmp.gt(Vu32." TYPE2 ",Vv32." TYPE2 ")", \
+        ATTRIBS(A_EXTENSION,A_CVI,A_CVI_VA,A_CVI_VA_2SRC,A_HVX_FLT), \
+        DESCR" greater than with predicate-or", \
+        VCMPGT_SF(QxV, fGETQBITS(QxV,WIDTH,MASK,i), |, ">", N, SRC, MASK, WIDTH)) \
+    EXTINSN(V6_vgt##TYPE, "Qd4=vcmp.gt(Vu32." TYPE2 ",Vv32." TYPE2 ")", \
+        ATTRIBS(A_EXTENSION,A_CVI,A_CVI_VA,A_CVI_VA_2SRC,A_HVX_FLT), \
+        DESCR" greater than", \
+        VCMPGT_SF(QdV, , , ">", N, SRC, MASK, WIDTH))
+
+/* Vector HF compare */
+#define MMVEC_CMPGT_HF(TYPE,TYPE2,DESCR,N,MASK,WIDTH,SRC) \
+    EXTINSN(V6_vgt##TYPE##_and, "Qx4&=vcmp.gt(Vu32." TYPE2 ",Vv32." TYPE2 ")", \
+        ATTRIBS(A_EXTENSION,A_CVI,A_CVI_VA,A_CVI_VA_2SRC,A_HVX_FLT), \
+        DESCR" greater than with predicate-and", \
+        VCMPGT_HF(QxV, fGETQBITS(QxV,WIDTH,MASK,i), &, ">", N, SRC, MASK, WIDTH)) \
+    EXTINSN(V6_vgt##TYPE##_xor, "Qx4^=vcmp.gt(Vu32." TYPE2 ",Vv32." TYPE2 ")", \
+        ATTRIBS(A_EXTENSION,A_CVI,A_CVI_VA,A_CVI_VA_2SRC,A_HVX_FLT), \
+        DESCR" greater than with predicate-xor", \
+        VCMPGT_HF(QxV, fGETQBITS(QxV,WIDTH,MASK,i), ^, ">", N, SRC, MASK, WIDTH)) \
+    EXTINSN(V6_vgt##TYPE##_or, "Qx4|=vcmp.gt(Vu32." TYPE2 ",Vv32." TYPE2 ")", \
+        ATTRIBS(A_EXTENSION,A_CVI,A_CVI_VA,A_CVI_VA_2SRC,A_HVX_FLT), \
+        DESCR" greater than with predicate-or", \
+        VCMPGT_HF(QxV, fGETQBITS(QxV,WIDTH,MASK,i), |, ">", N, SRC, MASK, WIDTH)) \
+    EXTINSN(V6_vgt##TYPE, "Qd4=vcmp.gt(Vu32." TYPE2 ",Vv32." TYPE2 ")", \
+        ATTRIBS(A_EXTENSION,A_CVI,A_CVI_VA,A_CVI_VA_2SRC,A_HVX_FLT), \
+        DESCR" greater than", \
+        VCMPGT_HF(QdV, , , ">", N, SRC, MASK, WIDTH))
+
+/* Vector BF compare */
+#define MMVEC_CMPGT_BF(TYPE,TYPE2,DESCR,N,MASK,WIDTH,SRC) \
+    EXTINSN(V6_vgt##TYPE##_and, "Qx4&=vcmp.gt(Vu32." TYPE2 ",Vv32." TYPE2 ")",\
+        ATTRIBS(A_EXTENSION,A_CVI,A_CVI_VA,A_CVI_VA_2SRC,A_HVX_FLT), \
+        DESCR" greater than with predicate-and", \
+        VCMPGT_BF(QxV, fGETQBITS(QxV,WIDTH,MASK,i), &, ">", N, SRC, MASK, WIDTH)) \
+    EXTINSN(V6_vgt##TYPE##_xor, "Qx4^=vcmp.gt(Vu32." TYPE2 ",Vv32." TYPE2 ")", \
+        ATTRIBS(A_EXTENSION,A_CVI,A_CVI_VA,A_CVI_VA_2SRC,A_HVX_FLT), \
+        DESCR" greater than with predicate-xor", \
+        VCMPGT_BF(QxV, fGETQBITS(QxV,WIDTH,MASK,i), ^, ">", N, SRC, MASK, WIDTH)) \
+    EXTINSN(V6_vgt##TYPE##_or, "Qx4|=vcmp.gt(Vu32." TYPE2 ",Vv32." TYPE2 ")", \
+        ATTRIBS(A_EXTENSION,A_CVI,A_CVI_VA,A_CVI_VA_2SRC,A_HVX_FLT), \
+        DESCR" greater than with predicate-or", \
+        VCMPGT_BF(QxV, fGETQBITS(QxV,WIDTH,MASK,i), |, ">", N, SRC, MASK, WIDTH)) \
+    EXTINSN(V6_vgt##TYPE, "Qd4=vcmp.gt(Vu32." TYPE2 ",Vv32." TYPE2 ")", \
+        ATTRIBS(A_EXTENSION,A_CVI,A_CVI_VA,A_CVI_VA_2SRC,A_HVX_FLT), \
+        DESCR" greater than", \
+        VCMPGT_BF(QdV, , , ">", N, SRC, MASK, WIDTH))
+
+MMVEC_CMPGT_SF(sf,"sf","Vector sf Compare ", fVELEM(32), 0xF, 4, sf)
+MMVEC_CMPGT_HF(hf,"hf","Vector hf Compare ", fVELEM(16), 0x3, 2, hf)
+MMVEC_CMPGT_BF(bf,"bf","Vector bf Compare ", fVELEM(16), 0x3, 2, bf)
+
+/******************************************************************************
+ BFloat arithmetic and max/min instructions
+ ******************************************************************************/
+
+ITERATOR_INSN_IEEE_FP_DOUBLE_32(32, vadd_sf_bf,
+    "Vdd32.sf=vadd(Vu32.bf,Vv32.bf)",  "Vector IEEE add: bf widen to sf",
+    VddV.v[0].sf[i] = fp_add_sf_bf(VuV.bf[2*i], VvV.bf[2*i]);
+    VddV.v[1].sf[i] = fp_add_sf_bf(VuV.bf[2*i+1], VvV.bf[2*i+1]); fBFLOAT())
+ITERATOR_INSN_IEEE_FP_DOUBLE_32(32, vsub_sf_bf,
+    "Vdd32.sf=vsub(Vu32.bf,Vv32.bf)",  "Vector IEEE sub: bf widen to sf",
+    VddV.v[0].sf[i] = fp_sub_sf_bf(VuV.bf[2*i], VvV.bf[2*i]);
+    VddV.v[1].sf[i] = fp_sub_sf_bf(VuV.bf[2*i+1], VvV.bf[2*i+1]); fBFLOAT())
+ITERATOR_INSN_IEEE_FP_DOUBLE_32(32, vmpy_sf_bf,
+    "Vdd32.sf=vmpy(Vu32.bf,Vv32.bf)",  "Vector IEEE mul: hf widen to sf",
+    VddV.v[0].sf[i] = fp_mult_sf_bf(VuV.bf[2*i], VvV.bf[2*i]);
+    VddV.v[1].sf[i] = fp_mult_sf_bf(VuV.bf[2*i+1], VvV.bf[2*i+1]); fBFLOAT())
+ITERATOR_INSN_IEEE_FP_DOUBLE_32(32, vmpy_sf_bf_acc,
+    "Vxx32.sf+=vmpy(Vu32.bf,Vv32.bf)", "Vector IEEE fma: hf widen to sf",
+    VxxV.v[0].sf[i] = fp_mult_sf_bf_acc(VuV.bf[2*i], VvV.bf[2*i], VxxV.v[0].sf[i]);
+    VxxV.v[1].sf[i] = fp_mult_sf_bf_acc(VuV.bf[2*i+1], VvV.bf[2*i+1], VxxV.v[1].sf[i]);
+    fCVI_VX_NO_TMP_LD(); fBFLOAT())
+ITERATOR_INSN_IEEE_FP_16(32, vcvt_bf_sf,
+    "Vd32.bf=vcvt(Vu32.sf,Vv32.sf)",   "Vector IEEE cvt: sf to bf",
+    VdV.bf[2*i]   = f32_to_bf16(VuV.sf[i], &env->hvx_fp_status);
+    VdV.bf[2*i+1] = f32_to_bf16(VvV.sf[i], &env->hvx_fp_status); fBFLOAT())
+
+ITERATOR_INSN_IEEE_FP_16_32_LATE(16, vmax_bf, "Vd32.bf=vmax(Vu32.bf,Vv32.bf)",
+    "Vector IEEE max: bf", VdV.bf[i] = fp_max_bf(VuV.bf[i], VvV.bf[i]);
+    fBFLOAT())
+ITERATOR_INSN_IEEE_FP_16_32_LATE(16, vmin_bf, "Vd32.bf=vmin(Vu32.bf,Vv32.bf)",
+    "Vector IEEE min: bf", VdV.bf[i] = fp_min_bf(VuV.bf[i], VvV.bf[i]);
+    fBFLOAT())
 
 /******************************************************************************
  DEBUG Vector/Register Printing
diff --git a/target/hexagon/meson.build b/target/hexagon/meson.build
index 59cb09c..4c921ee 100644
--- a/target/hexagon/meson.build
+++ b/target/hexagon/meson.build
@@ -252,6 +252,7 @@
     'fma_emu.c',
     'mmvec/decode_ext_mmvec.c',
     'mmvec/system_ext_mmvec.c',
+    'mmvec/hvx_ieee_fp.c',
 ))
 
 hexagon_softmmu_ss.add(files(
diff --git a/target/hexagon/mmvec/hvx_ieee_fp.c b/target/hexagon/mmvec/hvx_ieee_fp.c
new file mode 100644
index 0000000..d7751ad
--- /dev/null
+++ b/target/hexagon/mmvec/hvx_ieee_fp.c
@@ -0,0 +1,137 @@
+/*
+ *  Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ *  SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hvx_ieee_fp.h"
+
+float32 fp_mult_sf_hf(float16 a1, float16 a2, float_status *fp_status)
+{
+    return float32_mul(float16_to_float32(a1, true, fp_status),
+                       float16_to_float32(a2, true, fp_status), fp_status);
+}
+
+float32 fp_vdmpy(float16 a1, float16 a2, float16 a3, float16 a4,
+                 float_status *fp_status)
+{
+    return float32_add(fp_mult_sf_hf(a1, a3, fp_status),
+                       fp_mult_sf_hf(a2, a4, fp_status), fp_status);
+}
+
+#define float32_is_pos_nan(X) (float32_is_any_nan(X) && !float32_is_neg(X))
+#define float32_is_neg_nan(X) (float32_is_any_nan(X) && float32_is_neg(X))
+#define float16_is_pos_nan(X) (float16_is_any_nan(X) && !float16_is_neg(X))
+#define float16_is_neg_nan(X) (float16_is_any_nan(X) && float16_is_neg(X))
+
+/* Qfloat min/max treat +NaN as greater than +INF and -NaN as smaller than -INF */
+float32 qf_max_sf(float32 a1, float32 a2, float_status *fp_status)
+{
+    if (float32_is_pos_nan(a1) || float32_is_neg_nan(a2)) {
+        return a1;
+    }
+    if (float32_is_pos_nan(a2) || float32_is_neg_nan(a1)) {
+        return a2;
+    }
+    return float32_max(a1, a2, fp_status);
+}
+
+float32 qf_min_sf(float32 a1, float32 a2, float_status *fp_status)
+{
+    if (float32_is_pos_nan(a1) || float32_is_neg_nan(a2)) {
+        return a2;
+    }
+    if (float32_is_pos_nan(a2) || float32_is_neg_nan(a1)) {
+        return a1;
+    }
+    return float32_min(a1, a2, fp_status);
+}
+
+float16 qf_max_hf(float16 a1, float16 a2, float_status *fp_status)
+{
+    if (float16_is_pos_nan(a1) || float16_is_neg_nan(a2)) {
+        return a1;
+    }
+    if (float16_is_pos_nan(a2) || float16_is_neg_nan(a1)) {
+        return a2;
+    }
+    return float16_max(a1, a2, fp_status);
+}
+
+float16 qf_min_hf(float16 a1, float16 a2, float_status *fp_status)
+{
+    if (float16_is_pos_nan(a1) || float16_is_neg_nan(a2)) {
+        return a2;
+    }
+    if (float16_is_pos_nan(a2) || float16_is_neg_nan(a1)) {
+        return a1;
+    }
+    return float16_min(a1, a2, fp_status);
+}
+
+int32_t conv_w_sf(float32 a, float_status *fp_status)
+{
+    /* float32_to_int32 converts any NaN to MAX, hexagon looks at the sign. */
+    if (float32_is_any_nan(a)) {
+        return float32_is_neg(a) ? INT32_MIN : INT32_MAX;
+    }
+    return float32_to_int32_round_to_zero(a, fp_status);
+}
+
+int16_t conv_h_hf(float16 a, float_status *fp_status)
+{
+    /* float16_to_int16 converts any NaN to MAX, hexagon looks at the sign. */
+    if (float16_is_any_nan(a)) {
+        return float16_is_neg(a) ? INT16_MIN : INT16_MAX;
+    }
+    return float16_to_int16_round_to_zero(a, fp_status);
+}
+
+/*
+ * Returns true if f1 > f2, where at least one of the elements is guaranteed
+ * to be NaN.
+ * Up to v73, Hexagon HVX IEEE FP follows this order:
+ * QNaN > SNaN > +Inf > numbers > -Inf > SNaN_neg > QNaN_neg
+ */
+static bool float32_nan_compare(float32 f1, float32 f2, float_status *fp_status)
+{
+    /* opposite signs case */
+    if (float32_is_neg(f1) != float32_is_neg(f2)) {
+        return !float32_is_neg(f1);
+    }
+
+    /* same sign case */
+    bool result = (float32_is_any_nan(f1) && !float32_is_any_nan(f2)) ||
+        (float32_is_quiet_nan(f1, fp_status) && !float32_is_quiet_nan(f2, fp_status));
+    return float32_is_neg(f1) ? !result : result;
+}
+
+static bool float16_nan_compare(float16 f1, float16 f2, float_status *fp_status)
+{
+    /* opposite signs case */
+    if (float16_is_neg(f1) != float16_is_neg(f2)) {
+        return !float16_is_neg(f1);
+    }
+
+    /* same sign case */
+    bool result = (float16_is_any_nan(f1) && !float16_is_any_nan(f2)) ||
+        (float16_is_quiet_nan(f1, fp_status) && !float16_is_quiet_nan(f2, fp_status));
+    return float16_is_neg(f1) ? !result : result;
+}
+
+uint32_t cmpgt_sf(float32 a1, float32 a2, float_status *fp_status)
+{
+    if (float32_is_any_nan(a1) || float32_is_any_nan(a2)) {
+        return float32_nan_compare(a1, a2, fp_status);
+    }
+    return float32_compare(a1, a2, fp_status) == float_relation_greater;
+}
+
+uint16_t cmpgt_hf(float16 a1, float16 a2, float_status *fp_status)
+{
+    if (float16_is_any_nan(a1) || float16_is_any_nan(a2)) {
+        return float16_nan_compare(a1, a2, fp_status);
+    }
+    return float16_compare(a1, a2, fp_status) == float_relation_greater;
+}
diff --git a/target/hexagon/mmvec/hvx_ieee_fp.h b/target/hexagon/mmvec/hvx_ieee_fp.h
new file mode 100644
index 0000000..b7e379b
--- /dev/null
+++ b/target/hexagon/mmvec/hvx_ieee_fp.h
@@ -0,0 +1,69 @@
+/*
+ *  Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ *  SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HEXAGON_HVX_IEEE_H
+#define HEXAGON_HVX_IEEE_H
+
+#include "fpu/softfloat.h"
+
+#define FP32_DEF_NAN 0x7FFFFFFF
+
+#define f16_to_f32(A) float16_to_float32((A), true, &env->hvx_fp_status)
+#define f32_to_f16(A) float32_to_float16((A), true, &env->hvx_fp_status)
+#define bf16_to_f32(A) bfloat16_to_float32(A, &env->hvx_fp_status)
+
+float32 fp_mult_sf_hf(float16 a1, float16 a2, float_status *fp_status);
+float32 fp_vdmpy(float16 a1, float16 a2, float16 a3, float16 a4,
+                 float_status *fp_status);
+
+/* Qfloat min/max treat +NaN as greater than +INF and -NaN as smaller than -INF */
+float32 qf_max_sf(float32 a1, float32 a2, float_status *fp_status);
+float32 qf_min_sf(float32 a1, float32 a2, float_status *fp_status);
+float16 qf_max_hf(float16 a1, float16 a2, float_status *fp_status);
+float16 qf_min_hf(float16 a1, float16 a2, float_status *fp_status);
+
+int32_t conv_w_sf(float32 a, float_status *fp_status);
+int16_t conv_h_hf(float16 a, float_status *fp_status);
+
+/* IEEE - FP compare instructions */
+uint32_t cmpgt_sf(float32 a1, float32 a2, float_status *fp_status);
+uint16_t cmpgt_hf(float16 a1, float16 a2, float_status *fp_status);
+
+/* IEEE BFloat instructions */
+
+#define fp_mult_sf_bf(A, B) \
+    float32_mul(bf16_to_f32(A), bf16_to_f32(B), &env->hvx_fp_status)
+
+#define fp_add_sf_bf(A, B) \
+    float32_add(bf16_to_f32(A), bf16_to_f32(B), &env->hvx_fp_status)
+
+#define fp_sub_sf_bf(A, B) \
+    float32_sub(bf16_to_f32(A), bf16_to_f32(B), &env->hvx_fp_status)
+
+#define fp_mult_sf_bf_acc(f1, f2, f3) \
+    float32_muladd(bf16_to_f32(f1), bf16_to_f32(f2), f3, 0, &env->hvx_fp_status)
+
+static inline bfloat16 f32_to_bf16(float32 A, float_status *fp_status)
+{
+    uint32_t rslt = A;
+    if ((rslt & 0x1FFFF) == 0x08000) {
+        /* do not round up if exactly .5 and even already */
+    } else if ((rslt & 0x8000) == 0x8000) {
+        rslt += 0x8000; /* rounding to nearest number */
+    }
+    rslt = float32_is_any_nan(A) ? FP32_DEF_NAN : rslt;
+    return float32_to_bfloat16(rslt, fp_status);
+}
+
+#define fp_min_bf(A, B) \
+    f32_to_bf16(float32_min(bf16_to_f32(A), bf16_to_f32(B), &env->hvx_fp_status), \
+             &env->hvx_fp_status);
+
+#define fp_max_bf(A, B) \
+    f32_to_bf16(float32_max(bf16_to_f32(A), bf16_to_f32(B), &env->hvx_fp_status), \
+             &env->hvx_fp_status);
+
+#endif
diff --git a/target/hexagon/mmvec/macros.h b/target/hexagon/mmvec/macros.h
index c7840fb..b36f833 100644
--- a/target/hexagon/mmvec/macros.h
+++ b/target/hexagon/mmvec/macros.h
@@ -23,6 +23,10 @@
 #include "mmvec/system_ext_mmvec.h"
 #include "accel/tcg/getpc.h"
 #include "accel/tcg/probe.h"
+#include "mmvec/hvx_ieee_fp.h"
+
+#define fBFLOAT()
+#define fCVI_VX_NO_TMP_LD()
 
 #ifndef QEMU_GENERATE
 #define VdV      (*(MMVector *restrict)(VdV_void))
@@ -353,6 +357,11 @@
     do { \
         COE = (sextract32(VAL, 24 + 2 * POS, 2) << 8) | \
                extract32(VAL, POS * 8, 8); \
-    } while (0);
+    } while (0) \
+    ;
+
+#define fCMPGT_SF(A, B) cmpgt_sf(A, B, &env->hvx_fp_status)
+#define fCMPGT_HF(A, B) cmpgt_hf(A, B, &env->hvx_fp_status)
+#define fCMPGT_BF(A, B) fCMPGT_SF((uint32_t)(A) << 16, (uint32_t)(B) << 16)
 
 #endif
diff --git a/target/hexagon/mmvec/mmvec.h b/target/hexagon/mmvec/mmvec.h
index 4a4f6cc..8e72f2f 100644
--- a/target/hexagon/mmvec/mmvec.h
+++ b/target/hexagon/mmvec/mmvec.h
@@ -41,6 +41,9 @@
     int16_t   h[MAX_VEC_SIZE_BYTES / 2];
     uint8_t  ub[MAX_VEC_SIZE_BYTES / 1];
     int8_t    b[MAX_VEC_SIZE_BYTES / 1];
+    float32  sf[MAX_VEC_SIZE_BYTES / 4];
+    float16  hf[MAX_VEC_SIZE_BYTES / 2];
+    bfloat16 bf[MAX_VEC_SIZE_BYTES / 2];
 } MMVector;
 
 typedef union {
diff --git a/target/hexagon/op_helper.c b/target/hexagon/op_helper.c
index 3ce223c..23894ff 100644
--- a/target/hexagon/op_helper.c
+++ b/target/hexagon/op_helper.c
@@ -35,11 +35,13 @@
 #include "mmvec/macros.h"
 #include "op_helper.h"
 #include "cpu_helper.h"
+#include "tcg/tcg-gvec-desc.h"
 #include "translate.h"
 #ifndef CONFIG_USER_ONLY
 #include "hw/hexagon/hexagon_globalreg.h"
 #include "hex_mmu.h"
 #include "hw/hexagon/hexagon_tlb.h"
+#include "hw/intc/hex-l2vic.h"
 #include "hex_interrupts.h"
 #include "hexswi.h"
 #endif
@@ -1564,7 +1566,20 @@
 
 void HELPER(ciad)(CPUHexagonState *env, uint32_t mask)
 {
-    g_assert_not_reached();
+    uint32_t ipendad;
+    uint32_t iad;
+    HexagonCPU *cpu;
+
+    BQL_LOCK_GUARD();
+    cpu = env_archcpu(env);
+    ipendad = hexagon_globalreg_read(cpu->globalregs, HEX_SREG_IPENDAD,
+                                      env->threadId);
+    iad = fGET_FIELD(ipendad, IPENDAD_IAD);
+    fSET_FIELD(ipendad, IPENDAD_IAD, iad & ~(mask));
+    hexagon_globalreg_write(cpu->globalregs, HEX_SREG_IPENDAD,
+                            ipendad, env->threadId);
+    l2vic_clear_interrupt(cpu->l2vic);
+    hex_interrupt_update(env);
 }
 
 void HELPER(siad)(CPUHexagonState *env, uint32_t mask)
@@ -1947,3 +1962,23 @@
     printf("ERROR: bogus helper: " #tag "\n")
 
 #include "helper_funcs_generated.c.inc"
+
+#define DO_ABSDIFF(NAME, TYPE, UTYPE) \
+void HELPER(NAME)(void *vd, void *vn, void *vm, uint32_t desc) \
+{ \
+    intptr_t i, oprsz = simd_oprsz(desc); \
+    UTYPE *d = vd; \
+    TYPE *n = vn, *m = vm; \
+    \
+    for (i = 0; i < oprsz / sizeof(TYPE); i++) { \
+        d[i] = n[i] < m[i] ? (UTYPE)m[i] - (UTYPE)n[i] \
+                           : (UTYPE)n[i] - (UTYPE)m[i]; \
+    } \
+}
+
+DO_ABSDIFF(gvec_sabsdiff_h, int16_t, uint16_t)
+DO_ABSDIFF(gvec_sabsdiff_w, int32_t, uint32_t)
+DO_ABSDIFF(gvec_uabsdiff_b, uint8_t, uint8_t)
+DO_ABSDIFF(gvec_uabsdiff_h, uint16_t, uint16_t)
+
+#undef DO_ABSDIFF
diff --git a/target/hexagon/printinsn.c b/target/hexagon/printinsn.c
index a7e46f4..023ea12 100644
--- a/target/hexagon/printinsn.c
+++ b/target/hexagon/printinsn.c
@@ -23,18 +23,105 @@
 #include "internal.h"
 #include "decode.h"
 
-static const char *sreg2str(unsigned int reg)
+/*
+ * Used when there is some sort of error and we can't figure out the real
+ * system register name
+ */
+static const char *const generic_sreg_names[256] = {
+    "S000", "S001", "S002", "S003", "S004", "S005", "S006", "S007",
+    "S008", "S009", "S010", "S011", "S012", "S013", "S014", "S015",
+    "S016", "S017", "S018", "S019", "S020", "S021", "S022", "S023",
+    "S024", "S025", "S026", "S027", "S028", "S029", "S030", "S031",
+    "S032", "S033", "S034", "S035", "S036", "S037", "S038", "S039",
+    "S040", "S041", "S042", "S043", "S044", "S045", "S046", "S047",
+    "S048", "S049", "S050", "S051", "S052", "S053", "S054", "S055",
+    "S056", "S057", "S058", "S059", "S060", "S061", "S062", "S063",
+    "S064", "S065", "S066", "S067", "S068", "S069", "S070", "S071",
+    "S072", "S073", "S074", "S075", "S076", "S077", "S078", "S079",
+    "S080", "S081", "S082", "S083", "S084", "S085", "S086", "S087",
+    "S088", "S089", "S090", "S091", "S092", "S093", "S094", "S095",
+    "S096", "S097", "S098", "S099", "S100", "S101", "S102", "S103",
+    "S104", "S105", "S106", "S107", "S108", "S109", "S110", "S111",
+    "S112", "S113", "S114", "S115", "S116", "S117", "S118", "S119",
+    "S120", "S121", "S122", "S123", "S124", "S125", "S126", "S127",
+    "S128", "S129", "S130", "S131", "S132", "S133", "S134", "S135",
+    "S136", "S137", "S138", "S139", "S140", "S141", "S142", "S143",
+    "S144", "S145", "S146", "S147", "S148", "S149", "S150", "S151",
+    "S152", "S153", "S154", "S155", "S156", "S157", "S158", "S159",
+    "S160", "S161", "S162", "S163", "S164", "S165", "S166", "S167",
+    "S168", "S169", "S170", "S171", "S172", "S173", "S174", "S175",
+    "S176", "S177", "S178", "S179", "S180", "S181", "S182", "S183",
+    "S184", "S185", "S186", "S187", "S188", "S189", "S190", "S191",
+    "S192", "S193", "S194", "S195", "S196", "S197", "S198", "S199",
+    "S200", "S201", "S202", "S203", "S204", "S205", "S206", "S207",
+    "S208", "S209", "S210", "S211", "S212", "S213", "S214", "S215",
+    "S216", "S217", "S218", "S219", "S220", "S221", "S222", "S223",
+    "S224", "S225", "S226", "S227", "S228", "S229", "S230", "S231",
+    "S232", "S233", "S234", "S235", "S236", "S237", "S238", "S239",
+    "S240", "S241", "S242", "S243", "S244", "S245", "S246", "S247",
+    "S248", "S249", "S250", "S251", "S252", "S253", "S254", "S255",
+};
+
+static const char *sreg2str(uint8_t reg)
 {
-    if (reg < TOTAL_PER_THREAD_REGS) {
-        return hexagon_regnames[reg];
+#ifndef CONFIG_USER_ONLY
+    if (reg < NUM_SREGS) {
+        return hexagon_sregnames[reg];
     } else {
-        return "???";
+        return generic_sreg_names[reg];
     }
+#else
+    return generic_sreg_names[reg];
+#endif
 }
 
-static const char *creg2str(unsigned int reg)
+/*
+ * Used when there is some sort of error and we can't figure out the real
+ * control register name
+ */
+static const char *const generic_creg_names[256] = {
+    "C000", "C001", "C002", "C003", "C004", "C005", "C006", "C007",
+    "C008", "C009", "C010", "C011", "C012", "C013", "C014", "C015",
+    "C016", "C017", "C018", "C019", "C020", "C021", "C022", "C023",
+    "C024", "C025", "C026", "C027", "C028", "C029", "C030", "C031",
+    "C032", "C033", "C034", "C035", "C036", "C037", "C038", "C039",
+    "C040", "C041", "C042", "C043", "C044", "C045", "C046", "C047",
+    "C048", "C049", "C050", "C051", "C052", "C053", "C054", "C055",
+    "C056", "C057", "C058", "C059", "C060", "C061", "C062", "C063",
+    "C064", "C065", "C066", "C067", "C068", "C069", "C070", "C071",
+    "C072", "C073", "C074", "C075", "C076", "C077", "C078", "C079",
+    "C080", "C081", "C082", "C083", "C084", "C085", "C086", "C087",
+    "C088", "C089", "C090", "C091", "C092", "C093", "C094", "C095",
+    "C096", "C097", "C098", "C099", "C100", "C101", "C102", "C103",
+    "C104", "C105", "C106", "C107", "C108", "C109", "C110", "C111",
+    "C112", "C113", "C114", "C115", "C116", "C117", "C118", "C119",
+    "C120", "C121", "C122", "C123", "C124", "C125", "C126", "C127",
+    "C128", "C129", "C130", "C131", "C132", "C133", "C134", "C135",
+    "C136", "C137", "C138", "C139", "C140", "C141", "C142", "C143",
+    "C144", "C145", "C146", "C147", "C148", "C149", "C150", "C151",
+    "C152", "C153", "C154", "C155", "C156", "C157", "C158", "C159",
+    "C160", "C161", "C162", "C163", "C164", "C165", "C166", "C167",
+    "C168", "C169", "C170", "C171", "C172", "C173", "C174", "C175",
+    "C176", "C177", "C178", "C179", "C180", "C181", "C182", "C183",
+    "C184", "C185", "C186", "C187", "C188", "C189", "C190", "C191",
+    "C192", "C193", "C194", "C195", "C196", "C197", "C198", "C199",
+    "C200", "C201", "C202", "C203", "C204", "C205", "C206", "C207",
+    "C208", "C209", "C210", "C211", "C212", "C213", "C214", "C215",
+    "C216", "C217", "C218", "C219", "C220", "C221", "C222", "C223",
+    "C224", "C225", "C226", "C227", "C228", "C229", "C230", "C231",
+    "C232", "C233", "C234", "C235", "C236", "C237", "C238", "C239",
+    "C240", "C241", "C242", "C243", "C244", "C245", "C246", "C247",
+    "C248", "C249", "C250", "C251", "C252", "C253", "C254", "C255",
+};
+
+static const char *creg2str(uint8_t reg)
 {
-    return sreg2str(reg + HEX_REG_SA0);
+    uint8_t gpr = reg + HEX_REG_SA0;
+    if (gpr < TOTAL_PER_THREAD_REGS) {
+        return hexagon_regnames[gpr];
+    } else {
+        return generic_creg_names[reg];
+    }
 }
 
 static void snprintinsn(GString *buf, Insn *insn)
@@ -52,7 +139,7 @@
 }
 
 void snprint_a_pkt_disas(GString *buf, Packet *pkt, uint32_t *words,
-                         target_ulong pc, const HexagonCPUDef *hex_def)
+                         target_ulong pc, const HexagonCPUConfig *cfg)
 {
     bool has_endloop0 = false;
     bool has_endloop1 = false;
@@ -84,12 +171,17 @@
         }
 
         g_string_append(buf, "\t");
-        if (opcode_supported(pkt->insn[i].opcode, hex_def)) {
+        if (opcode_supported(pkt->insn[i].opcode, cfg->hex_def)) {
             snprintinsn(buf, &(pkt->insn[i]));
         } else {
             g_string_append(buf, "<invalid>");
         }
 
+        if (!cfg->ieee_fp_extension &&
+            GET_ATTRIB(pkt->insn[i].opcode, A_HVX_IEEE_FP)) {
+            g_string_append(buf, " (disabled: no ieee_fp)");
+        }
+
         if (i < pkt->num_insns - 1) {
             /*
              * Subinstructions are two instructions encoded
diff --git a/target/hexagon/printinsn.h b/target/hexagon/printinsn.h
index de962b5..c838940 100644
--- a/target/hexagon/printinsn.h
+++ b/target/hexagon/printinsn.h
@@ -22,6 +22,6 @@
 #include "insn.h"
 
 void snprint_a_pkt_disas(GString *buf, Packet *pkt, uint32_t *words,
-                         target_ulong pc, const HexagonCPUDef *hex_def);
+                         target_ulong pc, const HexagonCPUConfig *cfg);
 
 #endif
diff --git a/target/hexagon/translate.c b/target/hexagon/translate.c
index 199b4f8..06a8159 100644
--- a/target/hexagon/translate.c
+++ b/target/hexagon/translate.c
@@ -1209,8 +1209,9 @@
     ctx->num_hvx_insns = 0;
     ctx->branch_cond = TCG_COND_NEVER;
     ctx->is_tight_loop = FIELD_EX32(hex_flags, TB_FLAGS, IS_TIGHT_LOOP);
-    ctx->short_circuit = hex_cpu->short_circuit;
+    ctx->short_circuit = hex_cpu->cfg.short_circuit;
     ctx->hex_def = HEXAGON_CPU_GET_CLASS(hex_cpu)->hex_def;
+    ctx->ieee_fp_extension = hex_cpu->cfg.ieee_fp_extension;
 #ifndef CONFIG_USER_ONLY
     ctx->num_cycles = 0;
     ctx->pcycle_enabled = FIELD_EX32(hex_flags, TB_FLAGS, PCYCLE_ENABLED);
@@ -1267,7 +1268,7 @@
          * so end the TLB after every packet.
          */
         HexagonCPU *hex_cpu = env_archcpu(env);
-        if (hex_cpu->lldb_compat && qemu_loglevel_mask(CPU_LOG_TB_CPU)) {
+        if (hex_cpu->cfg.lldb_compat && qemu_loglevel_mask(CPU_LOG_TB_CPU)) {
             ctx->base.is_jmp = DISAS_TOO_MANY;
         }
     }
diff --git a/target/hexagon/translate.h b/target/hexagon/translate.h
index 2fca157..3c5773e 100644
--- a/target/hexagon/translate.h
+++ b/target/hexagon/translate.h
@@ -81,6 +81,7 @@
     target_ulong branch_dest;
     bool is_tight_loop;
     bool short_circuit;
+    bool ieee_fp_extension;
     bool read_after_write;
     bool has_hvx_overlap;
     TCGv new_value[TOTAL_PER_THREAD_REGS];
diff --git a/target/i386/kvm/kvm.c b/target/i386/kvm/kvm.c
index 4272b67..644c45f 100644
--- a/target/i386/kvm/kvm.c
+++ b/target/i386/kvm/kvm.c
@@ -5022,7 +5022,7 @@
         kvm_msr_entry_add(cpu, MSR_IA32_U_CET, 0);
         kvm_msr_entry_add(cpu, MSR_IA32_S_CET, 0);
 
-        if (env->features[FEAT_7_0_EDX] & CPUID_7_0_ECX_CET_SHSTK) {
+        if (env->features[FEAT_7_0_ECX] & CPUID_7_0_ECX_CET_SHSTK) {
             kvm_msr_entry_add(cpu, MSR_IA32_PL0_SSP, 0);
             kvm_msr_entry_add(cpu, MSR_IA32_PL1_SSP, 0);
             kvm_msr_entry_add(cpu, MSR_IA32_PL2_SSP, 0);
diff --git a/target/s390x/diag.c b/target/s390x/diag.c
index 80f0958..46d191b 100644
--- a/target/s390x/diag.c
+++ b/target/s390x/diag.c
@@ -185,7 +185,7 @@
             return false;
         }
 
-        if (kvm_enabled() && kvm_s390_get_hpage_1m()) {
+        if (kvm_enabled() && kvm_s390_get_hpage()) {
             error_report("Protected VMs can currently not be backed with "
                          "huge pages");
             env->regs[r1 + 1] = DIAG_308_RC_INVAL_FOR_PV;
diff --git a/target/s390x/kvm/kvm.c b/target/s390x/kvm/kvm.c
index 72031a5..803b878 100644
--- a/target/s390x/kvm/kvm.c
+++ b/target/s390x/kvm/kvm.c
@@ -145,7 +145,7 @@
 static int cap_mem_op_extension;
 static int cap_s390_irq;
 static int cap_ri;
-static int cap_hpage_1m;
+static int cap_hpage;
 static int cap_vcpu_resets;
 static int cap_protected;
 static int cap_zpci_op;
@@ -231,7 +231,7 @@
         .attr = KVM_S390_VM_MEM_ENABLE_CMMA,
     };
 
-    if (cap_hpage_1m) {
+    if (cap_hpage) {
         warn_report("CMM will not be enabled because it is not "
                     "compatible with huge memory backings.");
         return;
@@ -292,30 +292,28 @@
     }
 }
 
-void kvm_s390_set_max_pagesize(uint64_t pagesize, Error **errp)
+static bool kvm_s390_pgsize_cap(uint32_t capa, const char *s, Error **errp)
 {
-    if (pagesize == 4 * KiB) {
-        return;
+    if (kvm_vm_enable_cap(kvm_state, capa, 0)) {
+        error_setg(errp, "Memory backing with %s pages was specified, "
+                   "but KVM does not support this memory backing", s);
+        return false;
     }
-
-    if (pagesize != 1 * MiB) {
-        error_setg(errp, "Memory backing with 2G pages was specified, "
-                   "but KVM does not support this memory backing");
-        return;
-    }
-
-    if (kvm_vm_enable_cap(kvm_state, KVM_CAP_S390_HPAGE_1M, 0)) {
-        error_setg(errp, "Memory backing with 1M pages was specified, "
-                   "but KVM does not support this memory backing");
-        return;
-    }
-
-    cap_hpage_1m = 1;
+    return true;
 }
 
-int kvm_s390_get_hpage_1m(void)
+void kvm_s390_set_max_pagesize(uint64_t pagesize, Error **errp)
 {
-    return cap_hpage_1m;
+    if (pagesize == MiB) {
+        cap_hpage = kvm_s390_pgsize_cap(KVM_CAP_S390_HPAGE_1M, "1M", errp);
+    } else if (pagesize != 4 * KiB) {
+        cap_hpage = 2 * kvm_s390_pgsize_cap(KVM_CAP_S390_HPAGE_2G, "2G", errp);
+    }
+}
+
+int kvm_s390_get_hpage(void)
+{
+    return cap_hpage;
 }
 
 static void ccw_machine_class_foreach(ObjectClass *oc, void *opaque)
diff --git a/target/s390x/kvm/kvm_s390x.h b/target/s390x/kvm/kvm_s390x.h
index 7b1cce3..3c4fa04 100644
--- a/target/s390x/kvm/kvm_s390x.h
+++ b/target/s390x/kvm/kvm_s390x.h
@@ -25,7 +25,7 @@
 int kvm_s390_set_cpu_state(S390CPU *cpu, uint8_t cpu_state);
 void kvm_s390_vcpu_interrupt_pre_save(S390CPU *cpu);
 int kvm_s390_vcpu_interrupt_post_load(S390CPU *cpu);
-int kvm_s390_get_hpage_1m(void);
+int kvm_s390_get_hpage(void);
 int kvm_s390_get_protected_dump(void);
 int kvm_s390_get_ri(void);
 int kvm_s390_get_zpci_op(void);
diff --git a/target/s390x/kvm/stubs.c b/target/s390x/kvm/stubs.c
index 196127b..ebf3c83 100644
--- a/target/s390x/kvm/stubs.c
+++ b/target/s390x/kvm/stubs.c
@@ -143,7 +143,7 @@
     g_assert_not_reached();
 }
 
-int kvm_s390_get_hpage_1m(void)
+int kvm_s390_get_hpage(void)
 {
     g_assert_not_reached();
 }
diff --git a/target/s390x/tcg/crypto_helper.c b/target/s390x/tcg/crypto_helper.c
index 8fe0a22..6a5dbe1 100644
--- a/target/s390x/tcg/crypto_helper.c
+++ b/target/s390x/tcg/crypto_helper.c
@@ -16,6 +16,7 @@
 #include "qemu/guest-random.h"
 #include "s390x-internal.h"
 #include "tcg_s390x.h"
+#include "exec/cpu-common.h"
 #include "exec/helper-proto.h"
 #include "accel/tcg/cpu-ldst-common.h"
 #include "accel/tcg/cpu-mmu-index.h"
@@ -242,8 +243,8 @@
     return !len ? 0 : 3;
 }
 
-static void fill_buf_random(CPUS390XState *env, const int mmu_idx, uintptr_t ra,
-                            uint64_t *buf_reg, uint64_t *len_reg)
+static int fill_buf_random(CPUS390XState *env, const int mmu_idx, uintptr_t ra,
+                           uint64_t *buf_reg, uint64_t *len_reg)
 {
     const MemOpIdx oi = make_memop_idx(MO_8, mmu_idx);
     uint8_t tmp[256];
@@ -265,7 +266,13 @@
             --*len_reg;
         }
         len -= block;
+
+        if (cpu_loop_exit_requested(env_cpu(env))) {
+            break;
+        }
     }
+
+    return len == 0 ? 0 : 3;
 }
 
 uint32_t HELPER(msa)(CPUS390XState *env, uint32_t r1, uint32_t r2, uint32_t r3,
@@ -278,6 +285,7 @@
     uint8_t subfunc[16] = { 0 };
     uint64_t param_addr;
     MemOpIdx oi;
+    int cc;
 
     switch (type) {
     case S390_FEAT_TYPE_KMAC:
@@ -308,9 +316,13 @@
         return cpacf_sha512(env, mmu_idx, ra, env->regs[1], &env->regs[r2],
                             &env->regs[r2 + 1], type);
     case 114: /* CPACF_PRNO_TRNG */
-        fill_buf_random(env, mmu_idx, ra, &env->regs[r1], &env->regs[r1 + 1]);
-        fill_buf_random(env, mmu_idx, ra, &env->regs[r2], &env->regs[r2 + 1]);
-        break;
+        cc = fill_buf_random(env, mmu_idx, ra,
+                             &env->regs[r1], &env->regs[r1 + 1]);
+        if (cc == 0) {
+            cc = fill_buf_random(env, mmu_idx, ra,
+                                 &env->regs[r2], &env->regs[r2 + 1]);
+        }
+        return cc;
     default:
         /* we don't implement any other subfunction yet */
         g_assert_not_reached();
diff --git a/target/s390x/tcg/insn-data.h.inc b/target/s390x/tcg/insn-data.h.inc
index 0d5392e..1ea7224 100644
--- a/target/s390x/tcg/insn-data.h.inc
+++ b/target/s390x/tcg/insn-data.h.inc
@@ -887,8 +887,8 @@
     C(0xe32f, STRVG,   RXY_a, Z,   la2, r1_o, new, m1_64, rev64, 0)
 
 /* STORE CLOCK */
-    F(0xb205, STCK,    S,     Z,   la2, 0, new, m1_64, stck, 0, IF_IO)
-    F(0xb27c, STCKF,   S,     SCF, la2, 0, new, m1_64, stck, 0, IF_IO)
+    F(0xb205, STCK,    S,     Z,   la2, 0, new, 0, stck, 0, IF_IO)
+    F(0xb27c, STCKF,   S,     SCF, la2, 0, new, 0, stck, 0, IF_IO)
 /* STORE CLOCK EXTENDED */
     F(0xb278, STCKE,   S,     Z,   0, a2, 0, 0, stcke, 0, IF_IO)
 
diff --git a/target/s390x/tcg/int_helper.c b/target/s390x/tcg/int_helper.c
index fbda396..5aedd14 100644
--- a/target/s390x/tcg/int_helper.c
+++ b/target/s390x/tcg/int_helper.c
@@ -39,7 +39,8 @@
     int32_t b = b64;
     int64_t q, r;
 
-    if (b == 0) {
+    /* Catch divide by zero, and non-representable quotient (MIN / -1).  */
+    if (b == 0 || (b == -1 && a == (1ll << 63))) {
         tcg_s390_program_interrupt(env, PGM_FIXPT_DIVIDE, GETPC());
     }
 
diff --git a/target/s390x/tcg/translate.c b/target/s390x/tcg/translate.c
index 82165ac..1b60231 100644
--- a/target/s390x/tcg/translate.c
+++ b/target/s390x/tcg/translate.c
@@ -4108,7 +4108,9 @@
 static DisasJumpType op_stck(DisasContext *s, DisasOps *o)
 {
     gen_helper_stck(o->out, tcg_env);
+    tcg_gen_qemu_st_i64(o->out, o->addr1, get_mem_index(s), MO_BEUQ);
     /* ??? We don't implement clock states.  */
+    /* Set the CC after the store; a suppressed store must preserve it. */
     gen_op_movi_cc(s, 0);
     return DISAS_NEXT;
 }
diff --git a/tests/functional/aarch64/test_aspeed_ast2700a1.py b/tests/functional/aarch64/test_aspeed_ast2700a1.py
index ddddd2d..2113c78 100755
--- a/tests/functional/aarch64/test_aspeed_ast2700a1.py
+++ b/tests/functional/aarch64/test_aspeed_ast2700a1.py
@@ -85,13 +85,13 @@ def verify_openbmc_boot_and_login(self, name, enable_pcie=True):
         exec_command_and_wait_for_pattern(self, 'root', 'Password:')
         exec_command_and_wait_for_pattern(self, '0penBmc', f'root@{name}:~#')
 
-    ASSET_SDK_V1101_AST2700A1 = Asset(
-            'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.01/ast2700-a1-image.tar.gz',
-            '859808828531a51931aad3b4e70b28143eebb3cde1838ba7d8e7a2b844c8a1ab')
+    ASSET_SDK_V1103_AST2700A1 = Asset(
+            'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.03/ast2700-a1-image.tar.gz',
+            '540961dc380709d852e957c5817cd7ee0dbb0a66f3aa17413eac7b67518afbfe')
 
-    ASSET_SDK_V1101_AST2700A1_DCSCM = Asset(
-            'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.01/ast2700-a1-dcscm-image.tar.gz',
-            '4654eabad75da3fd33635cd6d29b7635181daefee7294b68feb124b9d4c24116')
+    ASSET_SDK_V1103_AST2700A1_DCSCM = Asset(
+            'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.03/ast2700-a1-dcscm-image.tar.gz',
+            '5f7c139330fcefa6025bc7565a20fd5ea42cacebf810d56dc9a76b07cf69b3e1')
 
     def do_ast2700_i2c_test(self, bus_id):
         bus_str = str(bus_id)
@@ -118,6 +118,11 @@ def do_ast2700_pcie_test(self):
             'ip addr show dev eth2',
             'inet 10.0.2.15/24')
 
+    def do_ast2700_usb_ehci_test(self):
+        exec_command_and_wait_for_pattern(self,
+            'lsusb',
+            'QEMU QEMU USB Keyboard')
+
     def start_ast2700_test(self, name, bus_id):
         num_cpu = 4
         load_images_list = [
@@ -127,7 +132,8 @@ def start_ast2700_test(self, name, bus_id):
             },
             {
                 'addr': '0x430000000',
-                'file': self.scratch_file(name, 'bl31.bin')
+                'file': self.scratch_file(name, 'trusted-firmware-a',
+                                          'bl31.bin')
             },
             {
                 'addr': '0x430080000',
@@ -154,34 +160,36 @@ def start_ast2700_test_vbootrom(self, name, bus_id):
         self.do_test_aarch64_aspeed_sdk_start(
                 self.scratch_file(name, 'image-bmc'), bus_id)
 
-    def test_aarch64_ast2700a1_evb_sdk_v11_01(self):
+    def test_aarch64_ast2700a1_evb_sdk_v11_03(self):
         self.set_machine('ast2700a1-evb')
         self.require_netdev('user')
 
-        self.archive_extract(self.ASSET_SDK_V1101_AST2700A1)
+        self.archive_extract(self.ASSET_SDK_V1103_AST2700A1)
         self.vm.add_args('-device', 'e1000e,netdev=net1,bus=pcie.2')
         self.vm.add_args('-netdev', 'user,id=net1')
+        self.vm.add_args('-device', 'usb-kbd,bus=usb-bus.3')
         self.start_ast2700_test('ast2700-a1-image', 1)
         self.verify_openbmc_boot_and_login('ast2700-a1')
         self.do_ast2700_i2c_test(1)
         self.do_ast2700_pcie_test()
+        self.do_ast2700_usb_ehci_test()
 
-    def test_aarch64_ast2700a1_evb_sdk_vbootrom_v11_01(self):
+    def test_aarch64_ast2700a1_evb_sdk_vbootrom_v11_03(self):
         self.set_machine('ast2700a1-evb')
         self.require_netdev('user')
 
-        self.archive_extract(self.ASSET_SDK_V1101_AST2700A1)
+        self.archive_extract(self.ASSET_SDK_V1103_AST2700A1)
         self.vm.add_args('-device', 'e1000e,netdev=net1,bus=pcie.2')
         self.vm.add_args('-netdev', 'user,id=net1')
         self.start_ast2700_test_vbootrom('ast2700-a1-image', 1)
         self.verify_vbootrom_firmware_flow()
         self.verify_openbmc_boot_start()
 
-    def test_aarch64_ast2700a1_evb_ioexp_v11_01(self):
+    def test_aarch64_ast2700a1_evb_ioexp_v11_03(self):
         self.set_machine('ast2700a1-evb')
         self.require_netdev('user')
 
-        self.archive_extract(self.ASSET_SDK_V1101_AST2700A1_DCSCM)
+        self.archive_extract(self.ASSET_SDK_V1103_AST2700A1_DCSCM)
         self.vm.set_machine('ast2700a1-evb,fmc-model=w25q512jv')
         self.vm.add_args('-device',
                          'tmp105,bus=ioexp0.0,address=0x4d,id=tmp-test-16')
diff --git a/tests/functional/aarch64/test_aspeed_ast2700a2.py b/tests/functional/aarch64/test_aspeed_ast2700a2.py
index 0fe04a6..01c7c4f 100755
--- a/tests/functional/aarch64/test_aspeed_ast2700a2.py
+++ b/tests/functional/aarch64/test_aspeed_ast2700a2.py
@@ -85,13 +85,13 @@ def verify_openbmc_boot_and_login(self, name, enable_pcie=True):
         exec_command_and_wait_for_pattern(self, 'root', 'Password:')
         exec_command_and_wait_for_pattern(self, '0penBmc', f'root@{name}:~#')
 
-    ASSET_SDK_V1101_AST2700A2 = Asset(
-            'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.01/ast2700-default-image.tar.gz',
-            'ce89dcd995cf284d41a6a4bd17a1b97d59939f0277bfe54fdaaf30e741ce7487')
+    ASSET_SDK_V1103_AST2700A2 = Asset(
+            'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.03/ast2700-default-image.tar.gz',
+            'b91450d53da234591060cfb926fa30f7534ce20eaab766cb0f80ec332f8f0adb')
 
-    ASSET_SDK_V1101_AST2700A2_DCSCM = Asset(
-            'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.01/ast2700-dcscm-image.tar.gz',
-            'b92ece9ca733dfd7a20193a12582f743b77f1898116b6d6f1abe57ac8db01c56')
+    ASSET_SDK_V1103_AST2700A2_DCSCM = Asset(
+            'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.03/ast2700-dcscm-image.tar.gz',
+            '7afd8323fc95097c14872b90d68c5cae5d078530c704591b192b74bd892c1dbf')
 
     def do_ast2700_i2c_test(self, bus_id):
         bus_str = str(bus_id)
@@ -121,6 +121,11 @@ def do_ast2700_pcie_test(self):
             'ip addr show dev eth2',
             'inet 10.0.2.15/24')
 
+    def do_ast2700_usb_ehci_test(self):
+        exec_command_and_wait_for_pattern(self,
+            'lsusb',
+            'QEMU QEMU USB Keyboard')
+
     def start_ast2700_test(self, name, bus_id):
         num_cpu = 4
         load_images_list = [
@@ -130,7 +135,8 @@ def start_ast2700_test(self, name, bus_id):
             },
             {
                 'addr': '0x430000000',
-                'file': self.scratch_file(name, 'bl31.bin')
+                'file': self.scratch_file(name, 'trusted-firmware-a',
+                                          'bl31.bin')
             },
             {
                 'addr': '0x430080000',
@@ -157,34 +163,36 @@ def start_ast2700_test_vbootrom(self, name, bus_id):
         self.do_test_aarch64_aspeed_sdk_start(
                 self.scratch_file(name, 'image-bmc'), bus_id)
 
-    def test_aarch64_ast2700a2_evb_sdk_v11_01(self):
+    def test_aarch64_ast2700a2_evb_sdk_v11_03(self):
         self.set_machine('ast2700a2-evb')
         self.require_netdev('user')
 
-        self.archive_extract(self.ASSET_SDK_V1101_AST2700A2)
+        self.archive_extract(self.ASSET_SDK_V1103_AST2700A2)
         self.vm.add_args('-device', 'e1000e,netdev=net1,bus=pcie.2')
         self.vm.add_args('-netdev', 'user,id=net1')
+        self.vm.add_args('-device', 'usb-kbd,bus=usb-bus.3')
         self.start_ast2700_test('ast2700-default-image', 1)
         self.verify_openbmc_boot_and_login('ast2700-default')
         self.do_ast2700_i2c_test(1)
         self.do_ast2700_pcie_test()
+        self.do_ast2700_usb_ehci_test()
 
-    def test_aarch64_ast2700a2_evb_sdk_vbootrom_v11_01(self):
+    def test_aarch64_ast2700a2_evb_sdk_vbootrom_v11_03(self):
         self.set_machine('ast2700a2-evb')
         self.require_netdev('user')
 
-        self.archive_extract(self.ASSET_SDK_V1101_AST2700A2)
+        self.archive_extract(self.ASSET_SDK_V1103_AST2700A2)
         self.vm.add_args('-device', 'e1000e,netdev=net1,bus=pcie.2')
         self.vm.add_args('-netdev', 'user,id=net1')
         self.start_ast2700_test_vbootrom('ast2700-default-image', 1)
         self.verify_vbootrom_firmware_flow()
         self.verify_openbmc_boot_start()
 
-    def test_aarch64_ast2700a2_evb_ioexp_v11_01(self):
+    def test_aarch64_ast2700a2_evb_ioexp_v11_03(self):
         self.set_machine('ast2700a2-evb')
         self.require_netdev('user')
 
-        self.archive_extract(self.ASSET_SDK_V1101_AST2700A2_DCSCM)
+        self.archive_extract(self.ASSET_SDK_V1103_AST2700A2_DCSCM)
         self.vm.set_machine('ast2700a2-evb,fmc-model=w25q512jv')
         self.vm.add_args('-device',
                          'tmp105,bus=ioexp0.0,address=0x4d,id=tmp-test-16')
diff --git a/tests/functional/aarch64/test_aspeed_ast2700fc.py b/tests/functional/aarch64/test_aspeed_ast2700fc.py
index 86270e6..704477d 100755
--- a/tests/functional/aarch64/test_aspeed_ast2700fc.py
+++ b/tests/functional/aarch64/test_aspeed_ast2700fc.py
@@ -66,9 +66,9 @@ def load_ast2700fc_coprocessor(self, name):
             self.vm.add_args('-device',
                              f'loader,file={file},cpu-num={cpu_num}')
 
-    ASSET_SDK_V1101_AST2700 = Asset(
-            'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.01/ast2700-default-image.tar.gz',
-            'ce89dcd995cf284d41a6a4bd17a1b97d59939f0277bfe54fdaaf30e741ce7487')
+    ASSET_SDK_V1103_AST2700 = Asset(
+            'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.03/ast2700-default-image.tar.gz',
+            'b91450d53da234591060cfb926fa30f7534ce20eaab766cb0f80ec332f8f0adb')
 
     def do_ast2700_i2c_test(self):
         exec_command_and_wait_for_pattern(self,
@@ -101,7 +101,7 @@ def do_ast2700fc_ssp_test(self):
 
         exec_command_and_wait_for_pattern(self, '\012', 'ssp_tsp:~$')
         exec_command_and_wait_for_pattern(self, 'version',
-                                          'Zephyr version 3.7.1')
+                                          'Zephyr version 3.7.2')
         exec_command_and_wait_for_pattern(self, 'md 72c02000 1',
                                           '[72c02000] 06020103')
 
@@ -112,7 +112,7 @@ def do_ast2700fc_tsp_test(self):
 
         exec_command_and_wait_for_pattern(self, '\012', 'tsp:~$')
         exec_command_and_wait_for_pattern(self, 'version',
-                                          'Zephyr version 3.7.1')
+                                          'Zephyr version 3.7.2')
         exec_command_and_wait_for_pattern(self, 'md 72c02000 1',
                                           '[72c02000] 06020103')
 
@@ -125,7 +125,8 @@ def start_ast2700fc_test(self, name):
             },
             {
                 'addr': '0x430000000',
-                'file': self.scratch_file(name, 'bl31.bin')
+                'file': self.scratch_file(name, 'trusted-firmware-a',
+                                          'bl31.bin')
             },
             {
                 'addr': '0x430080000',
@@ -153,11 +154,11 @@ def start_ast2700fc_test_vbootrom(self, name):
         self.do_test_aarch64_aspeed_sdk_start(
                 self.scratch_file(name, 'image-bmc'))
 
-    def test_aarch64_ast2700fc_sdk_v11_01(self):
+    def test_aarch64_ast2700fc_sdk_v11_03(self):
         self.set_machine('ast2700fc')
         self.require_netdev('user')
 
-        self.archive_extract(self.ASSET_SDK_V1101_AST2700)
+        self.archive_extract(self.ASSET_SDK_V1103_AST2700)
         self.start_ast2700fc_test('ast2700-default-image')
         self.verify_openbmc_boot_and_login('ast2700-default')
         self.do_ast2700_i2c_test()
@@ -165,10 +166,10 @@ def test_aarch64_ast2700fc_sdk_v11_01(self):
         self.do_ast2700fc_ssp_test()
         self.do_ast2700fc_tsp_test()
 
-    def test_aarch64_ast2700fc_sdk_vbootrom_v11_01(self):
+    def test_aarch64_ast2700fc_sdk_vbootrom_v11_03(self):
         self.set_machine('ast2700fc')
 
-        self.archive_extract(self.ASSET_SDK_V1101_AST2700)
+        self.archive_extract(self.ASSET_SDK_V1103_AST2700)
         self.start_ast2700fc_test_vbootrom('ast2700-default-image')
         self.verify_openbmc_boot_and_login('ast2700-default')
         self.do_ast2700fc_ssp_test()
diff --git a/tests/functional/aarch64/test_raspi4.py b/tests/functional/aarch64/test_raspi4.py
index 7a4302b..bacecbb 100755
--- a/tests/functional/aarch64/test_raspi4.py
+++ b/tests/functional/aarch64/test_raspi4.py
@@ -86,6 +86,23 @@ def test_arm_raspi4_initrd(self):
                                                 'BCM2835')
         exec_command_and_wait_for_pattern(self, 'cat /proc/iomem',
                                                 'cprman@7e101000')
+
+        # We used to get the RAM size wrong; guard against a regression
+        # (see git history for details).
+        mem_total_kb = None
+        cmd_output = exec_command_and_wait_for_pattern(
+            self, 'cat /proc/meminfo', 'MemAvailable')
+        for line in cmd_output.decode('ascii', errors='replace').splitlines():
+            if line.startswith('MemTotal:'):
+                mem_total_kb = int(line.split()[1])
+                break
+        self.assertIsNotNone(mem_total_kb, 'MemTotal line not found')
+        self.assertGreater(mem_total_kb, 1900000,
+                           'guest RAM (%d kB) is far below the ~1.9 GiB '
+                           'expected for a 2 GiB raspi4b -- the second '
+                           'memory node above the 1 GiB peripheral hole '
+                           'is probably not being added' % mem_total_kb)
+
         exec_command_and_wait_for_pattern(self, 'halt', 'reboot: System halted')
         # TODO: Raspberry Pi4 doesn't shut down properly with recent kernels
         # Wait for VM to shut down gracefully
diff --git a/tests/functional/arm/test_aspeed_ast1030.py b/tests/functional/arm/test_aspeed_ast1030.py
index 03fee55..83a96ec 100755
--- a/tests/functional/arm/test_aspeed_ast1030.py
+++ b/tests/functional/arm/test_aspeed_ast1030.py
@@ -12,17 +12,17 @@
 
 class AST1030Machine(AspeedTest):
 
-    ASSET_ZEPHYR_3_06 = Asset(
+    ASSET_ZEPHYR_3_08 = Asset(
         ('https://github.com/AspeedTech-BMC'
-         '/zephyr/releases/download/v00.03.06/ast1030-evb-demo.zip'),
-         '056f37fcd9f165308cedca3a08f2bed37ed40c0a1402c4fa515613b80a369f38')
+         '/zephyr/releases/download/v00.03.08/ast1030-evb-demo.zip'),
+         '9eac3691bc7bce1b912bbe2ae4e36608a6532ff8d607f4d1e44b88407a48d4e5')
 
-    def test_arm_ast1030_zephyros_3_06(self):
+    def test_arm_ast1030_zephyros_3_08(self):
         self.set_machine('ast1030-evb')
 
         kernel_name = "ast1030-evb-demo/zephyr.elf"
         kernel_file = self.archive_extract(
-            self.ASSET_ZEPHYR_3_06, member=kernel_name)
+            self.ASSET_ZEPHYR_3_08, member=kernel_name)
 
         self.vm.set_console()
         self.vm.add_args('-kernel', kernel_file, '-nographic')
@@ -72,7 +72,7 @@ def test_arm_ast1030_otp_blockdev_device(self):
         self.vm.set_machine("ast1030-evb")
 
         kernel_name = "ast1030-evb-demo/zephyr.elf"
-        kernel_file = self.archive_extract(self.ASSET_ZEPHYR_3_06,
+        kernel_file = self.archive_extract(self.ASSET_ZEPHYR_3_08,
                                            member=kernel_name)
         otp_img = self.generate_otpmem_image()
 
diff --git a/tests/functional/arm/test_aspeed_ast1060.py b/tests/functional/arm/test_aspeed_ast1060.py
index 833cfb8..d715825 100755
--- a/tests/functional/arm/test_aspeed_ast1060.py
+++ b/tests/functional/arm/test_aspeed_ast1060.py
@@ -11,18 +11,18 @@
 
 
 class AST1060Machine(AspeedTest):
-    ASSET_ASPEED_AST1060_PROT_3_05 = Asset(
+    ASSET_ASPEED_AST1060_PROT_3_07 = Asset(
         ('https://github.com/AspeedTech-BMC'
-         '/aspeed-zephyr-project/releases/download/v03.05'
-         '/ast1060_prot_v03.05.tgz'),
-         '63b36d7420290726ca80477de254474b7cb79539a42819bb1fe2665d598dadb5')
+         '/aspeed-zephyr-project/releases/download/v03.07'
+         '/ast1060_prot_v03.07.tgz'),
+         '55a7f51f0b77051a0ef2ada993a16c5033768e1b8e8be3babfc52c303eecd07f')
 
-    def test_arm_ast1060_prot_3_05(self):
+    def test_arm_ast1060_prot_3_07(self):
         self.set_machine('ast1060-evb')
 
         kernel_name = "ast1060_prot/zephyr.bin"
         kernel_file = self.archive_extract(
-            self.ASSET_ASPEED_AST1060_PROT_3_05, member=kernel_name)
+            self.ASSET_ASPEED_AST1060_PROT_3_07, member=kernel_name)
 
         self.vm.set_console()
         self.vm.add_args('-kernel', kernel_file, '-nographic')
@@ -35,7 +35,7 @@ def test_arm_ast1060_otp_blockdev_device(self):
         self.vm.set_machine("ast1060-evb")
 
         kernel_name = "ast1060_prot/zephyr.bin"
-        kernel_file = self.archive_extract(self.ASSET_ASPEED_AST1060_PROT_3_05,
+        kernel_file = self.archive_extract(self.ASSET_ASPEED_AST1060_PROT_3_07,
                                            member=kernel_name)
         otp_img = self.generate_otpmem_image()
 
diff --git a/tests/functional/arm/test_aspeed_ast2500_sdk.py b/tests/functional/arm/test_aspeed_ast2500_sdk.py
index 6ab498b..95df32b 100755
--- a/tests/functional/arm/test_aspeed_ast2500_sdk.py
+++ b/tests/functional/arm/test_aspeed_ast2500_sdk.py
@@ -10,14 +10,14 @@
 
 class AST2500Machine(AspeedTest):
 
-    ASSET_SDK_V1101_AST2500 = Asset(
-        'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.01/ast2500-default-obmc.tar.gz',
-        '3faa1188198da2216837be4b53861c483a58c3ad63784089720bf8421e157da1')
+    ASSET_SDK_V1103_AST2500 = Asset(
+        'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.03/ast2500-default-obmc.tar.gz',
+        '8e20cafddca04d73b799918d6f35b08c83c9f024e223a317b0ad71b97b84842f')
 
     def test_arm_ast2500_evb_sdk(self):
         self.set_machine('ast2500-evb')
 
-        self.archive_extract(self.ASSET_SDK_V1101_AST2500)
+        self.archive_extract(self.ASSET_SDK_V1103_AST2500)
 
         self.do_test_arm_aspeed_sdk_start(
             self.scratch_file("ast2500-default", "image-bmc"))
diff --git a/tests/functional/arm/test_aspeed_ast2500_sdk_515.py b/tests/functional/arm/test_aspeed_ast2500_sdk_515.py
index 8d39dc6..516e96e 100755
--- a/tests/functional/arm/test_aspeed_ast2500_sdk_515.py
+++ b/tests/functional/arm/test_aspeed_ast2500_sdk_515.py
@@ -10,14 +10,14 @@
 
 class AST2500Machine(AspeedTest):
 
-    ASSET_SDK_V1101_AST2500_515 = Asset(
-        'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.01/ast2500-default-515-obmc.tar.gz',
-        'b848ff620d2e9c83e2fb4736b4d1c39b82fdb041058cd42be42c3b177bf38eb9')
+    ASSET_SDK_V1103_AST2500_515 = Asset(
+        'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.03/ast2500-default-515-obmc.tar.gz',
+        'f17d3b0a5157bcf73c21c4981f838ea0b76c6406cc4a6409267d57d61758ebb6')
 
     def test_arm_ast2500_evb_sdk_515(self):
         self.set_machine('ast2500-evb')
 
-        self.archive_extract(self.ASSET_SDK_V1101_AST2500_515)
+        self.archive_extract(self.ASSET_SDK_V1103_AST2500_515)
 
         self.do_test_arm_aspeed_sdk_start(
             self.scratch_file("ast2500-default-515", "image-bmc"))
diff --git a/tests/functional/arm/test_aspeed_ast2600_sdk.py b/tests/functional/arm/test_aspeed_ast2600_sdk.py
index cabbe23..4fc594d 100755
--- a/tests/functional/arm/test_aspeed_ast2600_sdk.py
+++ b/tests/functional/arm/test_aspeed_ast2600_sdk.py
@@ -14,9 +14,9 @@
 
 class AST2600Machine(AspeedTest):
 
-    ASSET_SDK_V1101_AST2600 = Asset(
-        'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.01/ast2600-default-image.tar.gz',
-        '3c5b4d4ccf27b0d208a073f98426db54cd751b96143180cd15df1a83978f832c')
+    ASSET_SDK_V1103_AST2600 = Asset(
+        'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.03/ast2600-default-image.tar.gz',
+        '47e3656a14bf7a4de28d3dfbf48bc2325443bc42d270f3bc82646f92f6dea165')
 
     def do_ast2600_pcie_test(self):
         exec_command_and_wait_for_pattern(self,
@@ -49,7 +49,7 @@ def test_arm_ast2600_evb_sdk(self):
         self.set_machine('ast2600-evb')
         self.require_netdev('user')
 
-        self.archive_extract(self.ASSET_SDK_V1101_AST2600)
+        self.archive_extract(self.ASSET_SDK_V1103_AST2600)
 
         self.vm.add_args('-device',
             'tmp105,bus=aspeed.i2c.bus.5,address=0x4d,id=tmp-test')
diff --git a/tests/functional/arm/test_aspeed_ast2600_sdk_515.py b/tests/functional/arm/test_aspeed_ast2600_sdk_515.py
index a8e7faf..f5b14de 100755
--- a/tests/functional/arm/test_aspeed_ast2600_sdk_515.py
+++ b/tests/functional/arm/test_aspeed_ast2600_sdk_515.py
@@ -10,14 +10,14 @@
 
 class AST2600Machine(AspeedTest):
 
-    ASSET_SDK_V1101_AST2600_515 = Asset(
-        'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.01/ast2600-default-515-image.tar.gz',
-        'f3ccf1c08db71cf891637fc73131b80b2c0c0e005c06d5dcae0cf74fc458b43c')
+    ASSET_SDK_V1103_AST2600_515 = Asset(
+        'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.03/ast2600-default-515-image.tar.gz',
+        'c79d0197106f146476e82bb878e5438f6569bd30f3b53fbb520b59bc54f6b7dc')
 
     def test_arm_ast2600_evb_sdk_515(self):
         self.set_machine('ast2600-evb')
 
-        self.archive_extract(self.ASSET_SDK_V1101_AST2600_515)
+        self.archive_extract(self.ASSET_SDK_V1103_AST2600_515)
 
         self.do_test_arm_aspeed_sdk_start(
             self.scratch_file("ast2600-default-515-image", "image-bmc"))
diff --git a/tests/functional/arm/test_aspeed_ast2600_sdk_otp.py b/tests/functional/arm/test_aspeed_ast2600_sdk_otp.py
index f24dea1..5813c59 100755
--- a/tests/functional/arm/test_aspeed_ast2600_sdk_otp.py
+++ b/tests/functional/arm/test_aspeed_ast2600_sdk_otp.py
@@ -12,15 +12,15 @@
 
 class AST2600Machine(AspeedTest):
 
-    ASSET_SDK_V1101_AST2600 = Asset(
-        'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.01/ast2600-default-image.tar.gz',
-        '3c5b4d4ccf27b0d208a073f98426db54cd751b96143180cd15df1a83978f832c')
+    ASSET_SDK_V1103_AST2600 = Asset(
+        'https://github.com/AspeedTech-BMC/openbmc/releases/download/v11.03/ast2600-default-image.tar.gz',
+        '47e3656a14bf7a4de28d3dfbf48bc2325443bc42d270f3bc82646f92f6dea165')
 
     def test_arm_ast2600_otp_blockdev_device(self):
         self.vm.set_machine("ast2600-evb")
         self.require_netdev('user')
 
-        image_path = self.archive_extract(self.ASSET_SDK_V1101_AST2600)
+        image_path = self.archive_extract(self.ASSET_SDK_V1103_AST2600)
         otp_img = self.generate_otpmem_image()
 
         self.vm.set_console()
diff --git a/tests/functional/hexagon/test_arch_tests.py b/tests/functional/hexagon/test_arch_tests.py
index 54a1412..0834398 100755
--- a/tests/functional/hexagon/test_arch_tests.py
+++ b/tests/functional/hexagon/test_arch_tests.py
@@ -52,6 +52,44 @@ def test_guest_mode(self) -> None:
         """
         self.run_uart_test("test_guest_mode")
 
+    def test_int_steering(self) -> None:
+        """Tests interrupt steering via priority-based routing to
+        specific threads using STID priority and iassignw.
+        """
+        self.run_uart_test("test_int_steering")
+
+    def test_cache(self) -> None:
+        """Tests cache operations: dckill/ickill, l2kill, dczeroa,
+        dccleaninva, cache disable/enable, barriers, and dcinva/dccleana.
+        """
+        self.run_uart_test("test_cache")
+
+    def test_l2vic(self) -> None:
+        """Tests the L2VIC interrupt controller: enable readback,
+        interrupt type readback, VID capture, and the fast interface.
+        """
+        self.run_uart_test("test_l2vic")
+
+    def test_threads(self) -> None:
+        """Tests hardware thread management: start/stop, MODECTL state,
+        per-thread HTID, shared memory, wait/resume, STID priority, and
+        SCHEDCFG/BESTWAIT readback.
+        """
+        self.run_uart_test("test_threads")
+
+    def test_tlb_mmu(self) -> None:
+        """Tests TLB/MMU operations: write/read/probe/invalidate,
+        global entries, multiple entries, overwrite, ASID matching,
+        and permission checks.
+        """
+        self.run_uart_test("test_tlb_mmu")
+
+    def test_user_mode(self) -> None:
+        """Tests user mode / privilege transitions: supervisor mode,
+        SSR UM/IE/XE/CE/PE bits, and the trap0 user-mode exit handler.
+        """
+        self.run_uart_test("test_user_mode")
+
 
 if __name__ == "__main__":
     QemuSystemTest.main()
diff --git a/tests/qtest/adc128d818-test.c b/tests/qtest/adc128d818-test.c
new file mode 100644
index 0000000..91eda5c
--- /dev/null
+++ b/tests/qtest/adc128d818-test.c
@@ -0,0 +1,856 @@
+/*
+ * QTest testcase for the ADC128D818 ADC
+ *
+ * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/bitops.h"
+#include "libqos/i2c.h"
+#include "libqos/qgraph.h"
+#include "libqtest-single.h"
+#include "qobject/qdict.h"
+
+#define ADC128D818_TEST_ID      "adc128d818-test"
+#define ADC128D818_TEST_ADDR    0x1f
+
+/* Register addresses */
+#define REG_CONFIG              0x00
+#define REG_INT_STATUS          0x01
+#define REG_INT_MASK            0x03
+#define REG_CONV_RATE           0x07
+#define REG_CH_DISABLE          0x08
+#define REG_ONE_SHOT            0x09
+#define REG_DEEP_SHUTDOWN       0x0a
+#define REG_ADV_CONFIG          0x0b
+#define REG_BUSY_STATUS         0x0c
+
+/* Channel Reading Registers (16-bit, read-only) */
+#define REG_CH_READING_BASE     0x20
+
+/* Limit Registers (8-bit, read/write) */
+#define REG_LIMIT_BASE          0x2a
+
+/* ID Registers (read-only) */
+#define REG_MANUFACTURER_ID     0x3e
+#define REG_REVISION_ID         0x3f
+
+/* Configuration Register (0x00) bitfields */
+#define CONFIG_START            BIT(0)
+#define CONFIG_INT_ENABLE       BIT(1)
+#define CONFIG_INT_CLEAR        BIT(3)
+#define CONFIG_INITIALIZATION   BIT(7)
+
+/* Advanced Configuration Register (0x0b) bitfields */
+#define ADV_CONFIG_EXT_REF_EN   BIT(0)
+#define ADV_CONFIG_MODE_1       (1 << 1)
+#define ADV_CONFIG_MODE_2       (2 << 1)
+#define ADV_CONFIG_MODE_3       (3 << 1)
+
+/* Number of channels */
+#define NUM_CHANNELS            8
+
+/* Internal VREF in mV */
+#define INTERNAL_VREF_MV        2560
+
+/* QMP helpers for setting device properties */
+
+static void qmp_adc128d818_set(const char *property, int value)
+{
+    QDict *resp;
+
+    resp = qmp("{ 'execute': 'qom-set', 'arguments':"
+               " { 'path': %s, 'property': %s, 'value': %d } }",
+               ADC128D818_TEST_ID, property, value);
+    g_assert(qdict_haskey(resp, "return"));
+    qobject_unref(resp);
+}
+
+static int qmp_adc128d818_get(const char *property)
+{
+    QDict *resp;
+    int ret;
+
+    resp = qmp("{ 'execute': 'qom-get', 'arguments':"
+               " { 'path': %s, 'property': %s } }",
+               ADC128D818_TEST_ID, property);
+    g_assert(qdict_haskey(resp, "return"));
+    ret = qdict_get_int(resp, "return");
+    qobject_unref(resp);
+    return ret;
+}
+
+/* Manufacturer and Revision ID registers */
+static void test_id_registers(void *obj, void *data, QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+
+    g_assert_cmphex(i2c_get8(dev, REG_MANUFACTURER_ID), ==, 0x01);
+    g_assert_cmphex(i2c_get8(dev, REG_REVISION_ID), ==, 0x09);
+}
+
+/* Power-on-reset default values */
+static void test_defaults(void *obj, void *data, QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+    unsigned ch;
+
+    g_assert_cmphex(i2c_get8(dev, REG_CONFIG), ==, 0x08);
+    g_assert_cmphex(i2c_get8(dev, REG_INT_STATUS), ==, 0x00);
+    g_assert_cmphex(i2c_get8(dev, REG_INT_MASK), ==, 0x00);
+    g_assert_cmphex(i2c_get8(dev, REG_CONV_RATE), ==, 0x00);
+    g_assert_cmphex(i2c_get8(dev, REG_CH_DISABLE), ==, 0x00);
+    g_assert_cmphex(i2c_get8(dev, REG_DEEP_SHUTDOWN), ==, 0x00);
+    g_assert_cmphex(i2c_get8(dev, REG_ADV_CONFIG), ==, 0x00);
+    g_assert_cmphex(i2c_get8(dev, REG_BUSY_STATUS), ==, 0x02);
+
+    for (ch = 0u; ch < NUM_CHANNELS; ch++) {
+        g_assert_cmphex(i2c_get8(dev, REG_LIMIT_BASE + ch * 2u), ==, 0xFF);
+        g_assert_cmphex(i2c_get8(dev, REG_LIMIT_BASE + ch * 2u + 1u), ==, 0x00);
+    }
+}
+
+/* Software reset via INITIALIZATION bit */
+static void test_soft_reset(void *obj, void *data, QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+
+    i2c_set8(dev, REG_INT_MASK, 0xAA);
+    i2c_set8(dev, REG_CH_DISABLE, 0x55);
+    i2c_set8(dev, REG_LIMIT_BASE, 0x42);
+
+    g_assert_cmphex(i2c_get8(dev, REG_INT_MASK), ==, 0xAA);
+    g_assert_cmphex(i2c_get8(dev, REG_CH_DISABLE), ==, 0x55);
+    g_assert_cmphex(i2c_get8(dev, REG_LIMIT_BASE), ==, 0x42);
+
+    i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION);
+
+    g_assert_cmphex(i2c_get8(dev, REG_CONFIG), ==, 0x08);
+    g_assert_cmphex(i2c_get8(dev, REG_INT_MASK), ==, 0x00);
+    g_assert_cmphex(i2c_get8(dev, REG_CH_DISABLE), ==, 0x00);
+    g_assert_cmphex(i2c_get8(dev, REG_LIMIT_BASE), ==, 0xFF);
+    g_assert_cmphex(i2c_get8(dev, REG_BUSY_STATUS), ==, 0x02);
+}
+
+/* Verify ain property readback via QMP */
+static void test_ain_property(void *obj, void *data, QGuestAllocator *alloc)
+{
+    int value;
+
+    qmp_adc128d818_set("ain3", 1500);
+    value = qmp_adc128d818_get("ain3");
+    g_test_message("Set ain3 = 1500 mV, readback = %d mV", value);
+    g_assert_cmpint(value, ==, 1500);
+
+    qmp_adc128d818_set("temperature", 37500);
+    value = qmp_adc128d818_get("temperature");
+    g_test_message("Set temperature = 37500 mC, readback = %d mC", value);
+    g_assert_cmpint(value, ==, 37500);
+}
+
+/* Voltage conversion */
+static void test_voltage_conversion(void *obj, void *data,
+                                    QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+    uint16_t reading;
+
+    qmp_adc128d818_set("ain0", 1280);
+    g_test_message("Injected ain0 = 1280 mV");
+    i2c_set8(dev, REG_CONFIG, CONFIG_START);
+
+    reading = i2c_get16(dev, REG_CH_READING_BASE);
+    g_test_message("Read ch0: raw 0x%04x -> %u mV", reading,
+             (reading >> 4u) * INTERNAL_VREF_MV / 4096u);
+    g_assert_cmphex(reading, ==, 0x8000);
+
+    qmp_adc128d818_set("ain1", 2560);
+    g_test_message("Injected ain1 = 2560 mV");
+    reading = i2c_get16(dev, REG_CH_READING_BASE + 1u);
+    g_test_message("Read ch1: raw 0x%04x -> %u mV", reading,
+             (reading >> 4u) * INTERNAL_VREF_MV / 4096u);
+    g_assert_cmphex(reading, ==, 0xFFF0);
+
+    qmp_adc128d818_set("ain2", 0);
+    g_test_message("Injected ain2 = 0 mV");
+    reading = i2c_get16(dev, REG_CH_READING_BASE + 2u);
+    g_test_message("Read ch2: raw 0x%04x -> %u mV", reading,
+             (reading >> 4u) * INTERNAL_VREF_MV / 4096u);
+    g_assert_cmphex(reading, ==, 0x0000);
+}
+
+/* Temperature conversion (mode 0, channel 7 = temperature) */
+static void
+test_temperature_conversion(void *obj, void *data, QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+    uint16_t reading;
+
+    qmp_adc128d818_set("temperature", 25000);
+    g_test_message("Injected temperature = 25000 mC (25.0 deg C)");
+    i2c_set8(dev, REG_CONFIG, CONFIG_START);
+
+    reading = i2c_get16(dev, REG_CH_READING_BASE + 7u);
+    g_test_message("Read ch7: raw 0x%04x -> %d mC", reading,
+             (int16_t)(reading & 0xFF80u) * 500 / 128);
+    g_assert_cmphex(reading, ==, 0x1900);
+}
+
+/* Channels with distinct voltages */
+static void test_all_channels(void *obj, void *data, QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+    static const uint16_t ain_mv[NUM_CHANNELS] = {
+        0, 320, 640, 960, 1280, 1920, 2240, 2560
+    };
+    static const uint16_t expect[NUM_CHANNELS] = {
+        0x0000, 0x2000, 0x4000, 0x6000, 0x8000, 0xC000, 0xE000, 0xFFF0
+    };
+    uint16_t reading;
+    unsigned ch;
+
+    i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION);
+    i2c_set8(dev, REG_ADV_CONFIG, ADV_CONFIG_MODE_1);
+
+    for (ch = 0u; ch < NUM_CHANNELS; ch++) {
+        char name[8];
+        snprintf(name, sizeof(name), "ain%u", ch);
+        qmp_adc128d818_set(name, ain_mv[ch]);
+    }
+
+    i2c_set8(dev, REG_CONFIG, CONFIG_START);
+
+    for (ch = 0u; ch < NUM_CHANNELS; ch++) {
+        reading = i2c_get16(dev, REG_CH_READING_BASE + ch);
+        g_test_message("ch%u: ain %u mV -> raw 0x%04x (expect 0x%04x)",
+                 ch, ain_mv[ch], reading, expect[ch]);
+        g_assert_cmphex(reading, ==, expect[ch]);
+    }
+}
+
+/* Voltage conversion edge cases */
+static void test_voltage_edges(void *obj, void *data, QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+    uint16_t reading;
+
+    i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION);
+    i2c_set8(dev, REG_ADV_CONFIG, ADV_CONFIG_MODE_1);
+
+    qmp_adc128d818_set("ain0", 3000);
+    i2c_set8(dev, REG_CONFIG, CONFIG_START);
+
+    reading = i2c_get16(dev, REG_CH_READING_BASE);
+    g_test_message("Over-range 3000 mV: raw 0x%04x (expect 0xFFF0)", reading);
+    g_assert_cmphex(reading, ==, 0xFFF0);
+
+    qmp_adc128d818_set("ain1", 1);
+    reading = i2c_get16(dev, REG_CH_READING_BASE + 1u);
+    g_test_message("1 mV: raw 0x%04x (expect 0x0010)", reading);
+    g_assert_cmphex(reading, ==, 0x0010);
+
+    qmp_adc128d818_set("ain2", 640);
+    reading = i2c_get16(dev, REG_CH_READING_BASE + 2u);
+    g_test_message("640 mV (quarter): raw 0x%04x (expect 0x4000)", reading);
+    g_assert_cmphex(reading, ==, 0x4000);
+
+    qmp_adc128d818_set("ain3", 1920);
+    reading = i2c_get16(dev, REG_CH_READING_BASE + 3u);
+    g_test_message("1920 mV (3/4): raw 0x%04x (expect 0xC000)", reading);
+    g_assert_cmphex(reading, ==, 0xC000);
+}
+
+/* Temperature conversion edge cases */
+static void test_temperature_edges(void *obj, void *data,
+                                   QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+    uint16_t reading;
+
+    i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION);
+
+    qmp_adc128d818_set("temperature", 0);
+    i2c_set8(dev, REG_CONFIG, CONFIG_START);
+    reading = i2c_get16(dev, REG_CH_READING_BASE + 7u);
+    g_test_message("0 C: raw 0x%04x (expect 0x0000)", reading);
+    g_assert_cmphex(reading, ==, 0x0000);
+
+    qmp_adc128d818_set("temperature", -25000);
+    reading = i2c_get16(dev, REG_CH_READING_BASE + 7u);
+    g_test_message("-25 C: raw 0x%04x (expect 0xE700)", reading);
+    g_assert_cmphex(reading, ==, 0xE700);
+
+    qmp_adc128d818_set("temperature", 127500);
+    reading = i2c_get16(dev, REG_CH_READING_BASE + 7u);
+    g_test_message("+127.5 C: raw 0x%04x (expect 0x7F80)", reading);
+    g_assert_cmphex(reading, ==, 0x7F80);
+
+    qmp_adc128d818_set("temperature", -128000);
+    reading = i2c_get16(dev, REG_CH_READING_BASE + 7u);
+    g_test_message("-128 C: raw 0x%04x (expect 0x8000)", reading);
+    g_assert_cmphex(reading, ==, 0x8000);
+
+    qmp_adc128d818_set("temperature", 200000);
+    reading = i2c_get16(dev, REG_CH_READING_BASE + 7u);
+    g_test_message("200 C (clamped): raw 0x%04x (expect 0x7F80)", reading);
+    g_assert_cmphex(reading, ==, 0x7F80);
+
+    qmp_adc128d818_set("temperature", -200000);
+    reading = i2c_get16(dev, REG_CH_READING_BASE + 7u);
+    g_test_message("-200 C (clamped): raw 0x%04x (expect 0x8000)", reading);
+    g_assert_cmphex(reading, ==, 0x8000);
+}
+
+/* External voltage reference */
+static void test_ext_vref(void *obj, void *data, QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+    uint16_t reading;
+
+    i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION);
+    i2c_set8(dev, REG_ADV_CONFIG, ADV_CONFIG_MODE_1);
+
+    qmp_adc128d818_set("ext-vref-mv", 4096);
+    i2c_set8(dev, REG_ADV_CONFIG, ADV_CONFIG_EXT_REF_EN | ADV_CONFIG_MODE_1);
+
+    qmp_adc128d818_set("ain0", 1000);
+    i2c_set8(dev, REG_CONFIG, CONFIG_START);
+
+    reading = i2c_get16(dev, REG_CH_READING_BASE);
+    g_test_message("1000 mV / 4096 mV VREF: raw 0x%04x (expect 0x3E80)",
+                   reading);
+    g_assert_cmphex(reading, ==, 0x3E80);
+
+    qmp_adc128d818_set("ain1", 2048);
+    reading = i2c_get16(dev, REG_CH_READING_BASE + 1u);
+    g_test_message("2048 mV / 4096 mV VREF: raw 0x%04x (expect 0x8000)",
+                   reading);
+    g_assert_cmphex(reading, ==, 0x8000);
+}
+
+/* Interrupt status set on limit violation; persists while fault remains */
+static void test_interrupt_status(void *obj, void *data, QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+    uint8_t status;
+
+    i2c_set8(dev, REG_LIMIT_BASE, 0x10);
+    g_test_message("Set ch0 high limit = 0x10");
+
+    qmp_adc128d818_set("ain0", 2560);
+    g_test_message("Injected ain0 = 2560 mV (exceeds limit)");
+
+    i2c_set8(dev, REG_CONFIG, CONFIG_START | CONFIG_INT_ENABLE);
+
+    status = i2c_get8(dev, REG_INT_STATUS);
+    g_test_message("INT_STATUS = 0x%02x (expect bit 0 set)", status);
+    g_assert_cmphex(status & 0x01u, ==, 0x01);
+
+    status = i2c_get8(dev, REG_INT_STATUS);
+    g_test_message("INT_STATUS after re-read = 0x%02x (expect bit 0 still set)",
+             status);
+    g_assert_cmphex(status & 0x01u, ==, 0x01);
+
+    qmp_adc128d818_set("ain0", 80);
+    g_test_message("Injected ain0 = 80 mV (within limit)");
+    status = i2c_get8(dev, REG_INT_STATUS);
+    g_test_message("INT_STATUS after fault cleared = 0x%02x "
+                   "(expect bit 0 clear)", status);
+    g_assert_cmphex(status & 0x01u, ==, 0x00);
+}
+
+/* INT_CLEAR stops the round-robin monitoring loop */
+static void test_int_clear(void *obj, void *data, QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+    uint8_t status;
+
+    i2c_set8(dev, REG_LIMIT_BASE, 0x10);
+    qmp_adc128d818_set("ain0", 2560);
+
+    i2c_set8(dev, REG_CONFIG,
+             CONFIG_START | CONFIG_INT_ENABLE | CONFIG_INT_CLEAR);
+    status = i2c_get8(dev, REG_INT_STATUS);
+    g_test_message("INT_STATUS with INT_CLEAR set = 0x%02x (expect 0x00)",
+                   status);
+    g_assert_cmphex(status, ==, 0x00);
+
+    i2c_set8(dev, REG_CONFIG, CONFIG_START | CONFIG_INT_ENABLE);
+    status = i2c_get8(dev, REG_INT_STATUS);
+    g_test_message("INT_STATUS after INT_CLEAR cleared = 0x%02x (expect bit 0)",
+             status);
+    g_assert_cmphex(status & 0x01u, ==, 0x01);
+}
+
+/* Low-limit interrupt triggers correctly */
+static void test_low_limit(void *obj, void *data, QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+    uint8_t status;
+
+    i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION);
+
+    i2c_set8(dev, REG_LIMIT_BASE + 3u, 0x80);
+    g_test_message("Set ch1 low limit = 0x80");
+
+    i2c_set8(dev, REG_LIMIT_BASE + 5u, 0x80);
+    g_test_message("Set ch2 low limit = 0x80");
+
+    qmp_adc128d818_set("ain1", 640);
+    qmp_adc128d818_set("ain2", 1280);
+    qmp_adc128d818_set("ain0", 1280);
+    i2c_set8(dev, REG_CONFIG, CONFIG_START | CONFIG_INT_ENABLE);
+
+    status = i2c_get8(dev, REG_INT_STATUS);
+    g_test_message("INT_STATUS = 0x%02x (expect bits 1 and 2 set)", status);
+    g_assert_cmphex(status & 0x02u, ==, 0x02);
+    g_assert_cmphex(status & 0x04u, ==, 0x04);
+
+    g_assert_cmphex(status & 0x01u, ==, 0x00);
+}
+
+/* Temperature high-limit interrupt with hysteresis */
+static void test_temp_hysteresis(void *obj, void *data,
+                                 QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+    uint8_t status;
+
+    i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION);
+
+    i2c_set8(dev, REG_LIMIT_BASE + 7u * 2u, 0x32);
+    i2c_set8(dev, REG_LIMIT_BASE + 7u * 2u + 1u, 0x28);
+
+    qmp_adc128d818_set("temperature", 25000);
+    i2c_set8(dev, REG_CONFIG, CONFIG_START);
+
+    status = i2c_get8(dev, REG_INT_STATUS);
+    g_test_message("25 C: INT_STATUS = 0x%02x (temp bit expect clear)", status);
+    g_assert_cmphex(status & 0x80u, ==, 0x00);
+
+    qmp_adc128d818_set("temperature", 55000);
+    status = i2c_get8(dev, REG_INT_STATUS);
+    g_test_message("55 C: INT_STATUS = 0x%02x (temp bit expect set)", status);
+    g_assert_cmphex(status & 0x80u, ==, 0x80);
+
+    qmp_adc128d818_set("temperature", 45000);
+    status = i2c_get8(dev, REG_INT_STATUS);
+    g_test_message("45 C (hysteresis): INT_STATUS = 0x%02x "
+                   "(temp bit expect set)", status);
+    g_assert_cmphex(status & 0x80u, ==, 0x80);
+
+    qmp_adc128d818_set("temperature", 35000);
+    status = i2c_get8(dev, REG_INT_STATUS);
+    g_test_message("35 C: INT_STATUS = 0x%02x (temp bit expect clear)", status);
+    g_assert_cmphex(status & 0x80u, ==, 0x00);
+}
+
+/* Channel disable prevents conversion */
+static void test_channel_disable(void *obj, void *data, QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+    uint16_t reading;
+
+    i2c_set8(dev, REG_CH_DISABLE, 0x01);
+    g_test_message("Disabled channel 0");
+
+    qmp_adc128d818_set("ain0", 1280);
+    g_test_message("Injected ain0 = 1280 mV (disabled)");
+    i2c_set8(dev, REG_CONFIG, CONFIG_START);
+
+    reading = i2c_get16(dev, REG_CH_READING_BASE);
+    g_test_message("Read ch0 (disabled): raw 0x%04x", reading);
+    g_assert_cmphex(reading, ==, 0x0000);
+
+    qmp_adc128d818_set("ain1", 1280);
+    g_test_message("Injected ain1 = 1280 mV (enabled)");
+    reading = i2c_get16(dev, REG_CH_READING_BASE + 1u);
+    g_test_message("Read ch1 (enabled): raw 0x%04x -> %u mV", reading,
+             (reading >> 4u) * INTERNAL_VREF_MV / 4096u);
+    g_assert_cmphex(reading, ==, 0x8000);
+}
+
+/* One-shot conversion in shutdown mode */
+static void test_one_shot(void *obj, void *data, QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+    uint16_t reading;
+
+    qmp_adc128d818_set("ain0", 1280);
+    g_test_message("Injected ain0 = 1280 mV (device stopped)");
+
+    reading = i2c_get16(dev, REG_CH_READING_BASE);
+    g_test_message("Read ch0 before one-shot: raw 0x%04x", reading);
+    g_assert_cmphex(reading, ==, 0x0000);
+
+    g_assert_cmphex(i2c_get8(dev, REG_ONE_SHOT), ==, 0x00);
+
+    i2c_set8(dev, REG_ONE_SHOT, 0x00);
+    g_test_message("Triggered one-shot conversion with value 0x00");
+
+    reading = i2c_get16(dev, REG_CH_READING_BASE);
+    g_test_message("Read ch0 after one-shot: raw 0x%04x -> %u mV", reading,
+             (reading >> 4u) * INTERNAL_VREF_MV / 4096u);
+    g_assert_cmphex(reading, ==, 0x8000);
+}
+
+/* Mode 1 makes channel 7 a voltage input instead of temperature */
+static void test_mode_selection(void *obj, void *data, QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+    uint16_t reading;
+
+    i2c_set8(dev, REG_ADV_CONFIG, ADV_CONFIG_MODE_1);
+    g_test_message("Set mode 1 (all voltage channels)");
+
+    qmp_adc128d818_set("ain7", 1280);
+    qmp_adc128d818_set("temperature", 50000);
+    g_test_message("Injected ain7 = 1280 mV, temperature = 50000 mC");
+
+    i2c_set8(dev, REG_CONFIG, CONFIG_START);
+
+    reading = i2c_get16(dev, REG_CH_READING_BASE + 7u);
+    g_test_message("Read ch7 (mode 1): raw 0x%04x -> %u mV", reading,
+             (reading >> 4u) * INTERNAL_VREF_MV / 4096u);
+    g_assert_cmphex(reading, ==, 0x8000);
+}
+
+/* Mode 2 - 4 pseudo-differential pairs */
+static void test_mode2_diff(void *obj, void *data, QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+    uint16_t reading;
+
+    i2c_set8(dev, REG_ADV_CONFIG, ADV_CONFIG_MODE_2);
+    g_test_message("Set mode 2 (4 pseudo-differential pairs)");
+
+    qmp_adc128d818_set("ain0", 2000);
+    qmp_adc128d818_set("ain1", 720);
+    i2c_set8(dev, REG_CONFIG, CONFIG_START);
+
+    reading = i2c_get16(dev, REG_CH_READING_BASE);
+    g_test_message("Pair 0 (IN0-IN1): raw 0x%04x (expect 0x8000)", reading);
+    g_assert_cmphex(reading, ==, 0x8000);
+
+    qmp_adc128d818_set("ain3", 1920);
+    qmp_adc128d818_set("ain2", 640);
+
+    reading = i2c_get16(dev, REG_CH_READING_BASE + 1u);
+    g_test_message("Pair 1 (IN3-IN2): raw 0x%04x (expect 0x8000)", reading);
+    g_assert_cmphex(reading, ==, 0x8000);
+
+    qmp_adc128d818_set("ain4", 1500);
+    qmp_adc128d818_set("ain5", 220);
+
+    reading = i2c_get16(dev, REG_CH_READING_BASE + 2u);
+    g_test_message("Pair 2 (IN4-IN5): raw 0x%04x (expect 0x8000)", reading);
+    g_assert_cmphex(reading, ==, 0x8000);
+
+    qmp_adc128d818_set("ain7", 2560);
+    qmp_adc128d818_set("ain6", 1280);
+
+    reading = i2c_get16(dev, REG_CH_READING_BASE + 3u);
+    g_test_message("Pair 3 (IN7-IN6): raw 0x%04x (expect 0x8000)", reading);
+    g_assert_cmphex(reading, ==, 0x8000);
+
+    reading = i2c_get16(dev, REG_CH_READING_BASE + 4u);
+    g_test_message("Reserved ch4: raw 0x%04x (expect 0x0000)", reading);
+    g_assert_cmphex(reading, ==, 0x0000);
+
+    qmp_adc128d818_set("ain0", 500);
+    qmp_adc128d818_set("ain1", 1000);
+
+    reading = i2c_get16(dev, REG_CH_READING_BASE);
+    g_test_message("Pair 0 negative dV: raw 0x%04x (expect 0x0000)", reading);
+    g_assert_cmphex(reading, ==, 0x0000);
+}
+
+/* Mode 3 - 4 single-ended + 2 pseudo-differential pairs */
+static void test_mode3_mixed(void *obj, void *data, QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+    uint16_t reading;
+
+    i2c_set8(dev, REG_ADV_CONFIG, ADV_CONFIG_MODE_3);
+    g_test_message("Set mode 3 (4 single-ended + 2 differential)");
+
+    qmp_adc128d818_set("ain0", 1280);
+    i2c_set8(dev, REG_CONFIG, CONFIG_START);
+
+    reading = i2c_get16(dev, REG_CH_READING_BASE);
+    g_test_message("Ch0 single-ended: raw 0x%04x (expect 0x8000)", reading);
+    g_assert_cmphex(reading, ==, 0x8000);
+
+    qmp_adc128d818_set("ain4", 1500);
+    qmp_adc128d818_set("ain5", 220);
+
+    reading = i2c_get16(dev, REG_CH_READING_BASE + 4u);
+    g_test_message("Ch4 diff (IN4-IN5): raw 0x%04x (expect 0x8000)", reading);
+    g_assert_cmphex(reading, ==, 0x8000);
+
+    qmp_adc128d818_set("ain7", 2560);
+    qmp_adc128d818_set("ain6", 1280);
+
+    reading = i2c_get16(dev, REG_CH_READING_BASE + 5u);
+    g_test_message("Ch5 diff (IN7-IN6): raw 0x%04x (expect 0x8000)", reading);
+    g_assert_cmphex(reading, ==, 0x8000);
+
+    reading = i2c_get16(dev, REG_CH_READING_BASE + 6u);
+    g_test_message("Reserved ch6: raw 0x%04x (expect 0x0000)", reading);
+    g_assert_cmphex(reading, ==, 0x0000);
+
+    qmp_adc128d818_set("temperature", 25000);
+
+    reading = i2c_get16(dev, REG_CH_READING_BASE + 7u);
+    g_test_message("Ch7 temperature: raw 0x%04x (expect 0x1900)", reading);
+    g_assert_cmphex(reading, ==, 0x1900);
+}
+
+/* Mode change resets channel readings and interrupt status */
+static void test_mode_change_reset(void *obj, void *data,
+                                   QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+    uint16_t reading;
+    uint8_t status;
+
+    qmp_adc128d818_set("ain0", 1280);
+    i2c_set8(dev, REG_CONFIG, CONFIG_START);
+
+    reading = i2c_get16(dev, REG_CH_READING_BASE);
+    g_test_message("Before mode change, ch0: raw 0x%04x", reading);
+    g_assert_cmphex(reading, !=, 0x0000);
+
+    i2c_set8(dev, REG_CONFIG, 0x00);
+    i2c_set8(dev, REG_LIMIT_BASE, 0x10);
+    qmp_adc128d818_set("ain0", 2560);
+    i2c_set8(dev, REG_CONFIG, CONFIG_START);
+
+    i2c_set8(dev, REG_CONFIG, 0x00);
+    i2c_set8(dev, REG_ADV_CONFIG, ADV_CONFIG_MODE_2);
+
+    reading = i2c_get16(dev, REG_CH_READING_BASE);
+    g_test_message("After mode change, ch0: raw 0x%04x (expect 0x0000)",
+                   reading);
+    g_assert_cmphex(reading, ==, 0x0000);
+
+    status = i2c_get8(dev, REG_INT_STATUS);
+    g_test_message("After mode change, INT_STATUS: 0x%02x (expect 0x00)",
+                   status);
+    g_assert_cmphex(status, ==, 0x00);
+
+    g_assert_cmphex(i2c_get8(dev, REG_LIMIT_BASE), ==, 0x10);
+    g_test_message("Limit register preserved after mode change");
+}
+
+/* QOM property changes trigger correct differential conversion */
+static void test_diff_qom_trigger(void *obj, void *data,
+                                  QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+    uint16_t reading;
+
+    i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION);
+    i2c_set8(dev, REG_ADV_CONFIG, ADV_CONFIG_MODE_2);
+
+    qmp_adc128d818_set("ain0", 0);
+    qmp_adc128d818_set("ain1", 0);
+    qmp_adc128d818_set("ain2", 0);
+    qmp_adc128d818_set("ain3", 0);
+    i2c_set8(dev, REG_CONFIG, CONFIG_START);
+
+    qmp_adc128d818_set("ain0", 2000);
+    reading = i2c_get16(dev, REG_CH_READING_BASE);
+    g_test_message("After ain0=2000, ain1=0: pair0 = 0x%04x (expect 0xC800)",
+             reading);
+    g_assert_cmphex(reading, ==, 0xC800);
+
+    qmp_adc128d818_set("ain1", 720);
+    reading = i2c_get16(dev, REG_CH_READING_BASE);
+    g_test_message("After ain1=720: pair0 = 0x%04x (expect 0x8000)", reading);
+    g_assert_cmphex(reading, ==, 0x8000);
+
+    qmp_adc128d818_set("ain3", 1920);
+    qmp_adc128d818_set("ain2", 640);
+    reading = i2c_get16(dev, REG_CH_READING_BASE + 1u);
+    g_test_message("Pair 1 (IN3-IN2) via QOM: 0x%04x (expect 0x8000)", reading);
+    g_assert_cmphex(reading, ==, 0x8000);
+}
+
+/* One-shot conversion works in deep shutdown */
+static void test_deep_shutdown(void *obj, void *data, QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+    uint16_t reading;
+
+    i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION);
+
+    qmp_adc128d818_set("ain0", 1280);
+    i2c_set8(dev, REG_CONFIG, CONFIG_START);
+    reading = i2c_get16(dev, REG_CH_READING_BASE);
+    g_assert_cmphex(reading, ==, 0x8000);
+
+    i2c_set8(dev, REG_DEEP_SHUTDOWN, 0x01);
+    g_test_message("DEEP_SHUTDOWN write while running rejected");
+    g_assert_cmphex(i2c_get8(dev, REG_DEEP_SHUTDOWN), ==, 0x00);
+
+    i2c_set8(dev, REG_CONFIG, 0x00);
+    i2c_set8(dev, REG_DEEP_SHUTDOWN, 0x01);
+    qmp_adc128d818_set("ain0", 0);
+
+    reading = i2c_get16(dev, REG_CH_READING_BASE);
+    g_test_message("Deep shutdown, no one-shot: raw 0x%04x (expect 0x8000)",
+                   reading);
+    g_assert_cmphex(reading, ==, 0x8000);
+
+    i2c_set8(dev, REG_ONE_SHOT, 0x01);
+    reading = i2c_get16(dev, REG_CH_READING_BASE);
+    g_test_message("Deep shutdown one-shot: raw 0x%04x (expect 0x0000)",
+                   reading);
+    g_assert_cmphex(reading, ==, 0x0000);
+
+    g_assert_cmphex(i2c_get8(dev, REG_DEEP_SHUTDOWN), ==, 0x01);
+
+    i2c_set8(dev, REG_DEEP_SHUTDOWN, 0x00);
+    qmp_adc128d818_set("ain0", 1280);
+    i2c_set8(dev, REG_ONE_SHOT, 0x01);
+    reading = i2c_get16(dev, REG_CH_READING_BASE);
+    g_test_message("After exit shutdown: raw 0x%04x (expect 0x8000)", reading);
+    g_assert_cmphex(reading, ==, 0x8000);
+}
+
+/* BUSY_STATUS NOT_READY clears after first conversion */
+static void test_busy_status(void *obj, void *data, QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+
+    i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION);
+
+    g_assert_cmphex(i2c_get8(dev, REG_BUSY_STATUS) & 0x02, ==, 0x02);
+    g_test_message("After reset: BUSY_STATUS = 0x%02x (NOT_READY set)",
+             i2c_get8(dev, REG_BUSY_STATUS));
+
+    qmp_adc128d818_set("ain0", 0);
+    i2c_set8(dev, REG_CONFIG, CONFIG_START);
+
+    g_assert_cmphex(i2c_get8(dev, REG_BUSY_STATUS) & 0x02, ==, 0x00);
+    g_test_message("After conversion: BUSY_STATUS = 0x%02x (NOT_READY cleared)",
+             i2c_get8(dev, REG_BUSY_STATUS));
+}
+
+/* Programming Channel Disable resets channel readings */
+static void test_chan_disable_clears(void *obj, void *data,
+                                     QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+    uint16_t reading;
+
+    i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION);
+
+    qmp_adc128d818_set("ain0", 1280);
+    i2c_set8(dev, REG_CONFIG, CONFIG_START);
+    g_assert_cmphex(i2c_get16(dev, REG_CH_READING_BASE), ==, 0x8000);
+
+    i2c_set8(dev, REG_CONFIG, 0x00);
+    i2c_set8(dev, REG_CH_DISABLE, 0x02);
+    reading = i2c_get16(dev, REG_CH_READING_BASE);
+    g_test_message("After CH_DISABLE write: ch0 raw 0x%04x (expect 0x0000)",
+                   reading);
+    g_assert_cmphex(reading, ==, 0x0000);
+}
+
+/* Programming Advanced Configuration always resets channel readings */
+static void test_adv_config_clears(void *obj, void *data,
+                                   QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+    uint16_t reading;
+
+    i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION);
+
+    qmp_adc128d818_set("ain0", 1280);
+    i2c_set8(dev, REG_CONFIG, CONFIG_START);
+    g_assert_cmphex(i2c_get16(dev, REG_CH_READING_BASE), ==, 0x8000);
+
+    i2c_set8(dev, REG_CONFIG, 0x00);
+    i2c_set8(dev, REG_ADV_CONFIG, 0x00);
+    reading = i2c_get16(dev, REG_CH_READING_BASE);
+    g_test_message("After same-mode ADV_CONFIG: ch0 0x%04x (expect 0x0000)",
+                   reading);
+    g_assert_cmphex(reading, ==, 0x0000);
+
+    i2c_set8(dev, REG_CONFIG, CONFIG_START);
+    g_assert_cmphex(i2c_get16(dev, REG_CH_READING_BASE), ==, 0x8000);
+    i2c_set8(dev, REG_CONFIG, 0x00);
+    qmp_adc128d818_set("ext-vref-mv", 4096);
+    i2c_set8(dev, REG_ADV_CONFIG, ADV_CONFIG_EXT_REF_EN);
+    reading = i2c_get16(dev, REG_CH_READING_BASE);
+    g_test_message("After ext-vref toggle: ch0 0x%04x (expect 0x0000)",
+                   reading);
+    g_assert_cmphex(reading, ==, 0x0000);
+}
+
+/* Conversion Rate register may only be programmed while in shutdown */
+static void test_conv_rate(void *obj, void *data, QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+
+    i2c_set8(dev, REG_CONFIG, CONFIG_INITIALIZATION);
+
+    i2c_set8(dev, REG_CONV_RATE, 0x01);
+    g_assert_cmphex(i2c_get8(dev, REG_CONV_RATE), ==, 0x01);
+
+    i2c_set8(dev, REG_CONFIG, CONFIG_START);
+    i2c_set8(dev, REG_CONV_RATE, 0x00);
+    g_test_message("CONV_RATE while running: 0x%02x (expect unchanged 0x01)",
+             i2c_get8(dev, REG_CONV_RATE));
+    g_assert_cmphex(i2c_get8(dev, REG_CONV_RATE), ==, 0x01);
+}
+
+static void adc128d818_register_nodes(void)
+{
+    QOSGraphEdgeOptions opts = {
+        .extra_device_opts = "id=" ADC128D818_TEST_ID
+                             ",address=0x1f"
+    };
+    add_qi2c_address(&opts, &(QI2CAddress) { ADC128D818_TEST_ADDR });
+
+    qos_node_create_driver("adc128d818", i2c_device_create);
+    qos_node_consumes("adc128d818", "i2c-bus", &opts);
+
+    qos_add_test("id-registers", "adc128d818", test_id_registers, NULL);
+    qos_add_test("defaults", "adc128d818", test_defaults, NULL);
+    qos_add_test("soft-reset", "adc128d818", test_soft_reset, NULL);
+    qos_add_test("ain-property", "adc128d818", test_ain_property, NULL);
+    qos_add_test("voltage-conversion", "adc128d818", test_voltage_conversion,
+                 NULL);
+    qos_add_test("temperature-conversion", "adc128d818",
+                 test_temperature_conversion, NULL);
+    qos_add_test("all-channels", "adc128d818", test_all_channels, NULL);
+    qos_add_test("voltage-edges", "adc128d818", test_voltage_edges, NULL);
+    qos_add_test("temperature-edges", "adc128d818", test_temperature_edges,
+                 NULL);
+    qos_add_test("ext-vref", "adc128d818", test_ext_vref, NULL);
+    qos_add_test("interrupt-status", "adc128d818", test_interrupt_status, NULL);
+    qos_add_test("int-clear", "adc128d818", test_int_clear, NULL);
+    qos_add_test("low-limit", "adc128d818", test_low_limit, NULL);
+    qos_add_test("temp-hysteresis", "adc128d818", test_temp_hysteresis, NULL);
+    qos_add_test("channel-disable", "adc128d818", test_channel_disable, NULL);
+    qos_add_test("one-shot", "adc128d818", test_one_shot, NULL);
+    qos_add_test("mode-selection", "adc128d818", test_mode_selection, NULL);
+    qos_add_test("mode2-diff", "adc128d818", test_mode2_diff, NULL);
+    qos_add_test("mode3-mixed", "adc128d818", test_mode3_mixed, NULL);
+    qos_add_test("mode-change-reset", "adc128d818", test_mode_change_reset,
+                 NULL);
+    qos_add_test("diff-qom-trigger", "adc128d818", test_diff_qom_trigger, NULL);
+    qos_add_test("deep-shutdown", "adc128d818", test_deep_shutdown, NULL);
+    qos_add_test("busy-status", "adc128d818", test_busy_status, NULL);
+    qos_add_test("chan-disable-clears", "adc128d818", test_chan_disable_clears,
+                 NULL);
+    qos_add_test("adv-config-clears", "adc128d818", test_adv_config_clears,
+                 NULL);
+    qos_add_test("conv-rate", "adc128d818", test_conv_rate, NULL);
+}
+libqos_init(adc128d818_register_nodes);
diff --git a/tests/qtest/aspeed-hace-utils.c b/tests/qtest/aspeed-hace-utils.c
index 25450a2..260eec0 100644
--- a/tests/qtest/aspeed-hace-utils.c
+++ b/tests/qtest/aspeed-hace-utils.c
@@ -9,6 +9,7 @@
 #include "libqtest.h"
 #include "qemu/bitops.h"
 #include "qemu/bswap.h"
+#include "crypto/cipher.h"
 #include "aspeed-hace-utils.h"
 
 /*
@@ -645,3 +646,695 @@
     qtest_quit(s);
 }
 
+/*
+ * Crypto engine register layout (offsets from the HACE base).
+ */
+#define HACE_CRYPTO_SRC          0x00
+#define HACE_CRYPTO_DEST         0x04
+#define HACE_CRYPTO_CONTEXT      0x08
+#define HACE_CRYPTO_DATA_LEN     0x0c
+#define HACE_CRYPTO_CMD          0x10
+#define HACE_CRYPTO_GCM_ADD_LEN  0x14
+#define HACE_CRYPTO_GCM_TAG      0x18
+
+/* Crypto command bits */
+#define HACE_CMD_ENCRYPT         BIT(7)
+#define HACE_CMD_ISR_EN          BIT(12)
+#define HACE_CMD_DES_SELECT      BIT(16)
+#define HACE_CMD_TRIPLE_DES      BIT(17)
+#define HACE_CMD_SRC_SG_CTRL     BIT(18)
+#define HACE_CMD_DST_SG_CTRL     BIT(19)
+#define HACE_CMD_OP_MODE_MASK    (0x7 << 4)
+#define HACE_CMD_ECB             (0x0 << 4)
+#define HACE_CMD_CBC             (0x1 << 4)
+#define HACE_CMD_CTR             (0x4 << 4)
+#define HACE_CMD_GCM             (0x5 << 4)
+#define HACE_CMD_AES128          (0x0 << 2)
+#define HACE_CMD_AES256          (0x2 << 2)
+
+/* Context buffer layout: IV (DES at +8), key at +0x10 */
+#define HACE_CTX_KEY_OFFSET      0x10
+#define HACE_CTX_SIZE            0x30
+
+/*
+ * Crypto known-answer test vectors, taken verbatim from the Linux kernel
+ * crypto self-test templates in crypto/testmgr.h:
+ *
+ *   https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/crypto/testmgr.h?h=v6.18
+ *
+ * The originating template is noted above each block. CTR and the longer CBC
+ * vectors are truncated to a single block (still a valid known-answer test as
+ * the first block only depends on the IV).
+ */
+
+/* aes_tv_template[0] (FIPS-197) */
+static const uint8_t aes128_ecb_key[16] = {
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+    0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f };
+static const uint8_t aes128_ecb_ptext[16] = {
+    0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
+    0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff };
+static const uint8_t aes128_ecb_ctext[16] = {
+    0x69, 0xc4, 0xe0, 0xd8, 0x6a, 0x7b, 0x04, 0x30,
+    0xd8, 0xcd, 0xb7, 0x80, 0x70, 0xb4, 0xc5, 0x5a };
+
+/* aes_cbc_tv_template[0] (RFC 3602) */
+static const uint8_t aes128_cbc_key[16] = {
+    0x06, 0xa9, 0x21, 0x40, 0x36, 0xb8, 0xa1, 0x5b,
+    0x51, 0x2e, 0x03, 0xd5, 0x34, 0x12, 0x00, 0x06 };
+static const uint8_t aes128_cbc_iv[16] = {
+    0x3d, 0xaf, 0xba, 0x42, 0x9d, 0x9e, 0xb4, 0x30,
+    0xb4, 0x22, 0xda, 0x80, 0x2c, 0x9f, 0xac, 0x41 };
+static const uint8_t aes128_cbc_ptext[16] = {
+    0x53, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x62,
+    0x6c, 0x6f, 0x63, 0x6b, 0x20, 0x6d, 0x73, 0x67 };
+static const uint8_t aes128_cbc_ctext[16] = {
+    0xe3, 0x53, 0x77, 0x9c, 0x10, 0x79, 0xae, 0xb8,
+    0x27, 0x08, 0x94, 0x2d, 0xbe, 0x77, 0x18, 0x1a };
+static const uint8_t aes128_cbc_ivout[16] = {
+    0xe3, 0x53, 0x77, 0x9c, 0x10, 0x79, 0xae, 0xb8,
+    0x27, 0x08, 0x94, 0x2d, 0xbe, 0x77, 0x18, 0x1a };
+
+/* des_tv_template[0] (Applied Cryptography) */
+static const uint8_t des_ecb_key[8] = {
+    0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef };
+static const uint8_t des_ecb_ptext[8] = {
+    0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xe7 };
+static const uint8_t des_ecb_ctext[8] = {
+    0xc9, 0x57, 0x44, 0x25, 0x6a, 0x5e, 0xd3, 0x1d };
+
+/* des_cbc_tv_template[0] (OpenSSL), first block */
+static const uint8_t des_cbc_key[8] = {
+    0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef };
+static const uint8_t des_cbc_iv[8] = {
+    0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10 };
+static const uint8_t des_cbc_ptext[8] = {
+    0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31, 0x20 };
+static const uint8_t des_cbc_ctext[8] = {
+    0xcc, 0xd1, 0x73, 0xff, 0xab, 0x20, 0x39, 0xf4 };
+
+/* des3_ede_tv_template[0] (OpenSSL) */
+static const uint8_t tdes_ecb_key[24] = {
+    0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,
+    0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
+    0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10 };
+static const uint8_t tdes_ecb_ptext[8] = {
+    0x73, 0x6f, 0x6d, 0x65, 0x64, 0x61, 0x74, 0x61 };
+static const uint8_t tdes_ecb_ctext[8] = {
+    0x18, 0xd7, 0x48, 0xe5, 0x63, 0x62, 0x05, 0x72 };
+
+/* des3_ede_cbc_tv_template[0] (OpenSSL), first block */
+static const uint8_t tdes_cbc_key[24] = {
+    0xe9, 0xc0, 0xff, 0x2e, 0x76, 0x0b, 0x64, 0x24,
+    0x44, 0x4d, 0x99, 0x5a, 0x12, 0xd6, 0x40, 0xc0,
+    0xea, 0xc2, 0x84, 0xe8, 0x14, 0x95, 0xdb, 0xe8 };
+static const uint8_t tdes_cbc_iv[8] = {
+    0x7d, 0x33, 0x88, 0x93, 0x0f, 0x93, 0xb2, 0x42 };
+static const uint8_t tdes_cbc_ptext[8] = {
+    0x6f, 0x54, 0x20, 0x6f, 0x61, 0x4d, 0x79, 0x6e };
+static const uint8_t tdes_cbc_ctext[8] = {
+    0x0e, 0x2d, 0xb6, 0x97, 0x3c, 0x56, 0x33, 0xf4 };
+
+/* aes_ctr_tv_template[0] (NIST SP800-38A F.5.1), first block */
+static const uint8_t aes128_ctr_key[16] = {
+    0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6,
+    0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c };
+static const uint8_t aes128_ctr_iv[16] = {
+    0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
+    0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff };
+static const uint8_t aes128_ctr_ptext[16] = {
+    0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96,
+    0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a };
+static const uint8_t aes128_ctr_ctext[16] = {
+    0x87, 0x4d, 0x61, 0x91, 0xb6, 0x20, 0xe3, 0x26,
+    0x1b, 0xef, 0x68, 0x64, 0x99, 0x0d, 0xb6, 0xce };
+static const uint8_t aes128_ctr_ivout[16] = {
+    0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
+    0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xff, 0x00 };
+
+/* des_ctr_tv_template[0] (Crypto++), first block */
+static const uint8_t des_ctr_key[8] = {
+    0xc9, 0x83, 0xa6, 0xc9, 0xec, 0x0f, 0x32, 0x55 };
+static const uint8_t des_ctr_iv[8] = {
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd };
+static const uint8_t des_ctr_ptext[8] = {
+    0x50, 0xb9, 0x22, 0xae, 0x17, 0x80, 0x0c, 0x75 };
+static const uint8_t des_ctr_ctext[8] = {
+    0x2f, 0x96, 0x06, 0x0f, 0x50, 0xc9, 0x68, 0x03 };
+static const uint8_t des_ctr_ivout[8] = {
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe };
+
+/* des3_ede_ctr_tv_template[0] (Crypto++), first block */
+static const uint8_t tdes_ctr_key[24] = {
+    0x9c, 0xd6, 0xf3, 0x9c, 0xb9, 0x5a, 0x67, 0x00,
+    0x5a, 0x67, 0x00, 0x2d, 0xce, 0xeb, 0x2d, 0xce,
+    0xeb, 0xb4, 0x51, 0x72, 0xb4, 0x51, 0x72, 0x1f };
+static const uint8_t tdes_ctr_iv[8] = {
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
+static const uint8_t tdes_ctr_ptext[8] = {
+    0x05, 0xec, 0x77, 0xfb, 0x42, 0xd5, 0x59, 0x20 };
+static const uint8_t tdes_ctr_ctext[8] = {
+    0x07, 0xc2, 0x08, 0x20, 0x72, 0x1f, 0x49, 0xef };
+static const uint8_t tdes_ctr_ivout[8] = {
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+/*
+ * aes_gcm_tv_template[2] (AES-128) and [9] (AES-256), from the McGrew & Viega
+ * GCM spec (also NIST SP 800-38D), no AAD. Both cases share this plaintext/IV.
+ */
+static const uint8_t aes_gcm_ptext[64] = {
+    0xd9, 0x31, 0x32, 0x25, 0xf8, 0x84, 0x06, 0xe5,
+    0xa5, 0x59, 0x09, 0xc5, 0xaf, 0xf5, 0x26, 0x9a,
+    0x86, 0xa7, 0xa9, 0x53, 0x15, 0x34, 0xf7, 0xda,
+    0x2e, 0x4c, 0x30, 0x3d, 0x8a, 0x31, 0x8a, 0x72,
+    0x1c, 0x3c, 0x0c, 0x95, 0x95, 0x68, 0x09, 0x53,
+    0x2f, 0xcf, 0x0e, 0x24, 0x49, 0xa6, 0xb5, 0x25,
+    0xb1, 0x6a, 0xed, 0xf5, 0xaa, 0x0d, 0xe6, 0x57,
+    0xba, 0x63, 0x7b, 0x39, 0x1a, 0xaf, 0xd2, 0x55 };
+static const uint8_t aes_gcm_iv[12] = {
+    0xca, 0xfe, 0xba, 0xbe, 0xfa, 0xce, 0xdb, 0xad,
+    0xde, 0xca, 0xf8, 0x88 };
+
+/* aes_gcm_tv_template[2] (AES-128) */
+static const uint8_t aes128_gcm_key[16] = {
+    0xfe, 0xff, 0xe9, 0x92, 0x86, 0x65, 0x73, 0x1c,
+    0x6d, 0x6a, 0x8f, 0x94, 0x67, 0x30, 0x83, 0x08 };
+static const uint8_t aes128_gcm_ctext[64] = {
+    0x42, 0x83, 0x1e, 0xc2, 0x21, 0x77, 0x74, 0x24,
+    0x4b, 0x72, 0x21, 0xb7, 0x84, 0xd0, 0xd4, 0x9c,
+    0xe3, 0xaa, 0x21, 0x2f, 0x2c, 0x02, 0xa4, 0xe0,
+    0x35, 0xc1, 0x7e, 0x23, 0x29, 0xac, 0xa1, 0x2e,
+    0x21, 0xd5, 0x14, 0xb2, 0x54, 0x66, 0x93, 0x1c,
+    0x7d, 0x8f, 0x6a, 0x5a, 0xac, 0x84, 0xaa, 0x05,
+    0x1b, 0xa3, 0x0b, 0x39, 0x6a, 0x0a, 0xac, 0x97,
+    0x3d, 0x58, 0xe0, 0x91, 0x47, 0x3f, 0x59, 0x85 };
+static const uint8_t aes128_gcm_tag[16] = {
+    0x4d, 0x5c, 0x2a, 0xf3, 0x27, 0xcd, 0x64, 0xa6,
+    0x2c, 0xf3, 0x5a, 0xbd, 0x2b, 0xa6, 0xfa, 0xb4 };
+
+/* aes_gcm_tv_template[9] (AES-256) */
+static const uint8_t aes256_gcm_key[32] = {
+    0xfe, 0xff, 0xe9, 0x92, 0x86, 0x65, 0x73, 0x1c,
+    0x6d, 0x6a, 0x8f, 0x94, 0x67, 0x30, 0x83, 0x08,
+    0xfe, 0xff, 0xe9, 0x92, 0x86, 0x65, 0x73, 0x1c,
+    0x6d, 0x6a, 0x8f, 0x94, 0x67, 0x30, 0x83, 0x08 };
+static const uint8_t aes256_gcm_ctext[64] = {
+    0x52, 0x2d, 0xc1, 0xf0, 0x99, 0x56, 0x7d, 0x07,
+    0xf4, 0x7f, 0x37, 0xa3, 0x2a, 0x84, 0x42, 0x7d,
+    0x64, 0x3a, 0x8c, 0xdc, 0xbf, 0xe5, 0xc0, 0xc9,
+    0x75, 0x98, 0xa2, 0xbd, 0x25, 0x55, 0xd1, 0xaa,
+    0x8c, 0xb0, 0x8e, 0x48, 0x59, 0x0d, 0xbb, 0x3d,
+    0xa7, 0xb0, 0x8b, 0x10, 0x56, 0x82, 0x88, 0x38,
+    0xc5, 0xf6, 0x1e, 0x63, 0x93, 0xba, 0x7a, 0x0a,
+    0xbc, 0xc9, 0xf6, 0x62, 0x89, 0x80, 0x15, 0xad };
+static const uint8_t aes256_gcm_tag[16] = {
+    0xb0, 0x94, 0xda, 0xc5, 0xd9, 0x34, 0x71, 0xbd,
+    0xec, 0x1a, 0x50, 0x22, 0x70, 0xe3, 0xcc, 0x6c };
+
+typedef struct CryptTest {
+    QCryptoCipherMode mode;
+    QCryptoCipherAlgo alg;
+    /* expected context IV after encrypt, or NULL */
+    const uint8_t *iv_out;
+    const uint8_t *ptext;
+    const uint8_t *ctext;
+    /* expected GCM authentication tag, or NULL for non-AEAD modes */
+    const uint8_t *tag;
+    const uint8_t *key;
+    const uint8_t *iv;
+    const char *name;
+    size_t keylen;
+    size_t taglen;
+    /* algorithm | mode | key size selection */
+    uint32_t cmd;
+    size_t ivlen;
+    size_t len;
+} CryptTest;
+
+static const CryptTest crypt_tests[] = {
+    {
+        .name = "aes128-ecb",
+        .cmd = HACE_CMD_AES128 | HACE_CMD_ECB,
+        .alg = QCRYPTO_CIPHER_ALGO_AES_128,
+        .mode = QCRYPTO_CIPHER_MODE_ECB,
+        .key = aes128_ecb_key,
+        .keylen = sizeof(aes128_ecb_key),
+        .ptext = aes128_ecb_ptext,
+        .ctext = aes128_ecb_ctext,
+        .len = sizeof(aes128_ecb_ptext),
+    },
+    {
+        .name = "aes128-cbc",
+        .cmd = HACE_CMD_AES128 | HACE_CMD_CBC,
+        .alg = QCRYPTO_CIPHER_ALGO_AES_128,
+        .mode = QCRYPTO_CIPHER_MODE_CBC,
+        .key = aes128_cbc_key,
+        .keylen = sizeof(aes128_cbc_key),
+        .iv = aes128_cbc_iv,
+        .ivlen = sizeof(aes128_cbc_iv),
+        .ptext = aes128_cbc_ptext,
+        .ctext = aes128_cbc_ctext,
+        .iv_out = aes128_cbc_ivout,
+        .len = sizeof(aes128_cbc_ptext),
+    },
+    {
+        .name = "des-ecb",
+        .cmd = HACE_CMD_DES_SELECT | HACE_CMD_ECB,
+        .alg = QCRYPTO_CIPHER_ALGO_DES,
+        .mode = QCRYPTO_CIPHER_MODE_ECB,
+        .key = des_ecb_key,
+        .keylen = sizeof(des_ecb_key),
+        .ptext = des_ecb_ptext,
+        .ctext = des_ecb_ctext,
+        .len = sizeof(des_ecb_ptext),
+    },
+    {
+        .name = "des-cbc",
+        .cmd = HACE_CMD_DES_SELECT | HACE_CMD_CBC,
+        .alg = QCRYPTO_CIPHER_ALGO_DES,
+        .mode = QCRYPTO_CIPHER_MODE_CBC,
+        .key = des_cbc_key,
+        .keylen = sizeof(des_cbc_key),
+        .iv = des_cbc_iv,
+        .ivlen = sizeof(des_cbc_iv),
+        .ptext = des_cbc_ptext,
+        .ctext = des_cbc_ctext,
+        .len = sizeof(des_cbc_ptext),
+    },
+    {
+        .name = "des3_ede-ecb",
+        .cmd = HACE_CMD_DES_SELECT | HACE_CMD_TRIPLE_DES | HACE_CMD_ECB,
+        .alg = QCRYPTO_CIPHER_ALGO_3DES,
+        .mode = QCRYPTO_CIPHER_MODE_ECB,
+        .key = tdes_ecb_key,
+        .keylen = sizeof(tdes_ecb_key),
+        .ptext = tdes_ecb_ptext,
+        .ctext = tdes_ecb_ctext,
+        .len = sizeof(tdes_ecb_ptext),
+    },
+    {
+        .name = "des3_ede-cbc",
+        .cmd = HACE_CMD_DES_SELECT | HACE_CMD_TRIPLE_DES | HACE_CMD_CBC,
+        .alg = QCRYPTO_CIPHER_ALGO_3DES,
+        .mode = QCRYPTO_CIPHER_MODE_CBC,
+        .key = tdes_cbc_key,
+        .keylen = sizeof(tdes_cbc_key),
+        .iv = tdes_cbc_iv,
+        .ivlen = sizeof(tdes_cbc_iv),
+        .ptext = tdes_cbc_ptext,
+        .ctext = tdes_cbc_ctext,
+        .len = sizeof(tdes_cbc_ptext),
+    },
+    {
+        .name = "aes128-ctr",
+        .cmd = HACE_CMD_AES128 | HACE_CMD_CTR,
+        .alg = QCRYPTO_CIPHER_ALGO_AES_128,
+        .mode = QCRYPTO_CIPHER_MODE_CTR,
+        .key = aes128_ctr_key,
+        .keylen = sizeof(aes128_ctr_key),
+        .iv = aes128_ctr_iv,
+        .ivlen = sizeof(aes128_ctr_iv),
+        .ptext = aes128_ctr_ptext,
+        .ctext = aes128_ctr_ctext,
+        .iv_out = aes128_ctr_ivout,
+        .len = sizeof(aes128_ctr_ptext),
+    },
+    {
+        .name = "des-ctr",
+        .cmd = HACE_CMD_DES_SELECT | HACE_CMD_CTR,
+        .alg = QCRYPTO_CIPHER_ALGO_DES,
+        .mode = QCRYPTO_CIPHER_MODE_CTR,
+        .key = des_ctr_key,
+        .keylen = sizeof(des_ctr_key),
+        .iv = des_ctr_iv,
+        .ivlen = sizeof(des_ctr_iv),
+        .ptext = des_ctr_ptext,
+        .ctext = des_ctr_ctext,
+        .iv_out = des_ctr_ivout,
+        .len = sizeof(des_ctr_ptext),
+    },
+    {
+        .name = "des3_ede-ctr",
+        .cmd = HACE_CMD_DES_SELECT | HACE_CMD_TRIPLE_DES | HACE_CMD_CTR,
+        .alg = QCRYPTO_CIPHER_ALGO_3DES,
+        .mode = QCRYPTO_CIPHER_MODE_CTR,
+        .key = tdes_ctr_key,
+        .keylen = sizeof(tdes_ctr_key),
+        .iv = tdes_ctr_iv,
+        .ivlen = sizeof(tdes_ctr_iv),
+        .ptext = tdes_ctr_ptext,
+        .ctext = tdes_ctr_ctext,
+        .iv_out = tdes_ctr_ivout,
+        .len = sizeof(tdes_ctr_ptext),
+    },
+    {
+        .name = "aes128-gcm",
+        .cmd = HACE_CMD_AES128 | HACE_CMD_GCM,
+        .alg = QCRYPTO_CIPHER_ALGO_AES_128,
+        .mode = QCRYPTO_CIPHER_MODE_GCM,
+        .key = aes128_gcm_key,
+        .keylen = sizeof(aes128_gcm_key),
+        .iv = aes_gcm_iv,
+        .ivlen = sizeof(aes_gcm_iv),
+        .ptext = aes_gcm_ptext,
+        .ctext = aes128_gcm_ctext,
+        .tag = aes128_gcm_tag,
+        .taglen = sizeof(aes128_gcm_tag),
+        .len = sizeof(aes_gcm_ptext),
+    },
+    {
+        .name = "aes256-gcm",
+        .cmd = HACE_CMD_AES256 | HACE_CMD_GCM,
+        .alg = QCRYPTO_CIPHER_ALGO_AES_256,
+        .mode = QCRYPTO_CIPHER_MODE_GCM,
+        .key = aes256_gcm_key,
+        .keylen = sizeof(aes256_gcm_key),
+        .iv = aes_gcm_iv,
+        .ivlen = sizeof(aes_gcm_iv),
+        .ptext = aes_gcm_ptext,
+        .ctext = aes256_gcm_ctext,
+        .tag = aes256_gcm_tag,
+        .taglen = sizeof(aes256_gcm_tag),
+        .len = sizeof(aes_gcm_ptext),
+    },
+};
+
+/* DRAM offsets for the crypto test source, destination and context buffers. */
+#define CRYPT_OFF_SRC   0x10000
+#define CRYPT_OFF_DST   0x20000
+#define CRYPT_OFF_CTX   0x30000
+/* Scatter-gather list offsets (each list has CRYPT_SG_FRAGS entries). */
+#define CRYPT_OFF_SRC_SG  0x40000
+#define CRYPT_OFF_DST_SG  0x50000
+/*
+ * The scatter-gather tests split each buffer into CRYPT_SG_FRAGS fragments,
+ * each placed CRYPT_SG_FRAG_STRIDE apart so the fragments never abut. The gaps
+ * make the test fail if the engine ignores the list and reads one contiguous
+ * block.
+ */
+#define CRYPT_SG_FRAGS         3
+#define CRYPT_SG_FRAG_STRIDE   0x1000
+/* DRAM offset for the AES-GCM authentication tag write buffer. */
+#define CRYPT_OFF_TAG          0x60000
+
+/* Describes one registered crypto test (qtest_add_data_func() data pointer). */
+typedef struct AspeedCryptoTest {
+    const char *machine;
+    uint64_t dram;
+    uint32_t base;
+    int index;
+    bool sg;
+} AspeedCryptoTest;
+
+/* Map a command's operation mode (HACE10[6:4]) to a CRYPT_MODE_* flag. */
+static uint32_t crypt_mode_flag(uint32_t cmd)
+{
+    switch (cmd & HACE_CMD_OP_MODE_MASK) {
+    case HACE_CMD_ECB:
+        return CRYPT_MODE_ECB;
+    case HACE_CMD_CBC:
+        return CRYPT_MODE_CBC;
+    case HACE_CMD_CTR:
+        return CRYPT_MODE_CTR;
+    case HACE_CMD_GCM:
+        return CRYPT_MODE_GCM;
+    default:
+        return 0;
+    }
+}
+
+static void crypt_write_ctx(QTestState *s, uint64_t ctx_addr,
+                            const CryptTest *t)
+{
+    size_t iv_off = (t->cmd & HACE_CMD_DES_SELECT) ? 8 : 0;
+    uint8_t ctx[HACE_CTX_SIZE] = { 0 };
+
+    if (t->iv) {
+        memcpy(ctx + iv_off, t->iv, t->ivlen);
+    }
+    memcpy(ctx + HACE_CTX_KEY_OFFSET, t->key, t->keylen);
+    qtest_memwrite(s, ctx_addr, ctx, sizeof(ctx));
+}
+
+/* Run one crypto operation in direct access mode and read back the result. */
+static void crypt_run_direct(QTestState *s, uint32_t base, uint64_t dram,
+                             const CryptTest *t, bool encrypt, uint8_t *out)
+{
+    const uint8_t *in = encrypt ? t->ptext : t->ctext;
+    uint32_t cmd = t->cmd | HACE_CMD_ISR_EN;
+    uint64_t src = dram + CRYPT_OFF_SRC;
+    uint64_t dst = dram + CRYPT_OFF_DST;
+    uint64_t ctx = dram + CRYPT_OFF_CTX;
+
+    if (encrypt) {
+        cmd |= HACE_CMD_ENCRYPT;
+    }
+
+    crypt_write_ctx(s, ctx, t);
+    qtest_memwrite(s, src, in, t->len);
+
+    qtest_writel(s, base + HACE_CRYPTO_SRC, (uint32_t)src);
+    qtest_writel(s, base + HACE_CRYPTO_DEST, (uint32_t)dst);
+    qtest_writel(s, base + HACE_CRYPTO_CONTEXT, (uint32_t)ctx);
+    qtest_writel(s, base + HACE_CRYPTO_DATA_LEN, t->len);
+    qtest_writel(s, base + HACE_CRYPTO_CMD, cmd);
+
+    g_assert_cmphex(qtest_readl(s, base + HACE_STS) & HACE_CRYPTO_ISR, ==,
+                    HACE_CRYPTO_ISR);
+    qtest_writel(s, base + HACE_STS, HACE_CRYPTO_ISR);
+
+    qtest_memread(s, dst, out, t->len);
+}
+
+/*
+ * Byte range [*frag_off, *frag_off + *frag_len) of fragment @index when an
+ * @len-byte buffer is split into CRYPT_SG_FRAGS pieces; the last piece takes
+ * the remainder of an uneven split.
+ */
+static void crypt_frag_range(uint32_t len, int index,
+                             uint32_t *frag_off, uint32_t *frag_len)
+{
+    uint32_t base = len / CRYPT_SG_FRAGS;
+
+    *frag_off = base * index;
+    *frag_len = (index == CRYPT_SG_FRAGS - 1) ? len - *frag_off : base;
+}
+
+/*
+ * Scatter [in, len) across CRYPT_SG_FRAGS buffers based at @base_off and spaced
+ * CRYPT_SG_FRAG_STRIDE apart, then build the SG list describing them at @list.
+ * When @in is NULL only the list is built (used for the destination, which the
+ * engine fills in).
+ */
+static void crypt_make_sg(QTestState *s, uint64_t dram, uint32_t base_off,
+                          uint64_t list, const uint8_t *in, uint32_t len)
+{
+    struct AspeedSgList sg[CRYPT_SG_FRAGS];
+    uint32_t frag_off;
+    uint32_t frag_len;
+    uint64_t buf;
+    int i;
+
+    for (i = 0; i < CRYPT_SG_FRAGS; i++) {
+        crypt_frag_range(len, i, &frag_off, &frag_len);
+        buf = dram + base_off + i * CRYPT_SG_FRAG_STRIDE;
+
+        if (in) {
+            qtest_memwrite(s, buf, in + frag_off, frag_len);
+        }
+        sg[i].len = cpu_to_le32(frag_len | (i == CRYPT_SG_FRAGS - 1 ?
+                                            SG_LIST_LEN_LAST : 0));
+        sg[i].addr = cpu_to_le32((uint32_t)buf);
+    }
+
+    qtest_memwrite(s, list, sg, sizeof(sg));
+}
+
+/* Gather a scatter-gathered result back from the CRYPT_SG_FRAGS buffers. */
+static void crypt_gather_sg(QTestState *s, uint64_t dram, uint32_t base_off,
+                            uint8_t *out, uint32_t len)
+{
+    uint32_t frag_off;
+    uint32_t frag_len;
+    int i;
+
+    for (i = 0; i < CRYPT_SG_FRAGS; i++) {
+        crypt_frag_range(len, i, &frag_off, &frag_len);
+        qtest_memread(s, dram + base_off + i * CRYPT_SG_FRAG_STRIDE,
+                      out + frag_off, frag_len);
+    }
+}
+
+/*
+ * Run one block-cipher (ECB/CBC/CTR) operation in scatter-gather mode and read
+ * back the result. The source and destination are each split across
+ * CRYPT_SG_FRAGS non-adjacent DRAM buffers described by an SG list; the gaps
+ * ensure the test fails if the engine ignores the list and reads one
+ * contiguous block.
+ */
+static void crypt_run_sg(QTestState *s, uint32_t base, uint64_t dram,
+                         const CryptTest *t, bool encrypt, uint8_t *out)
+{
+    const uint8_t *in = encrypt ? t->ptext : t->ctext;
+    uint64_t src_sg = dram + CRYPT_OFF_SRC_SG;
+    uint64_t dst_sg = dram + CRYPT_OFF_DST_SG;
+    uint64_t ctx = dram + CRYPT_OFF_CTX;
+    uint32_t cmd = t->cmd | HACE_CMD_ISR_EN | HACE_CMD_SRC_SG_CTRL |
+                   HACE_CMD_DST_SG_CTRL;
+
+    if (encrypt) {
+        cmd |= HACE_CMD_ENCRYPT;
+    }
+
+    crypt_write_ctx(s, ctx, t);
+    crypt_make_sg(s, dram, CRYPT_OFF_SRC, src_sg, in, t->len);
+    crypt_make_sg(s, dram, CRYPT_OFF_DST, dst_sg, NULL, t->len);
+
+    qtest_writel(s, base + HACE_CRYPTO_SRC, (uint32_t)src_sg);
+    qtest_writel(s, base + HACE_CRYPTO_DEST, (uint32_t)dst_sg);
+    qtest_writel(s, base + HACE_CRYPTO_CONTEXT, (uint32_t)ctx);
+    qtest_writel(s, base + HACE_CRYPTO_DATA_LEN, t->len);
+    qtest_writel(s, base + HACE_CRYPTO_CMD, cmd);
+
+    g_assert_cmphex(qtest_readl(s, base + HACE_STS) & HACE_CRYPTO_ISR, ==,
+                    HACE_CRYPTO_ISR);
+    qtest_writel(s, base + HACE_STS, HACE_CRYPTO_ISR);
+
+    crypt_gather_sg(s, dram, CRYPT_OFF_DST, out, t->len);
+}
+
+/*
+ * Run one AES-GCM operation in scatter-gather mode: like crypt_run_sg() but
+ * also program the tag write buffer (HACE18) with no associated data, and read
+ * the authentication tag back into @out_tag.
+ */
+static void crypt_run_gcm(QTestState *s, uint32_t base, uint64_t dram,
+                          const CryptTest *t, bool encrypt, uint8_t *out,
+                          uint8_t *out_tag)
+{
+    const uint8_t *in = encrypt ? t->ptext : t->ctext;
+    uint64_t src_sg = dram + CRYPT_OFF_SRC_SG;
+    uint64_t dst_sg = dram + CRYPT_OFF_DST_SG;
+    uint64_t ctx = dram + CRYPT_OFF_CTX;
+    uint32_t cmd = t->cmd | HACE_CMD_ISR_EN | HACE_CMD_SRC_SG_CTRL |
+                   HACE_CMD_DST_SG_CTRL;
+
+    if (encrypt) {
+        cmd |= HACE_CMD_ENCRYPT;
+    }
+
+    crypt_write_ctx(s, ctx, t);
+    crypt_make_sg(s, dram, CRYPT_OFF_SRC, src_sg, in, t->len);
+    crypt_make_sg(s, dram, CRYPT_OFF_DST, dst_sg, NULL, t->len);
+
+    qtest_writel(s, base + HACE_CRYPTO_SRC, (uint32_t)src_sg);
+    qtest_writel(s, base + HACE_CRYPTO_DEST, (uint32_t)dst_sg);
+    qtest_writel(s, base + HACE_CRYPTO_CONTEXT, (uint32_t)ctx);
+    qtest_writel(s, base + HACE_CRYPTO_DATA_LEN, t->len);
+    qtest_writel(s, base + HACE_CRYPTO_GCM_ADD_LEN, 0);
+    qtest_writel(s, base + HACE_CRYPTO_GCM_TAG,
+                 (uint32_t)(dram + CRYPT_OFF_TAG));
+    qtest_writel(s, base + HACE_CRYPTO_CMD, cmd);
+
+    g_assert_cmphex(qtest_readl(s, base + HACE_STS) & HACE_CRYPTO_ISR, ==,
+                    HACE_CRYPTO_ISR);
+    qtest_writel(s, base + HACE_STS, HACE_CRYPTO_ISR);
+
+    crypt_gather_sg(s, dram, CRYPT_OFF_DST, out, t->len);
+    qtest_memread(s, dram + CRYPT_OFF_TAG, out_tag, t->taglen);
+}
+
+static void aspeed_test_crypto(const void *data)
+{
+    const AspeedCryptoTest *c = data;
+    const CryptTest *t = &crypt_tests[c->index];
+    QTestState *s = qtest_init(c->machine);
+    uint8_t out[64];
+    uint8_t iv[16];
+    size_t iv_off;
+
+    g_assert_cmpuint(t->len, <=, sizeof(out));
+
+    /* Encrypt: ptext -> ctext */
+    if (c->sg) {
+        crypt_run_sg(s, c->base, c->dram, t, true, out);
+    } else {
+        crypt_run_direct(s, c->base, c->dram, t, true, out);
+    }
+    g_assert_cmpmem(out, t->len, t->ctext, t->len);
+
+    if (t->iv_out) {
+        iv_off = (t->cmd & HACE_CMD_DES_SELECT) ? 8 : 0;
+        qtest_memread(s, c->dram + CRYPT_OFF_CTX + iv_off, iv, t->ivlen);
+        g_assert_cmpmem(iv, t->ivlen, t->iv_out, t->ivlen);
+    }
+
+    /* Decrypt: ctext -> ptext */
+    if (c->sg) {
+        crypt_run_sg(s, c->base, c->dram, t, false, out);
+    } else {
+        crypt_run_direct(s, c->base, c->dram, t, false, out);
+    }
+    g_assert_cmpmem(out, t->len, t->ptext, t->len);
+
+    qtest_quit(s);
+}
+
+static void aspeed_test_crypto_gcm(const void *data)
+{
+    const AspeedCryptoTest *c = data;
+    const CryptTest *t = &crypt_tests[c->index];
+    QTestState *s = qtest_init(c->machine);
+    uint8_t out[64];
+    uint8_t tag[16];
+
+    g_assert_cmpuint(t->len, <=, sizeof(out));
+
+    /* Encrypt: ptext -> ctext, then check the authentication tag. */
+    crypt_run_gcm(s, c->base, c->dram, t, true, out, tag);
+    g_assert_cmpmem(out, t->len, t->ctext, t->len);
+    g_assert_cmpmem(tag, t->taglen, t->tag, t->taglen);
+
+    /* Decrypt: ctext -> ptext, the recomputed tag must match. */
+    crypt_run_gcm(s, c->base, c->dram, t, false, out, tag);
+    g_assert_cmpmem(out, t->len, t->ptext, t->len);
+    g_assert_cmpmem(tag, t->taglen, t->tag, t->taglen);
+
+    qtest_quit(s);
+}
+
+void aspeed_add_crypto_tests(const char *prefix, const char *machine,
+                             uint32_t base, uint64_t dram, uint32_t modes,
+                             bool sg)
+{
+    int i;
+
+    for (i = 0; i < ARRAY_SIZE(crypt_tests); i++) {
+        bool is_gcm = crypt_tests[i].mode == QCRYPTO_CIPHER_MODE_GCM;
+        g_autofree char *path = NULL;
+        AspeedCryptoTest *t;
+
+        if (!(modes & crypt_mode_flag(crypt_tests[i].cmd))) {
+            continue;
+        }
+
+        if (!qcrypto_cipher_supports(crypt_tests[i].alg,
+                                     crypt_tests[i].mode)) {
+            g_printerr("# skip unsupported %s\n", crypt_tests[i].name);
+            continue;
+        }
+
+        path = g_strdup_printf("%s/hace/crypto/%s", prefix,
+                               crypt_tests[i].name);
+        t = g_new0(AspeedCryptoTest, 1);
+        t->machine = machine;
+        t->base = base;
+        t->dram = dram;
+        t->index = i;
+        t->sg = sg;
+        qtest_add_data_func_full(path, t,
+                                 is_gcm ? aspeed_test_crypto_gcm :
+                                 aspeed_test_crypto, g_free);
+    }
+}
+
diff --git a/tests/qtest/aspeed-hace-utils.h b/tests/qtest/aspeed-hace-utils.h
index 27ab2bb..a5601a3 100644
--- a/tests/qtest/aspeed-hace-utils.h
+++ b/tests/qtest/aspeed-hace-utils.h
@@ -79,5 +79,25 @@
 void aspeed_test_addresses(const char *machine, const uint32_t base,
                            const struct AspeedMasks *expected);
 
+/*
+ * Cipher modes a SoC's crypto engine supports, for aspeed_add_crypto_tests().
+ */
+enum {
+    CRYPT_MODE_ECB = 1 << 0,
+    CRYPT_MODE_CBC = 1 << 1,
+    CRYPT_MODE_CTR = 1 << 2,
+    CRYPT_MODE_GCM = 1 << 3,
+};
+
+/*
+ * Register the crypto known-answer tests that @modes selects (a mask of
+ * CRYPT_MODE_*) for the given machine. Each test is named
+ * "<prefix>/hace/crypto/<mode>". @sg selects scatter-gather mode (used by the
+ * AST2600 and later) instead of the AST2500 direct access mode.
+ */
+void aspeed_add_crypto_tests(const char *prefix, const char *machine,
+                             uint32_t base, uint64_t dram, uint32_t modes,
+                             bool sg);
+
 #endif /* TESTS_ASPEED_HACE_UTILS_H */
 
diff --git a/tests/qtest/aspeed-smc-utils.c b/tests/qtest/aspeed-smc-utils.c
index c27d09e..1463322 100644
--- a/tests/qtest/aspeed-smc-utils.c
+++ b/tests/qtest/aspeed-smc-utils.c
@@ -4,23 +4,7 @@
  *
  * Copyright (C) 2016 IBM Corp.
  *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
+ * SPDX-License-Identifier: MIT
  */
 
 #include "qemu/osdep.h"
@@ -73,6 +57,28 @@
     return qtest_readl(data->s, data->flash_base + offset);
 }
 
+/*
+ * Data FIFO port, in spi_base's register bank (not flash_base). Accesses
+ * through the FIFO require the complete user-mode transaction (opcode,
+ * address, and data). Assumes CS0, whose FIFO slot is at R_DATA_FIFO.
+ */
+static inline void datafifo_writeb(const AspeedSMCTestData *data,
+                                   uint8_t value)
+{
+    qtest_writeb(data->s, data->spi_base + R_DATA_FIFO, value);
+}
+
+static inline void datafifo_writel(const AspeedSMCTestData *data,
+                                   uint32_t value)
+{
+    spi_writel(data, R_DATA_FIFO, value);
+}
+
+static inline uint32_t datafifo_readl(const AspeedSMCTestData *data)
+{
+    return spi_readl(data, R_DATA_FIFO);
+}
+
 static void spi_conf(const AspeedSMCTestData *data, uint32_t value)
 {
     uint32_t conf = spi_readl(data, R_CONF);
@@ -107,6 +113,29 @@
     spi_writel(data, ctrl_reg, ctrl);
 }
 
+/* Set FREADMODE with a fast read command and 1 dummy byte */
+static void spi_ctrl_set_fast_read(const AspeedSMCTestData *data, uint8_t cmd)
+{
+    uint32_t ctrl_reg = R_CTRL0 + data->cs * 4;
+    uint32_t ctrl = spi_readl(data, ctrl_reg);
+    uint32_t iomode = 0;
+
+    if (cmd == DOR) {
+        iomode = CTRL_IO_DUAL_DATA;
+    } else if (cmd == QOR) {
+        iomode = CTRL_IO_QUAD_DATA;
+    }
+
+    ctrl &= ~(CTRL_USERMODE | (0xff << 16) |
+              (0x3 << CTRL_DUMMY_LOW_SHIFT) |
+              (0x1 << CTRL_DUMMY_HIGH_SHIFT) |
+              CTRL_IO_MODE_MASK);
+    ctrl |= CTRL_FREADMODE | (cmd << 16) |
+            (1 << CTRL_DUMMY_LOW_SHIFT) |
+            iomode;
+    spi_writel(data, ctrl_reg, ctrl);
+}
+
 static void spi_ctrl_start_user(const AspeedSMCTestData *data)
 {
     uint32_t ctrl_reg = R_CTRL0 + data->cs * 4;
@@ -186,6 +215,9 @@
     }
 }
 
+typedef void (*read_page_mem_fn)(const AspeedSMCTestData *data,
+                                 uint32_t addr, uint32_t *page);
+
 static void write_page_mem(const AspeedSMCTestData *data, uint32_t addr,
                            uint32_t write_value)
 {
@@ -327,9 +359,9 @@
     flash_reset(test_data);
 }
 
-void aspeed_smc_test_write_page(const void *data)
+static void test_write_page(const AspeedSMCTestData *test_data,
+                            read_page_mem_fn reader)
 {
-    const AspeedSMCTestData *test_data = (const AspeedSMCTestData *)data;
     uint32_t my_page_addr = test_data->page_addr;
     uint32_t some_page_addr = my_page_addr + FLASH_PAGE_SIZE;
     uint32_t page[FLASH_PAGE_SIZE / 4];
@@ -350,13 +382,13 @@
     spi_ctrl_stop_user(test_data);
 
     /* Check what was written */
-    read_page(test_data, my_page_addr, page);
+    reader(test_data, my_page_addr, page);
     for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) {
         g_assert_cmphex(page[i], ==, my_page_addr + i * 4);
     }
 
     /* Check some other page. It should be full of 0xff */
-    read_page(test_data, some_page_addr, page);
+    reader(test_data, some_page_addr, page);
     for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) {
         g_assert_cmphex(page[i], ==, 0xffffffff);
     }
@@ -364,9 +396,14 @@
     flash_reset(test_data);
 }
 
-void aspeed_smc_test_read_page_mem(const void *data)
+void aspeed_smc_test_write_page(const void *data)
 {
-    const AspeedSMCTestData *test_data = (const AspeedSMCTestData *)data;
+    test_write_page(data, read_page);
+}
+
+static void test_read_page_mem(const AspeedSMCTestData *test_data,
+                               read_page_mem_fn reader)
+{
     uint32_t my_page_addr = test_data->page_addr;
     uint32_t some_page_addr = my_page_addr + FLASH_PAGE_SIZE;
     uint32_t page[FLASH_PAGE_SIZE / 4];
@@ -393,13 +430,13 @@
     spi_conf_remove(test_data, 1 << (CONF_ENABLE_W0 + test_data->cs));
 
     /* Check what was written */
-    read_page_mem(test_data, my_page_addr, page);
+    reader(test_data, my_page_addr, page);
     for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) {
         g_assert_cmphex(page[i], ==, my_page_addr + i * 4);
     }
 
     /* Check some other page. It should be full of 0xff */
-    read_page_mem(test_data, some_page_addr, page);
+    reader(test_data, some_page_addr, page);
     for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) {
         g_assert_cmphex(page[i], ==, 0xffffffff);
     }
@@ -407,6 +444,11 @@
     flash_reset(test_data);
 }
 
+void aspeed_smc_test_read_page_mem(const void *data)
+{
+    test_read_page_mem(data, read_page_mem);
+}
+
 void aspeed_smc_test_write_page_mem(const void *data)
 {
     const AspeedSMCTestData *test_data = (const AspeedSMCTestData *)data;
@@ -684,3 +726,205 @@
     flash_reset(test_data);
 }
 
+static void read_page_mem_fast_read(const AspeedSMCTestData *data,
+                                uint32_t addr, uint32_t *page)
+{
+    int i;
+
+    spi_ctrl_set_fast_read(data, FAST_READ);
+
+    for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) {
+        page[i] = make_be32(flash_readl(data, addr + i * 4));
+    }
+}
+
+void aspeed_smc_test_read_page_mem_fast_read(const void *data)
+{
+    test_read_page_mem(data, read_page_mem_fast_read);
+}
+
+static void read_page_fast_read(const AspeedSMCTestData *data,
+                                uint32_t addr, uint32_t *page)
+{
+    int i;
+
+    spi_ctrl_start_user(data);
+
+    flash_writeb(data, 0, EN_4BYTE_ADDR);
+    flash_writeb(data, 0, FAST_READ);
+    flash_writel(data, 0, make_be32(addr));
+    /* 1 dummy byte for standard SPI fast-read */
+    flash_writeb(data, 0, 0x00);
+
+    for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) {
+        page[i] = make_be32(flash_readl(data, 0));
+    }
+    spi_ctrl_stop_user(data);
+}
+
+void aspeed_smc_test_write_page_fast_read(const void *data)
+{
+    test_write_page(data, read_page_fast_read);
+}
+
+static void read_page_mem_dor(const AspeedSMCTestData *data,
+                              uint32_t addr, uint32_t *page)
+{
+    int i;
+
+    spi_ctrl_set_fast_read(data, DOR);
+
+    for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) {
+        page[i] = make_be32(flash_readl(data, addr + i * 4));
+    }
+}
+
+void aspeed_smc_test_read_page_mem_dor(const void *data)
+{
+    test_read_page_mem(data, read_page_mem_dor);
+}
+
+static void read_page_dor(const AspeedSMCTestData *data,
+                          uint32_t addr, uint32_t *page)
+{
+    int i;
+
+    spi_ctrl_start_user(data);
+
+    flash_writeb(data, 0, EN_4BYTE_ADDR);
+    flash_writeb(data, 0, DOR);
+    flash_writel(data, 0, make_be32(addr));
+    /* 1 dummy byte for standard SPI DOR */
+    flash_writeb(data, 0, 0x00);
+
+    for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) {
+        page[i] = make_be32(flash_readl(data, 0));
+    }
+    spi_ctrl_stop_user(data);
+}
+
+void aspeed_smc_test_write_page_dor(const void *data)
+{
+    test_write_page(data, read_page_dor);
+}
+
+static void read_page_mem_qor(const AspeedSMCTestData *data,
+                              uint32_t addr, uint32_t *page)
+{
+    int i;
+
+    spi_ctrl_set_fast_read(data, QOR);
+
+    for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) {
+        page[i] = make_be32(flash_readl(data, addr + i * 4));
+    }
+}
+
+void aspeed_smc_test_read_page_mem_qor(const void *data)
+{
+    test_read_page_mem(data, read_page_mem_qor);
+}
+
+static void read_page_qor(const AspeedSMCTestData *data,
+                          uint32_t addr, uint32_t *page)
+{
+    int i;
+
+    spi_ctrl_start_user(data);
+
+    flash_writeb(data, 0, EN_4BYTE_ADDR);
+    flash_writeb(data, 0, QOR);
+    flash_writel(data, 0, make_be32(addr));
+    /* 1 dummy byte for standard SPI QOR */
+    flash_writeb(data, 0, 0x00);
+
+    for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) {
+        page[i] = make_be32(flash_readl(data, 0));
+    }
+    spi_ctrl_stop_user(data);
+}
+
+void aspeed_smc_test_write_page_qor(const void *data)
+{
+    test_write_page(data, read_page_qor);
+}
+
+void aspeed_smc_test_write_page_datafifo(const void *data)
+{
+    const AspeedSMCTestData *test_data = (const AspeedSMCTestData *)data;
+    uint32_t my_page_addr = test_data->page_addr;
+    uint32_t some_page_addr = my_page_addr + FLASH_PAGE_SIZE;
+    uint32_t page[FLASH_PAGE_SIZE / 4];
+    int i;
+
+    spi_conf(test_data, 1 << (CONF_ENABLE_W0 + test_data->cs));
+
+    /*
+     * Send the complete user-mode transaction (opcode, address, data)
+     * through the Data FIFO port.
+     */
+    spi_ctrl_start_user(test_data);
+    datafifo_writeb(test_data, EN_4BYTE_ADDR);
+    datafifo_writeb(test_data, WREN);
+    datafifo_writeb(test_data, PP);
+    datafifo_writel(test_data, make_be32(my_page_addr));
+
+    for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) {
+        datafifo_writel(test_data, make_be32(my_page_addr + i * 4));
+    }
+    spi_ctrl_stop_user(test_data);
+
+    /* Check what was written, using the regular read path */
+    read_page(test_data, my_page_addr, page);
+    for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) {
+        g_assert_cmphex(page[i], ==, my_page_addr + i * 4);
+    }
+
+    /* Check some other page. It should be full of 0xff */
+    read_page(test_data, some_page_addr, page);
+    for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) {
+        g_assert_cmphex(page[i], ==, 0xffffffff);
+    }
+
+    flash_reset(test_data);
+}
+
+void aspeed_smc_test_read_page_datafifo(const void *data)
+{
+    const AspeedSMCTestData *test_data = (const AspeedSMCTestData *)data;
+    uint32_t my_page_addr = test_data->page_addr;
+    uint32_t page[FLASH_PAGE_SIZE / 4];
+    int i;
+
+    spi_conf(test_data, 1 << (CONF_ENABLE_W0 + test_data->cs));
+
+    /* Write the page the regular way */
+    spi_ctrl_start_user(test_data);
+    flash_writeb(test_data, 0, EN_4BYTE_ADDR);
+    flash_writeb(test_data, 0, WREN);
+    flash_writeb(test_data, 0, PP);
+    flash_writel(test_data, 0, make_be32(my_page_addr));
+    for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) {
+        flash_writel(test_data, 0, make_be32(my_page_addr + i * 4));
+    }
+    spi_ctrl_stop_user(test_data);
+
+    /*
+     * Read it back through the data FIFO port, again sending the whole
+     * transaction (opcode, address, data) through it.
+     */
+    spi_ctrl_start_user(test_data);
+    datafifo_writeb(test_data, EN_4BYTE_ADDR);
+    datafifo_writeb(test_data, READ);
+    datafifo_writel(test_data, make_be32(my_page_addr));
+    for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) {
+        page[i] = make_be32(datafifo_readl(test_data));
+    }
+    spi_ctrl_stop_user(test_data);
+
+    for (i = 0; i < FLASH_PAGE_SIZE / 4; i++) {
+        g_assert_cmphex(page[i], ==, my_page_addr + i * 4);
+    }
+
+    flash_reset(test_data);
+}
diff --git a/tests/qtest/aspeed-smc-utils.h b/tests/qtest/aspeed-smc-utils.h
index e2fd8ff..e4f538e 100644
--- a/tests/qtest/aspeed-smc-utils.h
+++ b/tests/qtest/aspeed-smc-utils.h
@@ -4,23 +4,7 @@
  *
  * Copyright (C) 2016 IBM Corp.
  *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
+ * SPDX-License-Identifier: MIT
  */
 
 #ifndef TESTS_ASPEED_SMC_UTILS_H
@@ -44,7 +28,13 @@
 #define   CTRL_FREADMODE       0x1
 #define   CTRL_WRITEMODE       0x2
 #define   CTRL_USERMODE        0x3
+#define   CTRL_IO_QUAD_DATA     BIT(30)
+#define   CTRL_IO_DUAL_DATA     BIT(29)
+#define   CTRL_DUMMY_LOW_SHIFT   6
+#define   CTRL_DUMMY_HIGH_SHIFT  14
 #define SR_WEL BIT(1)
+/* Data fifo */
+#define R_DATA_FIFO 0x200
 
 /*
  * Flash commands
@@ -55,6 +45,9 @@
     WRDI = 0x4,
     BULK_ERASE = 0xc7,
     READ = 0x03,
+    FAST_READ = 0x0b,
+    DOR = 0x3b,
+    QOR = 0x6b,
     PP = 0x02,
     WRSR = 0x1,
     WREN = 0x6,
@@ -90,5 +83,13 @@
 void aspeed_smc_test_write_block_protect(const void *data);
 void aspeed_smc_test_write_block_protect_bottom_bit(const void *data);
 void aspeed_smc_test_write_page_qpi(const void *data);
+void aspeed_smc_test_read_page_mem_fast_read(const void *data);
+void aspeed_smc_test_write_page_fast_read(const void *data);
+void aspeed_smc_test_read_page_mem_dor(const void *data);
+void aspeed_smc_test_write_page_dor(const void *data);
+void aspeed_smc_test_read_page_mem_qor(const void *data);
+void aspeed_smc_test_write_page_qor(const void *data);
+void aspeed_smc_test_write_page_datafifo(const void *data);
+void aspeed_smc_test_read_page_datafifo(const void *data);
 
 #endif /* TESTS_ASPEED_SMC_UTILS_H */
diff --git a/tests/qtest/aspeed_hace-test.c b/tests/qtest/aspeed_hace-test.c
index 3877702..42130df 100644
--- a/tests/qtest/aspeed_hace-test.c
+++ b/tests/qtest/aspeed_hace-test.c
@@ -210,6 +210,12 @@
     qtest_add_func("ast1030/hace/sha384_accum", test_sha384_accum_ast1030);
     qtest_add_func("ast1030/hace/sha256_accum", test_sha256_accum_ast1030);
 
+    /* The AST1030 reuses the AST2600 crypto engine (scatter-gather, CTR). */
+    aspeed_add_crypto_tests("ast1030", "-machine ast1030-evb", 0x7e6d0000,
+                            0x00000000,
+                            CRYPT_MODE_ECB | CRYPT_MODE_CBC | CRYPT_MODE_CTR,
+                            true);
+
     qtest_add_func("ast2600/hace/addresses", test_addresses_ast2600);
     qtest_add_func("ast2600/hace/sha512", test_sha512_ast2600);
     qtest_add_func("ast2600/hace/sha384", test_sha384_ast2600);
@@ -224,11 +230,23 @@
     qtest_add_func("ast2600/hace/sha384_accum", test_sha384_accum_ast2600);
     qtest_add_func("ast2600/hace/sha256_accum", test_sha256_accum_ast2600);
 
+    /* The AST2600 crypto engine uses scatter-gather mode and adds CTR. */
+    aspeed_add_crypto_tests("ast2600", "-machine ast2600-evb", 0x1e6d0000,
+                            0x80000000,
+                            CRYPT_MODE_ECB | CRYPT_MODE_CBC | CRYPT_MODE_CTR,
+                            true);
+
     qtest_add_func("ast2500/hace/addresses", test_addresses_ast2500);
     qtest_add_func("ast2500/hace/sha512", test_sha512_ast2500);
     qtest_add_func("ast2500/hace/sha256", test_sha256_ast2500);
     qtest_add_func("ast2500/hace/md5", test_md5_ast2500);
 
+    /*
+     * The AST2500 crypto engine uses direct access mode and supports ECB/CBC.
+     */
+    aspeed_add_crypto_tests("ast2500", "-machine ast2500-evb", 0x1e6e3000,
+                            0x80000000, CRYPT_MODE_ECB | CRYPT_MODE_CBC, false);
+
     qtest_add_func("ast2400/hace/addresses", test_addresses_ast2400);
     qtest_add_func("ast2400/hace/sha512", test_sha512_ast2400);
     qtest_add_func("ast2400/hace/sha256", test_sha256_ast2400);
diff --git a/tests/qtest/aspeed_smc-test.c b/tests/qtest/aspeed_smc-test.c
index 39af1df..59c96bd 100644
--- a/tests/qtest/aspeed_smc-test.c
+++ b/tests/qtest/aspeed_smc-test.c
@@ -4,23 +4,7 @@
  *
  * Copyright (C) 2016 IBM Corp.
  *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
+ * SPDX-License-Identifier: MIT
  */
 
 #include "qemu/osdep.h"
@@ -68,6 +52,23 @@
                         data, aspeed_smc_test_read_status_reg);
     qtest_add_data_func("/ast2400/smc/status_reg_write_protection",
                         data, aspeed_smc_test_status_reg_write_protection);
+    qtest_add_data_func("/ast2400/smc/read_page_mem_fast_read",
+                        data, aspeed_smc_test_read_page_mem_fast_read);
+    qtest_add_data_func("/ast2400/smc/write_page_fast_read",
+                        data, aspeed_smc_test_write_page_fast_read);
+    qtest_add_data_func("/ast2400/smc/read_page_mem_dor",
+                        data, aspeed_smc_test_read_page_mem_dor);
+    qtest_add_data_func("/ast2400/smc/write_page_dor",
+                        data, aspeed_smc_test_write_page_dor);
+    qtest_add_data_func("/ast2400/smc/read_page_mem_qor",
+                        data, aspeed_smc_test_read_page_mem_qor);
+    qtest_add_data_func("/ast2400/smc/write_page_qor",
+                        data, aspeed_smc_test_write_page_qor);
+    /*
+     * Block protect tests must be run last because the block protect
+     * state is not cleared by reset_memory() and silently prevents
+     * subsequent flash writes.
+     */
     qtest_add_data_func("/ast2400/smc/write_block_protect",
                         data, aspeed_smc_test_write_block_protect);
     qtest_add_data_func("/ast2400/smc/write_block_protect_bottom_bit",
@@ -115,6 +116,18 @@
                         data, aspeed_smc_test_read_status_reg);
     qtest_add_data_func("/ast2500/smc/write_page_qpi",
                         data, aspeed_smc_test_write_page_qpi);
+    qtest_add_data_func("/ast2500/smc/read_page_mem_fast_read",
+                        data, aspeed_smc_test_read_page_mem_fast_read);
+    qtest_add_data_func("/ast2500/smc/write_page_fast_read",
+                        data, aspeed_smc_test_write_page_fast_read);
+    qtest_add_data_func("/ast2500/smc/read_page_mem_dor",
+                        data, aspeed_smc_test_read_page_mem_dor);
+    qtest_add_data_func("/ast2500/smc/write_page_dor",
+                        data, aspeed_smc_test_write_page_dor);
+    qtest_add_data_func("/ast2500/smc/read_page_mem_qor",
+                        data, aspeed_smc_test_read_page_mem_qor);
+    qtest_add_data_func("/ast2500/smc/write_page_qor",
+                        data, aspeed_smc_test_write_page_qor);
 }
 
 static void test_ast2600_evb(AspeedSMCTestData *data)
@@ -158,6 +171,18 @@
                         data, aspeed_smc_test_read_status_reg);
     qtest_add_data_func("/ast2600/smc/write_page_qpi",
                         data, aspeed_smc_test_write_page_qpi);
+    qtest_add_data_func("/ast2600/smc/read_page_mem_fast_read",
+                        data, aspeed_smc_test_read_page_mem_fast_read);
+    qtest_add_data_func("/ast2600/smc/write_page_fast_read",
+                        data, aspeed_smc_test_write_page_fast_read);
+    qtest_add_data_func("/ast2600/smc/read_page_mem_dor",
+                        data, aspeed_smc_test_read_page_mem_dor);
+    qtest_add_data_func("/ast2600/smc/write_page_dor",
+                        data, aspeed_smc_test_write_page_dor);
+    qtest_add_data_func("/ast2600/smc/read_page_mem_qor",
+                        data, aspeed_smc_test_read_page_mem_qor);
+    qtest_add_data_func("/ast2600/smc/write_page_qor",
+                        data, aspeed_smc_test_write_page_qor);
 }
 
 static void test_ast1030_evb(AspeedSMCTestData *data)
@@ -201,6 +226,18 @@
                         data, aspeed_smc_test_read_status_reg);
     qtest_add_data_func("/ast1030/smc/write_page_qpi",
                         data, aspeed_smc_test_write_page_qpi);
+    qtest_add_data_func("/ast1030/smc/read_page_mem_fast_read",
+                        data, aspeed_smc_test_read_page_mem_fast_read);
+    qtest_add_data_func("/ast1030/smc/write_page_fast_read",
+                        data, aspeed_smc_test_write_page_fast_read);
+    qtest_add_data_func("/ast1030/smc/read_page_mem_dor",
+                        data, aspeed_smc_test_read_page_mem_dor);
+    qtest_add_data_func("/ast1030/smc/write_page_dor",
+                        data, aspeed_smc_test_write_page_dor);
+    qtest_add_data_func("/ast1030/smc/read_page_mem_qor",
+                        data, aspeed_smc_test_read_page_mem_qor);
+    qtest_add_data_func("/ast1030/smc/write_page_qor",
+                        data, aspeed_smc_test_write_page_qor);
 }
 
 int main(int argc, char **argv)
diff --git a/tests/qtest/ast2700-hace-test.c b/tests/qtest/ast2700-hace-test.c
index 508a34d..3f0217d 100644
--- a/tests/qtest/ast2700-hace-test.c
+++ b/tests/qtest/ast2700-hace-test.c
@@ -94,5 +94,14 @@
     qtest_add_func("ast2700/hace/sha384_accum", test_sha384_accum_ast2700);
     qtest_add_func("ast2700/hace/sha256_accum", test_sha256_accum_ast2700);
 
+    /*
+     * The AST2700 crypto engine uses scatter-gather with 64-bit DMA and adds
+     * AES-GCM on top of the ECB/CBC/CTR modes shared with the AST2600.
+     */
+    aspeed_add_crypto_tests("ast2700", "-machine ast2700-evb", 0x12070000,
+                            0x400000000,
+                            CRYPT_MODE_ECB | CRYPT_MODE_CBC | CRYPT_MODE_CTR |
+                            CRYPT_MODE_GCM, true);
+
     return g_test_run();
 }
diff --git a/tests/qtest/ast2700-smc-test.c b/tests/qtest/ast2700-smc-test.c
index 33fc472..925dbcf 100644
--- a/tests/qtest/ast2700-smc-test.c
+++ b/tests/qtest/ast2700-smc-test.c
@@ -52,6 +52,22 @@
                         data, aspeed_smc_test_read_status_reg);
     qtest_add_data_func("/ast2700/smc/write_page_qpi",
                         data, aspeed_smc_test_write_page_qpi);
+    qtest_add_data_func("/ast2700/smc/read_page_mem_fast_read",
+                        data, aspeed_smc_test_read_page_mem_fast_read);
+    qtest_add_data_func("/ast2700/smc/write_page_fast_read",
+                        data, aspeed_smc_test_write_page_fast_read);
+    qtest_add_data_func("/ast2700/smc/read_page_mem_dor",
+                        data, aspeed_smc_test_read_page_mem_dor);
+    qtest_add_data_func("/ast2700/smc/write_page_dor",
+                        data, aspeed_smc_test_write_page_dor);
+    qtest_add_data_func("/ast2700/smc/read_page_mem_qor",
+                        data, aspeed_smc_test_read_page_mem_qor);
+    qtest_add_data_func("/ast2700/smc/write_page_qor",
+                        data, aspeed_smc_test_write_page_qor);
+    qtest_add_data_func("/ast2700/smc/write_page_datafifo",
+                        data, aspeed_smc_test_write_page_datafifo);
+    qtest_add_data_func("/ast2700/smc/read_page_datafifo",
+                        data, aspeed_smc_test_read_page_datafifo);
 }
 
 int main(int argc, char **argv)
diff --git a/tests/qtest/fuzz-virtio-scsi-test.c b/tests/qtest/fuzz-virtio-scsi-test.c
index e37b48b..102a5cc 100644
--- a/tests/qtest/fuzz-virtio-scsi-test.c
+++ b/tests/qtest/fuzz-virtio-scsi-test.c
@@ -19,7 +19,7 @@
 {
     QTestState *s;
 
-    s = qtest_init("-M pc-q35-5.2 -m 512M "
+    s = qtest_init("-M q35 -m 512M "
                    "-device virtio-scsi,num_queues=8,addr=03.0 ");
 
     qtest_outl(s, 0xcf8, 0x80001811);
diff --git a/tests/qtest/l2vic-test.c b/tests/qtest/l2vic-test.c
new file mode 100644
index 0000000..adb4dc7
--- /dev/null
+++ b/tests/qtest/l2vic-test.c
@@ -0,0 +1,249 @@
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * QTest testcase for the L2VIC Interrupt Controller
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest-single.h"
+#include "hw/hexagon/hexagon.h"
+
+#include "hw/hexagon/machine_cfg_v66g_1024.h.inc"
+#include "hw/hexagon/machine_cfg_v68n_1024.h.inc"
+
+/* L2VIC register offsets exercised by this test */
+#define L2VIC_INT_ENABLEn 0x100 /* Read/Write */
+#define L2VIC_INT_ENABLE_CLEARn 0x180 /* Write */
+#define L2VIC_INT_ENABLE_SETn 0x200 /* Write */
+#define L2VIC_INT_TYPEn 0x280 /* Read/Write */
+#define L2VIC_INT_STATUSn 0x380 /* Read */
+#define L2VIC_INT_CLEARn 0x400 /* Write */
+#define L2VIC_SOFT_INTn 0x480 /* Write */
+#define L2VIC_INT_PENDINGn 0x500 /* Read */
+#define L2VIC_INT_GRPn_0 0x600 /* Read/Write */
+#define L2VIC_INT_GRPn_1 0x680 /* Read/Write */
+#define L2VIC_INT_GRPn_2 0x700 /* Read/Write */
+#define L2VIC_INT_GRPn_3 0x780 /* Read/Write */
+
+/*
+ * VID group readback: records which irq last fired through each VID
+ * group.  Outputs themselves are momentary pulses (see l2vic_update()),
+ * so these registers -- not the qtest IRQ level snapshot -- are how the
+ * test observes VID steering.
+ */
+#define L2VIC_VID_GRP_0 0x0
+#define L2VIC_VID_GRP_1 0x4
+#define L2VIC_VID_GRP_2 0x8
+#define L2VIC_VID_GRP_3 0xC
+
+typedef struct {
+    const char *machine;
+    const struct hexagon_machine_config *cfg;
+} L2VICMachineCfg;
+
+static const L2VICMachineCfg l2vic_machines[] = {
+    { "virt",      &v68n_1024 },
+    { "V66G_1024", &v66g_1024 },
+};
+
+static uint32_t l2vic_read32(uint64_t base, uint32_t offset)
+{
+    return readl(base + offset);
+}
+
+static void l2vic_write32(uint64_t base, uint32_t offset, uint32_t value)
+{
+    writel(base + offset, value);
+}
+
+static void test_l2vic_register_access(uint64_t base)
+{
+    uint32_t val;
+
+    l2vic_write32(base, L2VIC_INT_ENABLE_SETn, 0x1);
+    val = l2vic_read32(base, L2VIC_INT_ENABLEn);
+    g_assert_cmpuint(val & 0x1, ==, 0x1);
+
+    l2vic_write32(base, L2VIC_INT_ENABLE_CLEARn, 0x1);
+    val = l2vic_read32(base, L2VIC_INT_ENABLEn);
+    g_assert_cmpuint(val & 0x1, ==, 0x0);
+}
+
+static void test_l2vic_interrupt_enable(uint64_t base)
+{
+    uint32_t val;
+
+    val = l2vic_read32(base, L2VIC_INT_ENABLEn);
+    g_assert_cmpuint(val, ==, 0);
+
+    /* Enable IRQ 0 and 2 */
+    l2vic_write32(base, L2VIC_INT_ENABLE_SETn, 0x5);
+    val = l2vic_read32(base, L2VIC_INT_ENABLEn);
+    g_assert_cmpuint(val & 0x5, ==, 0x5);
+
+    /* Disable IRQ 0, leaving IRQ 2 enabled */
+    l2vic_write32(base, L2VIC_INT_ENABLE_CLEARn, 0x1);
+    val = l2vic_read32(base, L2VIC_INT_ENABLEn);
+    g_assert_cmpuint(val & 0x1, ==, 0x0);
+    g_assert_cmpuint(val & 0x4, ==, 0x4);
+}
+
+static void test_l2vic_basic_functionality(uint64_t base)
+{
+    l2vic_read32(base, L2VIC_INT_ENABLEn);
+    l2vic_read32(base, L2VIC_INT_PENDINGn);
+    l2vic_read32(base, L2VIC_INT_STATUSn);
+    l2vic_read32(base, L2VIC_INT_TYPEn);
+
+    l2vic_write32(base, L2VIC_INT_ENABLE_SETn, 0);
+    l2vic_write32(base, L2VIC_INT_ENABLE_CLEARn, 0);
+}
+
+/*
+ * IRQs 0-7 pack their group-enable/VID-select nibbles into
+ * L2VIC_INT_GRPn_0 (int_group_n[0]), 4 bits per irq: bit 3 enables
+ * VID steering, bits 0-2 select the VID group (0-3), which pulses
+ * output line vid+2.
+ */
+static void l2vic_set_vid_group(uint64_t base, int irq, int vid)
+{
+    uint32_t val = l2vic_read32(base, L2VIC_INT_GRPn_0);
+    uint32_t nibble = 0x8 | (vid & 0x7);
+
+    val &= ~(0xFu << (irq * 4));
+    val |= nibble << (irq * 4);
+    l2vic_write32(base, L2VIC_INT_GRPn_0, val);
+}
+
+static void test_l2vic_irq_outputs(uint64_t base)
+{
+    uint32_t val;
+
+    l2vic_write32(base, L2VIC_INT_ENABLE_CLEARn, 0xFFFFFFFF);
+    l2vic_write32(base, L2VIC_INT_CLEARn, 0xFFFFFFFF);
+    l2vic_write32(base, L2VIC_INT_TYPEn, 0);
+    l2vic_write32(base, L2VIC_INT_GRPn_0, 0);
+
+    /* Group 0 / IRQ2: soft interrupts require edge-triggered config */
+    l2vic_write32(base, L2VIC_INT_TYPEn, 0x1);
+    l2vic_write32(base, L2VIC_INT_ENABLE_SETn, 0x1);
+    l2vic_write32(base, L2VIC_SOFT_INTn, 0x1);
+
+    val = l2vic_read32(base, L2VIC_INT_STATUSn);
+    g_assert_cmpuint(val & 0x1, ==, 0x1);
+    /* Default VID group (0) records the delivering irq, output line 2 */
+    g_assert_cmpuint(l2vic_read32(base, L2VIC_VID_GRP_0), ==, 0);
+
+    l2vic_write32(base, L2VIC_INT_CLEARn, 0x1);
+    val = l2vic_read32(base, L2VIC_INT_STATUSn);
+    g_assert_cmpuint(val & 0x1, ==, 0x0);
+
+    /* IRQ1 steered to VID group 1 -> output line 3 */
+    l2vic_write32(base, L2VIC_INT_TYPEn, 0x2);
+    l2vic_set_vid_group(base, 1, 1);
+    l2vic_write32(base, L2VIC_INT_ENABLE_SETn, 0x2);
+    l2vic_write32(base, L2VIC_SOFT_INTn, 0x2);
+
+    val = l2vic_read32(base, L2VIC_INT_STATUSn);
+    g_assert_cmpuint(val & 0x2, ==, 0x2);
+    g_assert_cmpuint(l2vic_read32(base, L2VIC_VID_GRP_1), ==, 1);
+
+    l2vic_write32(base, L2VIC_INT_CLEARn, 0x2);
+
+    /* IRQ4 steered to VID group 2 -> output line 4 */
+    l2vic_write32(base, L2VIC_INT_TYPEn, 0x10);
+    l2vic_set_vid_group(base, 4, 2);
+    l2vic_write32(base, L2VIC_INT_ENABLE_SETn, 0x10);
+    l2vic_write32(base, L2VIC_SOFT_INTn, 0x10);
+
+    val = l2vic_read32(base, L2VIC_INT_STATUSn);
+    g_assert_cmpuint(val & 0x10, ==, 0x10);
+    g_assert_cmpuint(l2vic_read32(base, L2VIC_VID_GRP_2), ==, 4);
+
+    l2vic_write32(base, L2VIC_INT_CLEARn, 0x10);
+
+    /* IRQ5 steered to VID group 3 -> output line 5 */
+    l2vic_write32(base, L2VIC_INT_TYPEn, 0x20);
+    l2vic_set_vid_group(base, 5, 3);
+    l2vic_write32(base, L2VIC_INT_ENABLE_SETn, 0x20);
+    l2vic_write32(base, L2VIC_SOFT_INTn, 0x20);
+
+    val = l2vic_read32(base, L2VIC_INT_STATUSn);
+    g_assert_cmpuint(val & 0x20, ==, 0x20);
+    g_assert_cmpuint(l2vic_read32(base, L2VIC_VID_GRP_3), ==, 5);
+
+    l2vic_write32(base, L2VIC_INT_CLEARn, 0x20);
+
+    /* Restore defaults; the block below reuses IRQ 3-5 without VID steering */
+    l2vic_write32(base, L2VIC_INT_GRPn_0, 0);
+    l2vic_write32(base, L2VIC_INT_TYPEn, 0);
+
+    /* Multiple pending: at most one active at a time */
+    l2vic_write32(base, L2VIC_INT_TYPEn, 0xF);
+    l2vic_write32(base, L2VIC_INT_ENABLE_SETn, 0xF);
+    l2vic_write32(base, L2VIC_SOFT_INTn, 0xF);
+
+    val = l2vic_read32(base, L2VIC_INT_STATUSn);
+    g_assert_cmpuint(val & 0xF, !=, 0x0);
+
+    /*
+     * Only one irq becomes active per delivery; each clear unblocks the
+     * next pending one, so drain until all four have been delivered.
+     */
+    while ((val = l2vic_read32(base, L2VIC_INT_STATUSn)) & 0xF) {
+        l2vic_write32(base, L2VIC_INT_CLEARn, val & 0xF);
+    }
+
+    /* Level-triggered sources ignore soft interrupts */
+    l2vic_write32(base, L2VIC_INT_TYPEn, 0x0);
+    l2vic_write32(base, L2VIC_INT_ENABLE_SETn, 0x20);
+    l2vic_write32(base, L2VIC_SOFT_INTn, 0x20);
+
+    val = l2vic_read32(base, L2VIC_INT_STATUSn);
+    g_assert_cmpuint(val & 0x20, ==, 0x0);
+
+    /* Same source, now edge-triggered, does fire */
+    l2vic_write32(base, L2VIC_INT_TYPEn, 0x20);
+    l2vic_write32(base, L2VIC_SOFT_INTn, 0x20);
+    val = l2vic_read32(base, L2VIC_INT_STATUSn);
+    g_assert_cmpuint(val & 0x20, ==, 0x20);
+
+    l2vic_write32(base, L2VIC_INT_ENABLE_CLEARn, 0xFFFFFFFF);
+    l2vic_write32(base, L2VIC_INT_CLEARn, 0xFFFFFFFF);
+    l2vic_write32(base, L2VIC_INT_GRPn_0, 0);
+    l2vic_write32(base, L2VIC_INT_GRPn_1, 0);
+    l2vic_write32(base, L2VIC_INT_GRPn_2, 0);
+    l2vic_write32(base, L2VIC_INT_GRPn_3, 0);
+}
+
+static void test_l2vic_on_machine(gconstpointer data)
+{
+    const L2VICMachineCfg *mc = data;
+    g_autofree char *args = g_strdup_printf("-machine %s", mc->machine);
+    uint64_t base = mc->cfg->l2vic_base;
+
+    qtest_start(args);
+
+    test_l2vic_register_access(base);
+    test_l2vic_interrupt_enable(base);
+    test_l2vic_basic_functionality(base);
+    test_l2vic_irq_outputs(base);
+
+    qtest_end();
+}
+
+int main(int argc, char **argv)
+{
+    size_t i;
+
+    g_test_init(&argc, &argv, NULL);
+
+    for (i = 0; i < ARRAY_SIZE(l2vic_machines); i++) {
+        g_autofree char *path = g_strdup_printf("/l2vic/%s/all-tests",
+                                                l2vic_machines[i].machine);
+        qtest_add_data_func(path, &l2vic_machines[i], test_l2vic_on_machine);
+    }
+
+    return g_test_run();
+}
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 56ff860..f7c7d06 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -299,11 +299,12 @@
    ['iommu-riscv-test'] : []) + \
   (config_all_devices.has_key('CONFIG_K230') ? ['k230-wdt-test'] : [])
 
-qtests_hexagon = ['boot-serial-test']
+qtests_hexagon = ['boot-serial-test', 'l2vic-test', 'qct-qtimer-test']
 
 qos_test_ss = ss.source_set()
 qos_test_ss.add(
   'ac97-test.c',
+  'adc128d818-test.c',
   'adm1272-test.c',
   'adm1266-test.c',
   'ds1338-test.c',
@@ -318,6 +319,8 @@
   'tulip-test.c',
   'nvme-test.c',
   'pca9552-test.c',
+  'pca9554-test.c',
+  'pca9555-test.c',
   'pci-test.c',
   'pcnet-test.c',
   'rs5c372-test.c',
@@ -389,9 +392,11 @@
 endif
 
 qtests = {
-  'aspeed_hace-test': files('aspeed-hace-utils.c', 'aspeed_hace-test.c'),
+  'aspeed_hace-test': [files('aspeed-hace-utils.c', 'aspeed_hace-test.c'),
+                       crypto],
   'aspeed_smc-test': files('aspeed-smc-utils.c', 'aspeed_smc-test.c'),
-  'ast2700-hace-test': files('aspeed-hace-utils.c', 'ast2700-hace-test.c'),
+  'ast2700-hace-test': [files('aspeed-hace-utils.c', 'ast2700-hace-test.c'),
+                        crypto],
   'ast2700-smc-test': files('aspeed-smc-utils.c', 'ast2700-smc-test.c'),
   'bios-tables-test': [io, 'boot-sector.c', 'acpi-utils.c', 'tpm-emu.c'],
   'cdrom-test': files('boot-sector.c'),
diff --git a/tests/qtest/pca9552-test.c b/tests/qtest/pca9552-test.c
index 7474957..3718dfb 100644
--- a/tests/qtest/pca9552-test.c
+++ b/tests/qtest/pca9552-test.c
@@ -77,6 +77,76 @@
     g_assert_cmphex(value, ==, 0xEF);
 }
 
+/* Verify the power-on reset defaults. */
+static void test_reset_defaults(void *obj, void *data, QGuestAllocator *alloc)
+{
+    QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+    /* Prescalers, PWM duty cycles and LED selectors (all LEDs off) */
+    g_assert_cmphex(i2c_get8(i2cdev, PCA9552_PSC0), ==, 0xFF);
+    g_assert_cmphex(i2c_get8(i2cdev, PCA9552_PWM0), ==, 0x80);
+    g_assert_cmphex(i2c_get8(i2cdev, PCA9552_PSC1), ==, 0xFF);
+    g_assert_cmphex(i2c_get8(i2cdev, PCA9552_PWM1), ==, 0x80);
+    g_assert_cmphex(i2c_get8(i2cdev, PCA9552_LS0), ==, 0x55);
+    g_assert_cmphex(i2c_get8(i2cdev, PCA9552_LS1), ==, 0x55);
+    g_assert_cmphex(i2c_get8(i2cdev, PCA9552_LS2), ==, 0x55);
+    g_assert_cmphex(i2c_get8(i2cdev, PCA9552_LS3), ==, 0x55);
+
+    /* All LEDs off, so every pin floats high through its pull-up */
+    g_assert_cmphex(i2c_get8(i2cdev, PCA9552_INPUT0), ==, 0xFF);
+    g_assert_cmphex(i2c_get8(i2cdev, PCA9552_INPUT1), ==, 0xFF);
+}
+
+/*
+ * The PCA9552 only advances the command pointer when the AI bit is set, and
+ * it wraps modulo the full 10-register map.
+ */
+static void test_autoinc_requires_ai_bit(void *obj, void *data,
+                                         QGuestAllocator *alloc)
+{
+    QI2CDevice *i2cdev = (QI2CDevice *)obj;
+    uint8_t reg;
+    uint8_t resp;
+
+    /*
+     * With the AI bit, reading from LS3 (register 9) rolls over to INPUT0
+     * (register 0), not to a sibling in a register pair. All LEDs are off
+     * after reset so the input ports read 0xFF.
+     */
+    reg = PCA9552_LS3 | PCA9552_AUTOINC;
+    qi2c_send(i2cdev, &reg, 1);
+    qi2c_recv(i2cdev, &resp, 1); /* LS3 */
+    g_assert_cmphex(resp, ==, 0x55);
+    qi2c_recv(i2cdev, &resp, 1); /* wraps to INPUT0 */
+    g_assert_cmphex(resp, ==, 0xFF);
+    qi2c_recv(i2cdev, &resp, 1); /* INPUT1 */
+    g_assert_cmphex(resp, ==, 0xFF);
+
+    /*
+     * Without the AI bit the pointer must not advance: repeated reads keep
+     * returning the same register.
+     */
+    i2c_set8(i2cdev, PCA9552_LS0, 0x54);
+    reg = PCA9552_LS0;
+    qi2c_send(i2cdev, &reg, 1);
+    qi2c_recv(i2cdev, &resp, 1);
+    g_assert_cmphex(resp, ==, 0x54);
+    qi2c_recv(i2cdev, &resp, 1);
+    g_assert_cmphex(resp, ==, 0x54);
+}
+
+/*
+ * The PCA9552 decodes a 4-bit command and has no register past LS3 (9), so
+ * addressing register 0x0A reads back 0xFF.
+ */
+static void test_command_out_of_range(void *obj, void *data,
+                                      QGuestAllocator *alloc)
+{
+    QI2CDevice *i2cdev = (QI2CDevice *)obj;
+
+    g_assert_cmphex(i2c_get8(i2cdev, 0x0A), ==, 0xFF);
+}
+
 static void pca9552_register_nodes(void)
 {
     QOSGraphEdgeOptions opts = {
@@ -89,5 +159,11 @@
 
     qos_add_test("tx-rx", "pca9552", send_and_receive, NULL);
     qos_add_test("rx-autoinc", "pca9552", receive_autoinc, NULL);
+    qos_add_test("reset-defaults", "pca9552", test_reset_defaults, NULL);
+    qos_add_test("autoinc-requires-ai-bit", "pca9552",
+                 test_autoinc_requires_ai_bit, NULL);
+    qos_add_test("command-out-of-range", "pca9552", test_command_out_of_range,
+                 NULL);
 }
+
 libqos_init(pca9552_register_nodes);
diff --git a/tests/qtest/pca9554-test.c b/tests/qtest/pca9554-test.c
new file mode 100644
index 0000000..5366e71
--- /dev/null
+++ b/tests/qtest/pca9554-test.c
@@ -0,0 +1,223 @@
+/*
+ * QTest testcase for the PCA9554/PCA9536 I/O port expanders
+ *
+ * Copyright (c) Meta Platforms, Inc. and affiliates. (http://www.meta.com)
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/gpio/pca9554_regs.h"
+#include "libqos/i2c.h"
+#include "libqos/qgraph.h"
+
+#define PCA9554_TEST_ADDR 0x20
+
+/* Verify power-on reset defaults match the PCA9554 datasheet. */
+static void test_reset_defaults(void *obj, void *data, QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+
+    /* All pins are inputs, pulled high, with no polarity inversion. */
+    g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0xFF);
+    g_assert_cmphex(i2c_get8(dev, PCA9554_OUTPUT), ==, 0xFF);
+    g_assert_cmphex(i2c_get8(dev, PCA9554_POLARITY), ==, 0x00);
+    g_assert_cmphex(i2c_get8(dev, PCA9554_CONFIG), ==, 0xFF);
+}
+
+/*
+ * A pin configured as output (config=0) drives its OUTPUT register level onto
+ * the pin (push-pull), which the INPUT register reflects. A pin configured as
+ * input (config=1) floats high through its pull-up.
+ */
+static void test_output_drives_input(void *obj, void *data,
+                                     QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+
+    /* Low nibble output, high nibble input (pull-up). */
+    i2c_set8(dev, PCA9554_CONFIG, 0xF0);
+    i2c_set8(dev, PCA9554_OUTPUT, 0xFA);
+    g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0xFA);
+
+    /* All outputs, driven low then high. */
+    i2c_set8(dev, PCA9554_CONFIG, 0x00);
+    i2c_set8(dev, PCA9554_OUTPUT, 0x00);
+    g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0x00);
+
+    i2c_set8(dev, PCA9554_OUTPUT, 0xFF);
+    g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0xFF);
+}
+
+/*
+ * With all pins configured as inputs the pull-ups make the INPUT register read
+ * all ones regardless of the OUTPUT register; switching a pin to output with
+ * output=0 drives it low.
+ */
+static void test_input_pullup(void *obj, void *data, QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+
+    g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0xFF);
+
+    i2c_set8(dev, PCA9554_OUTPUT, 0x00);
+    g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0xFF);
+
+    i2c_set8(dev, PCA9554_CONFIG, 0x00);
+    g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0x00);
+}
+
+/*
+ * Polarity inversion: reading INPUT returns the XOR of the pin levels and the
+ * polarity register.
+ */
+static void test_polarity_inversion(void *obj, void *data,
+                                    QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+
+    g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0xFF);
+
+    i2c_set8(dev, PCA9554_POLARITY, 0xFF);
+    g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0x00);
+
+    i2c_set8(dev, PCA9554_POLARITY, 0x0F);
+    g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0xF0);
+}
+
+/* Polarity inversion combined with output-driven pins. */
+static void test_polarity_with_output(void *obj, void *data,
+                                      QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+
+    i2c_set8(dev, PCA9554_CONFIG, 0x00);
+    i2c_set8(dev, PCA9554_OUTPUT, 0xA5);
+    g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0xA5);
+
+    i2c_set8(dev, PCA9554_POLARITY, 0xFF);
+    g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0x5A);
+
+    /* Inversion only affects the INPUT read, not the OUTPUT register. */
+    g_assert_cmphex(i2c_get8(dev, PCA9554_OUTPUT), ==, 0xA5);
+}
+
+/*
+ * The PCA9554 has no auto-increment: the command pointer never advances, so
+ * multi-byte reads and writes all target the addressed register.
+ */
+static void test_no_autoincrement(void *obj, void *data,
+                                  QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+    uint8_t buf[2];
+
+    /* Distinct values in adjacent registers. */
+    i2c_set8(dev, PCA9554_OUTPUT, 0xAA);
+    i2c_set8(dev, PCA9554_POLARITY, 0x33);
+
+    /* Two reads from OUTPUT return OUTPUT twice, not OUTPUT then POLARITY. */
+    i2c_read_block(dev, PCA9554_OUTPUT, buf, 2);
+    g_assert_cmphex(buf[0], ==, 0xAA);
+    g_assert_cmphex(buf[1], ==, 0xAA);
+
+    /* The second written byte overwrites OUTPUT; POLARITY is untouched. */
+    buf[0] = 0x12;
+    buf[1] = 0x34;
+    i2c_write_block(dev, PCA9554_OUTPUT, buf, 2);
+    g_assert_cmphex(i2c_get8(dev, PCA9554_OUTPUT), ==, 0x34);
+    g_assert_cmphex(i2c_get8(dev, PCA9554_POLARITY), ==, 0x33);
+}
+
+/*
+ * The PCA9536 shares the PCA9554 register map but only has four pins, so its
+ * reset defaults and pin logic are masked to the low nibble.
+ */
+static void test_pca9536_reset_defaults(void *obj, void *data,
+                                        QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+
+    g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0x0F);
+    g_assert_cmphex(i2c_get8(dev, PCA9554_OUTPUT), ==, 0x0F);
+    g_assert_cmphex(i2c_get8(dev, PCA9554_POLARITY), ==, 0x00);
+    g_assert_cmphex(i2c_get8(dev, PCA9554_CONFIG), ==, 0x0F);
+}
+
+/* Only the four low pins are driven; the upper nibble stays low. */
+static void test_pca9536_output_drives_input(void *obj, void *data,
+                                             QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+
+    i2c_set8(dev, PCA9554_CONFIG, 0x00);
+
+    i2c_set8(dev, PCA9554_OUTPUT, 0x0A);
+    g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0x0A);
+
+    i2c_set8(dev, PCA9554_OUTPUT, 0x00);
+    g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0x00);
+}
+
+/*
+ * The four upper bits address pins that do not exist on the PCA9536, so writes
+ * to the register map discard them: the writable registers read back with bits
+ * [7:4] cleared, and driving them onto the pins never surfaces in INPUT.
+ */
+static void test_pca9536_ignores_upper_bits(void *obj, void *data,
+                                            QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+
+    /* Bits [7:4] are dropped on write; bits [3:0] survive. */
+    i2c_set8(dev, PCA9554_OUTPUT, 0xFA);
+    g_assert_cmphex(i2c_get8(dev, PCA9554_OUTPUT), ==, 0x0A);
+
+    i2c_set8(dev, PCA9554_POLARITY, 0xF5);
+    g_assert_cmphex(i2c_get8(dev, PCA9554_POLARITY), ==, 0x05);
+
+    i2c_set8(dev, PCA9554_CONFIG, 0xF3);
+    g_assert_cmphex(i2c_get8(dev, PCA9554_CONFIG), ==, 0x03);
+
+    /*
+     * With all four pins as outputs, driving 0xFF only affects the low
+     * nibble.
+     */
+    i2c_set8(dev, PCA9554_POLARITY, 0x00);
+    i2c_set8(dev, PCA9554_CONFIG, 0x00);
+    i2c_set8(dev, PCA9554_OUTPUT, 0xFF);
+    g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0x0F);
+}
+
+static void pca9554_register_nodes(void)
+{
+    QOSGraphEdgeOptions opts = {
+        .extra_device_opts = "address=0x20"
+    };
+    add_qi2c_address(&opts, &(QI2CAddress) { PCA9554_TEST_ADDR });
+
+    qos_node_create_driver("pca9554", i2c_device_create);
+    qos_node_consumes("pca9554", "i2c-bus", &opts);
+
+    qos_add_test("reset-defaults", "pca9554", test_reset_defaults, NULL);
+    qos_add_test("output-drives-input", "pca9554", test_output_drives_input,
+                 NULL);
+    qos_add_test("input-pullup", "pca9554", test_input_pullup, NULL);
+    qos_add_test("polarity-inversion", "pca9554", test_polarity_inversion,
+                 NULL);
+    qos_add_test("polarity-with-output", "pca9554", test_polarity_with_output,
+                 NULL);
+    qos_add_test("no-autoincrement", "pca9554", test_no_autoincrement, NULL);
+
+    qos_node_create_driver("pca9536", i2c_device_create);
+    qos_node_consumes("pca9536", "i2c-bus", &opts);
+
+    qos_add_test("reset-defaults", "pca9536", test_pca9536_reset_defaults,
+                 NULL);
+    qos_add_test("output-drives-input", "pca9536",
+                 test_pca9536_output_drives_input, NULL);
+    qos_add_test("ignores-upper-bits", "pca9536",
+                 test_pca9536_ignores_upper_bits, NULL);
+}
+
+libqos_init(pca9554_register_nodes);
diff --git a/tests/qtest/pca9555-test.c b/tests/qtest/pca9555-test.c
new file mode 100644
index 0000000..84d771b
--- /dev/null
+++ b/tests/qtest/pca9555-test.c
@@ -0,0 +1,251 @@
+/*
+ * QTest testcase for the PCA9555 16-bit I/O port expander
+ *
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/gpio/pca9552_regs.h"
+#include "libqos/i2c.h"
+#include "libqos/qgraph.h"
+
+#define PCA9555_TEST_ADDR 0x20
+
+/* Verify power-on reset defaults match the PCA9555 datasheet. */
+static void test_reset_defaults(void *obj, void *data, QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+
+    g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT0), ==, 0xFF);
+    g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT1), ==, 0xFF);
+    g_assert_cmphex(i2c_get8(dev, PCA9535_OUTPUT0), ==, 0xFF);
+    g_assert_cmphex(i2c_get8(dev, PCA9535_OUTPUT1), ==, 0xFF);
+    g_assert_cmphex(i2c_get8(dev, PCA9535_POLARITY0), ==, 0x00);
+    g_assert_cmphex(i2c_get8(dev, PCA9535_POLARITY1), ==, 0x00);
+    g_assert_cmphex(i2c_get8(dev, PCA9535_CONFIG0), ==, 0xFF);
+    g_assert_cmphex(i2c_get8(dev, PCA9535_CONFIG1), ==, 0xFF);
+}
+
+/*
+ * When a pin is configured as output and driven low (output=0, config=0),
+ * the input register should reflect 0 for that pin.
+ * When driven high (output=1, config=0), input should reflect 1.
+ * When configured as input (config=1), PCA5555 pull-up makes it read 1.
+ */
+static void test_output_drives_input(void *obj, void *data,
+                                     QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+
+    i2c_set8(dev, PCA9535_CONFIG0, 0xF0);
+    i2c_set8(dev, PCA9535_OUTPUT0, 0xFA);
+
+    g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT0), ==, 0xFA);
+
+    g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT1), ==, 0xFF);
+
+    i2c_set8(dev, PCA9535_CONFIG0, 0x00);
+    i2c_set8(dev, PCA9535_OUTPUT0, 0x00);
+    g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT0), ==, 0x00);
+
+    i2c_set8(dev, PCA9535_OUTPUT0, 0xFF);
+    g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT0), ==, 0xFF);
+}
+
+/*
+ * When all pins are inputs (config=0xFF) and no external driver,
+ * PCA9555 pull-ups should make the input register read all ones.
+ * Switching a pin to output mode with output=0 should drive it low.
+ */
+static void test_input_pullup(void *obj, void *data, QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+
+    g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT0), ==, 0xFF);
+    g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT1), ==, 0xFF);
+
+    i2c_set8(dev, PCA9535_OUTPUT0, 0x00);
+    g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT0), ==, 0xFF);
+
+    i2c_set8(dev, PCA9535_CONFIG0, 0x00);
+    g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT0), ==, 0x00);
+}
+
+/*
+ * Test that both ports are independent: changing port 0 registers
+ * should not affect port 1 and vice versa.
+ */
+static void test_port_independence(void *obj, void *data,
+                                   QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+
+    i2c_set8(dev, PCA9535_CONFIG0, 0x00);
+    i2c_set8(dev, PCA9535_OUTPUT0, 0x00);
+
+    g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT0), ==, 0x00);
+    g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT1), ==, 0xFF);
+    g_assert_cmphex(i2c_get8(dev, PCA9535_CONFIG1), ==, 0xFF);
+    g_assert_cmphex(i2c_get8(dev, PCA9535_OUTPUT1), ==, 0xFF);
+
+    i2c_set8(dev, PCA9535_CONFIG1, 0x00);
+    i2c_set8(dev, PCA9535_OUTPUT1, 0xAA);
+
+    g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT0), ==, 0x00);
+    g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT1), ==, 0xAA);
+    g_assert_cmphex(i2c_get8(dev, PCA9535_OUTPUT0), ==, 0x00);
+    g_assert_cmphex(i2c_get8(dev, PCA9535_OUTPUT1), ==, 0xAA);
+}
+
+/*
+ * Polarity inversion: reading INPUT with polarity bits set should
+ * return the XOR of the actual input state and the polarity register.
+ */
+static void test_polarity_inversion(void *obj, void *data,
+                                    QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+
+    g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT0), ==, 0xFF);
+
+    i2c_set8(dev, PCA9535_POLARITY0, 0xFF);
+    g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT0), ==, 0x00);
+
+    i2c_set8(dev, PCA9535_POLARITY0, 0x0F);
+    g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT0), ==, 0xF0);
+
+    g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT1), ==, 0xFF);
+
+    i2c_set8(dev, PCA9535_POLARITY1, 0xAA);
+    g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT1), ==, 0x55);
+}
+
+/* Polarity inversion combined with output-driven pins. */
+static void test_polarity_with_output(void *obj, void *data,
+                                      QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+
+    i2c_set8(dev, PCA9535_CONFIG0, 0x00);
+    i2c_set8(dev, PCA9535_OUTPUT0, 0xA5);
+
+    g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT0), ==, 0xA5);
+
+    i2c_set8(dev, PCA9535_POLARITY0, 0xFF);
+    g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT0), ==, 0x5A);
+
+    g_assert_cmphex(i2c_get8(dev, PCA9535_OUTPUT0), ==, 0xA5);
+}
+
+/*
+ * The PCA9555 auto-increments by toggling bit 0 of the command pointer
+ * within a register pair. Reading two bytes from INPUT0 should yield
+ * INPUT0 then INPUT1.
+ */
+static void test_auto_increment_read(void *obj, void *data,
+                                     QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+    uint8_t buf[2];
+
+    i2c_set8(dev, PCA9535_CONFIG0, 0x00);
+    i2c_set8(dev, PCA9535_CONFIG1, 0x00);
+    i2c_set8(dev, PCA9535_OUTPUT0, 0xAA);
+    i2c_set8(dev, PCA9535_OUTPUT1, 0x55);
+
+    i2c_read_block(dev, PCA9535_INPUT0, buf, 2);
+    g_assert_cmphex(buf[0], ==, 0xAA);
+    g_assert_cmphex(buf[1], ==, 0x55);
+
+    i2c_read_block(dev, PCA9535_OUTPUT0, buf, 2);
+    g_assert_cmphex(buf[0], ==, 0xAA);
+    g_assert_cmphex(buf[1], ==, 0x55);
+}
+
+/*
+ * Auto-increment write: writing two data bytes after a command byte
+ * should write to port 0 then port 1 of the addressed register pair.
+ */
+static void test_auto_increment_write(void *obj, void *data,
+                                      QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+    uint8_t buf[2];
+
+    buf[0] = 0x12;
+    buf[1] = 0x34;
+    i2c_write_block(dev, PCA9535_OUTPUT0, buf, 2);
+
+    g_assert_cmphex(i2c_get8(dev, PCA9535_OUTPUT0), ==, 0x12);
+    g_assert_cmphex(i2c_get8(dev, PCA9535_OUTPUT1), ==, 0x34);
+
+    buf[0] = 0x0F;
+    buf[1] = 0xF0;
+    i2c_write_block(dev, PCA9535_CONFIG0, buf, 2);
+
+    g_assert_cmphex(i2c_get8(dev, PCA9535_CONFIG0), ==, 0x0F);
+    g_assert_cmphex(i2c_get8(dev, PCA9535_CONFIG1), ==, 0xF0);
+}
+
+/*
+ * Auto-increment toggles within the pair: starting from port 1 should
+ * wrap back to port 0 (toggle bit 0).
+ */
+static void test_auto_increment_toggle(void *obj, void *data,
+                                       QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+    uint8_t buf[2];
+
+    i2c_set8(dev, PCA9535_OUTPUT0, 0xAA);
+    i2c_set8(dev, PCA9535_OUTPUT1, 0x55);
+
+    i2c_read_block(dev, PCA9535_OUTPUT1, buf, 2);
+    g_assert_cmphex(buf[0], ==, 0x55);
+    g_assert_cmphex(buf[1], ==, 0xAA);
+}
+
+/*
+ * Verify the command byte wraps at 3 bits: register addresses
+ * beyond 7 should alias to the same register (bits [2:0] only).
+ */
+static void test_command_wrapping(void *obj, void *data, QGuestAllocator *alloc)
+{
+    QI2CDevice *dev = (QI2CDevice *)obj;
+
+    i2c_set8(dev, PCA9535_OUTPUT0, 0x42);
+
+    g_assert_cmphex(i2c_get8(dev, 0x0A), ==, 0x42);
+}
+
+static void pca9555_register_nodes(void)
+{
+    QOSGraphEdgeOptions opts = {
+        .extra_device_opts = "address=0x20"
+    };
+    add_qi2c_address(&opts, &(QI2CAddress) { PCA9555_TEST_ADDR });
+
+    qos_node_create_driver("pca9555", i2c_device_create);
+    qos_node_consumes("pca9555", "i2c-bus", &opts);
+
+    qos_add_test("reset-defaults", "pca9555", test_reset_defaults, NULL);
+    qos_add_test("output-drives-input", "pca9555", test_output_drives_input,
+                 NULL);
+    qos_add_test("input-pullup", "pca9555", test_input_pullup, NULL);
+    qos_add_test("port-independence", "pca9555", test_port_independence, NULL);
+    qos_add_test("polarity-inversion", "pca9555", test_polarity_inversion,
+                 NULL);
+    qos_add_test("polarity-with-output", "pca9555", test_polarity_with_output,
+                 NULL);
+    qos_add_test("auto-increment-read", "pca9555", test_auto_increment_read,
+                 NULL);
+    qos_add_test("auto-increment-write", "pca9555", test_auto_increment_write,
+                 NULL);
+    qos_add_test("auto-increment-toggle", "pca9555", test_auto_increment_toggle,
+                 NULL);
+    qos_add_test("command-wrapping", "pca9555", test_command_wrapping, NULL);
+}
+
+libqos_init(pca9555_register_nodes);
diff --git a/tests/qtest/qct-qtimer-test.c b/tests/qtest/qct-qtimer-test.c
new file mode 100644
index 0000000..fd8bb0a
--- /dev/null
+++ b/tests/qtest/qct-qtimer-test.c
@@ -0,0 +1,385 @@
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * QTest testcase for the QCT QTimer
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest-single.h"
+#include "hw/hexagon/hexagon.h"
+#include "qemu/bitops.h"
+
+#include "hw/hexagon/machine_cfg_v68n_1024.h.inc"
+#include "hw/hexagon/machine_cfg_v66g_1024.h.inc"
+
+#define QTIMER_DEFAULT_FREQ_HZ 19200000ULL
+
+#define QCT_QTIMER_CNTPCT_LO (0x000)
+#define QCT_QTIMER_CNTPCT_HI (0x004)
+#define QCT_QTIMER_CNT_FREQ (0x010)
+#define QCT_QTIMER_CNTP_CVAL_LO (0x020)
+#define QCT_QTIMER_CNTP_TVAL (0x028)
+#define QCT_QTIMER_CNTP_CTL (0x02c)
+#define QCT_QTIMER_CNTP_CTL_ENABLE (1 << 0)
+
+#define QTIMER_FRAME_STRIDE 0x1000
+/* Frames instantiated by hex-subsys, and the L2VIC input frame 0 drives. */
+#define QTIMER_NR_FRAMES 3
+#define QTIMER_L2VIC_IRQ_BASE 2
+
+#define QCT_QTIMER_AC_CNTFRQ (0x000)
+#define QCT_QTIMER_AC_CNTSR (0x004)
+#define QCT_QTIMER_AC_CNTTID_0 (0x08)
+#define QCT_QTIMER_AC_CNTACR_START (0x40)
+#define QCT_QTIMER_AC_CNTACR_ALL (0x3f)
+#define QCT_QTIMER_AC_CNTACR_RWPT (1 << 5) /* R/W of CNTP_* regs */
+#define QCT_QTIMER_AC_CNTACR_RFRQ (1 << 2) /* R/W of CNTFRQ register */
+#define QCT_QTIMER_AC_CNTACR_RPCT (1 << 0) /* R/W of CNTPCT register */
+
+static uint64_t qtimer_view_base;
+static uint64_t qtimer_ac_base;
+
+#define TIMER_TEST_OFFSET 1000
+/* TIMER_TEST_OFFSET ticks expressed in nanoseconds of QEMU_CLOCK_VIRTUAL */
+#define TIMER_TEST_NS \
+    ((TIMER_TEST_OFFSET * 1000000000ULL) / QTIMER_DEFAULT_FREQ_HZ)
+
+static uint32_t qtimer_read32(uint64_t base, uint32_t offset)
+{
+    return readl(base + offset);
+}
+
+static void qtimer_write32(uint64_t base, uint32_t offset, uint32_t value)
+{
+    writel(base + offset, value);
+}
+
+static uint64_t qtimer_read64(uint64_t base, uint32_t offset)
+{
+    uint32_t lo = qtimer_read32(base, offset);
+    uint32_t hi = qtimer_read32(base, offset + 4);
+
+    return ((uint64_t)hi << 32) | lo;
+}
+
+static void qtimer_write64(uint64_t base, uint32_t offset, uint64_t value)
+{
+    qtimer_write32(base, offset, extract64(value, 0, 32));
+    qtimer_write32(base, offset + 4, extract64(value, 32, 32));
+}
+
+static void test_qtimer_basic_access(void)
+{
+    uint32_t val;
+
+    val = qtimer_read32(qtimer_view_base, QCT_QTIMER_CNT_FREQ);
+    g_assert_cmpuint(val, ==, QTIMER_DEFAULT_FREQ_HZ);
+}
+
+static void test_qtimer_multiple_frames(void)
+{
+    uint32_t val;
+    uint64_t frame0_base = qtimer_view_base;
+    uint64_t frame1_base = qtimer_view_base + 0x1000;
+
+    val = qtimer_read32(frame0_base, QCT_QTIMER_CNT_FREQ);
+    g_assert_cmpuint(val, ==, QTIMER_DEFAULT_FREQ_HZ);
+
+    val = qtimer_read32(frame1_base, QCT_QTIMER_CNT_FREQ);
+    g_assert_cmpuint(val, ==, QTIMER_DEFAULT_FREQ_HZ);
+}
+
+static void test_qtimer_register_reads(void)
+{
+    qtimer_read32(qtimer_view_base, QCT_QTIMER_CNT_FREQ);
+    qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTPCT_LO);
+    qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTP_CVAL_LO);
+    qtimer_read32(qtimer_view_base, QCT_QTIMER_CNTP_CTL);
+    qtimer_read32(qtimer_view_base, QCT_QTIMER_CNTP_TVAL);
+}
+
+static void test_qtimer_control_registers(void)
+{
+    uint32_t ctl_val;
+    uint64_t cval_before, cval_after;
+
+    cval_before = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTPCT_LO);
+
+    qtimer_write32(qtimer_view_base, QCT_QTIMER_CNTP_TVAL, 1000);
+
+    qtimer_write32(qtimer_view_base, QCT_QTIMER_CNTP_CTL, 1);
+    ctl_val = qtimer_read32(qtimer_view_base, QCT_QTIMER_CNTP_CTL);
+    g_assert_cmpuint(ctl_val & 1, ==, 1);
+
+    /* CVAL should be greater than before since we set TVAL */
+    cval_after = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTP_CVAL_LO);
+    g_assert_cmpuint(cval_after, >, cval_before);
+
+    qtimer_write32(qtimer_view_base, QCT_QTIMER_CNTP_CTL, 0);
+    ctl_val = qtimer_read32(qtimer_view_base, QCT_QTIMER_CNTP_CTL);
+    g_assert_cmpuint(ctl_val & 1, ==, 0);
+}
+
+static void test_qtimer_cval_access(void)
+{
+    uint64_t current_time, test_cval, read_cval;
+
+    current_time = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTPCT_LO);
+    test_cval = current_time + 10000;
+
+    qtimer_write64(qtimer_view_base, QCT_QTIMER_CNTP_CVAL_LO, test_cval);
+    read_cval = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTP_CVAL_LO);
+    g_assert_cmpuint(read_cval, ==, test_cval);
+}
+
+static void test_qtimer_counter_progression(void)
+{
+    uint32_t freq;
+    uint64_t count1, count2;
+
+    /*
+     * In qtest mode the virtual clock does not advance on its own, so
+     * reading the counter twice must give the same value.
+     */
+    count1 = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTPCT_LO);
+    count2 = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTPCT_LO);
+    g_assert_cmpuint(count2, ==, count1);
+
+    freq = qtimer_read32(qtimer_view_base, QCT_QTIMER_CNT_FREQ);
+    g_assert_cmpuint(freq, ==, QTIMER_DEFAULT_FREQ_HZ);
+}
+
+static void test_qtimer_timer_behavior(void)
+{
+    uint64_t current_count, target_count, read_cval, new_count;
+    uint64_t ctl_val, count_after_disable;
+
+    current_count = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTPCT_LO);
+
+    target_count = current_count + TIMER_TEST_OFFSET;
+    qtimer_write64(qtimer_view_base, QCT_QTIMER_CNTP_CVAL_LO, target_count);
+
+    read_cval = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTP_CVAL_LO);
+    g_assert_cmpuint(read_cval, ==, target_count);
+
+    qtimer_write32(qtimer_view_base, QCT_QTIMER_CNTP_CTL, 1);
+
+    ctl_val = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTP_CTL);
+    /* EN set, IMASK clear, ISTAT not yet pending */
+    g_assert_cmpuint(ctl_val, ==, 0x1);
+
+    /* Step forward but not past the target */
+    qtest_clock_step(global_qtest, TIMER_TEST_NS / 2);
+    new_count = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTPCT_LO);
+    g_assert_cmpuint(new_count, >=, current_count);
+
+    /* Step past the target */
+    qtest_clock_step(global_qtest, TIMER_TEST_NS);
+    new_count = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTPCT_LO);
+    g_assert_cmpuint(new_count, >=, target_count);
+
+    qtimer_write32(qtimer_view_base, QCT_QTIMER_CNTP_CTL, 0);
+
+    ctl_val = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTP_CTL);
+    /* EN cleared, ISTAT set since new_count >= target_count */
+    g_assert_cmpuint(ctl_val, ==, 0x4);
+
+    /*
+     * CNTPCT runs independently of CNTP_CTL.EN: only the compare/IRQ
+     * logic is gated by EN, so the counter must keep advancing.
+     */
+    qtest_clock_step(global_qtest, TIMER_TEST_NS / 2);
+    count_after_disable = qtimer_read64(qtimer_view_base,
+                                        QCT_QTIMER_CNTPCT_LO);
+    g_assert_cmpuint(count_after_disable, >, new_count);
+
+    /* ISTAT remains set while CNTPCT >= CVAL, even with EN=0 */
+    ctl_val = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTP_CTL);
+    g_assert_cmpuint(ctl_val, ==, 0x4);
+}
+
+/* Test the access-control region: CNTFRQ, CNTSR, CNTTID_0, CNTACR frame 0 */
+static void test_qtimer_ac_region(void)
+{
+    uint32_t freq, sr, tid0, acr0;
+
+    freq = qtimer_read32(qtimer_ac_base, QCT_QTIMER_AC_CNTFRQ);
+    g_assert_cmpuint(freq, ==, QTIMER_DEFAULT_FREQ_HZ);
+
+    /* A write of 0 to CNTFRQ must be ignored (freq-hz must stay nonzero). */
+    qtimer_write32(qtimer_ac_base, QCT_QTIMER_AC_CNTFRQ, 0);
+    freq = qtimer_read32(qtimer_ac_base, QCT_QTIMER_AC_CNTFRQ);
+    g_assert_cmpuint(freq, ==, QTIMER_DEFAULT_FREQ_HZ);
+
+    qtimer_write32(qtimer_ac_base, QCT_QTIMER_AC_CNTSR, 0x3);
+    sr = qtimer_read32(qtimer_ac_base, QCT_QTIMER_AC_CNTSR);
+    g_assert_cmpuint(sr, ==, 0x3);
+
+    tid0 = qtimer_read32(qtimer_ac_base, QCT_QTIMER_AC_CNTTID_0);
+    g_assert_cmpuint(tid0, ==, 0x111);
+
+    /* CNTACR for frame 0 defaults to full read/write permissions. */
+    acr0 = qtimer_read32(qtimer_ac_base, QCT_QTIMER_AC_CNTACR_START);
+    g_assert_cmpuint(acr0, !=, 0);
+
+    qtimer_write32(qtimer_ac_base, QCT_QTIMER_AC_CNTACR_START, 0);
+    acr0 = qtimer_read32(qtimer_ac_base, QCT_QTIMER_AC_CNTACR_START);
+    g_assert_cmpuint(acr0, ==, 0);
+
+    /* Restore full permissions so later view-region tests keep working. */
+    qtimer_write32(qtimer_ac_base, QCT_QTIMER_AC_CNTACR_START,
+                   QCT_QTIMER_AC_CNTACR_ALL);
+}
+
+static void test_qtimer_access_denied(void)
+{
+    uint32_t acr_all = QCT_QTIMER_AC_CNTACR_ALL;
+    uint64_t cval, saved_cval;
+    uint32_t val;
+
+    /* Park CVAL at a known nonzero value while access is still permitted. */
+    saved_cval = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTPCT_LO) + 10000;
+    qtimer_write64(qtimer_view_base, QCT_QTIMER_CNTP_CVAL_LO, saved_cval);
+    g_assert_cmpuint(qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTP_CVAL_LO),
+                     ==, saved_cval);
+
+    /* Clearing RFRQ denies CNTFRQ reads. */
+    qtimer_write32(qtimer_ac_base, QCT_QTIMER_AC_CNTACR_START,
+                   acr_all & ~QCT_QTIMER_AC_CNTACR_RFRQ);
+    val = qtimer_read32(qtimer_view_base, QCT_QTIMER_CNT_FREQ);
+    g_assert_cmpuint(val, ==, 0);
+
+    /* Clearing RPCT denies CNTPCT reads, both halves. */
+    qtimer_write32(qtimer_ac_base, QCT_QTIMER_AC_CNTACR_START,
+                   acr_all & ~QCT_QTIMER_AC_CNTACR_RPCT);
+    val = qtimer_read32(qtimer_view_base, QCT_QTIMER_CNTPCT_LO);
+    g_assert_cmpuint(val, ==, 0);
+    val = qtimer_read32(qtimer_view_base, QCT_QTIMER_CNTPCT_HI);
+    g_assert_cmpuint(val, ==, 0);
+
+    /*
+     * Clearing RWPT denies the whole CNTP_* set: CVAL/TVAL/CTL reads all
+     * read back 0 even though CVAL holds saved_cval.
+     */
+    qtimer_write32(qtimer_ac_base, QCT_QTIMER_AC_CNTACR_START,
+                   acr_all & ~QCT_QTIMER_AC_CNTACR_RWPT);
+    cval = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTP_CVAL_LO);
+    g_assert_cmpuint(cval, ==, 0);
+    val = qtimer_read32(qtimer_view_base, QCT_QTIMER_CNTP_TVAL);
+    g_assert_cmpuint(val, ==, 0);
+    val = qtimer_read32(qtimer_view_base, QCT_QTIMER_CNTP_CTL);
+    g_assert_cmpuint(val, ==, 0);
+
+    /* Denied writes must be dropped rather than applied. */
+    qtimer_write64(qtimer_view_base, QCT_QTIMER_CNTP_CVAL_LO, 0x1234);
+    qtimer_write32(qtimer_view_base, QCT_QTIMER_CNTP_TVAL, 0x5678);
+    qtimer_write32(qtimer_view_base, QCT_QTIMER_CNTP_CTL, 1);
+
+    /* Restoring RWPT reveals that CVAL still holds the pre-denial value. */
+    qtimer_write32(qtimer_ac_base, QCT_QTIMER_AC_CNTACR_START, acr_all);
+    cval = qtimer_read64(qtimer_view_base, QCT_QTIMER_CNTP_CVAL_LO);
+    g_assert_cmpuint(cval, ==, saved_cval);
+    val = qtimer_read32(qtimer_view_base, QCT_QTIMER_CNTP_CTL);
+    g_assert_cmpuint(val & 1, ==, 0);
+
+    /* CNTFRQ and CNTPCT work again once permissions are back. */
+    val = qtimer_read32(qtimer_view_base, QCT_QTIMER_CNT_FREQ);
+    g_assert_cmpuint(val, ==, QTIMER_DEFAULT_FREQ_HZ);
+
+    /*
+     * An unimplemented offset in the view region is also an access error,
+     * and reads back as 0.
+     */
+    val = qtimer_read32(qtimer_view_base, 0x100);
+    g_assert_cmpuint(val, ==, 0);
+
+    /* Leave the frame with full permissions for any later test. */
+    qtimer_write32(qtimer_ac_base, QCT_QTIMER_AC_CNTACR_START, acr_all);
+}
+
+static void test_qtimer_frame_irq_routing(void)
+{
+    unsigned int frame, other;
+    uint64_t frame_base;
+
+    qtest_irq_intercept_in(global_qtest, "/machine/l2vic");
+
+    for (frame = 0; frame < QTIMER_NR_FRAMES; frame++) {
+        frame_base = qtimer_view_base + frame * QTIMER_FRAME_STRIDE;
+
+        qtimer_write32(frame_base, QCT_QTIMER_CNTP_TVAL, TIMER_TEST_OFFSET);
+        qtimer_write32(frame_base, QCT_QTIMER_CNTP_CTL,
+                       QCT_QTIMER_CNTP_CTL_ENABLE);
+        g_assert_false(qtest_get_irq(global_qtest,
+                                     QTIMER_L2VIC_IRQ_BASE + frame));
+
+        /* Step past the deadline so the frame raises its interrupt. */
+        qtest_clock_step(global_qtest, TIMER_TEST_NS * 2);
+        g_assert_true(qtest_get_irq(global_qtest,
+                                    QTIMER_L2VIC_IRQ_BASE + frame));
+
+        /* No other frame's line may be disturbed. */
+        for (other = 0; other < QTIMER_NR_FRAMES; other++) {
+            if (other != frame) {
+                g_assert_false(qtest_get_irq(global_qtest,
+                                             QTIMER_L2VIC_IRQ_BASE + other));
+            }
+        }
+
+        /* Clearing EN drops the interrupt, leaving a clean slate. */
+        qtimer_write32(frame_base, QCT_QTIMER_CNTP_CTL, 0);
+        g_assert_false(qtest_get_irq(global_qtest,
+                                     QTIMER_L2VIC_IRQ_BASE + frame));
+    }
+}
+
+typedef struct {
+    const char *machine;
+    const struct hexagon_machine_config *cfg;
+} QtimerMachineCfg;
+
+static const QtimerMachineCfg qtimer_machines[] = {
+    { "virt",      &v68n_1024 },
+    { "V66G_1024", &v66g_1024 },
+};
+
+static void test_qtimer_on_machine(gconstpointer data)
+{
+    const QtimerMachineCfg *mc = data;
+    g_autofree char *args = g_strdup_printf(
+        "-machine %s -global qct-qtimer.freq-scale=1", mc->machine);
+
+    qtimer_view_base = mc->cfg->qtmr_region;
+    qtimer_ac_base = mc->cfg->csr_base;
+
+    qtest_start(args);
+
+    test_qtimer_basic_access();
+    test_qtimer_multiple_frames();
+    test_qtimer_register_reads();
+    test_qtimer_control_registers();
+    test_qtimer_cval_access();
+    test_qtimer_counter_progression();
+    test_qtimer_timer_behavior();
+    test_qtimer_ac_region();
+    test_qtimer_access_denied();
+    test_qtimer_frame_irq_routing();
+
+    qtest_end();
+}
+
+int main(int argc, char **argv)
+{
+    size_t i;
+
+    g_test_init(&argc, &argv, NULL);
+
+    for (i = 0; i < ARRAY_SIZE(qtimer_machines); i++) {
+        g_autofree char *path = g_strdup_printf("/qct-qtimer/%s/all-tests",
+                                                qtimer_machines[i].machine);
+        qtest_add_data_func(path, &qtimer_machines[i], test_qtimer_on_machine);
+    }
+
+    return g_test_run();
+}
diff --git a/tests/tcg/hexagon/Makefile.target b/tests/tcg/hexagon/Makefile.target
index a2a0ffc..61adf63 100644
--- a/tests/tcg/hexagon/Makefile.target
+++ b/tests/tcg/hexagon/Makefile.target
@@ -50,7 +50,12 @@
 HEX_TESTS += scatter_gather
 HEX_TESTS += hvx_misc
 HEX_TESTS += hvx_histogram
+HEX_TESTS += fp_hvx
+HEX_TESTS += fp_hvx_cvt
+HEX_TESTS += fp_hvx_cmp
+HEX_TESTS += fp_hvx_disabled
 HEX_TESTS += invalid-slots
+HEX_TESTS += valid-slots
 HEX_TESTS += invalid-encoding
 HEX_TESTS += multiple-writes
 HEX_TESTS += unaligned_pc
@@ -135,6 +140,16 @@
 v69_hvx: v69_hvx.c hvx_misc.h
 v69_hvx: CFLAGS += -mhvx -Wno-unused-function
 v73_scalar: CFLAGS += -Wno-unused-function
+fp_hvx: fp_hvx.c hvx_misc.h hex_test.h
+fp_hvx: CFLAGS += -mhvx -mhvx-ieee-fp
+fp_hvx_disabled: fp_hvx_disabled.c hvx_misc.h hex_test.h
+fp_hvx_disabled: CFLAGS += -mhvx -mhvx-ieee-fp
+fp_hvx_cvt: fp_hvx_cvt.c hvx_misc.h hex_test.h
+fp_hvx_cvt: CFLAGS += -mhvx -mhvx-ieee-fp
+fp_hvx_cmp: fp_hvx_cmp.c hvx_misc.h hex_test.h
+fp_hvx_cmp: CFLAGS += -mhvx -mhvx-ieee-fp
+
+run-fp_hvx_disabled: QEMU_OPTS += -cpu v73,ieee-fp=false
 
 hvx_histogram: hvx_histogram.c hvx_histogram_row.S
 	$(CC) $(CFLAGS) $(CROSS_CC_GUEST_CFLAGS) $^ -o $@ $(LDFLAGS)
diff --git a/tests/tcg/hexagon/fp_hvx.c b/tests/tcg/hexagon/fp_hvx.c
new file mode 100644
index 0000000..4543a0a
--- /dev/null
+++ b/tests/tcg/hexagon/fp_hvx.c
@@ -0,0 +1,226 @@
+/*
+ *  Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ *  SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <hexagon_types.h>
+#include <hvx_hexagon_protos.h>
+
+int err;
+#include "hvx_misc.h"
+
+#if __HEXAGON_ARCH__ > 75
+#error "After v75, compiler will replace some FP HVX instructions."
+#endif
+
+/******************************************************************************
+ * NAN handling
+ *****************************************************************************/
+
+#define isnan(X) \
+     (sizeof(X) == bytes_hf ? ((raw_hf(X) & ~0x8000) > 0x7c00) : \
+                              ((raw_sf(X) & ~(1 << 31)) > 0x7f800000UL))
+
+#define CHECK_NAN(A, DEF_NAN) (isnan(A) ? DEF_NAN : (A))
+#define NAN_SF float_sf(0x7FFFFFFF)
+#define NAN_HF float_hf(0x7FFF)
+#define NAN_BF float_hf(0x7FFF)
+
+/******************************************************************************
+ * Binary operations
+ *****************************************************************************/
+
+#define DEF_TEST_OP_2(vop, op, type_res, type_arg) \
+    static void test_##vop##_##type_res##_##type_arg(void) \
+    { \
+        memset(expect, 0xff, sizeof(expect)); \
+        memset(output, 0xff, sizeof(output)); \
+        for (int i = 0; i < BUFSIZE; i++) { \
+            HVX_Vector *hvx_output = (HVX_Vector *)&output[i]; \
+            HVX_Vector hvx_buffer0 = *(HVX_Vector *)&buffer0[i]; \
+            HVX_Vector hvx_buffer1 = *(HVX_Vector *)&buffer1[i]; \
+            *hvx_output = \
+                Q6_V##type_res##_##vop##_V##type_arg##V##type_arg(hvx_buffer0, \
+                                                                  hvx_buffer1); \
+            for (int j = 0; j < MAX_VEC_SIZE_BYTES / bytes_##type_res; j++) { \
+                expect[i].type_res[j] = \
+                    raw_##type_res(op(float_##type_arg(buffer0[i].type_arg[j]), \
+                                      float_##type_arg(buffer1[i].type_arg[j]))); \
+            } \
+        } \
+        check_output_##type_res(__LINE__, BUFSIZE); \
+    }
+
+#define SUM(X, Y, DEF_NAN) CHECK_NAN((X) + (Y), DEF_NAN)
+#define SUB(X, Y, DEF_NAN) CHECK_NAN((X) - (Y), DEF_NAN)
+#define MULT(X, Y, DEF_NAN) CHECK_NAN((X) * (Y), DEF_NAN)
+
+#define SUM_SF(X, Y) SUM(X, Y, NAN_SF)
+#define SUM_HF(X, Y) SUM(X, Y, NAN_HF)
+#define SUB_SF(X, Y) SUB(X, Y, NAN_SF)
+#define SUB_HF(X, Y) SUB(X, Y, NAN_HF)
+#define MULT_SF(X, Y) MULT(X, Y, NAN_SF)
+#define MULT_HF(X, Y) MULT(X, Y, NAN_HF)
+
+DEF_TEST_OP_2(vadd, SUM_SF, sf, sf);
+DEF_TEST_OP_2(vadd, SUM_HF, hf, hf);
+DEF_TEST_OP_2(vsub, SUB_SF, sf, sf);
+DEF_TEST_OP_2(vsub, SUB_HF, hf, hf);
+DEF_TEST_OP_2(vmpy, MULT_SF, sf, sf);
+DEF_TEST_OP_2(vmpy, MULT_HF, hf, hf);
+
+#define signbit_fp(X) \
+    (sizeof(X) == bytes_hf ? ((raw_hf(X) & 0x8000) != 0) : \
+                             ((raw_sf(X) & 0x80000000) != 0))
+
+#define STD_MIN(X, Y) ((X) < (Y) ? (X) : (Y))
+#define STD_MAX(X, Y) ((X) > (Y) ? (X) : (Y))
+
+#define MIN(X, Y, DEF_NAN) \
+    ((isnan(X) || isnan(Y)) ? DEF_NAN : \
+     ((X) != (Y)) ? STD_MIN(X, Y) : (signbit_fp(X) ? (X) : (Y))) /* -0 < +0 */
+#define MAX(X, Y, DEF_NAN) \
+    ((isnan(X) || isnan(Y)) ? DEF_NAN : \
+     ((X) != (Y)) ? STD_MAX(X, Y) : (signbit_fp(X) ? (Y) : (X))) /* -0 < +0 */
+
+#define MIN_HF(X, Y) MIN(X, Y, NAN_HF)
+#define MAX_HF(X, Y) MAX(X, Y, NAN_HF)
+#define MIN_SF(X, Y) MIN(X, Y, NAN_SF)
+#define MAX_SF(X, Y) MAX(X, Y, NAN_SF)
+#define MIN_BF(X, Y) MIN(X, Y, NAN_BF)
+#define MAX_BF(X, Y) MAX(X, Y, NAN_BF)
+
+DEF_TEST_OP_2(vfmin, MIN_SF, sf, sf);
+DEF_TEST_OP_2(vfmax, MAX_SF, sf, sf);
+DEF_TEST_OP_2(vfmin, MIN_HF, hf, hf);
+DEF_TEST_OP_2(vfmax, MAX_HF, hf, hf);
+DEF_TEST_OP_2(vmin, MIN_BF, bf, bf);
+DEF_TEST_OP_2(vmax, MAX_BF, bf, bf);
+
+#define DEF_TEST_OP_2_INTERLEAVED(vop, op, type_res, type_arg) \
+    static void test_##vop##_##type_res##_##type_arg(void) \
+    { \
+        memset(expect, 0xff, sizeof(expect)); \
+        memset(output, 0xff, sizeof(output)); \
+        for (int i = 0; i < BUFSIZE / 2; i++) { \
+            HVX_VectorPair *hvx_output = (HVX_VectorPair *)&output[2 * i]; \
+            HVX_Vector hvx_buffer0 = *(HVX_Vector *)&buffer0[i]; \
+            HVX_Vector hvx_buffer1 = *(HVX_Vector *)&buffer1[i]; \
+            *hvx_output = \
+                Q6_W##type_res##_##vop##_V##type_arg##V##type_arg(hvx_buffer0, \
+                                                                  hvx_buffer1); \
+            for (int j = 0; j < MAX_VEC_SIZE_BYTES / bytes_##type_res; j++) { \
+                expect[2 * i].type_res[j] = \
+                    raw_##type_res(op(float_##type_arg(buffer0[i].type_arg[2 * j]), \
+                                      float_##type_arg(buffer1[i].type_arg[2 * j]))); \
+                expect[2 * i + 1].type_res[j] = \
+                    raw_##type_res(op(float_##type_arg(buffer0[i].type_arg[2 * j + 1]), \
+                                    float_##type_arg(buffer1[i].type_arg[2 * j + 1]))); \
+            } \
+        } \
+        check_output_##type_res(__LINE__, BUFSIZE); \
+    }
+
+DEF_TEST_OP_2_INTERLEAVED(vadd, SUM_SF, sf, bf);
+DEF_TEST_OP_2_INTERLEAVED(vsub, SUB_SF, sf, bf);
+DEF_TEST_OP_2_INTERLEAVED(vmpy, MULT_SF, sf, bf);
+
+/******************************************************************************
+ * Other tests
+ *****************************************************************************/
+
+static void test_vdmpy_sf_hf(bool acc)
+{
+    memset(expect, 0xff, sizeof(expect));
+
+    for (int i = 0; i < BUFSIZE; i++) {
+        HVX_Vector hvx_buffer0 = *(HVX_Vector *)&buffer0[i];
+        HVX_Vector hvx_buffer1 = *(HVX_Vector *)&buffer1[i];
+        HVX_Vector *hvx_output = (HVX_Vector *)&output[i];
+
+        uint32_t PREFIL_VAL = 0x111222;
+        *hvx_output = Q6_V_vsplat_R(PREFIL_VAL);
+
+        if (!acc) {
+            *hvx_output = Q6_Vsf_vdmpy_VhfVhf(hvx_buffer0, hvx_buffer1);
+        } else {
+            *hvx_output = Q6_Vsf_vdmpyacc_VsfVhfVhf(*hvx_output, hvx_buffer0,
+                                                    hvx_buffer1);
+        }
+
+        for (int j = 0; j < MAX_VEC_SIZE_BYTES / 4; j++) {
+            float a1 = float_hf_to_sf(float_hf(buffer0[i].hf[2 * j + 1]));
+            float a2 = float_hf_to_sf(float_hf(buffer0[i].hf[2 * j]));
+            float a3 = float_hf_to_sf(float_hf(buffer1[i].hf[2 * j + 1]));
+            float a4 = float_hf_to_sf(float_hf(buffer1[i].hf[2 * j]));
+            /*
+             * Note, IEEE FP specifies +0.0 + -0.0 == +0.0. So we use -0.0 in
+             * the default case to preserve the zero sign.
+             */
+            float prev = acc ? float_sf(PREFIL_VAL) : -0.0;
+            expect[i].sf[j] = raw_sf(CHECK_NAN((a1 * a3) + (a2 * a4) + prev, NAN_SF));
+        }
+    }
+    check_output_sf(__LINE__, BUFSIZE);
+}
+
+static void test_new(void)
+{
+    asm volatile("r0 = #%2\n"
+                 "v0 = vsplat(r0)\n"
+                 "vmem(%1 + #0) = v0\n"
+                 "r1 = #%3\n"
+                 "v1 = vsplat(r1)\n"
+                 "v2 = vsplat(r1)\n"
+                 "{\n"
+                 "  v0.sf = vadd(v1.sf, v2.sf)\n"
+                 "  vmem(%0 + #0) = v0.new\n"
+                 "}\n"
+                 :
+                 : "r"(output), "r"(expect), "i"(SF_two), "i"(SF_one)
+                 : "r0", "r1", "v0", "v1", "v2", "memory");
+    check_output_w(__LINE__, 1);
+}
+
+int main(void)
+{
+    init_buffers_fp();
+
+    /* add/sub */
+    test_vadd_sf_sf();
+    test_vadd_hf_hf();
+    test_vsub_sf_sf();
+    test_vsub_hf_hf();
+
+    /* multiply */
+    test_vmpy_sf_sf();
+    test_vmpy_hf_hf();
+
+    /* dot product */
+    test_vdmpy_sf_hf(false);
+    test_vdmpy_sf_hf(true);
+
+    test_new();
+
+    /* min/max */
+    test_vfmin_sf_sf();
+    test_vfmin_hf_hf();
+    test_vfmax_sf_sf();
+    test_vfmax_hf_hf();
+
+    /* bfloat */
+    init_buffers_bf();
+    test_vmin_bf_bf();
+    test_vmax_bf_bf();
+    test_vadd_sf_bf();
+    test_vsub_sf_bf();
+    test_vmpy_sf_bf();
+
+    puts(err ? "FAIL" : "PASS");
+    return err ? 1 : 0;
+}
diff --git a/tests/tcg/hexagon/fp_hvx_cmp.c b/tests/tcg/hexagon/fp_hvx_cmp.c
new file mode 100644
index 0000000..63fb423
--- /dev/null
+++ b/tests/tcg/hexagon/fp_hvx_cmp.c
@@ -0,0 +1,275 @@
+/*
+ *  Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ *  SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <assert.h>
+#include <hexagon_types.h>
+#include <hvx_hexagon_protos.h>
+
+#if __HEXAGON_ARCH__ > 75
+#error "After v75, compiler will replace some FP HVX instructions."
+#endif
+
+int err;
+#include "hvx_misc.h"
+#include "hex_test.h"
+
+#define MAX_TESTS_hf (MAX_VEC_SIZE_BYTES / 2)
+#define MAX_TESTS_sf (MAX_VEC_SIZE_BYTES / 4)
+#define MAX_TESTS_bf (MAX_VEC_SIZE_BYTES / 2)
+
+#define TRUE_MASK_sf 0xffffffff
+#define TRUE_MASK_hf 0xffff
+#define TRUE_MASK_bf 0xffff
+
+static const char *comparisons[MAX_TESTS_sf][2];
+static HVX_Vector *hvx_output = (HVX_Vector *)&output[0];
+static HVX_Vector buffers[2], true_vec, false_vec;
+static int exp_index;
+
+#define ADD_TEST_CMP(TYPE, VAL1, VAL2, EXP) do { \
+    ((MMVector *)&buffers[0])->TYPE[exp_index] = VAL1; \
+    ((MMVector *)&buffers[1])->TYPE[exp_index] = VAL2; \
+    expect[0].TYPE[exp_index] = EXP ? TRUE_MASK_##TYPE : 0; \
+    comparisons[exp_index][0] = #VAL1; \
+    comparisons[exp_index][1] = #VAL2; \
+    assert(exp_index < MAX_TESTS_##TYPE); \
+    exp_index++; \
+} while (0)
+
+#define TEST_CMP_GT(TYPE, VAL1, VAL2) do { \
+    ADD_TEST_CMP(TYPE, VAL1, VAL2, true); \
+    ADD_TEST_CMP(TYPE, VAL2, VAL1, false); \
+} while (0)
+
+#define PREP_TEST() do { \
+    memset(&buffers, 0, sizeof(buffers)); \
+    memset(expect, 0, sizeof(expect)); \
+    exp_index = 0; \
+} while (0)
+
+#define CHECK(TYPE, TYPESZ) do { \
+    HVX_VectorPred pred = Q6_Q_vcmp_gt_V##TYPE##V##TYPE(buffers[0], buffers[1]); \
+    *hvx_output = Q6_V_vmux_QVV(pred, true_vec, false_vec); \
+    for (int j = 0; j < MAX_VEC_SIZE_BYTES / TYPESZ; j++) { \
+        if (output[0].TYPE[j] != expect[0].TYPE[j]) { \
+            printf("ERROR: expected %s %s %s\n", comparisons[j][0], \
+                   (expect[0].TYPE[j] != 0 ? ">" : "<="), comparisons[j][1]); \
+            err++; \
+        } \
+    } \
+} while (0)
+
+static void test_cmp_sf(void)
+{
+    /*
+     * General ordering for sf:
+     * QNaN > SNaN > +Inf > numbers > -Inf > SNaN_neg > QNaN_neg
+     */
+
+    /* Test equality */
+    PREP_TEST();
+    ADD_TEST_CMP(sf, raw_sf(2.2),  raw_sf(2.2),  false);
+    ADD_TEST_CMP(sf, SF_SNaN,      SF_SNaN,      false);
+    CHECK(sf, 4);
+
+    /* Common numbers */
+    PREP_TEST();
+    TEST_CMP_GT(sf, raw_sf(2.2),  raw_sf(2.1));
+    TEST_CMP_GT(sf, raw_sf(0),    raw_sf(-2.2));
+    CHECK(sf, 4);
+
+    /* Infinity vs Infinity/NaN */
+    PREP_TEST();
+    TEST_CMP_GT(sf, SF_QNaN,      SF_INF);
+    TEST_CMP_GT(sf, SF_SNaN,      SF_INF);
+    TEST_CMP_GT(sf, SF_INF,       SF_INF_neg);
+    TEST_CMP_GT(sf, SF_INF,       SF_SNaN_neg);
+    TEST_CMP_GT(sf, SF_INF,       SF_QNaN_neg);
+    TEST_CMP_GT(sf, SF_INF_neg,   SF_SNaN_neg);
+    TEST_CMP_GT(sf, SF_INF_neg,   SF_QNaN_neg);
+    TEST_CMP_GT(sf, SF_SNaN,      SF_INF_neg);
+    TEST_CMP_GT(sf, SF_QNaN,      SF_INF_neg);
+    CHECK(sf, 4);
+
+    /* NaN vs NaN */
+    PREP_TEST();
+    TEST_CMP_GT(sf, SF_QNaN,      SF_SNaN);
+    TEST_CMP_GT(sf, SF_SNaN,      SF_SNaN_neg);
+    TEST_CMP_GT(sf, SF_SNaN_neg,  SF_QNaN_neg);
+    CHECK(sf, 4);
+
+    /* NaN vs non-NaN */
+    PREP_TEST();
+    TEST_CMP_GT(sf, SF_QNaN,      SF_one);
+    TEST_CMP_GT(sf, SF_SNaN,      SF_one);
+    TEST_CMP_GT(sf, SF_one,       SF_QNaN_neg);
+    TEST_CMP_GT(sf, SF_one,       SF_SNaN_neg);
+    CHECK(sf, 4);
+}
+
+static void test_cmp_hf(void)
+{
+    /*
+     * General ordering for hf:
+     * QNaN > SNaN > +Inf > numbers > -Inf > QSNaN_neg > QNaN_neg
+     */
+
+    /* Test equality */
+    PREP_TEST();
+    ADD_TEST_CMP(hf, raw_hf((_Float16)2.2),  raw_hf((_Float16)2.2),  false);
+    ADD_TEST_CMP(hf, HF_SNaN,                HF_SNaN,      false);
+    CHECK(hf, 2);
+
+    /* Common numbers */
+    PREP_TEST();
+    TEST_CMP_GT(hf, raw_hf((_Float16)2.2),  raw_hf((_Float16)2.1));
+    TEST_CMP_GT(hf, raw_hf((_Float16)0),    raw_hf((_Float16) - 2.2));
+    CHECK(hf, 2);
+
+    /* Infinity vs Infinity/NaN */
+    PREP_TEST();
+    TEST_CMP_GT(hf, HF_QNaN,      HF_INF);
+    TEST_CMP_GT(hf, HF_SNaN,      HF_INF);
+    TEST_CMP_GT(hf, HF_INF,       HF_INF_neg);
+    TEST_CMP_GT(hf, HF_INF,       HF_SNaN_neg);
+    TEST_CMP_GT(hf, HF_INF,       HF_QNaN_neg);
+    TEST_CMP_GT(hf, HF_INF_neg,   HF_SNaN_neg);
+    TEST_CMP_GT(hf, HF_INF_neg,   HF_QNaN_neg);
+    TEST_CMP_GT(hf, HF_SNaN,      HF_INF_neg);
+    TEST_CMP_GT(hf, HF_QNaN,      HF_INF_neg);
+    CHECK(hf, 2);
+
+    /* NaN vs NaN */
+    PREP_TEST();
+    TEST_CMP_GT(hf, HF_QNaN,      HF_SNaN);
+    TEST_CMP_GT(hf, HF_SNaN,      HF_SNaN_neg);
+    TEST_CMP_GT(hf, HF_SNaN_neg,  HF_QNaN_neg);
+    CHECK(hf, 2);
+
+    /* NaN vs non-NaN */
+    PREP_TEST();
+    TEST_CMP_GT(hf, HF_QNaN,      HF_one);
+    TEST_CMP_GT(hf, HF_SNaN,      HF_one);
+    TEST_CMP_GT(hf, HF_one,       HF_QNaN_neg);
+    TEST_CMP_GT(hf, HF_one,       HF_SNaN_neg);
+    CHECK(hf, 2);
+}
+
+static void test_cmp_bf(void)
+{
+    /*
+     * General ordering for bf:
+     * QNaN > SNaN > +Inf > numbers > -Inf > SNaN_neg > QNaN_neg
+     */
+
+    /* Test equality */
+    PREP_TEST();
+    ADD_TEST_CMP(bf, 0,       0,       false);
+    ADD_TEST_CMP(bf, BF_SNaN, BF_SNaN, false);
+    CHECK(bf, 2);
+
+    /* Common numbers */
+    PREP_TEST();
+    TEST_CMP_GT(bf, BF_two, BF_one);
+    TEST_CMP_GT(bf, BF_one, BF_zero);
+    CHECK(bf, 2);
+
+    /* Infinity vs Infinity/NaN */
+    PREP_TEST();
+    TEST_CMP_GT(bf, BF_QNaN,      BF_INF);
+    TEST_CMP_GT(bf, BF_SNaN,      BF_INF);
+    TEST_CMP_GT(bf, BF_INF,       BF_INF_neg);
+    TEST_CMP_GT(bf, BF_INF,       BF_SNaN_neg);
+    TEST_CMP_GT(bf, BF_INF,       BF_QNaN_neg);
+    TEST_CMP_GT(bf, BF_INF_neg,   BF_SNaN_neg);
+    TEST_CMP_GT(bf, BF_INF_neg,   BF_QNaN_neg);
+    TEST_CMP_GT(bf, BF_SNaN,      BF_INF_neg);
+    TEST_CMP_GT(bf, BF_QNaN,      BF_INF_neg);
+    CHECK(bf, 2);
+
+    /* NaN vs NaN */
+    PREP_TEST();
+    TEST_CMP_GT(bf, BF_QNaN,      BF_SNaN);
+    TEST_CMP_GT(bf, BF_SNaN,      BF_SNaN_neg);
+    TEST_CMP_GT(bf, BF_SNaN_neg,  BF_QNaN_neg);
+    CHECK(bf, 2);
+
+    /* NaN vs non-NaN */
+    PREP_TEST();
+    TEST_CMP_GT(bf, BF_QNaN,      BF_one);
+    TEST_CMP_GT(bf, BF_SNaN,      BF_one);
+    TEST_CMP_GT(bf, BF_one,       BF_QNaN_neg);
+    TEST_CMP_GT(bf, BF_one,       BF_SNaN_neg);
+    CHECK(bf, 2);
+}
+
+static void check_byte_pred(HVX_VectorPred pred, int byte_idx, uint8_t exp_mask,
+                            int line)
+{
+    /*
+     * Note: ((uint8_t *)&pred)[N] returns the expanded value of bit N:
+     * 0xFF if bit is set, 0x00 if clear.
+     */
+    for (int i = 0; i < 8; i++) {
+        int idx = byte_idx * 8 + i;
+        int val = ((uint8_t *)&pred)[idx];
+        int exp = (exp_mask >> i) & 1 ? 0xff : 0x00;
+        if (exp != val) {
+            printf("ERROR line %d: pred bit %d is 0x%x, should be 0x%x\n",
+                   line, idx, val, exp);
+            err++;
+        }
+    }
+}
+
+#define CHECK_BYTE_PRED(PRED, BYTE, EXP) check_byte_pred(PRED, BYTE, EXP, __LINE__)
+
+static void test_cmp_variants(void)
+{
+    HVX_VectorPred pred;
+
+    /*
+     * Setup: comparison result will have bits 4-7 set (0xF0 in pred byte 0)
+     * - sf[0]: SF_zero > SF_one = false -> bits 0-3 = 0
+     * - sf[1]: SF_one > SF_zero = true  -> bits 4-7 = 1
+     */
+    PREP_TEST();
+    ADD_TEST_CMP(sf, SF_zero, SF_one,  false);
+    ADD_TEST_CMP(sf, SF_one,  SF_zero, true);
+
+    /* greater and: 0xF0 & 0xF0 = 0xF0 */
+    memset(&pred, 0xF0, sizeof(pred));
+    pred = Q6_Q_vcmp_gtand_QVsfVsf(pred, buffers[0], buffers[1]);
+    CHECK_BYTE_PRED(pred, 0, 0xF0);
+
+    /* greater or: 0x0F | 0xF0 = 0xFF */
+    memset(&pred, 0x0F, sizeof(pred));
+    pred = Q6_Q_vcmp_gtor_QVsfVsf(pred, buffers[0], buffers[1]);
+    CHECK_BYTE_PRED(pred, 0, 0xFF);
+
+    /* greater xor: 0xFF ^ 0xF0 = 0x0F */
+    memset(&pred, 0xFF, sizeof(pred));
+    pred = Q6_Q_vcmp_gtxacc_QVsfVsf(pred, buffers[0], buffers[1]);
+    CHECK_BYTE_PRED(pred, 0, 0x0F);
+}
+
+int main(void)
+{
+    memset(&true_vec, 0xff, sizeof(true_vec));
+    memset(&false_vec, 0, sizeof(false_vec));
+
+    test_cmp_sf();
+    test_cmp_hf();
+    test_cmp_bf();
+    test_cmp_variants();
+
+    puts(err ? "FAIL" : "PASS");
+    return err ? 1 : 0;
+}
diff --git a/tests/tcg/hexagon/fp_hvx_cvt.c b/tests/tcg/hexagon/fp_hvx_cvt.c
new file mode 100644
index 0000000..7196bc9
--- /dev/null
+++ b/tests/tcg/hexagon/fp_hvx_cvt.c
@@ -0,0 +1,219 @@
+/*
+ *  Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ *  SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <hexagon_types.h>
+#include <hvx_hexagon_protos.h>
+
+#if __HEXAGON_ARCH__ > 75
+#error "After v75, compiler will replace some FP HVX instructions."
+#endif
+
+int err;
+#include "hvx_misc.h"
+#include "hex_test.h"
+
+#define NAN_BF 0x7FFF
+
+#define TEST_EXP(TO, FROM, VAL, EXP) do { \
+    ((MMVector *)&buffer)->FROM[index] = VAL; \
+    expect[0].TO[index] = EXP; \
+    index++; \
+} while (0)
+
+#define DEF_TEST_CVT(TO, FROM, TESTS) \
+    static void test_vcvt_##TO##_##FROM(void) \
+    { \
+        HVX_Vector *hvx_output = (HVX_Vector *)&output[0]; \
+        HVX_Vector buffer; \
+        int index = 0; \
+        memset(&buffer, 0, sizeof(buffer)); \
+        memset(expect, 0, sizeof(expect)); \
+        TESTS \
+        * hvx_output = Q6_V##TO##_vcvt_V##FROM(buffer); \
+        check_output_##TO(__LINE__, 1); \
+    }
+
+DEF_TEST_CVT(uh, hf, { \
+    TEST_EXP(uh, hf, HF_QNaN, UINT16_MAX); \
+    TEST_EXP(uh, hf, HF_SNaN, UINT16_MAX); \
+    TEST_EXP(uh, hf, HF_QNaN_neg, UINT16_MAX); \
+    TEST_EXP(uh, hf, HF_INF, UINT16_MAX); \
+    TEST_EXP(uh, hf, HF_INF_neg, 0); \
+    TEST_EXP(uh, hf, HF_neg_two, 0); \
+    TEST_EXP(uh, hf, HF_zero_neg, 0); \
+    TEST_EXP(uh, hf, raw_hf((_Float16)2.1), 2); \
+    TEST_EXP(uh, hf, HF_one_recip, 1); \
+})
+
+DEF_TEST_CVT(h, hf, { \
+    TEST_EXP(h, hf, HF_QNaN, INT16_MAX); \
+    TEST_EXP(h, hf, HF_SNaN, INT16_MAX); \
+    TEST_EXP(h, hf, HF_QNaN_neg, INT16_MAX); \
+    TEST_EXP(h, hf, HF_INF, INT16_MAX); \
+    TEST_EXP(h, hf, HF_INF_neg, INT16_MIN); \
+    TEST_EXP(h, hf, HF_neg_two, -2); \
+    TEST_EXP(h, hf, HF_zero_neg, 0); \
+    TEST_EXP(h, hf, raw_hf((_Float16)2.1), 2); \
+    TEST_EXP(h, hf, HF_one_recip, 1); \
+})
+
+/*
+ * Some cvt operations take two vectors as input and perform the following:
+ *    VdV.TO[4*i]   = OP(VuV.FROM[2*i]);
+ *    VdV.TO[4*i+1] = OP(VuV.FROM[2*i+1]);
+ *    VdV.TO[4*i+2] = OP(VvV.FROM[2*i]);
+ *    VdV.TO[4*i+3] = OP(VvV.FROM[2*i+1]))
+ * We use bf_index and index in a way that the tests are always done either
+ * using the first or third line of the above snippet.
+ */
+#define TEST_EXP_2(TO, FROM, VAL, EXP) do { \
+    ((MMVector *)&buffers[bf_index])->FROM[2 * index] = VAL; \
+    expect[0].TO[(4 * index) + (2 * bf_index)] = EXP; \
+    index++; \
+    bf_index = (bf_index + 1) % 2; \
+} while (0)
+
+#define DEF_TEST_CVT_2(TO, FROM, TESTS) \
+    static void test_vcvt_##TO##_##FROM(void) \
+    { \
+        HVX_Vector *hvx_output = (HVX_Vector *)&output[0]; \
+        HVX_Vector buffers[2]; \
+        int index = 0, bf_index = 0; \
+        memset(&buffers, 0, sizeof(buffers)); \
+        memset(expect, 0, sizeof(expect)); \
+        TESTS \
+        * hvx_output = Q6_V##TO##_vcvt_V##FROM##V##FROM(buffers[0], buffers[1]); \
+        check_output_##TO(__LINE__, 1); \
+    }
+
+DEF_TEST_CVT_2(ub, hf, { \
+    TEST_EXP_2(ub, hf, HF_QNaN, UINT8_MAX); \
+    TEST_EXP_2(ub, hf, HF_SNaN, UINT8_MAX); \
+    TEST_EXP_2(ub, hf, HF_QNaN_neg, UINT8_MAX); \
+    TEST_EXP_2(ub, hf, HF_INF, UINT8_MAX); \
+    TEST_EXP_2(ub, hf, HF_INF_neg, 0); \
+    TEST_EXP_2(ub, hf, HF_small_neg, 0); \
+    TEST_EXP_2(ub, hf, HF_neg_two, 0); \
+    TEST_EXP_2(ub, hf, HF_zero_neg, 0); \
+    TEST_EXP_2(ub, hf, raw_hf((_Float16)2.1), 2); \
+    TEST_EXP_2(ub, hf, HF_one_recip, 1); \
+})
+
+DEF_TEST_CVT_2(b, hf, { \
+    TEST_EXP_2(b, hf, HF_QNaN, INT8_MAX); \
+    TEST_EXP_2(b, hf, HF_SNaN, INT8_MAX); \
+    TEST_EXP_2(b, hf, HF_QNaN_neg, INT8_MAX); \
+    TEST_EXP_2(b, hf, HF_INF, INT8_MAX); \
+    TEST_EXP_2(b, hf, HF_INF_neg, INT8_MIN); \
+    TEST_EXP_2(b, hf, HF_small_neg, 0); \
+    TEST_EXP_2(b, hf, HF_neg_two, -2); \
+    TEST_EXP_2(b, hf, HF_zero_neg, 0); \
+    TEST_EXP_2(b, hf, raw_hf((_Float16)2.1), 2); \
+    TEST_EXP_2(b, hf, HF_one_recip, 1); \
+})
+
+#define DEF_TEST_VCONV(TO, FROM, TESTS) \
+    static void test_vconv_##TO##_##FROM(void) \
+    { \
+        HVX_Vector *hvx_output = (HVX_Vector *)&output[0]; \
+        HVX_Vector buffer; \
+        int index = 0; \
+        memset(&buffer, 0, sizeof(buffer)); \
+        memset(expect, 0, sizeof(expect)); \
+        TESTS \
+        * hvx_output = Q6_V##TO##_equals_V##FROM(buffer); \
+        check_output_##TO(__LINE__, 1); \
+    }
+
+DEF_TEST_VCONV(w, sf, { \
+    TEST_EXP(w, sf, SF_QNaN, INT32_MAX); \
+    TEST_EXP(w, sf, SF_SNaN, INT32_MAX); \
+    TEST_EXP(w, sf, SF_QNaN_neg, INT32_MIN); \
+    TEST_EXP(w, sf, SF_INF, INT32_MAX); \
+    TEST_EXP(w, sf, SF_INF_neg, INT32_MIN); \
+    TEST_EXP(w, sf, SF_small_neg, 0); \
+    TEST_EXP(w, sf, SF_neg_two, -2); \
+    TEST_EXP(w, sf, SF_zero_neg, 0); \
+    TEST_EXP(w, sf, raw_sf(2.1f), 2); \
+    TEST_EXP(w, sf, raw_sf(2.8f), 2); \
+})
+
+DEF_TEST_VCONV(h, hf, { \
+    TEST_EXP(h, hf, HF_QNaN, INT16_MAX); \
+    TEST_EXP(h, hf, HF_SNaN, INT16_MAX); \
+    TEST_EXP(h, hf, HF_QNaN_neg, INT16_MIN); \
+    TEST_EXP(h, hf, HF_INF, INT16_MAX); \
+    TEST_EXP(h, hf, HF_INF_neg, INT16_MIN); \
+    TEST_EXP(h, hf, HF_small_neg, 0); \
+    TEST_EXP(h, hf, HF_neg_two, -2); \
+    TEST_EXP(h, hf, HF_zero_neg, 0); \
+    TEST_EXP(h, hf, raw_hf((_Float16)2.1), 2); \
+    TEST_EXP(h, hf, raw_hf((_Float16)2.8), 2); \
+})
+
+DEF_TEST_VCONV(hf, h, { \
+    TEST_EXP(hf, h, 0, HF_zero); \
+    TEST_EXP(hf, h, 2, HF_two); \
+    TEST_EXP(hf, h, -2, HF_neg_two); \
+    TEST_EXP(hf, h, 2049, raw_hf((_Float16)2048)); /* rounds DOWN */ \
+    TEST_EXP(hf, h, 2051, raw_hf((_Float16)2052)); /* rounds UP */ \
+})
+
+DEF_TEST_VCONV(sf, w, { \
+    TEST_EXP(sf, w, 0, SF_zero); \
+    TEST_EXP(sf, w, 2, SF_two); \
+    TEST_EXP(sf, w, -2, SF_neg_two); \
+    TEST_EXP(sf, w, 16777217, raw_sf((float)16777216)); /* rounds DOWN */ \
+    TEST_EXP(sf, w, 16777219, raw_sf((float)16777220)); /* rounds UP */ \
+})
+
+#define TEST_EXP_BF(VAL, EXP) do { \
+    ((MMVector *)&buffers[1])->sf[index] = VAL; \
+    ((MMVector *)&buffers[0])->sf[index] = VAL; \
+    expect[0].bf[2 * index] = EXP; \
+    expect[0].bf[2 * index + 1] = EXP; \
+    index++; \
+} while (0)
+
+static void test_vconv_bf_sf(void)
+{
+    HVX_Vector *hvx_output = (HVX_Vector *)&output[0];
+    HVX_Vector buffers[2];
+    int index = 0;
+    memset(&buffers, 0, sizeof(buffers));
+    memset(expect, 0, sizeof(expect));
+
+    TEST_EXP_BF(SF_QNaN, NAN_BF);
+    TEST_EXP_BF(SF_SNaN, NAN_BF);
+    TEST_EXP_BF(SF_QNaN_neg, NAN_BF);
+    TEST_EXP_BF(SF_INF, BF_INF);
+    TEST_EXP_BF(SF_INF_neg, BF_INF_neg);
+    TEST_EXP_BF(SF_one, BF_one);
+    TEST_EXP_BF(SF_zero_neg, BF_zero_neg);
+
+    *hvx_output = Q6_Vbf_vcvt_VsfVsf(buffers[0], buffers[1]);
+    check_output_hf(__LINE__, 1);
+}
+
+int main(void)
+{
+    test_vcvt_uh_hf();
+    test_vcvt_h_hf();
+    test_vcvt_ub_hf();
+    test_vcvt_b_hf();
+    test_vconv_w_sf();
+    test_vconv_sf_w();
+    test_vconv_h_hf();
+    test_vconv_hf_h();
+    test_vconv_bf_sf();
+
+    puts(err ? "FAIL" : "PASS");
+    return err ? 1 : 0;
+}
diff --git a/tests/tcg/hexagon/fp_hvx_disabled.c b/tests/tcg/hexagon/fp_hvx_disabled.c
new file mode 100644
index 0000000..388a42e
--- /dev/null
+++ b/tests/tcg/hexagon/fp_hvx_disabled.c
@@ -0,0 +1,57 @@
+/*
+ *  Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ *  SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <hexagon_types.h>
+#include <hvx_hexagon_protos.h>
+
+int err;
+#include "hvx_misc.h"
+
+static void test_disabled(void)
+{
+    memset(output, 0xAA, sizeof(output));
+    memset(expect, 0, sizeof(expect));
+    asm volatile("r0 = #0xff\n"
+                 "v0 = vsplat(r0)\n"
+                 "r1 = #0x1\n"
+                 "v1 = vsplat(r1)\n"
+                 "v2 = vsplat(r1)\n"
+                 "v0.sf = vadd(v1.sf, v2.sf)\n"
+                 "vmem(%0 + #0) = v0\n"
+                 :
+                 : "r"(output)
+                 : "r0", "r1", "v0", "v1", "v2", "memory");
+    check_output_w(__LINE__, 1);
+}
+
+static void test_disabled_with_new(void)
+{
+    memset(output, 0xAA, sizeof(output));
+    memset(expect, 0, sizeof(expect));
+    asm volatile("r0 = #0xff\n"
+                 "v0 = vsplat(r0)\n"
+                 "r1 = #0x1\n"
+                 "v1 = vsplat(r1)\n"
+                 "v2 = vsplat(r1)\n"
+                 "{\n"
+                 "    v0.sf = vadd(v1.sf, v2.sf)\n"
+                 "    vmem(%0 + #0) = v0.new\n"
+                 "}\n"
+                 :
+                 : "r"(output)
+                 : "r0", "r1", "v0", "v1", "v2", "memory");
+    check_output_w(__LINE__, 1);
+}
+
+int main(void)
+{
+    test_disabled();
+    test_disabled_with_new();
+    puts(err ? "FAIL" : "PASS");
+    return err ? 1 : 0;
+}
diff --git a/tests/tcg/hexagon/hex_test.h b/tests/tcg/hexagon/hex_test.h
index cfed06a..f86e6e1 100644
--- a/tests/tcg/hexagon/hex_test.h
+++ b/tests/tcg/hexagon/hex_test.h
@@ -19,6 +19,8 @@
 #ifndef HEX_TEST_H
 #define HEX_TEST_H
 
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
+
 static inline void __check32(int line, uint32_t val, uint32_t expect)
 {
     if (val != expect) {
@@ -109,7 +111,36 @@
     "usr = r2\n\t"
 
 /* Some useful floating point values */
+const uint16_t HF_INF = 0x7c00;
+const uint16_t HF_INF_neg = 0xfc00;
+const uint16_t HF_QNaN = 0x7e00;
+const uint16_t HF_SNaN = 0x7d00;
+const uint16_t HF_SNaN_neg = 0xfd00;
+const uint16_t HF_QNaN_neg = 0xfe00;
+const uint16_t HF_zero = 0x0000;
+const uint16_t HF_zero_neg = 0x8000;
+const uint16_t HF_one = 0x3c00;
+const uint16_t HF_one_recip = 0x3bf9;
+const uint16_t HF_two = 0x4000;
+const uint16_t HF_small_neg = 0x8010;
+const uint16_t HF_any = 0x3c00;
+const uint16_t HF_neg_two = 0xc000;
+
+const uint16_t BF_INF           = 0x7f80;
+const uint16_t BF_INF_neg       = 0xff80;
+const uint16_t BF_QNaN          = 0x7fc0;
+const uint16_t BF_SNaN          = 0x7f81;
+const uint16_t BF_QNaN_neg      = 0xffc0;
+const uint16_t BF_SNaN_neg      = 0xff81;
+const uint16_t BF_HEX_NaN       = 0x7fff;
+const uint16_t BF_zero          = 0x0000;
+const uint16_t BF_zero_neg      = 0x8000;
+const uint16_t BF_one           = 0x3f80;
+const uint16_t BF_two           = 0x4000;
+const uint16_t BF_four          = 0x4080;
+
 const uint32_t SF_INF =              0x7f800000;
+const uint32_t SF_INF_neg =          0xff800000;
 const uint32_t SF_QNaN =             0x7fc00000;
 const uint32_t SF_QNaN_special =     0x7f800001;
 const uint32_t SF_SNaN =             0x7fb00000;
@@ -128,6 +159,7 @@
 const uint32_t SF_any =              0x3f800000;
 const uint32_t SF_denorm =           0x00000001;
 const uint32_t SF_random =           0x346001d6;
+const uint32_t SF_neg_two =          0xc0000000;
 
 const uint64_t DF_QNaN =             0x7ff8000000000000ULL;
 const uint64_t DF_SNaN =             0x7ff7000000000000ULL;
diff --git a/tests/tcg/hexagon/hvx_misc.c b/tests/tcg/hexagon/hvx_misc.c
index 90c3733..32a3661 100644
--- a/tests/tcg/hexagon/hvx_misc.c
+++ b/tests/tcg/hexagon/hvx_misc.c
@@ -20,6 +20,8 @@
 #include <stdbool.h>
 #include <string.h>
 #include <limits.h>
+#include <hexagon_types.h>
+#include <hvx_hexagon_protos.h>
 
 int err;
 
@@ -315,6 +317,34 @@
 TEST_VEC_OP2(vor, vor, , d, 8, |)
 TEST_VEC_OP1(vnot, vnot, , d, 8, ~)
 
+#define TEST_VEC_ABSDIFF(NAME, INTRINSIC, SRC_FIELD, DST_FIELD, \
+                         CHECK_FIELD, FIELDSZ) \
+static inline void test_##NAME(void) \
+{ \
+    HVX_Vector v0; \
+    HVX_Vector v1; \
+    HVX_Vector vres; \
+    for (int i = 0; i < BUFSIZE; i++) { \
+        memcpy(&v0, &buffer0[i], sizeof(MMVector)); \
+        memcpy(&v1, &buffer1[i], sizeof(MMVector)); \
+        vres = INTRINSIC(v0, v1); \
+        memcpy(&output[i], &vres, sizeof(MMVector)); \
+    } \
+    for (int i = 0; i < BUFSIZE; i++) { \
+        for (int j = 0; j < MAX_VEC_SIZE_BYTES / FIELDSZ; j++) { \
+            int64_t diff = (int64_t)buffer0[i].SRC_FIELD[j] - \
+                           (int64_t)buffer1[i].SRC_FIELD[j]; \
+            expect[i].DST_FIELD[j] = diff < 0 ? -diff : diff; \
+        } \
+    } \
+    check_output_##CHECK_FIELD(__LINE__, BUFSIZE); \
+}
+
+TEST_VEC_ABSDIFF(vabsdiffub, Q6_Vub_vabsdiff_VubVub, ub, ub, b, 1)
+TEST_VEC_ABSDIFF(vabsdiffuh, Q6_Vuh_vabsdiff_VuhVuh, uh, uh, h, 2)
+TEST_VEC_ABSDIFF(vabsdiffh,  Q6_Vuh_vabsdiff_VhVh,   h,  uh, h, 2)
+TEST_VEC_ABSDIFF(vabsdiffw,  Q6_Vuw_vabsdiff_VwVw,   w,  uw, w, 4)
+
 TEST_PRED_OP2(pred_or, or, |, "")
 TEST_PRED_OP2(pred_or_n, or, |, "!")
 TEST_PRED_OP2(pred_and, and, &, "")
@@ -386,6 +416,47 @@
     check_output_w(__LINE__, 2);
 }
 
+static void test_vsubwsat(void)
+{
+    const int32_t x0 = INT32_MIN;
+    const int32_t y0 = 1;
+    const int32_t x1 = INT32_MAX;
+    const int32_t y1 = -1;
+    HVX_Vector v0;
+    HVX_Vector v1;
+    HVX_Vector vres;
+
+    /* INT32_MIN - 1 underflows and must saturate to INT32_MIN */
+    memset(expect, 0x12, sizeof(MMVector));
+    memset(output, 0x34, sizeof(MMVector));
+
+    v0 = Q6_V_vsplat_R(x0);
+    v1 = Q6_V_vsplat_R(y0);
+    vres = Q6_Vw_vsub_VwVw_sat(v0, v1);
+    memcpy(&output[0], &vres, sizeof(MMVector));
+
+    for (int j = 0; j < MAX_VEC_SIZE_BYTES / 4; j++) {
+        expect[0].w[j] = INT32_MIN;
+    }
+
+    check_output_w(__LINE__, 1);
+
+    /* INT32_MAX - (-1) overflows and must saturate to INT32_MAX */
+    memset(expect, 0x12, sizeof(MMVector));
+    memset(output, 0x34, sizeof(MMVector));
+
+    v0 = Q6_V_vsplat_R(x1);
+    v1 = Q6_V_vsplat_R(y1);
+    vres = Q6_Vw_vsub_VwVw_sat(v0, v1);
+    memcpy(&output[0], &vres, sizeof(MMVector));
+
+    for (int j = 0; j < MAX_VEC_SIZE_BYTES / 4; j++) {
+        expect[0].w[j] = INT32_MAX;
+    }
+
+    check_output_w(__LINE__, 1);
+}
+
 static void test_load_tmp_predicated(void)
 {
     void *p0 = buffer0;
@@ -530,6 +601,12 @@
 
     test_vadduwsat();
     test_vsubuwsat_dv();
+    test_vsubwsat();
+
+    test_vabsdiffub();
+    test_vabsdiffuh();
+    test_vabsdiffh();
+    test_vabsdiffw();
 
     test_load_tmp_predicated();
     test_load_cur_predicated();
diff --git a/tests/tcg/hexagon/hvx_misc.h b/tests/tcg/hexagon/hvx_misc.h
index 2e86834..c21ea97 100644
--- a/tests/tcg/hexagon/hvx_misc.h
+++ b/tests/tcg/hexagon/hvx_misc.h
@@ -18,6 +18,8 @@
 #ifndef HVX_MISC_H
 #define HVX_MISC_H
 
+#include "hex_test.h"
+
 static inline void check(int line, int i, int j,
                          uint64_t result, uint64_t expect)
 {
@@ -34,9 +36,12 @@
     uint64_t ud[MAX_VEC_SIZE_BYTES / 8];
     int64_t   d[MAX_VEC_SIZE_BYTES / 8];
     uint32_t uw[MAX_VEC_SIZE_BYTES / 4];
+    uint32_t sf[MAX_VEC_SIZE_BYTES / 4]; /* convenience alias */
     int32_t   w[MAX_VEC_SIZE_BYTES / 4];
     uint16_t uh[MAX_VEC_SIZE_BYTES / 2];
+    uint16_t hf[MAX_VEC_SIZE_BYTES / 2]; /* convenience alias */
     int16_t   h[MAX_VEC_SIZE_BYTES / 2];
+    uint16_t  bf[MAX_VEC_SIZE_BYTES / 2];
     uint8_t  ub[MAX_VEC_SIZE_BYTES / 1];
     int8_t    b[MAX_VEC_SIZE_BYTES / 1];
 } MMVector;
@@ -63,8 +68,13 @@
 
 CHECK_OUTPUT_FUNC(d,  8)
 CHECK_OUTPUT_FUNC(w,  4)
+CHECK_OUTPUT_FUNC(sf, 4)
 CHECK_OUTPUT_FUNC(h,  2)
+CHECK_OUTPUT_FUNC(uh, 2)
+CHECK_OUTPUT_FUNC(hf, 2)
+CHECK_OUTPUT_FUNC(ub,  1)
 CHECK_OUTPUT_FUNC(b,  1)
+CHECK_OUTPUT_FUNC(bf,  2)
 
 static inline void init_buffers(void)
 {
@@ -81,6 +91,58 @@
     }
 }
 
+static const uint32_t FP_VALUES[] = {
+    SF_INF, SF_INF_neg, SF_QNaN, SF_QNaN_special, SF_SNaN, SF_QNaN_neg,
+    SF_SNaN_neg, SF_HEX_NaN, SF_zero, SF_zero_neg, SF_one, SF_one_recip,
+    SF_one_invsqrta, SF_two, SF_four, SF_small_neg, SF_large_pos, SF_any,
+    SF_denorm, SF_random, SF_neg_two,
+};
+#define FP_VALUES_MAX ARRAY_SIZE(FP_VALUES)
+
+static const uint16_t BF_VALUES[] = {
+    BF_INF, BF_INF_neg, BF_QNaN, BF_SNaN, BF_QNaN_neg, BF_SNaN_neg,
+    BF_HEX_NaN, BF_zero, BF_zero_neg, BF_one, BF_two, BF_four,
+};
+#define BF_VALUES_MAX ARRAY_SIZE(BF_VALUES)
+
+static inline void init_buffers_fp(void)
+{
+    _Static_assert(BUFSIZE * (MAX_VEC_SIZE_BYTES / 4) >
+                   FP_VALUES_MAX * FP_VALUES_MAX,
+                   "test arrays can't fit all FP_VALUES combinations");
+    int counter1 = 0, counter2 = 0;
+    for (int i = 0; i < BUFSIZE; i++) {
+        for (int j = 0; j < MAX_VEC_SIZE_BYTES / 4; j++) {
+            buffer0[i].sf[j] = FP_VALUES[counter1];
+            buffer1[i].sf[j] = FP_VALUES[counter2];
+            counter2++;
+            if (counter2 == FP_VALUES_MAX) {
+                counter2 = 0;
+                counter1 = (counter1 + 1) % FP_VALUES_MAX;
+            }
+        }
+    }
+}
+
+static inline void init_buffers_bf(void)
+{
+    _Static_assert(BUFSIZE * (MAX_VEC_SIZE_BYTES / 2) >
+                   BF_VALUES_MAX * BF_VALUES_MAX,
+                   "test arrays can't fit all BF_VALUES combinations");
+    int counter1 = 0, counter2 = 0;
+    for (int i = 0; i < BUFSIZE; i++) {
+        for (int j = 0; j < MAX_VEC_SIZE_BYTES / 2; j++) {
+            buffer0[i].bf[j] = BF_VALUES[counter1];
+            buffer1[i].bf[j] = BF_VALUES[counter2];
+            counter2++;
+            if (counter2 == BF_VALUES_MAX) {
+                counter2 = 0;
+                counter1 = (counter1 + 1) % BF_VALUES_MAX;
+            }
+        }
+    }
+}
+
 #define VEC_OP1(ASM, EL, IN, OUT) \
     asm("v2 = vmem(%0 + #0)\n\t" \
         "v2" #EL " = " #ASM "(v2" #EL ")\n\t" \
@@ -175,4 +237,15 @@
     check_output_b(__LINE__, BUFSIZE); \
 }
 
+#define float_sf(x) ({ typeof(x) _x = (x); *((float *)&(_x)); })
+#define float_hf(x) ({ typeof(x) _x = (x); *((_Float16 *) &(_x)); })
+#define float_bf(x) ({ uint32_t _u = ((uint32_t)(x)) << 16; *((float *)&(_u)); })
+#define raw_sf(x) ({ typeof(x) _x = (x); *((uint32_t *)&(_x)); })
+#define raw_hf(x) ({ typeof(x) _x = (x); *((uint16_t *)&(_x)); })
+#define raw_bf(x) ({ typeof(x) _x = (x); (uint16_t)(*((uint32_t *)&(_x)) >> 16); })
+#define float_hf_to_sf(x) ((float)x)
+#define bytes_hf 2
+#define bytes_sf 4
+#define bytes_bf 2
+
 #endif
diff --git a/tests/tcg/hexagon/invalid-slots.c b/tests/tcg/hexagon/invalid-slots.c
index 607027f..f2dace2 100644
--- a/tests/tcg/hexagon/invalid-slots.c
+++ b/tests/tcg/hexagon/invalid-slots.c
@@ -55,6 +55,75 @@
     return sig;
 }
 
+/* Load then indirect jump, load encoded first: no high slot left for jump. */
+static int test_invalid_slots_highslot(void)
+{
+    int sig;
+
+    asm volatile(
+        "r0 = #0\n"
+        "r1 = ##1f\n"
+        "memw(%1) = r1\n"
+        "r3 = #mem\n"
+        ".word 0x91834006\n" /* { r6 = memw(r3+#0); */
+        ".word 0x529fc000\n" /*   jumpr r31 }        */
+        "1:\n"
+        "%0 = r0\n"
+        : "=r"(sig)
+        : "r"(&resume_pc)
+        : "r0", "r1", "r3", "r6", "memory");
+
+    return sig;
+}
+
+/*
+ * Three predicate-logical ops: each is restricted to slots 2 and 3, so the
+ * fourth-and-fifth-slot-free packet still has only two slots for three ops.
+ * No change-of-flow is involved, so the only reason to reject it is the slot
+ * conflict.
+ */
+static int test_invalid_slots_crslot23(void)
+{
+    int sig;
+
+    asm volatile(
+        "r0 = #0\n"
+        "r1 = ##1f\n"
+        "memw(%1) = r1\n"
+        ".word 0x6b024100\n" /* { p0 = and(p1, p2); */
+        ".word 0x6b224103\n" /*   p3 = or(p1, p2);   */
+        ".word 0x6b42c301\n" /*   p1 = xor(p2, p3) } */
+        "1:\n"
+        "%0 = r0\n"
+        : "=r"(sig)
+        : "r"(&resume_pc)
+        : "r0", "r1", "p0", "p1", "p3", "memory");
+
+    return sig;
+}
+
+/* Three transfers plus a duplex: five ops for four slots. */
+static int test_invalid_slots_five(void)
+{
+    int sig;
+
+    asm volatile(
+        "r0 = #0\n"
+        "r1 = ##1f\n"
+        "memw(%1) = r1\n"
+        ".word 0x78004020\n" /* { r0 = #1;           */
+        ".word 0x78004041\n" /*   r1 = #2;            */
+        ".word 0x78004062\n" /*   r2 = #3;            */
+        ".word 0x28452856\n" /*   r5 = #4; r6 = #5 }  */
+        "1:\n"
+        "%0 = r0\n"
+        : "=r"(sig)
+        : "r"(&resume_pc)
+        : "r0", "r1", "r2", "r5", "r6", "memory");
+
+    return sig;
+}
+
 int main()
 {
     struct sigaction act;
@@ -65,6 +134,9 @@
     assert(sigaction(SIGILL, &act, NULL) == 0);
 
     assert(test_invalid_slots() == SIGILL);
+    assert(test_invalid_slots_highslot() == SIGILL);
+    assert(test_invalid_slots_crslot23() == SIGILL);
+    assert(test_invalid_slots_five() == SIGILL);
 
     puts("PASS");
     return EXIT_SUCCESS;
diff --git a/tests/tcg/hexagon/read_write_overlap.c b/tests/tcg/hexagon/read_write_overlap.c
index 95c54cc..7eaf75f 100644
--- a/tests/tcg/hexagon/read_write_overlap.c
+++ b/tests/tcg/hexagon/read_write_overlap.c
@@ -115,12 +115,59 @@
     check32(swiz(0x11223344), 0x44332211);
 }
 
+#define CMPY(NAME, ASM) \
+static inline uint32_t NAME##_rd_eq_rs(uint32_t x, uint32_t y) \
+{ \
+    uint32_t res; \
+    asm("r7 = %1\n\t" \
+        ASM("r7", "%2") "\n\t" \
+        "%0 = r7\n\t" \
+        : "=r"(res) : "r"(x), "r"(y) : "r7"); \
+    return res; \
+} \
+static inline uint32_t NAME##_rd_eq_rt(uint32_t x, uint32_t y) \
+{ \
+    uint32_t res; \
+    asm("r7 = %2\n\t" \
+        ASM("%1", "r7") "\n\t" \
+        "%0 = r7\n\t" \
+        : "=r"(res) : "r"(x), "r"(y) : "r7"); \
+    return res; \
+}
+
+#define CMPY_RND_SAT(RS, RT)     "r7 = cmpy(" RS "," RT "):rnd:sat"
+#define CMPY_S1_RND_SAT(RS, RT)  "r7 = cmpy(" RS "," RT "):<<1:rnd:sat"
+#define CMPYC_RND_SAT(RS, RT)    "r7 = cmpy(" RS "," RT "*):rnd:sat"
+#define CMPYC_S1_RND_SAT(RS, RT) "r7 = cmpy(" RS "," RT "*):<<1:rnd:sat"
+
+CMPY(cmpyrs_s0, CMPY_RND_SAT)
+CMPY(cmpyrs_s1, CMPY_S1_RND_SAT)
+CMPY(cmpyrsc_s0, CMPYC_RND_SAT)
+CMPY(cmpyrsc_s1, CMPYC_S1_RND_SAT)
+
+static void test_cmpy(void)
+{
+    check32(cmpyrs_s0_rd_eq_rs(0x32195ce2, 0xef862430), 0x011b105b);
+    check32(cmpyrs_s0_rd_eq_rt(0x32195ce2, 0xef862430), 0x011b105b);
+    check32(cmpyrs_s1_rd_eq_rs(0x32195ce2, 0xef862430), 0x023520b5);
+    check32(cmpyrs_s1_rd_eq_rt(0x32195ce2, 0xef862430), 0x023520b5);
+    check32(cmpyrsc_s0_rd_eq_rs(0x32195ce2, 0xef862430), 0x0d0f09e8);
+    check32(cmpyrsc_s0_rd_eq_rt(0x32195ce2, 0xef862430), 0x0d0f09e8);
+    check32(cmpyrsc_s1_rd_eq_rs(0x32195ce2, 0xef862430), 0x1a1f13d0);
+    check32(cmpyrsc_s1_rd_eq_rt(0x32195ce2, 0xef862430), 0x1a1f13d0);
+
+    /* Both halves saturate */
+    check32(cmpyrs_s1_rd_eq_rs(0x80008000, 0x80008000), 0x7fff0000);
+    check32(cmpyrsc_s1_rd_eq_rs(0x7fff8001, 0x80017fff), 0x00008000);
+}
+
 int main()
 {
     test_insert();
     test_insert_rp();
     test_asr_r_svw_trun();
     test_swiz();
+    test_cmpy();
 
     puts(err ? "FAIL" : "PASS");
     return err ? EXIT_FAILURE : EXIT_SUCCESS;
diff --git a/tests/tcg/hexagon/valid-slots.c b/tests/tcg/hexagon/valid-slots.c
new file mode 100644
index 0000000..70d9b0b
--- /dev/null
+++ b/tests/tcg/hexagon/valid-slots.c
@@ -0,0 +1,62 @@
+/*
+ * Regression tests for valid packets that qemu incorrectly rejected as
+ * invalid.
+ *
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+
+int err;
+
+#include "hex_test.h"
+
+/* volatile to keep the load from being optimized away */
+static volatile int buf[2] = { 0x1234, 0 };
+
+/* Load and register transfer in one packet, load encoded first. */
+static int32_t load_imm_pair(void)
+{
+    int32_t out;
+    /* { r6 = memw(r3+#-4); r7 = #0x4ae6 } */
+    asm volatile(
+        "{ r3 = %1 }\n\t"
+        ".word 0x97837fe6\n\t"
+        ".word 0x7845dcc7\n\t"
+        "{ %0 = r6 }\n\t"
+        : "=r"(out) : "r"(&buf[1]) : "r3", "r6", "r7");
+    return out;
+}
+
+static int32_t dcbuf[8] __attribute__((aligned(32)));
+
+/* Slot-0-only op (dczeroa) packed last with three transfers. */
+static void slot0_restricted(int32_t *out)
+{
+    asm volatile(
+        "{ %0 = #0x11\n\t"
+        "  %1 = #0x22\n\t"
+        "  %2 = #0x33\n\t"
+        "  dczeroa(%3) }\n\t"
+        : "=r"(out[0]), "=r"(out[1]), "=r"(out[2])
+        : "r"(dcbuf) : "memory");
+}
+
+int main()
+{
+    int32_t r[3];
+
+    check32(load_imm_pair(), 0x1234);
+
+    dcbuf[0] = 0x5a5a5a5a;
+    slot0_restricted(r);
+    check32(r[0], 0x11);
+    check32(r[1], 0x22);
+    check32(r[2], 0x33);
+    check32(dcbuf[0], 0);       /* dczeroa cleared the line */
+
+    puts(err ? "FAIL" : "PASS");
+    return err;
+}
diff --git a/tests/tcg/s390x/Makefile.target b/tests/tcg/s390x/Makefile.target
index 0ca030d..97c0f02 100644
--- a/tests/tcg/s390x/Makefile.target
+++ b/tests/tcg/s390x/Makefile.target
@@ -50,6 +50,7 @@
 TESTS+=ts
 TESTS+=ex-smc
 TESTS+=divide-to-integer
+TESTS+=stckf
 
 cdsg: CFLAGS+=-pthread
 cdsg: LDFLAGS+=-pthread
@@ -75,6 +76,7 @@
 Z13_TESTS+=vstl
 Z13_TESTS+=vrep
 Z13_TESTS+=precise-smc-user
+Z13_TESTS+=prno-trng
 $(Z13_TESTS): CFLAGS+=-march=z13 -O2
 TESTS+=$(Z13_TESTS)
 
diff --git a/tests/tcg/s390x/div.c b/tests/tcg/s390x/div.c
index 6ad9900..124c9ec 100644
--- a/tests/tcg/s390x/div.c
+++ b/tests/tcg/s390x/div.c
@@ -1,6 +1,15 @@
 #include <assert.h>
+#include <signal.h>
 #include <stdint.h>
 
+/* Set asynchronously by the signal handler. */
+static volatile int signum;
+
+static void signal_handler(int n)
+{
+    signum = n;
+}
+
 static void test_dr(void)
 {
     register int32_t r0 asm("r0") = -1;
@@ -65,11 +74,39 @@
     assert(r == 1);
 }
 
+/*
+ * The most negative dividend divided by -1 yields a quotient that does not
+ * fit into 32 bits, so DR must raise a fixed-point-divide exception.
+ */
+static void test_dr_overflow(void)
+{
+    struct sigaction act = { .sa_handler = signal_handler };
+    register int32_t r0 asm("r0");
+    register int32_t r1 asm("r1");
+    int32_t b = -1;
+    int err;
+
+    err = sigaction(SIGFPE, &act, NULL);
+    assert(err == 0);
+    signum = -1;
+
+    r0 = 0x80000000;
+    r1 = 0;
+    asm volatile("dr %[r0],%[b]"
+                 : [r0] "+r" (r0), [r1] "+r" (r1)
+                 : [b] "r" (b)
+                 : "cc");
+    assert(signum == SIGFPE);
+
+    signal(SIGFPE, SIG_DFL);
+}
+
 int main(void)
 {
     test_dr();
     test_dlr();
     test_dsgr();
     test_dlgr();
+    test_dr_overflow();
     return 0;
 }
diff --git a/tests/tcg/s390x/prno-trng.c b/tests/tcg/s390x/prno-trng.c
new file mode 100644
index 0000000..43eea5d
--- /dev/null
+++ b/tests/tcg/s390x/prno-trng.c
@@ -0,0 +1,67 @@
+/*
+ * Test that PERFORM RANDOM NUMBER OPERATION TRNG is interruptible.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+#include <assert.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <asm/ucontext.h>
+
+static unsigned char buf1[16 * 1024 * 1024];
+static unsigned char buf2[16 * 1024 * 1024];
+
+static volatile sig_atomic_t interrupted;
+
+static void sigprof_handler(int sig, siginfo_t *info, void *ucontext)
+{
+    struct ucontext *uc = ucontext;
+    unsigned long addr = uc->uc_mcontext.regs.psw.addr;
+
+    if (*(unsigned short *)(addr - 4) == 0xb93c) {
+        interrupted++;
+    }
+}
+
+static void prno_trng(void *b1, unsigned long l1, void *b2, unsigned long l2)
+{
+    register unsigned long r0 asm("r0") = 114;  /* TRNG */
+    register unsigned long r2 asm("r2") = (unsigned long)b1;
+    register unsigned long r3 asm("r3") = l1;
+    register unsigned long r4 asm("r4") = (unsigned long)b2;
+    register unsigned long r5 asm("r5") = l2;
+
+    asm volatile("0: ppno %[r2],%[r4]\n"  /* prno alias for old toolchains */
+                 "   jo 0b"
+                 : [r2] "+r" (r2), [r3] "+r" (r3)
+                 , [r4] "+r" (r4), [r5] "+r" (r5)
+                 : "r" (r0)
+                 : "cc", "memory");
+}
+
+int main(void)
+{
+    struct itimerval it = {
+        .it_interval = { .tv_usec = 10000 },  /* 0.01s */
+        .it_value = { .tv_usec = 10000 },
+    };
+    struct sigaction act = {
+        .sa_sigaction = sigprof_handler,
+        .sa_flags = SA_SIGINFO,
+    };
+    int err;
+
+    err = sigaction(SIGPROF, &act, NULL);
+    assert(err == 0);
+    err = setitimer(ITIMER_PROF, &it, NULL);
+    assert(err == 0);
+
+    prno_trng(buf1, sizeof(buf1), buf2, sizeof(buf2));
+    printf("interrupted %d times\n", interrupted);
+    assert(interrupted >= 3);
+
+    return EXIT_SUCCESS;
+}
diff --git a/tests/tcg/s390x/stckf.c b/tests/tcg/s390x/stckf.c
new file mode 100644
index 0000000..51c8c9d
--- /dev/null
+++ b/tests/tcg/s390x/stckf.c
@@ -0,0 +1,44 @@
+/*
+ * Test that a faulting STORE CLOCK FAST does not clobber the condition code.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+#include <assert.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+static void handle_sigsegv(int sig, siginfo_t *info, void *ucontext)
+{
+    mcontext_t *mcontext = &((ucontext_t *)ucontext)->uc_mcontext;
+
+    /* The condition code must be the one set by SLGR, not garbage. */
+    _exit(((mcontext->psw.mask >> 44) & 3) == 3 ? EXIT_SUCCESS : EXIT_FAILURE);
+}
+
+int main(void)
+{
+    struct sigaction act = {
+        .sa_sigaction = handle_sigsegv,
+        .sa_flags = SA_SIGINFO,
+    };
+    int err;
+
+    err = sigaction(SIGSEGV, &act, NULL);
+    assert(err == 0);
+
+    asm volatile(
+        "lghi %%r1,100\n"
+        "lghi %%r2,0\n"
+        "clgr %%r1,%%r2\n"  /* CC_OP_LTUGTU_64 */
+                            /* cc_src=100 is not valid for CC_OP_SUBU */
+        "ipm %%r0\n"        /* force cc_src to env */
+        "lghi %%r3,5\n"
+        "lghi %%r4,3\n"
+        "slgr %%r3,%%r4\n"  /* CC_OP_SUBU, cc=3 */
+        "lghi %%r5,0\n"
+        "stckf 0(%%r5)\n"   /* faults; cc must stay 3 */
+        : : : "r0", "r1", "r2", "r3", "r4", "r5", "cc", "memory");
+
+    return EXIT_FAILURE;
+}
diff --git a/tests/unit/test-crypto-cipher.c b/tests/unit/test-crypto-cipher.c
index 1331d55..420c826 100644
--- a/tests/unit/test-crypto-cipher.c
+++ b/tests/unit/test-crypto-cipher.c
@@ -810,6 +810,230 @@
     qcrypto_cipher_free(cipher);
 }
 
+typedef struct QCryptoCipherGcmTestData QCryptoCipherGcmTestData;
+struct QCryptoCipherGcmTestData {
+    const char *path;
+    QCryptoCipherAlgo alg;
+    const char *key;
+    const char *iv;
+    /* associated data, or NULL for none */
+    const char *aad;
+    const char *plaintext;
+    const char *ciphertext;
+    const char *tag;
+};
+
+/*
+ * AES-GCM test vectors from "The Galois/Counter Mode of Operation (GCM)"
+ * (McGrew & Viega, also NIST SP 800-38D), with a 96-bit IV and a 128-bit
+ * tag. Each entry's "Test case N" label is the numbered test case from that
+ * document (Appendix B / the GCM specification's test vectors).
+ */
+static QCryptoCipherGcmTestData gcm_test_data[] = {
+    {
+        /* Test case 2 */
+        .path = "/crypto/cipher/aes-gcm-128-2",
+        .alg = QCRYPTO_CIPHER_ALGO_AES_128,
+        .key = "00000000000000000000000000000000",
+        .iv = "000000000000000000000000",
+        .plaintext = "00000000000000000000000000000000",
+        .ciphertext = "0388dace60b6a392f328c2b971b2fe78",
+        .tag = "ab6e47d42cec13bdf53a67b21257bddf",
+    },
+    {
+        /* Test case 3 (no AAD) */
+        .path = "/crypto/cipher/aes-gcm-128-3",
+        .alg = QCRYPTO_CIPHER_ALGO_AES_128,
+        .key = "feffe9928665731c6d6a8f9467308308",
+        .iv = "cafebabefacedbaddecaf888",
+        .plaintext =
+            "d9313225f88406e5a55909c5aff5269a"
+            "86a7a9531534f7da2e4c303d8a318a72"
+            "1c3c0c95956809532fcf0e2449a6b525"
+            "b16aedf5aa0de657ba637b391aafd255",
+        .ciphertext =
+            "42831ec2217774244b7221b784d0d49c"
+            "e3aa212f2c02a4e035c17e2329aca12e"
+            "21d514b25466931c7d8f6a5aac84aa05"
+            "1ba30b396a0aac973d58e091473f5985",
+        .tag = "4d5c2af327cd64a62cf35abd2ba6fab4",
+    },
+    {
+        /* Test case 4 (with AAD) */
+        .path = "/crypto/cipher/aes-gcm-128-4",
+        .alg = QCRYPTO_CIPHER_ALGO_AES_128,
+        .key = "feffe9928665731c6d6a8f9467308308",
+        .iv = "cafebabefacedbaddecaf888",
+        .aad = "feedfacedeadbeeffeedfacedeadbeefabaddad2",
+        .plaintext =
+            "d9313225f88406e5a55909c5aff5269a"
+            "86a7a9531534f7da2e4c303d8a318a72"
+            "1c3c0c95956809532fcf0e2449a6b525"
+            "b16aedf5aa0de657ba637b39",
+        .ciphertext =
+            "42831ec2217774244b7221b784d0d49c"
+            "e3aa212f2c02a4e035c17e2329aca12e"
+            "21d514b25466931c7d8f6a5aac84aa05"
+            "1ba30b396a0aac973d58e091",
+        .tag = "5bc94fbc3221a5db94fae95ae7121a47",
+    },
+    {
+        /* Test case 15 (AES-256, no AAD) */
+        .path = "/crypto/cipher/aes-gcm-256-15",
+        .alg = QCRYPTO_CIPHER_ALGO_AES_256,
+        .key =
+            "feffe9928665731c6d6a8f9467308308"
+            "feffe9928665731c6d6a8f9467308308",
+        .iv = "cafebabefacedbaddecaf888",
+        .plaintext =
+            "d9313225f88406e5a55909c5aff5269a"
+            "86a7a9531534f7da2e4c303d8a318a72"
+            "1c3c0c95956809532fcf0e2449a6b525"
+            "b16aedf5aa0de657ba637b391aafd255",
+        .ciphertext =
+            "522dc1f099567d07f47f37a32a84427d"
+            "643a8cdcbfe5c0c97598a2bd2555d1aa"
+            "8cb08e48590dbb3da7b08b1056828838"
+            "c5f61e6393ba7a0abcc9f662898015ad",
+        .tag = "b094dac5d93471bdec1a502270e3cc6c",
+    },
+    {
+        /* Test case 16 (AES-256, with AAD) */
+        .path = "/crypto/cipher/aes-gcm-256-16",
+        .alg = QCRYPTO_CIPHER_ALGO_AES_256,
+        .key =
+            "feffe9928665731c6d6a8f9467308308"
+            "feffe9928665731c6d6a8f9467308308",
+        .iv = "cafebabefacedbaddecaf888",
+        .aad = "feedfacedeadbeeffeedfacedeadbeefabaddad2",
+        .plaintext =
+            "d9313225f88406e5a55909c5aff5269a"
+            "86a7a9531534f7da2e4c303d8a318a72"
+            "1c3c0c95956809532fcf0e2449a6b525"
+            "b16aedf5aa0de657ba637b39",
+        .ciphertext =
+            "522dc1f099567d07f47f37a32a84427d"
+            "643a8cdcbfe5c0c97598a2bd2555d1aa"
+            "8cb08e48590dbb3da7b08b1056828838"
+            "c5f61e6393ba7a0abcc9f662",
+        .tag = "76fc6ece0f4e1768cddf8853bb2d551b",
+    },
+};
+
+static void test_cipher_gcm(const void *opaque)
+{
+    const QCryptoCipherGcmTestData *data = opaque;
+    g_autofree uint8_t *key = NULL;
+    g_autofree uint8_t *iv = NULL;
+    g_autofree uint8_t *aad = NULL;
+    g_autofree uint8_t *ptext = NULL;
+    g_autofree uint8_t *ctext = NULL;
+    g_autofree uint8_t *tagexp = NULL;
+    g_autofree uint8_t *out = NULL;
+    uint8_t tag[16];
+    size_t nkey;
+    size_t niv;
+    size_t naad = 0;
+    size_t nptext;
+    size_t nctext;
+    size_t ntag;
+    QCryptoCipher *cipher;
+
+    nkey = unhex_string(data->key, &key);
+    niv = unhex_string(data->iv, &iv);
+    nptext = unhex_string(data->plaintext, &ptext);
+    nctext = unhex_string(data->ciphertext, &ctext);
+    ntag = unhex_string(data->tag, &tagexp);
+    if (data->aad) {
+        naad = unhex_string(data->aad, &aad);
+    }
+
+    g_assert_cmpint(nptext, ==, nctext);
+    g_assert_cmpint(ntag, ==, sizeof(tag));
+    out = g_new0(uint8_t, nptext);
+
+    /* Encrypt: plaintext -> ciphertext, then read back the tag. */
+    cipher = qcrypto_cipher_new(data->alg, QCRYPTO_CIPHER_MODE_GCM,
+                                key, nkey, &error_abort);
+    g_assert(cipher != NULL);
+    g_assert(qcrypto_cipher_setiv(cipher, iv, niv, &error_abort) == 0);
+    if (naad) {
+        g_assert(qcrypto_cipher_setaad(cipher, aad, naad, &error_abort) == 0);
+    }
+    g_assert(qcrypto_cipher_encrypt(cipher, ptext, out, nptext,
+                                    &error_abort) == 0);
+    g_assert_cmpmem(out, nptext, ctext, nctext);
+    g_assert(qcrypto_cipher_gettag(cipher, tag, sizeof(tag),
+                                   &error_abort) == 0);
+    g_assert_cmpmem(tag, sizeof(tag), tagexp, ntag);
+    qcrypto_cipher_free(cipher);
+
+    /* Decrypt: ciphertext -> plaintext, recomputed tag must match. */
+    memset(out, 0, nptext);
+    cipher = qcrypto_cipher_new(data->alg, QCRYPTO_CIPHER_MODE_GCM,
+                                key, nkey, &error_abort);
+    g_assert(cipher != NULL);
+    g_assert(qcrypto_cipher_setiv(cipher, iv, niv, &error_abort) == 0);
+    if (naad) {
+        g_assert(qcrypto_cipher_setaad(cipher, aad, naad, &error_abort) == 0);
+    }
+    g_assert(qcrypto_cipher_decrypt(cipher, ctext, out, nctext,
+                                    &error_abort) == 0);
+    g_assert_cmpmem(out, nctext, ptext, nptext);
+    g_assert(qcrypto_cipher_gettag(cipher, tag, sizeof(tag),
+                                   &error_abort) == 0);
+    g_assert_cmpmem(tag, sizeof(tag), tagexp, ntag);
+    qcrypto_cipher_free(cipher);
+}
+
+/*
+ * Corrupt one ciphertext byte and confirm the recomputed GCM tag no longer
+ * matches: the authentication tag must detect tampering.
+ */
+static void test_cipher_gcm_tamper(const void *opaque)
+{
+    const QCryptoCipherGcmTestData *data = opaque;
+    g_autofree uint8_t *key = NULL;
+    g_autofree uint8_t *iv = NULL;
+    g_autofree uint8_t *aad = NULL;
+    g_autofree uint8_t *ctext = NULL;
+    g_autofree uint8_t *tagexp = NULL;
+    g_autofree uint8_t *out = NULL;
+    uint8_t tag[16];
+    size_t nkey;
+    size_t niv;
+    size_t naad = 0;
+    size_t nctext;
+    size_t ntag;
+    QCryptoCipher *cipher;
+
+    nkey = unhex_string(data->key, &key);
+    niv = unhex_string(data->iv, &iv);
+    nctext = unhex_string(data->ciphertext, &ctext);
+    ntag = unhex_string(data->tag, &tagexp);
+    if (data->aad) {
+        naad = unhex_string(data->aad, &aad);
+    }
+    out = g_new0(uint8_t, nctext);
+
+    /* Flip one ciphertext bit before decrypting. */
+    ctext[0] ^= 0x01;
+
+    cipher = qcrypto_cipher_new(data->alg, QCRYPTO_CIPHER_MODE_GCM,
+                                key, nkey, &error_abort);
+    g_assert(cipher != NULL);
+    g_assert(qcrypto_cipher_setiv(cipher, iv, niv, &error_abort) == 0);
+    if (naad) {
+        g_assert(qcrypto_cipher_setaad(cipher, aad, naad, &error_abort) == 0);
+    }
+    g_assert(qcrypto_cipher_decrypt(cipher, ctext, out, nctext,
+                                    &error_abort) == 0);
+    g_assert(qcrypto_cipher_gettag(cipher, tag, sizeof(tag),
+                                   &error_abort) == 0);
+    g_assert(memcmp(tag, tagexp, ntag) != 0);
+    qcrypto_cipher_free(cipher);
+}
+
 int main(int argc, char **argv)
 {
     size_t i;
@@ -828,6 +1052,22 @@
         }
     }
 
+    for (i = 0; i < G_N_ELEMENTS(gcm_test_data); i++) {
+        if (qcrypto_cipher_supports(gcm_test_data[i].alg,
+                                    QCRYPTO_CIPHER_MODE_GCM)) {
+            g_autofree char *tamper = g_strdup_printf("%s/tamper",
+                                                      gcm_test_data[i].path);
+
+            g_test_add_data_func(gcm_test_data[i].path, &gcm_test_data[i],
+                                 test_cipher_gcm);
+            g_test_add_data_func(tamper, &gcm_test_data[i],
+                                 test_cipher_gcm_tamper);
+        } else {
+            g_printerr("# skip unsupported %s:gcm\n",
+                       QCryptoCipherAlgo_str(gcm_test_data[i].alg));
+        }
+    }
+
     if (qcrypto_cipher_supports(QCRYPTO_CIPHER_ALGO_AES_256,
                                 QCRYPTO_CIPHER_MODE_CBC)) {
         g_test_add_func("/crypto/cipher/null-iv",