Merge tag 's390x-20260629' of https://gitlab.com/cohuck/qemu into staging

Fix a potential way for an s390x/kvm guest to crash QEMU while filling a STSI buffer.

# -----BEGIN PGP SIGNATURE-----
#
# iIgEABYKADAWIQRpo7U29cv8ZSCAJsHeiLtWQd5mwQUCakJyoxIcY29odWNrQHJl
# ZGhhdC5jb20ACgkQ3oi7VkHeZsGXNQEA1pKKaD39nYxYTa3/wRQFIaa+eQ8q8IsH
# GrbveEStsgwBAKWj6+tAgb4SQDdugu9UADMgdvZyAewmbYzL29/qn1MB
# =Gqpa
# -----END PGP SIGNATURE-----
# gpg: Signature made Mon 29 Jun 2026 15:26:59 CEST
# gpg:                using EDDSA key 69A3B536F5CBFC65208026C1DE88BB5641DE66C1
# gpg:                issuer "cohuck@redhat.com"
# gpg: Good signature from "Cornelia Huck <conny@cornelia-huck.de>" [unknown]
# gpg:                 aka "Cornelia Huck <cohuck@kernel.org>" [unknown]
# gpg:                 aka "Cornelia Huck <cornelia.huck@de.ibm.com>" [full]
# gpg:                 aka "Cornelia Huck <huckc@linux.vnet.ibm.com>" [full]
# gpg:                 aka "Cornelia Huck <cohuck@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: C3D0 D66D C362 4FF6 A8C0  18CE DECF 6B93 C6F0 2FAF
#      Subkey fingerprint: 69A3 B536 F5CB FC65 2080  26C1 DE88 BB56 41DE 66C1

* tag 's390x-20260629' of https://gitlab.com/cohuck/qemu:
  s390x/kvm: clamp stsi 3.2.2 size

Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
diff --git a/.gitlab-ci.d/base.yml b/.gitlab-ci.d/base.yml
index 714d2ed..8fe0080 100644
--- a/.gitlab-ci.d/base.yml
+++ b/.gitlab-ci.d/base.yml
@@ -134,3 +134,51 @@
         QEMU_CI_CONTAINER_TAG: $CI_COMMIT_REF_SLUG
 
     - when: on_success
+
+# Any job running meson should capture meson logs
+# by default. Some jobs might override the artifacts
+# to capture further files
+.base_meson_job_template:
+  extends: .base_job_template
+  artifacts:
+    name: "$CI_JOB_NAME-$CI_COMMIT_REF_SLUG"
+    when: always
+    expire_in: 7 days
+    paths:
+      - build/meson-logs
+      - ci-runner-env
+    reports:
+      junit: build/meson-logs/*.junit.xml
+  before_script:
+    - source scripts/ci/gitlab-ci-section
+    - section_start setup "Pre-script setup"
+    - JOBS=$(expr $(nproc) + 1)
+    # Prevent logs (if any) from prior build job artifacts
+    # from being duplicated in the new job artifacts
+    - rm -f build/meson-logs/*
+    - mkdir -p ci-runner-env
+    - test -f /packages.txt && cp /packages.txt ci-runner-env/packages.txt
+    - cp /proc/mounts ci-runner-env/mounts.txt
+    - cp /proc/cpuinfo ci-runner-env/cpuinfo.txt
+    - cp /proc/meminfo ci-runner-env/meminfo.txt
+    - ls -l /dev > ci-runner-env/devices.txt
+    - uname -a > ci-runner-env/kernel.txt
+    - id -a > ci-runner-env/user.txt
+    - printenv | sort > ci-runner-env/environ.txt
+    - section_end setup
+
+.base_meson_ccache_job_template:
+  extends: .base_meson_job_template
+  cache:
+    paths:
+      - ccache
+    key: "$CI_JOB_NAME"
+    when: always
+  script:
+    - export CCACHE_BASEDIR="$(pwd)"
+    - export CCACHE_DIR="$CCACHE_BASEDIR/ccache"
+    - export CCACHE_MAXSIZE="500M"
+    - export PATH="$CCACHE_WRAPPERSDIR:$PATH"
+    - ccache --zero-stats
+  after_script:
+    - ccache --show-stats
diff --git a/.gitlab-ci.d/buildtest-template.yml b/.gitlab-ci.d/buildtest-template.yml
index 0050586..b5f3760 100644
--- a/.gitlab-ci.d/buildtest-template.yml
+++ b/.gitlab-ci.d/buildtest-template.yml
@@ -1,42 +1,13 @@
 
-# Any job running meson should capture meson logs
-# by default. Some jobs might override the artifacts
-# to capture further files
-.meson_job_template:
-  extends: .base_job_template
-  artifacts:
-    name: "$CI_JOB_NAME-$CI_COMMIT_REF_SLUG"
-    when: always
-    expire_in: 7 days
-    paths:
-      - build/meson-logs
-    reports:
-      junit: build/meson-logs/*.junit.xml
-
 .native_build_job_template:
-  extends: .meson_job_template
+  extends: .base_meson_ccache_job_template
   stage: build
   image: $CI_REGISTRY_IMAGE/qemu/$IMAGE:$QEMU_CI_CONTAINER_TAG
-  cache:
-    paths:
-      - ccache
-    key: "$CI_JOB_NAME"
-    when: always
-  before_script:
-    - source scripts/ci/gitlab-ci-section
-    - section_start setup "Pre-script setup"
-    - JOBS=$(expr $(nproc) + 1)
-    - cat /packages.txt
-    - section_end setup
   script:
-    - export CCACHE_BASEDIR="$(pwd)"
-    - export CCACHE_DIR="$CCACHE_BASEDIR/ccache"
-    - export CCACHE_MAXSIZE="500M"
-    - export PATH="$CCACHE_WRAPPERSDIR:$PATH"
+    - !reference [.base_meson_ccache_job_template, script]
     - du -sh .git
     - mkdir build
     - cd build
-    - ccache --zero-stats
     - section_start configure "Running configure"
     - ../configure --enable-werror --disable-docs --enable-fdt=system
           --disable-debug-info
@@ -57,7 +28,6 @@
         $MAKE -j"$JOBS" $MAKE_CHECK_ARGS ;
       fi
     - section_end test
-    - ccache --show-stats
 
 # We jump some hoops in common_test_job_template to avoid
 # rebuilding all the object files we skip in the artifacts
@@ -68,6 +38,7 @@
     paths:
       - build
       - .git-submodule-status
+      - ci-runner-env
     exclude:
       - build/**/*.p
       - build/**/*.a.p
@@ -75,9 +46,18 @@
       - build/**/*.c.o.d
 
 .common_test_job_template:
-  extends: .meson_job_template
+  extends: .base_meson_job_template
   stage: test
   image: $CI_REGISTRY_IMAGE/qemu/$IMAGE:$QEMU_CI_CONTAINER_TAG
+  artifacts:
+    name: "$CI_JOB_NAME-$CI_COMMIT_REF_SLUG"
+    when: always
+    expire_in: 7 days
+    paths:
+      - build/meson-logs
+      - build/tests/functional/*/*/*.log
+    reports:
+      junit: build/meson-logs/*.junit.xml
   script:
     - source scripts/ci/gitlab-ci-section
     - section_start buildenv "Setting up to run tests"
@@ -98,11 +78,6 @@
 
 .native_test_job_template:
   extends: .common_test_job_template
-  before_script:
-    # Prevent logs from the build job that run earlier
-    # from being duplicated in the test job artifacts
-    - rm -f build/meson-logs/*
-
 
 .functional_test_job_template:
   extends: .common_test_job_template
@@ -111,6 +86,12 @@
     paths:
       - ${CI_PROJECT_DIR}/functional-cache
     policy: pull-push
+  before_script:
+    - export QEMU_TEST_ALLOW_UNTRUSTED_CODE=1
+    - export QEMU_TEST_CACHE_DIR=${CI_PROJECT_DIR}/functional-cache
+    # Prevent logs from the build job that run earlier
+    # from being duplicated in the test job artifacts
+    - rm -f build/meson-logs/*
   artifacts:
     name: "$CI_JOB_NAME-$CI_COMMIT_REF_SLUG"
     when: always
@@ -118,29 +99,21 @@
     paths:
       - build/meson-logs
       - build/tests/functional/*/*/*.log
+      - ci-runner-env
     reports:
       junit: build/meson-logs/*.junit.xml
-  before_script:
-    - export QEMU_TEST_ALLOW_UNTRUSTED_CODE=1
-    - export QEMU_TEST_CACHE_DIR=${CI_PROJECT_DIR}/functional-cache
-    # Prevent logs from the build job that run earlier
-    # from being duplicated in the test job artifacts
-    - rm -f build/meson-logs/*
   after_script:
     - cd build
     - du -chs ${CI_PROJECT_DIR}/*-cache
   variables:
     QEMU_JOB_FUNCTIONAL: 1
+    QEMU_TEST_ALLOW_UNTRUSTED_CODE: 1
+    QEMU_TEST_CACHE_DIR: ${CI_PROJECT_DIR}/functional-cache
 
 .wasm_build_job_template:
-  extends: .meson_job_template
+  extends: .base_meson_job_template
   stage: build
   image: $CI_REGISTRY_IMAGE/qemu/$IMAGE:$QEMU_CI_CONTAINER_TAG
-  before_script:
-    - source scripts/ci/gitlab-ci-section
-    - section_start setup "Pre-script setup"
-    - JOBS=$(expr $(nproc) + 1)
-    - section_end setup
   script:
     - du -sh .git
     - mkdir build
diff --git a/.gitlab-ci.d/crossbuild-template.yml b/.gitlab-ci.d/crossbuild-template.yml
index 58136d0..9442381 100644
--- a/.gitlab-ci.d/crossbuild-template.yml
+++ b/.gitlab-ci.d/crossbuild-template.yml
@@ -1,27 +1,12 @@
 .cross_system_build_job:
-  extends: .base_job_template
+  extends: .base_meson_ccache_job_template
   stage: build
   image: $CI_REGISTRY_IMAGE/qemu/$IMAGE:$QEMU_CI_CONTAINER_TAG
-  cache:
-    paths:
-      - ccache
-    key: "$CI_JOB_NAME"
-    when: always
   timeout: 80m
-  before_script:
-    - source scripts/ci/gitlab-ci-section
-    - section_start setup "Pre-script setup"
-    - JOBS=$(expr $(nproc) + 1)
-    - cat /packages.txt
-    - section_end setup
   script:
-    - export CCACHE_BASEDIR="$(pwd)"
-    - export CCACHE_DIR="$CCACHE_BASEDIR/ccache"
-    - export CCACHE_MAXSIZE="500M"
-    - export PATH="$CCACHE_WRAPPERSDIR:$PATH"
+    - !reference [.base_meson_ccache_job_template, script]
     - mkdir build
     - cd build
-    - ccache --zero-stats
     - section_start configure "Running configure"
     - ../configure --enable-werror --disable-docs --enable-fdt=system
         --disable-user $QEMU_CONFIGURE_OPTS $EXTRA_CONFIGURE_OPTS
@@ -46,7 +31,6 @@
       mv -v qemu-setup*.exe qemu-setup-${version}.exe;
       fi
     - section_end installer
-    - ccache --show-stats
 
 # Job to cross-build specific accelerators.
 #
@@ -54,22 +38,12 @@
 # KVM), and set extra options (such disabling other accelerators) via the
 # $EXTRA_CONFIGURE_OPTS variable.
 .cross_accel_build_job:
-  extends: .base_job_template
+  extends: .base_meson_ccache_job_template
   stage: build
   image: $CI_REGISTRY_IMAGE/qemu/$IMAGE:$QEMU_CI_CONTAINER_TAG
   timeout: 60m
-  cache:
-    paths:
-      - ccache/
-    key: "$CI_JOB_NAME"
-  before_script:
-    - source scripts/ci/gitlab-ci-section
-    - JOBS=$(expr $(nproc) + 1)
   script:
-    - export CCACHE_BASEDIR="$(pwd)"
-    - export CCACHE_DIR="$CCACHE_BASEDIR/ccache"
-    - export CCACHE_MAXSIZE="500M"
-    - export PATH="$CCACHE_WRAPPERSDIR:$PATH"
+    - !reference [.base_meson_ccache_job_template, script]
     - mkdir build
     - cd build
     - section_start configure "Running configure"
@@ -87,20 +61,11 @@
     - section_end test
 
 .cross_user_build_job:
-  extends: .base_job_template
+  extends: .base_meson_ccache_job_template
   stage: build
   image: $CI_REGISTRY_IMAGE/qemu/$IMAGE:$QEMU_CI_CONTAINER_TAG
-  cache:
-    paths:
-      - ccache/
-    key: "$CI_JOB_NAME"
-  before_script:
-    - source scripts/ci/gitlab-ci-section
-    - JOBS=$(expr $(nproc) + 1)
   script:
-    - export CCACHE_BASEDIR="$(pwd)"
-    - export CCACHE_DIR="$CCACHE_BASEDIR/ccache"
-    - export CCACHE_MAXSIZE="500M"
+    - !reference [.base_meson_ccache_job_template, script]
     - mkdir build
     - cd build
     - section_start configure "Running configure"
@@ -119,15 +84,3 @@
         $MAKE -j"$JOBS" $MAKE_CHECK_ARGS ;
       fi
     - section_end test
-
-# We can still run some tests on some of our cross build jobs. They can add this
-# template to their extends to save the build logs and test results
-.cross_test_artifacts:
-  artifacts:
-    name: "$CI_JOB_NAME-$CI_COMMIT_REF_SLUG"
-    when: always
-    expire_in: 7 days
-    paths:
-      - build/meson-logs
-    reports:
-      junit: build/meson-logs/*.junit.xml
diff --git a/.gitlab-ci.d/macos.yml b/.gitlab-ci.d/macos.yml
index 84ad249..53b6e6c 100644
--- a/.gitlab-ci.d/macos.yml
+++ b/.gitlab-ci.d/macos.yml
@@ -29,7 +29,6 @@
     - cd build
     - ../configure --enable-werror $CONFIGURE_ARGS || { cat config.log meson-logs/meson-log.txt; exit 1; }
     - $MAKE -j$(sysctl -n hw.ncpu)
-    - for TARGET in $TEST_BINARIES ; do $MAKE $TARGET ; done
     - for TARGET in $TEST_TARGETS ; do $MAKE $TARGET ; done
 
 aarch64-macos-15-build:
@@ -46,8 +45,7 @@
         --cross-prefix-i386=i686-elf-
         --cross-prefix-x86_64=x86_64-elf-
         --disable-plugins
-    TEST_BINARIES: qemu-system-aarch64 qemu-system-i386 qemu-system-x86_64
-    TEST_TARGETS: check-unit run-tcg-tests-aarch64-softmmu run-tcg-tests-i386-softmmu run-tcg-tests-x86_64-softmmu
+    TEST_TARGETS: check-unit check-tcg
 
 aarch64-macos-26-build:
   extends: .macos_job_template
@@ -64,5 +62,4 @@
         --cross-prefix-i386=i686-elf-
         --cross-prefix-x86_64=x86_64-elf-
         --disable-plugins
-    TEST_BINARIES: qemu-system-aarch64 qemu-system-i386 qemu-system-x86_64
-    TEST_TARGETS: check-unit run-tcg-tests-aarch64-softmmu run-tcg-tests-i386-softmmu run-tcg-tests-x86_64-softmmu
+    TEST_TARGETS: check-unit check-tcg
diff --git a/.gitlab/issue_templates/bug.md b/.gitlab/issue_templates/bug.md
index e20f586..faeeb00 100644
--- a/.gitlab/issue_templates/bug.md
+++ b/.gitlab/issue_templates/bug.md
@@ -13,8 +13,9 @@
 See https://www.qemu.org/contribute/report-a-bug/ for additional
 guidance.
 
-If this is a security issue, please consult
-https://www.qemu.org/contribute/security-process/
+If this is a security issue, ensure this ticket is marked 'confidential'
+before submission. See https://www.qemu.org/contribute/security-process/
+for additional guidance
 -->
 
 ## Host environment
@@ -49,6 +50,12 @@
 2.
 3.
 
+<!--
+Note: if this issue was discovered with the assistance of automated
+tooling LLM, static analysis, fuzzers), the reporter must disclose
+that in the description. The steps to reproduce, and any other findings,
+must be fully validated by the user of the tool prior to submission.
+-->
 
 ## Additional information
 
diff --git a/MAINTAINERS b/MAINTAINERS
index 72357df..3b305d8 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -256,8 +256,12 @@
 F: tests/tcg/hexagon/
 F: disas/hexagon.c
 F: configs/targets/hexagon-linux-user.mak
+F: configs/targets/hexagon-softmmu.mak
 F: tests/docker/dockerfiles/debian-hexagon-cross.docker
 F: gdbstub/gdb-xml/hexagon*.xml
+F: docs/system/target-hexagon.rst
+F: docs/system/hexagon/
+F: docs/devel/hexagon-sys.rst
 T: git https://github.com/qualcomm/qemu.git hex-next
 
 Hexagon idef-parser
@@ -1341,6 +1345,18 @@
 F: roms/seabios-hppa/
 F: tests/functional/hppa/
 
+Hexagon Machines
+----------------
+V66G_1024, V68N_1024, sa8775-cdsp0
+M: Brian Cain <brian.cain@oss.qualcomm.com>
+R: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>
+S: Supported
+F: hw/hexagon/
+F: include/hw/hexagon/
+F: configs/devices/hexagon-softmmu/default.mak
+F: docs/system/hexagon/
+F: docs/devel/hexagon-sys.rst
+
 LoongArch Machines
 ------------------
 Virt
diff --git a/accel/accel-irq.c b/accel/accel-irq.c
index 7f864e3..830be27 100644
--- a/accel/accel-irq.c
+++ b/accel/accel-irq.c
@@ -10,19 +10,18 @@
  */
 
 #include "qemu/osdep.h"
+#include "qemu/error-report.h"
 #include "hw/pci/msi.h"
 
 #include "system/kvm.h"
 #include "system/mshv.h"
 #include "system/accel-irq.h"
 
-int accel_irqchip_add_msi_route(KVMRouteChange *c, int vector, PCIDevice *dev)
+int accel_irqchip_add_msi_route(AccelRouteChange *c, int vector, PCIDevice *dev)
 {
-#ifdef CONFIG_MSHV_IS_POSSIBLE
     if (mshv_msi_via_irqfd_enabled()) {
-        return mshv_irqchip_add_msi_route(vector, dev);
+        return mshv_irqchip_add_msi_route(c, vector, dev);
     }
-#endif
     if (kvm_enabled()) {
         return kvm_irqchip_add_msi_route(c, vector, dev);
     }
@@ -31,36 +30,28 @@
 
 int accel_irqchip_update_msi_route(int vector, MSIMessage msg, PCIDevice *dev)
 {
-#ifdef CONFIG_MSHV_IS_POSSIBLE
     if (mshv_msi_via_irqfd_enabled()) {
         return mshv_irqchip_update_msi_route(vector, msg, dev);
     }
-#endif
     if (kvm_enabled()) {
         return kvm_irqchip_update_msi_route(kvm_state, vector, msg, dev);
     }
     return -ENOSYS;
 }
 
-void accel_irqchip_commit_route_changes(KVMRouteChange *c)
+void accel_irqchip_commit_route_changes(AccelRouteChange *c)
 {
-#ifdef CONFIG_MSHV_IS_POSSIBLE
-    if (mshv_msi_via_irqfd_enabled()) {
-        mshv_irqchip_commit_routes();
-    }
-#endif
-    if (kvm_enabled()) {
-        kvm_irqchip_commit_route_changes(c);
+    if (c->changes) {
+        accel_irqchip_commit_routes();
+        c->changes = 0;
     }
 }
 
 void accel_irqchip_commit_routes(void)
 {
-#ifdef CONFIG_MSHV_IS_POSSIBLE
     if (mshv_msi_via_irqfd_enabled()) {
-        mshv_irqchip_commit_routes();
+        mshv_irqchip_commit_routes(mshv_state);
     }
-#endif
     if (kvm_enabled()) {
         kvm_irqchip_commit_routes(kvm_state);
     }
@@ -68,11 +59,9 @@
 
 void accel_irqchip_release_virq(int virq)
 {
-#ifdef CONFIG_MSHV_IS_POSSIBLE
     if (mshv_msi_via_irqfd_enabled()) {
-        mshv_irqchip_release_virq(virq);
+        mshv_irqchip_release_virq(mshv_state, virq);
     }
-#endif
     if (kvm_enabled()) {
         kvm_irqchip_release_virq(kvm_state, virq);
     }
@@ -81,11 +70,9 @@
 int accel_irqchip_add_irqfd_notifier_gsi(EventNotifier *n, EventNotifier *rn,
                                          int virq)
 {
-#ifdef CONFIG_MSHV_IS_POSSIBLE
     if (mshv_msi_via_irqfd_enabled()) {
         return mshv_irqchip_add_irqfd_notifier_gsi(n, rn, virq);
     }
-#endif
     if (kvm_enabled()) {
         return kvm_irqchip_add_irqfd_notifier_gsi(kvm_state, n, rn, virq);
     }
@@ -94,13 +81,29 @@
 
 int accel_irqchip_remove_irqfd_notifier_gsi(EventNotifier *n, int virq)
 {
-#ifdef CONFIG_MSHV_IS_POSSIBLE
     if (mshv_msi_via_irqfd_enabled()) {
         return mshv_irqchip_remove_irqfd_notifier_gsi(n, virq);
     }
-#endif
     if (kvm_enabled()) {
         return kvm_irqchip_remove_irqfd_notifier_gsi(kvm_state, n, virq);
     }
     return -ENOSYS;
 }
+
+inline AccelRouteChange accel_irqchip_begin_route_changes(void)
+{
+    if (mshv_msi_via_irqfd_enabled()) {
+        return (AccelRouteChange) {
+            .accel = ACCEL(mshv_state),
+            .changes = 0,
+        };
+    }
+    if (kvm_enabled()) {
+        return (AccelRouteChange) {
+            .accel = ACCEL(kvm_state),
+            .changes = 0,
+        };
+    }
+    error_report("can't initiate route change, no accel irqchip available");
+    abort();
+}
diff --git a/accel/kvm/kvm-all.c b/accel/kvm/kvm-all.c
index 8ddfbaf..5d55cb4 100644
--- a/accel/kvm/kvm-all.c
+++ b/accel/kvm/kvm-all.c
@@ -2359,11 +2359,11 @@
     return kvm_vm_ioctl(s, KVM_SIGNAL_MSI, &msi);
 }
 
-int kvm_irqchip_add_msi_route(KVMRouteChange *c, int vector, PCIDevice *dev)
+int kvm_irqchip_add_msi_route(AccelRouteChange *c, int vector, PCIDevice *dev)
 {
     struct kvm_irq_routing_entry kroute = {};
     int virq;
-    KVMState *s = c->s;
+    KVMState *s = KVM_STATE(c->accel);
     MSIMessage msg = {0, 0};
 
     if (pci_available && dev) {
@@ -2506,7 +2506,7 @@
     abort();
 }
 
-int kvm_irqchip_add_msi_route(KVMRouteChange *c, int vector, PCIDevice *dev)
+int kvm_irqchip_add_msi_route(AccelRouteChange *c, int vector, PCIDevice *dev)
 {
     return -ENOSYS;
 }
diff --git a/accel/mshv/irq.c b/accel/mshv/irq.c
index 3c238c3..4828ac5 100644
--- a/accel/mshv/irq.c
+++ b/accel/mshv/irq.c
@@ -25,177 +25,6 @@
 #define MSHV_IRQFD_RESAMPLE_FLAG (1 << MSHV_IRQFD_BIT_RESAMPLE)
 #define MSHV_IRQFD_BIT_DEASSIGN_FLAG (1 << MSHV_IRQFD_BIT_DEASSIGN)
 
-static MshvMsiControl *msi_control;
-static QemuMutex msi_control_mutex;
-
-void mshv_init_msicontrol(void)
-{
-    qemu_mutex_init(&msi_control_mutex);
-    msi_control = g_new0(MshvMsiControl, 1);
-    msi_control->gsi_routes = g_hash_table_new(g_direct_hash, g_direct_equal);
-    msi_control->updated = false;
-}
-
-static int set_msi_routing(uint32_t gsi, uint64_t addr, uint32_t data)
-{
-    struct mshv_user_irq_entry *entry;
-    uint32_t high_addr = addr >> 32;
-    uint32_t low_addr = addr & 0xFFFFFFFF;
-    GHashTable *gsi_routes;
-
-    trace_mshv_set_msi_routing(gsi, addr, data);
-
-    if (gsi >= MSHV_MAX_MSI_ROUTES) {
-        error_report("gsi >= MSHV_MAX_MSI_ROUTES");
-        return -1;
-    }
-
-    assert(msi_control);
-
-    WITH_QEMU_LOCK_GUARD(&msi_control_mutex) {
-        gsi_routes = msi_control->gsi_routes;
-        entry = g_hash_table_lookup(gsi_routes, GINT_TO_POINTER(gsi));
-
-        if (entry
-            && entry->address_hi == high_addr
-            && entry->address_lo == low_addr
-            && entry->data == data)
-        {
-            /* nothing to update */
-            return 0;
-        }
-
-        /* free old entry */
-        g_free(entry);
-
-        /* create new entry */
-        entry = g_new0(struct mshv_user_irq_entry, 1);
-        entry->gsi = gsi;
-        entry->address_hi = high_addr;
-        entry->address_lo = low_addr;
-        entry->data = data;
-
-        g_hash_table_insert(gsi_routes, GINT_TO_POINTER(gsi), entry);
-        msi_control->updated = true;
-    }
-
-    return 0;
-}
-
-static int add_msi_routing(uint64_t addr, uint32_t data)
-{
-    struct mshv_user_irq_entry *route_entry;
-    uint32_t high_addr = addr >> 32;
-    uint32_t low_addr = addr & 0xFFFFFFFF;
-    int gsi;
-    GHashTable *gsi_routes;
-
-    trace_mshv_add_msi_routing(addr, data);
-
-    assert(msi_control);
-
-    WITH_QEMU_LOCK_GUARD(&msi_control_mutex) {
-        /* find an empty slot */
-        gsi = 0;
-        gsi_routes = msi_control->gsi_routes;
-        while (gsi < MSHV_MAX_MSI_ROUTES) {
-            route_entry = g_hash_table_lookup(gsi_routes, GINT_TO_POINTER(gsi));
-            if (!route_entry) {
-                break;
-            }
-            gsi++;
-        }
-        if (gsi >= MSHV_MAX_MSI_ROUTES) {
-            error_report("No empty gsi slot available");
-            return -1;
-        }
-
-        /* create new entry */
-        route_entry = g_new0(struct mshv_user_irq_entry, 1);
-        route_entry->gsi = gsi;
-        route_entry->address_hi = high_addr;
-        route_entry->address_lo = low_addr;
-        route_entry->data = data;
-
-        g_hash_table_insert(gsi_routes, GINT_TO_POINTER(gsi), route_entry);
-        msi_control->updated = true;
-    }
-
-    return gsi;
-}
-
-static int commit_msi_routing_table(int vm_fd)
-{
-    guint len;
-    int i, ret;
-    size_t table_size;
-    struct mshv_user_irq_table *table;
-    GHashTableIter iter;
-    gpointer key, value;
-
-    assert(msi_control);
-
-    WITH_QEMU_LOCK_GUARD(&msi_control_mutex) {
-        if (!msi_control->updated) {
-            /* nothing to update */
-            return 0;
-        }
-
-        /* Calculate the size of the table */
-        len = g_hash_table_size(msi_control->gsi_routes);
-        table_size = sizeof(struct mshv_user_irq_table)
-                     + len * sizeof(struct mshv_user_irq_entry);
-        table = g_malloc0(table_size);
-
-        g_hash_table_iter_init(&iter, msi_control->gsi_routes);
-        i = 0;
-        while (g_hash_table_iter_next(&iter, &key, &value)) {
-            struct mshv_user_irq_entry *entry = value;
-            table->entries[i] = *entry;
-            i++;
-        }
-        table->nr = i;
-
-        trace_mshv_commit_msi_routing_table(vm_fd, len);
-
-        ret = ioctl(vm_fd, MSHV_SET_MSI_ROUTING, table);
-        g_free(table);
-        if (ret < 0) {
-            error_report("Failed to commit msi routing table");
-            return -1;
-        }
-        msi_control->updated = false;
-    }
-    return 0;
-}
-
-static int remove_msi_routing(uint32_t gsi)
-{
-    struct mshv_user_irq_entry *route_entry;
-    GHashTable *gsi_routes;
-
-    trace_mshv_remove_msi_routing(gsi);
-
-    if (gsi >= MSHV_MAX_MSI_ROUTES) {
-        error_report("Invalid GSI: %u", gsi);
-        return -1;
-    }
-
-    assert(msi_control);
-
-    WITH_QEMU_LOCK_GUARD(&msi_control_mutex) {
-        gsi_routes = msi_control->gsi_routes;
-        route_entry = g_hash_table_lookup(gsi_routes, GINT_TO_POINTER(gsi));
-        if (route_entry) {
-            g_hash_table_remove(gsi_routes, GINT_TO_POINTER(gsi));
-            g_free(route_entry);
-            msi_control->updated = true;
-        }
-    }
-
-    return 0;
-}
-
 /* Pass an eventfd which is to be used for injecting interrupts from userland */
 static int irqfd(int vm_fd, int fd, int resample_fd, uint32_t gsi,
                  uint32_t flags)
@@ -278,35 +107,155 @@
     return register_irqfd(vm_fd, fd, virq);
 }
 
-
-int mshv_irqchip_add_msi_route(int vector, PCIDevice *dev)
+static int irqchip_allocate_gsi(MshvState *s, int *gsi)
 {
-    MSIMessage msg = { 0, 0 };
-    int virq = 0;
+    int next_gsi;
 
-    if (pci_available && dev) {
-        msg = pci_get_msi_message(dev, vector);
-        virq = add_msi_routing(msg.address, le32_to_cpu(msg.data));
+    /* Return the lowest unused GSI in the bitmap */
+    next_gsi = find_first_zero_bit(s->used_gsi_bitmap, s->gsi_count);
+    if (next_gsi >= s->gsi_count) {
+        return -ENOSPC;
     }
 
-    return virq;
+    *gsi = next_gsi;
+
+    return 0;
 }
 
-void mshv_irqchip_release_virq(int virq)
+static void irqchip_release_gsi(MshvState *s, int gsi)
 {
-    remove_msi_routing(virq);
+    clear_bit(gsi, s->used_gsi_bitmap);
+}
+
+static void add_routing_entry(MshvState *s, struct mshv_user_irq_entry *entry)
+{
+    struct mshv_user_irq_entry *new;
+    int n, size;
+
+    if (s->irq_routes->nr == s->nr_allocated_irq_routes) {
+        n = s->nr_allocated_irq_routes * 2;
+        if (n < MSHV_MIN_ALLOCATED_MSI_ROUTES) {
+            n = MSHV_MIN_ALLOCATED_MSI_ROUTES;
+        }
+        size = sizeof(struct mshv_user_irq_table);
+        size += n * sizeof(*new);
+        s->irq_routes = g_realloc(s->irq_routes, size);
+        s->nr_allocated_irq_routes = n;
+    }
+
+    n = s->irq_routes->nr;
+    s->irq_routes->nr++;
+    new = &s->irq_routes->entries[n];
+
+    *new = *entry;
+
+    set_bit(entry->gsi, s->used_gsi_bitmap);
+
+    trace_mshv_add_msi_routing(entry->address_lo | entry->address_hi,
+                               entry->data);
+}
+
+int mshv_irqchip_add_msi_route(AccelRouteChange *c, int vector, PCIDevice *dev)
+{
+    struct mshv_user_irq_entry entry = { 0 };
+    MSIMessage msg = { 0 };
+    uint32_t data, high_addr, low_addr;
+    int gsi, ret;
+    MshvState *s = MSHV_STATE(c->accel);
+
+    if (!pci_available || !dev) {
+        return 0;
+    }
+
+    msg = pci_get_msi_message(dev, vector);
+
+    ret = irqchip_allocate_gsi(mshv_state, &gsi);
+    if (ret < 0) {
+        error_report("Could not allocate GSI for MSI route");
+        return -1;
+    }
+    high_addr = msg.address >> 32;
+    low_addr = msg.address & 0xFFFFFFFF;
+    data = le32_to_cpu(msg.data);
+
+    entry.gsi = gsi;
+    entry.address_hi = high_addr;
+    entry.address_lo = low_addr;
+    entry.data = data;
+
+    if (s->irq_routes->nr < s->gsi_count) {
+        add_routing_entry(s, &entry);
+        c->changes++;
+    } else {
+        irqchip_release_gsi(s, gsi);
+        return -ENOSPC;
+    }
+
+    return gsi;
+}
+
+void mshv_irqchip_release_virq(MshvState *s, int virq)
+{
+    struct mshv_user_irq_entry *e;
+    int i;
+
+    for (i = 0; i < s->irq_routes->nr; i++) {
+        e = &s->irq_routes->entries[i];
+        if (e->gsi == virq) {
+            s->irq_routes->nr--;
+            *e = s->irq_routes->entries[s->irq_routes->nr];
+        }
+    }
+    irqchip_release_gsi(s, virq);
+
+    trace_mshv_remove_msi_routing(virq);
+}
+
+static int update_routing_entry(MshvState *s,
+                                struct mshv_user_irq_entry *new_entry)
+{
+    struct mshv_user_irq_entry *entry;
+    int n;
+
+    for (n = 0; n < s->irq_routes->nr; n++) {
+        entry = &s->irq_routes->entries[n];
+        if (entry->gsi != new_entry->gsi) {
+            continue;
+        }
+
+        if (!memcmp(entry, new_entry, sizeof *entry)) {
+            return 0;
+        }
+
+        *entry = *new_entry;
+
+        return 0;
+    }
+
+    return -ESRCH;
 }
 
 int mshv_irqchip_update_msi_route(int virq, MSIMessage msg, PCIDevice *dev)
 {
+    uint32_t addr_hi = msg.address >> 32;
+    uint32_t addr_lo = msg.address & 0xFFFFFFFF;
+    uint32_t data = le32_to_cpu(msg.data);
+    struct mshv_user_irq_entry entry = {
+        .gsi = virq,
+        .address_hi = addr_hi,
+        .address_lo = addr_lo,
+        .data = data,
+    };
     int ret;
 
-    ret = set_msi_routing(virq, msg.address, le32_to_cpu(msg.data));
+    ret = update_routing_entry(mshv_state, &entry);
     if (ret < 0) {
-        error_report("Failed to set msi routing");
-        return -1;
+        error_report("Failed to set msi routing for gsi %d", virq);
+        abort();
     }
 
+    trace_mshv_set_msi_routing(virq, msg.address, data);
+
     return 0;
 }
 
@@ -347,16 +296,17 @@
     return 0;
 }
 
-void mshv_irqchip_commit_routes(void)
+void mshv_irqchip_commit_routes(MshvState *s)
 {
     int ret;
-    int vm_fd = mshv_state->vm;
+    int vm_fd = s->vm;
 
-    ret = commit_msi_routing_table(vm_fd);
+    ret = ioctl(vm_fd, MSHV_SET_MSI_ROUTING, s->irq_routes);
     if (ret < 0) {
         error_report("Failed to commit msi routing table");
         abort();
     }
+    trace_mshv_commit_msi_routing_table(vm_fd, s->irq_routes->nr);
 }
 
 int mshv_irqchip_add_irqfd_notifier_gsi(const EventNotifier *event,
@@ -372,27 +322,45 @@
     return irqchip_update_irqfd_notifier_gsi(event, NULL, virq, false);
 }
 
-int mshv_reserve_ioapic_msi_routes(int vm_fd)
+static int mshv_reserve_ioapic_msi_routes(MshvState *s)
 {
-    int ret, gsi;
+    int ret, i;
+    int gsi = 0;
+    struct mshv_user_irq_entry blank_entry = { 0 };
 
     /*
      * Reserve GSI 0-23 for IOAPIC pins, to avoid conflicts of legacy
      * peripherals with MSI-X devices
      */
-    for (gsi = 0; gsi < IOAPIC_NUM_PINS; gsi++) {
-        ret = add_msi_routing(0, 0);
+    for (i = 0; i < IOAPIC_NUM_PINS; i++) {
+        /* ret = add_msi_routing(0, 0); */
+        ret = irqchip_allocate_gsi(s, &gsi);
         if (ret < 0) {
-            error_report("Failed to reserve GSI %d", gsi);
+            error_report("Failed to reserve GSI %d: %s", gsi, strerror(-ret));
             return -1;
         }
+        blank_entry.gsi = gsi;
+        add_routing_entry(s, &blank_entry);
     }
 
-    ret = commit_msi_routing_table(vm_fd);
-    if (ret < 0) {
-        error_report("Failed to commit reserved IOAPIC MSI routes");
-        return -1;
-    }
+    mshv_irqchip_commit_routes(s);
 
     return 0;
 }
+
+void mshv_init_irq_routing(MshvState *s)
+{
+    int ret;
+    int gsi_count = MSHV_MAX_MSI_ROUTES;
+
+    s->irq_routes = g_malloc0(sizeof(*s->irq_routes));
+    s->nr_allocated_irq_routes = 0;
+    s->gsi_count = gsi_count;
+    s->used_gsi_bitmap = bitmap_new(gsi_count);
+
+    ret = mshv_reserve_ioapic_msi_routes(s);
+    if (ret < 0) {
+        error_report("Failed to reserve IOAPIC MSI routes");
+        abort();
+    }
+}
diff --git a/accel/mshv/mem.c b/accel/mshv/mem.c
index e55c38d..820f87e 100644
--- a/accel/mshv/mem.c
+++ b/accel/mshv/mem.c
@@ -12,10 +12,13 @@
 
 #include "qemu/osdep.h"
 #include "qemu/error-report.h"
+#include "qapi/error.h"
 #include "linux/mshv.h"
 #include "system/address-spaces.h"
 #include "system/mshv.h"
 #include "system/mshv_int.h"
+#include "hw/hyperv/hvhdk_mini.h"
+#include "system/physmem.h"
 #include "exec/memattrs.h"
 #include <sys/ioctl.h>
 #include "trace.h"
@@ -211,3 +214,211 @@
         abort();
     }
 }
+
+static int enable_dirty_page_tracking(int vm_fd)
+{
+    int ret;
+    struct hv_input_set_partition_property in = {0};
+    struct mshv_root_hvcall args = {0};
+
+    in.property_code = HV_PARTITION_PROPERTY_GPA_PAGE_ACCESS_TRACKING;
+    in.property_value = 1;
+
+    args.code = HVCALL_SET_PARTITION_PROPERTY;
+    args.in_sz = sizeof(in);
+    args.in_ptr = (uint64_t)&in;
+
+    ret = mshv_hvcall(vm_fd, &args);
+    if (ret < 0) {
+        error_report("Failed to enable dirty page tracking: %s",
+                     strerror(errno));
+        return -1;
+    }
+
+    return 0;
+}
+
+/*
+ * Retrieve dirty page bitmap for a GPA range, clearing the dirty bits
+ * atomically. Large ranges are handled in batches.
+ */
+static int get_dirty_log(int vm_fd, uint64_t base_pfn, uint64_t page_count,
+                         unsigned long *bitmap, size_t bitmap_size)
+{
+    uint64_t batch, bitmap_offset, completed = 0;
+    struct mshv_gpap_access_bitmap args = {0};
+    int ret;
+
+    QEMU_BUILD_BUG_ON(MSHV_DIRTY_PAGES_BATCH_SIZE % BITS_PER_LONG != 0);
+    assert(bitmap_size >= ROUND_UP(page_count, BITS_PER_LONG) / 8);
+
+    while (completed < page_count) {
+        batch = MIN(MSHV_DIRTY_PAGES_BATCH_SIZE, page_count - completed);
+        bitmap_offset = completed / BITS_PER_LONG;
+
+        args.access_type = MSHV_GPAP_ACCESS_TYPE_DIRTY;
+        args.access_op   = MSHV_GPAP_ACCESS_OP_CLEAR;
+        args.page_count  = batch;
+        args.gpap_base   = base_pfn + completed;
+        args.bitmap_ptr  = (uint64_t)(bitmap + bitmap_offset);
+
+        ret = ioctl(vm_fd, MSHV_GET_GPAP_ACCESS_BITMAP, &args);
+        if (ret < 0) {
+            error_report("Failed to get dirty log (base_pfn=0x%" PRIx64
+                         " batch=%" PRIu64 "): %s",
+                         base_pfn + completed, batch, strerror(errno));
+            return -1;
+        }
+        completed += batch;
+    }
+
+    return 0;
+}
+
+bool mshv_log_global_start(MemoryListener *listener, Error **errp)
+{
+    int ret;
+
+    ret = enable_dirty_page_tracking(mshv_state->vm);
+    if (ret < 0) {
+        error_setg_errno(errp, -ret, "Failed to enable dirty page tracking");
+        return false;
+    }
+    return true;
+}
+
+static int disable_dirty_page_tracking(int vm_fd)
+{
+    int ret;
+    struct hv_input_set_partition_property in = {0};
+    struct mshv_root_hvcall args = {0};
+
+    in.property_code = HV_PARTITION_PROPERTY_GPA_PAGE_ACCESS_TRACKING;
+    in.property_value = 0;
+
+    args.code = HVCALL_SET_PARTITION_PROPERTY;
+    args.in_sz = sizeof(in);
+    args.in_ptr = (uint64_t)&in;
+
+    ret = mshv_hvcall(vm_fd, &args);
+    if (ret < 0) {
+        error_report("Failed to disable dirty page tracking: %s",
+                     strerror(errno));
+        return -1;
+    }
+
+    return 0;
+}
+
+static int set_dirty_pages(int vm_fd, uint64_t base_pfn, uint64_t page_count)
+{
+    uint64_t batch, completed = 0;
+    unsigned long bitmap[MSHV_DIRTY_PAGES_BATCH_SIZE / BITS_PER_LONG];
+    struct mshv_gpap_access_bitmap args = {0};
+    int ret;
+
+    while (completed < page_count) {
+        batch = MIN(MSHV_DIRTY_PAGES_BATCH_SIZE, page_count - completed);
+
+        args.access_type = MSHV_GPAP_ACCESS_TYPE_DIRTY;
+        args.access_op   = MSHV_GPAP_ACCESS_OP_SET;
+        args.page_count  = batch;
+        args.gpap_base   = base_pfn + completed;
+        args.bitmap_ptr  = (uint64_t)bitmap;
+
+        ret = ioctl(vm_fd, MSHV_GET_GPAP_ACCESS_BITMAP, &args);
+        if (ret < 0) {
+            error_report("Failed to set dirty pages (base_pfn=0x%" PRIx64
+                         " batch=%" PRIu64 "): %s",
+                         base_pfn + completed, batch, strerror(errno));
+            return -1;
+        }
+        completed += batch;
+    }
+
+    return 0;
+}
+
+static bool set_dirty_bits_cb(Int128 start, Int128 len, const MemoryRegion *mr,
+                              hwaddr offset_in_region, void *opaque)
+{
+    int ret, *errp = opaque;
+    hwaddr gpa, size;
+    uint64_t page_count, base_pfn;
+
+    gpa = int128_get64(start);
+    size = int128_get64(len);
+    page_count = size >> MSHV_PAGE_SHIFT;
+    base_pfn = gpa >> MSHV_PAGE_SHIFT;
+
+    if (!mr->ram || mr->readonly) {
+        return false;
+    }
+
+    if (page_count == 0) {
+        return false;
+    }
+
+    ret = set_dirty_pages(mshv_state->vm, base_pfn, page_count);
+
+    /* true aborts the iteration, which is what we want if there's an error */
+    if (ret < 0) {
+        *errp = ret;
+        return true;
+    }
+
+    return false;
+}
+
+void mshv_log_global_stop(MemoryListener *listener)
+{
+    int err = 0;
+    /* MSHV requires all dirty bits to be set before disabling tracking. */
+    FlatView *fv = address_space_to_flatview(&address_space_memory);
+    flatview_for_each_range(fv, set_dirty_bits_cb, &err);
+
+    if (err < 0) {
+        error_report("Failed to set dirty bits before disabling tracking");
+    }
+
+    disable_dirty_page_tracking(mshv_state->vm);
+}
+
+void mshv_log_sync(MemoryListener *listener, MemoryRegionSection *section)
+{
+    hwaddr size, start_addr, mr_offset;
+    uint64_t page_count, base_pfn;
+    size_t bitmap_size;
+    unsigned long *bitmap;
+    ram_addr_t ram_addr;
+    int ret;
+    MemoryRegion *mr = section->mr;
+
+    if (!memory_region_is_ram(mr) || memory_region_is_rom(mr)) {
+        return;
+    }
+
+    size = align_section(section, &start_addr);
+    if (!size) {
+        return;
+    }
+
+    page_count = size >> MSHV_PAGE_SHIFT;
+    base_pfn = start_addr >> MSHV_PAGE_SHIFT;
+    bitmap_size = ROUND_UP(page_count, BITS_PER_LONG) / 8;
+    bitmap = g_malloc0(bitmap_size);
+
+    ret = get_dirty_log(mshv_state->vm, base_pfn, page_count, bitmap,
+                        bitmap_size);
+    if (ret < 0) {
+        g_free(bitmap);
+        return;
+    }
+
+    mr_offset = section->offset_within_region + start_addr -
+                section->offset_within_address_space;
+    ram_addr = memory_region_get_ram_addr(mr) + mr_offset;
+
+    physical_memory_set_dirty_lebitmap(bitmap, ram_addr, page_count);
+    g_free(bitmap);
+}
diff --git a/accel/mshv/meson.build b/accel/mshv/meson.build
index c1b1787..e433187 100644
--- a/accel/mshv/meson.build
+++ b/accel/mshv/meson.build
@@ -1,6 +1,5 @@
 system_ss.add(when: 'CONFIG_MSHV', if_true: files(
   'irq.c',
   'mem.c',
-  'msr.c',
   'mshv-all.c'
 ))
diff --git a/accel/mshv/mshv-all.c b/accel/mshv/mshv-all.c
index 58d8638..9452504 100644
--- a/accel/mshv/mshv-all.c
+++ b/accel/mshv/mshv-all.c
@@ -43,10 +43,6 @@
 #include <err.h>
 #include <sys/ioctl.h>
 
-#define TYPE_MSHV_ACCEL ACCEL_CLASS_NAME("mshv")
-
-DECLARE_INSTANCE_CHECKER(MshvState, MSHV_STATE, TYPE_MSHV_ACCEL)
-
 bool mshv_allowed;
 
 MshvState *mshv_state;
@@ -110,21 +106,136 @@
     return 0;
 }
 
+static int get_host_partition_property(int mshv_fd, uint32_t property_code,
+                                       uint64_t *value)
+{
+    int ret;
+    struct hv_input_get_partition_property in = {0};
+    struct hv_output_get_partition_property out = {0};
+    struct mshv_root_hvcall args = {0};
+
+    in.property_code = property_code;
+
+    args.code    = HVCALL_GET_PARTITION_PROPERTY;
+    args.in_sz   = sizeof(in);
+    args.in_ptr  = (uint64_t)&in;
+    args.out_sz  = sizeof(out);
+    args.out_ptr = (uint64_t)&out;
+
+    ret = ioctl(mshv_fd, MSHV_ROOT_HVCALL, &args);
+    if (ret < 0) {
+        error_report("Failed to get host partition property bank: %s",
+                     strerror(errno));
+        return -1;
+    }
+
+    *value = out.property_value;
+    return 0;
+}
+
+static int get_partition_property(int vm_fd, uint32_t feature_bank,
+                                  uint64_t *value)
+{
+    struct hv_input_get_partition_property in = {0};
+    struct hv_output_get_partition_property out = {0};
+    struct mshv_root_hvcall args = {0};
+    int ret;
+
+    in.property_code = feature_bank;
+
+    args.code    = HVCALL_GET_PARTITION_PROPERTY;
+    args.in_sz   = sizeof(in);
+    args.in_ptr  = (uint64_t)&in;
+    args.out_sz  = sizeof(out);
+    args.out_ptr = (uint64_t)&out;
+
+    ret = ioctl(vm_fd, MSHV_ROOT_HVCALL, &args);
+    if (ret < 0) {
+        error_report("Failed to get guest partition property bank: %s",
+                     strerror(errno));
+        return -1;
+    }
+
+    *value = out.property_value;
+    return 0;
+}
+
+static int get_proc_features(int vm_fd,
+                             union hv_partition_processor_features *features)
+{
+    int ret;
+
+    ret = get_partition_property(vm_fd,
+                                 HV_PARTITION_PROPERTY_PROCESSOR_FEATURES0,
+                                 features[0].as_uint64);
+    if (ret < 0) {
+        error_report("Failed to get processor features bank 0");
+        return -1;
+    }
+
+    ret = get_partition_property(vm_fd,
+                                 HV_PARTITION_PROPERTY_PROCESSOR_FEATURES1,
+                                 features[1].as_uint64);
+    if (ret < 0) {
+        error_report("Failed to get processor features bank 1");
+        return -1;
+    }
+
+    return 0;
+}
+
 static int create_partition(int mshv_fd, int *vm_fd)
 {
     int ret;
-    struct mshv_create_partition args = {0};
+    uint64_t pt_flags, host_proc_features;
+    union hv_partition_processor_xsave_features disabled_xsave_features;
+    union hv_partition_processor_features disabled_partition_features = {0};
+
+    struct mshv_create_partition_v2 args = {0};
+
+    QEMU_BUILD_BUG_ON(MSHV_NUM_CPU_FEATURES_BANKS != 2);
 
     /* Initialize pt_flags with the desired features */
-    uint64_t pt_flags = (1ULL << MSHV_PT_BIT_LAPIC) |
-                        (1ULL << MSHV_PT_BIT_X2APIC) |
-                        (1ULL << MSHV_PT_BIT_GPA_SUPER_PAGES);
+    pt_flags = (1ULL << MSHV_PT_BIT_LAPIC) |
+               (1ULL << MSHV_PT_BIT_X2APIC) |
+               (1ULL << MSHV_PT_BIT_GPA_SUPER_PAGES) |
+               (1ULL << MSHV_PT_BIT_CPU_AND_XSAVE_FEATURES);
 
-    /* Set default isolation type */
-    uint64_t pt_isolation = MSHV_PT_ISOLATION_NONE;
+    /* enable all */
+    disabled_xsave_features.as_uint64 = 0;
 
+    /*
+     * query host for supported processor features and disable unsupported
+     * features: (0 means supported, 1 means disabled, hence the negation)
+     */
+    ret = get_host_partition_property(mshv_fd,
+                                      HV_PARTITION_PROPERTY_PROCESSOR_FEATURES0,
+                                      &host_proc_features);
+    if (ret < 0) {
+        error_report("Failed to get host processor feature bank 0");
+        return -1;
+    }
+    args.pt_cpu_fbanks[0] = ~host_proc_features;
+
+    ret = get_host_partition_property(mshv_fd,
+                                      HV_PARTITION_PROPERTY_PROCESSOR_FEATURES1,
+                                      &host_proc_features);
+    if (ret < 0) {
+        error_report("Failed to get host processor feature bank 1");
+        return -1;
+    }
+    args.pt_cpu_fbanks[1] = ~host_proc_features;
+
+    /* arch-specific features we disable regardless of host support */
+    mshv_arch_disable_partition_proc_features(&disabled_partition_features);
+    args.pt_cpu_fbanks[0] |= disabled_partition_features.as_uint64[0];
+    args.pt_cpu_fbanks[1] |= disabled_partition_features.as_uint64[1];
+
+    /* populate args structure */
     args.pt_flags = pt_flags;
-    args.pt_isolation = pt_isolation;
+    args.pt_isolation = MSHV_PT_ISOLATION_NONE;
+    args.pt_disabled_xsave = disabled_xsave_features.as_uint64;
+    args.pt_num_cpu_fbanks = MSHV_NUM_CPU_FEATURES_BANKS;
 
     ret = ioctl(mshv_fd, MSHV_CREATE_PARTITION, &args);
     if (ret < 0) {
@@ -204,11 +315,6 @@
         return -1;
     }
 
-    ret = mshv_reserve_ioapic_msi_routes(*vm_fd);
-    if (ret < 0) {
-        return -1;
-    }
-
     ret = mshv_arch_post_init_vm(*vm_fd);
     if (ret < 0) {
         return -1;
@@ -371,6 +477,9 @@
     .region_del = mem_region_del,
     .eventfd_add = mem_ioeventfd_add,
     .eventfd_del = mem_ioeventfd_del,
+    .log_sync = mshv_log_sync,
+    .log_global_start = mshv_log_global_start,
+    .log_global_stop = mshv_log_global_stop,
 };
 
 static MemoryListener mshv_io_listener = {
@@ -415,13 +524,13 @@
     int ret;
 
     cpu->accel = g_new0(AccelCPUState, 1);
-    mshv_arch_init_vcpu(cpu);
 
     ret = mshv_create_vcpu(vm_fd, vp_index, &cpu->accel->cpufd);
     if (ret < 0) {
         return -1;
     }
 
+    mshv_arch_init_vcpu(cpu);
     cpu->accel->dirty = true;
 
     return 0;
@@ -450,8 +559,6 @@
 
     mshv_init_mmio_emu();
 
-    mshv_init_msicontrol();
-
     ret = create_vm(mshv_fd, &vm_fd);
     if (ret < 0) {
         close(mshv_fd);
@@ -467,11 +574,19 @@
 
     s->vm = vm_fd;
     s->fd = mshv_fd;
+
+    ret = get_proc_features(vm_fd, &s->processor_features);
+    if (ret < 0) {
+        return -1;
+    }
+
     s->nr_as = 1;
     s->as = g_new0(MshvAddressSpace, s->nr_as);
 
     mshv_state = s;
 
+    mshv_init_irq_routing(s);
+
     register_mshv_memory_listener(s, &s->memory_listener, &address_space_memory,
                                   0, "mshv-memory");
     memory_listener_register(&mshv_io_listener, &address_space_io);
@@ -503,7 +618,7 @@
 
     do {
         if (cpu->accel->dirty) {
-            ret = mshv_arch_put_registers(cpu);
+            ret = mshv_arch_store_vcpu_state(cpu);
             if (ret) {
                 error_report("Failed to put registers after init: %s",
                               strerror(-ret));
@@ -625,7 +740,7 @@
 static void do_mshv_cpu_synchronize_post_init(CPUState *cpu,
                                               run_on_cpu_data arg)
 {
-    int ret = mshv_arch_put_registers(cpu);
+    int ret = mshv_arch_store_vcpu_state(cpu);
     if (ret < 0) {
         error_report("Failed to put registers after init: %s", strerror(-ret));
         abort();
@@ -641,7 +756,7 @@
 
 static void mshv_cpu_synchronize_post_reset(CPUState *cpu)
 {
-    int ret = mshv_arch_put_registers(cpu);
+    int ret = mshv_arch_store_vcpu_state(cpu);
     if (ret) {
         error_report("Failed to put registers after reset: %s",
                      strerror(-ret));
@@ -665,7 +780,7 @@
 static void do_mshv_cpu_synchronize(CPUState *cpu, run_on_cpu_data arg)
 {
     if (!cpu->accel->dirty) {
-        int ret = mshv_load_regs(cpu);
+        int ret = mshv_arch_load_vcpu_state(cpu);
         if (ret < 0) {
             error_report("Failed to load registers for vcpu %d",
                          cpu->cpu_index);
diff --git a/accel/mshv/msr.c b/accel/mshv/msr.c
deleted file mode 100644
index e6e5bae..0000000
--- a/accel/mshv/msr.c
+++ /dev/null
@@ -1,375 +0,0 @@
-/*
- * QEMU MSHV support
- *
- * Copyright Microsoft, Corp. 2025
- *
- * Authors: Magnus Kulke  <magnuskulke@microsoft.com>
- *
- * SPDX-License-Identifier: GPL-2.0-or-later
- */
-
-#include "qemu/osdep.h"
-#include "system/mshv.h"
-#include "system/mshv_int.h"
-#include "hw/hyperv/hvgdk_mini.h"
-#include "linux/mshv.h"
-#include "qemu/error-report.h"
-
-static uint32_t supported_msrs[64] = {
-    IA32_MSR_TSC,
-    IA32_MSR_EFER,
-    IA32_MSR_KERNEL_GS_BASE,
-    IA32_MSR_APIC_BASE,
-    IA32_MSR_PAT,
-    IA32_MSR_SYSENTER_CS,
-    IA32_MSR_SYSENTER_ESP,
-    IA32_MSR_SYSENTER_EIP,
-    IA32_MSR_STAR,
-    IA32_MSR_LSTAR,
-    IA32_MSR_CSTAR,
-    IA32_MSR_SFMASK,
-    IA32_MSR_MTRR_DEF_TYPE,
-    IA32_MSR_MTRR_PHYSBASE0,
-    IA32_MSR_MTRR_PHYSMASK0,
-    IA32_MSR_MTRR_PHYSBASE1,
-    IA32_MSR_MTRR_PHYSMASK1,
-    IA32_MSR_MTRR_PHYSBASE2,
-    IA32_MSR_MTRR_PHYSMASK2,
-    IA32_MSR_MTRR_PHYSBASE3,
-    IA32_MSR_MTRR_PHYSMASK3,
-    IA32_MSR_MTRR_PHYSBASE4,
-    IA32_MSR_MTRR_PHYSMASK4,
-    IA32_MSR_MTRR_PHYSBASE5,
-    IA32_MSR_MTRR_PHYSMASK5,
-    IA32_MSR_MTRR_PHYSBASE6,
-    IA32_MSR_MTRR_PHYSMASK6,
-    IA32_MSR_MTRR_PHYSBASE7,
-    IA32_MSR_MTRR_PHYSMASK7,
-    IA32_MSR_MTRR_FIX64K_00000,
-    IA32_MSR_MTRR_FIX16K_80000,
-    IA32_MSR_MTRR_FIX16K_A0000,
-    IA32_MSR_MTRR_FIX4K_C0000,
-    IA32_MSR_MTRR_FIX4K_C8000,
-    IA32_MSR_MTRR_FIX4K_D0000,
-    IA32_MSR_MTRR_FIX4K_D8000,
-    IA32_MSR_MTRR_FIX4K_E0000,
-    IA32_MSR_MTRR_FIX4K_E8000,
-    IA32_MSR_MTRR_FIX4K_F0000,
-    IA32_MSR_MTRR_FIX4K_F8000,
-    IA32_MSR_TSC_AUX,
-    IA32_MSR_DEBUG_CTL,
-    HV_X64_MSR_GUEST_OS_ID,
-    HV_X64_MSR_SINT0,
-    HV_X64_MSR_SINT1,
-    HV_X64_MSR_SINT2,
-    HV_X64_MSR_SINT3,
-    HV_X64_MSR_SINT4,
-    HV_X64_MSR_SINT5,
-    HV_X64_MSR_SINT6,
-    HV_X64_MSR_SINT7,
-    HV_X64_MSR_SINT8,
-    HV_X64_MSR_SINT9,
-    HV_X64_MSR_SINT10,
-    HV_X64_MSR_SINT11,
-    HV_X64_MSR_SINT12,
-    HV_X64_MSR_SINT13,
-    HV_X64_MSR_SINT14,
-    HV_X64_MSR_SINT15,
-    HV_X64_MSR_SCONTROL,
-    HV_X64_MSR_SIEFP,
-    HV_X64_MSR_SIMP,
-    HV_X64_MSR_REFERENCE_TSC,
-    HV_X64_MSR_EOM,
-};
-static const size_t msr_count = ARRAY_SIZE(supported_msrs);
-
-static int compare_msr_index(const void *a, const void *b)
-{
-    return *(uint32_t *)a - *(uint32_t *)b;
-}
-
-__attribute__((constructor))
-static void init_sorted_msr_map(void)
-{
-    qsort(supported_msrs, msr_count, sizeof(uint32_t), compare_msr_index);
-}
-
-static int mshv_is_supported_msr(uint32_t msr)
-{
-    return bsearch(&msr, supported_msrs, msr_count, sizeof(uint32_t),
-                   compare_msr_index) != NULL;
-}
-
-static int mshv_msr_to_hv_reg_name(uint32_t msr, uint32_t *hv_reg)
-{
-    switch (msr) {
-    case IA32_MSR_TSC:
-        *hv_reg = HV_X64_REGISTER_TSC;
-        return 0;
-    case IA32_MSR_EFER:
-        *hv_reg = HV_X64_REGISTER_EFER;
-        return 0;
-    case IA32_MSR_KERNEL_GS_BASE:
-        *hv_reg = HV_X64_REGISTER_KERNEL_GS_BASE;
-        return 0;
-    case IA32_MSR_APIC_BASE:
-        *hv_reg = HV_X64_REGISTER_APIC_BASE;
-        return 0;
-    case IA32_MSR_PAT:
-        *hv_reg = HV_X64_REGISTER_PAT;
-        return 0;
-    case IA32_MSR_SYSENTER_CS:
-        *hv_reg = HV_X64_REGISTER_SYSENTER_CS;
-        return 0;
-    case IA32_MSR_SYSENTER_ESP:
-        *hv_reg = HV_X64_REGISTER_SYSENTER_ESP;
-        return 0;
-    case IA32_MSR_SYSENTER_EIP:
-        *hv_reg = HV_X64_REGISTER_SYSENTER_EIP;
-        return 0;
-    case IA32_MSR_STAR:
-        *hv_reg = HV_X64_REGISTER_STAR;
-        return 0;
-    case IA32_MSR_LSTAR:
-        *hv_reg = HV_X64_REGISTER_LSTAR;
-        return 0;
-    case IA32_MSR_CSTAR:
-        *hv_reg = HV_X64_REGISTER_CSTAR;
-        return 0;
-    case IA32_MSR_SFMASK:
-        *hv_reg = HV_X64_REGISTER_SFMASK;
-        return 0;
-    case IA32_MSR_MTRR_CAP:
-        *hv_reg = HV_X64_REGISTER_MSR_MTRR_CAP;
-        return 0;
-    case IA32_MSR_MTRR_DEF_TYPE:
-        *hv_reg = HV_X64_REGISTER_MSR_MTRR_DEF_TYPE;
-        return 0;
-    case IA32_MSR_MTRR_PHYSBASE0:
-        *hv_reg = HV_X64_REGISTER_MSR_MTRR_PHYS_BASE0;
-        return 0;
-    case IA32_MSR_MTRR_PHYSMASK0:
-        *hv_reg = HV_X64_REGISTER_MSR_MTRR_PHYS_MASK0;
-        return 0;
-    case IA32_MSR_MTRR_PHYSBASE1:
-        *hv_reg = HV_X64_REGISTER_MSR_MTRR_PHYS_BASE1;
-        return 0;
-    case IA32_MSR_MTRR_PHYSMASK1:
-        *hv_reg = HV_X64_REGISTER_MSR_MTRR_PHYS_MASK1;
-        return 0;
-    case IA32_MSR_MTRR_PHYSBASE2:
-        *hv_reg = HV_X64_REGISTER_MSR_MTRR_PHYS_BASE2;
-        return 0;
-    case IA32_MSR_MTRR_PHYSMASK2:
-        *hv_reg = HV_X64_REGISTER_MSR_MTRR_PHYS_MASK2;
-        return 0;
-    case IA32_MSR_MTRR_PHYSBASE3:
-        *hv_reg = HV_X64_REGISTER_MSR_MTRR_PHYS_BASE3;
-        return 0;
-    case IA32_MSR_MTRR_PHYSMASK3:
-        *hv_reg = HV_X64_REGISTER_MSR_MTRR_PHYS_MASK3;
-        return 0;
-    case IA32_MSR_MTRR_PHYSBASE4:
-        *hv_reg = HV_X64_REGISTER_MSR_MTRR_PHYS_BASE4;
-        return 0;
-    case IA32_MSR_MTRR_PHYSMASK4:
-        *hv_reg = HV_X64_REGISTER_MSR_MTRR_PHYS_MASK4;
-        return 0;
-    case IA32_MSR_MTRR_PHYSBASE5:
-        *hv_reg = HV_X64_REGISTER_MSR_MTRR_PHYS_BASE5;
-        return 0;
-    case IA32_MSR_MTRR_PHYSMASK5:
-        *hv_reg = HV_X64_REGISTER_MSR_MTRR_PHYS_MASK5;
-        return 0;
-    case IA32_MSR_MTRR_PHYSBASE6:
-        *hv_reg = HV_X64_REGISTER_MSR_MTRR_PHYS_BASE6;
-        return 0;
-    case IA32_MSR_MTRR_PHYSMASK6:
-        *hv_reg = HV_X64_REGISTER_MSR_MTRR_PHYS_MASK6;
-        return 0;
-    case IA32_MSR_MTRR_PHYSBASE7:
-        *hv_reg = HV_X64_REGISTER_MSR_MTRR_PHYS_BASE7;
-        return 0;
-    case IA32_MSR_MTRR_PHYSMASK7:
-        *hv_reg = HV_X64_REGISTER_MSR_MTRR_PHYS_MASK7;
-        return 0;
-    case IA32_MSR_MTRR_FIX64K_00000:
-        *hv_reg = HV_X64_REGISTER_MSR_MTRR_FIX64K00000;
-        return 0;
-    case IA32_MSR_MTRR_FIX16K_80000:
-        *hv_reg = HV_X64_REGISTER_MSR_MTRR_FIX16K80000;
-        return 0;
-    case IA32_MSR_MTRR_FIX16K_A0000:
-        *hv_reg = HV_X64_REGISTER_MSR_MTRR_FIX16KA0000;
-        return 0;
-    case IA32_MSR_MTRR_FIX4K_C0000:
-        *hv_reg = HV_X64_REGISTER_MSR_MTRR_FIX4KC0000;
-        return 0;
-    case IA32_MSR_MTRR_FIX4K_C8000:
-        *hv_reg = HV_X64_REGISTER_MSR_MTRR_FIX4KC8000;
-        return 0;
-    case IA32_MSR_MTRR_FIX4K_D0000:
-        *hv_reg = HV_X64_REGISTER_MSR_MTRR_FIX4KD0000;
-        return 0;
-    case IA32_MSR_MTRR_FIX4K_D8000:
-        *hv_reg = HV_X64_REGISTER_MSR_MTRR_FIX4KD8000;
-        return 0;
-    case IA32_MSR_MTRR_FIX4K_E0000:
-        *hv_reg = HV_X64_REGISTER_MSR_MTRR_FIX4KE0000;
-        return 0;
-    case IA32_MSR_MTRR_FIX4K_E8000:
-        *hv_reg = HV_X64_REGISTER_MSR_MTRR_FIX4KE8000;
-        return 0;
-    case IA32_MSR_MTRR_FIX4K_F0000:
-        *hv_reg = HV_X64_REGISTER_MSR_MTRR_FIX4KF0000;
-        return 0;
-    case IA32_MSR_MTRR_FIX4K_F8000:
-        *hv_reg = HV_X64_REGISTER_MSR_MTRR_FIX4KF8000;
-        return 0;
-    case IA32_MSR_TSC_AUX:
-        *hv_reg = HV_X64_REGISTER_TSC_AUX;
-        return 0;
-    case IA32_MSR_BNDCFGS:
-        *hv_reg = HV_X64_REGISTER_BNDCFGS;
-        return 0;
-    case IA32_MSR_DEBUG_CTL:
-        *hv_reg = HV_X64_REGISTER_DEBUG_CTL;
-        return 0;
-    case IA32_MSR_TSC_ADJUST:
-        *hv_reg = HV_X64_REGISTER_TSC_ADJUST;
-        return 0;
-    case IA32_MSR_SPEC_CTRL:
-        *hv_reg = HV_X64_REGISTER_SPEC_CTRL;
-        return 0;
-    case HV_X64_MSR_GUEST_OS_ID:
-        *hv_reg = HV_REGISTER_GUEST_OS_ID;
-        return 0;
-    case HV_X64_MSR_SINT0:
-        *hv_reg = HV_REGISTER_SINT0;
-        return 0;
-    case HV_X64_MSR_SINT1:
-        *hv_reg = HV_REGISTER_SINT1;
-        return 0;
-    case HV_X64_MSR_SINT2:
-        *hv_reg = HV_REGISTER_SINT2;
-        return 0;
-    case HV_X64_MSR_SINT3:
-        *hv_reg = HV_REGISTER_SINT3;
-        return 0;
-    case HV_X64_MSR_SINT4:
-        *hv_reg = HV_REGISTER_SINT4;
-        return 0;
-    case HV_X64_MSR_SINT5:
-        *hv_reg = HV_REGISTER_SINT5;
-        return 0;
-    case HV_X64_MSR_SINT6:
-        *hv_reg = HV_REGISTER_SINT6;
-        return 0;
-    case HV_X64_MSR_SINT7:
-        *hv_reg = HV_REGISTER_SINT7;
-        return 0;
-    case HV_X64_MSR_SINT8:
-        *hv_reg = HV_REGISTER_SINT8;
-        return 0;
-    case HV_X64_MSR_SINT9:
-        *hv_reg = HV_REGISTER_SINT9;
-        return 0;
-    case HV_X64_MSR_SINT10:
-        *hv_reg = HV_REGISTER_SINT10;
-        return 0;
-    case HV_X64_MSR_SINT11:
-        *hv_reg = HV_REGISTER_SINT11;
-        return 0;
-    case HV_X64_MSR_SINT12:
-        *hv_reg = HV_REGISTER_SINT12;
-        return 0;
-    case HV_X64_MSR_SINT13:
-        *hv_reg = HV_REGISTER_SINT13;
-        return 0;
-    case HV_X64_MSR_SINT14:
-        *hv_reg = HV_REGISTER_SINT14;
-        return 0;
-    case HV_X64_MSR_SINT15:
-        *hv_reg = HV_REGISTER_SINT15;
-        return 0;
-    case IA32_MSR_MISC_ENABLE:
-        *hv_reg = HV_X64_REGISTER_MSR_IA32_MISC_ENABLE;
-        return 0;
-    case HV_X64_MSR_SCONTROL:
-        *hv_reg = HV_REGISTER_SCONTROL;
-        return 0;
-    case HV_X64_MSR_SIEFP:
-        *hv_reg = HV_REGISTER_SIEFP;
-        return 0;
-    case HV_X64_MSR_SIMP:
-        *hv_reg = HV_REGISTER_SIMP;
-        return 0;
-    case HV_X64_MSR_REFERENCE_TSC:
-        *hv_reg = HV_REGISTER_REFERENCE_TSC;
-        return 0;
-    case HV_X64_MSR_EOM:
-        *hv_reg = HV_REGISTER_EOM;
-        return 0;
-    default:
-        error_report("failed to map MSR %u to HV register name", msr);
-        return -1;
-    }
-}
-
-static int set_msrs(const CPUState *cpu, GList *msrs)
-{
-    size_t n_msrs;
-    GList *entries;
-    MshvMsrEntry *entry;
-    enum hv_register_name name;
-    struct hv_register_assoc *assoc;
-    int ret;
-    size_t i = 0;
-
-    n_msrs = g_list_length(msrs);
-    hv_register_assoc *assocs = g_new0(hv_register_assoc, n_msrs);
-
-    entries = msrs;
-    for (const GList *elem = entries; elem != NULL; elem = elem->next) {
-        entry = elem->data;
-        ret = mshv_msr_to_hv_reg_name(entry->index, &name);
-        if (ret < 0) {
-            g_free(assocs);
-            return ret;
-        }
-        assoc = &assocs[i];
-        assoc->name = name;
-        /* the union has been initialized to 0 */
-        assoc->value.reg64 = entry->data;
-        i++;
-    }
-    ret = mshv_set_generic_regs(cpu, assocs, n_msrs);
-    g_free(assocs);
-    if (ret < 0) {
-        error_report("failed to set msrs");
-        return -1;
-    }
-    return 0;
-}
-
-
-int mshv_configure_msr(const CPUState *cpu, const MshvMsrEntry *msrs,
-                       size_t n_msrs)
-{
-    GList *valid_msrs = NULL;
-    uint32_t msr_index;
-    int ret;
-
-    for (size_t i = 0; i < n_msrs; i++) {
-        msr_index = msrs[i].index;
-        /* check whether index of msrs is in SUPPORTED_MSRS */
-        if (mshv_is_supported_msr(msr_index)) {
-            valid_msrs = g_list_append(valid_msrs, (void *) &msrs[i]);
-        }
-    }
-
-    ret = set_msrs(cpu, valid_msrs);
-    g_list_free(valid_msrs);
-
-    return ret;
-}
diff --git a/accel/stubs/kvm-stub.c b/accel/stubs/kvm-stub.c
index c4617ca..32b4b07 100644
--- a/accel/stubs/kvm-stub.c
+++ b/accel/stubs/kvm-stub.c
@@ -44,7 +44,7 @@
     return 1;
 }
 
-int kvm_irqchip_add_msi_route(KVMRouteChange *c, int vector, PCIDevice *dev)
+int kvm_irqchip_add_msi_route(AccelRouteChange *c, int vector, PCIDevice *dev)
 {
     return -ENOSYS;
 }
diff --git a/accel/stubs/mshv-stub.c b/accel/stubs/mshv-stub.c
index e499b19..566a17f 100644
--- a/accel/stubs/mshv-stub.c
+++ b/accel/stubs/mshv-stub.c
@@ -13,13 +13,14 @@
 #include "system/mshv.h"
 
 bool mshv_allowed;
+MshvState *mshv_state;
 
-int mshv_irqchip_add_msi_route(int vector, PCIDevice *dev)
+int mshv_irqchip_add_msi_route(AccelRouteChange *c, int vector, PCIDevice *dev)
 {
     return -ENOSYS;
 }
 
-void mshv_irqchip_release_virq(int virq)
+void mshv_irqchip_release_virq(MshvState *s, int virq)
 {
 }
 
@@ -28,7 +29,7 @@
     return -ENOSYS;
 }
 
-void mshv_irqchip_commit_routes(void)
+void mshv_irqchip_commit_routes(MshvState *s)
 {
 }
 
diff --git a/configs/devices/hexagon-softmmu/default.mak b/configs/devices/hexagon-softmmu/default.mak
new file mode 100644
index 0000000..6f20077
--- /dev/null
+++ b/configs/devices/hexagon-softmmu/default.mak
@@ -0,0 +1,7 @@
+# Default configuration for hexagon-softmmu
+
+# Uncomment the following lines to disable these optional devices:
+
+# Boards are selected by default, uncomment to keep out of the build.
+# CONFIG_HEX_VIRT=y
+# CONFIG_HEX_DSP=y
diff --git a/configs/meson/emscripten.txt b/configs/meson/emscripten.txt
index 4230e88..56c3976 100644
--- a/configs/meson/emscripten.txt
+++ b/configs/meson/emscripten.txt
@@ -6,3 +6,8 @@
 # configuration so explicitly add the flag here.
 c_link_args = ['-pthread','-sASYNCIFY=1','-sPROXY_TO_PTHREAD=1','-sFORCE_FILESYSTEM','-sALLOW_TABLE_GROWTH','-sTOTAL_MEMORY=2GB','-sWASM_BIGINT','-sEXPORT_ES6=1','-sASYNCIFY_IMPORTS=ffi_call_js','-sEXPORTED_RUNTIME_METHODS=addFunction,removeFunction,TTY,FS']
 cpp_link_args = ['-pthread','-sASYNCIFY=1','-sPROXY_TO_PTHREAD=1','-sFORCE_FILESYSTEM','-sALLOW_TABLE_GROWTH','-sTOTAL_MEMORY=2GB','-sWASM_BIGINT','-sEXPORT_ES6=1','-sASYNCIFY_IMPORTS=ffi_call_js','-sEXPORTED_RUNTIME_METHODS=addFunction,removeFunction,TTY,FS']
+
+[project options]
+# Workaround for failure to find glib-2.0 when building idef parser,
+# this may be a bug in the cross-build setup used by emscripten:
+hexagon_idef_parser = false
diff --git a/configs/targets/hexagon-softmmu.mak b/configs/targets/hexagon-softmmu.mak
new file mode 100644
index 0000000..a77c100
--- /dev/null
+++ b/configs/targets/hexagon-softmmu.mak
@@ -0,0 +1,8 @@
+# Default configuration for hexagon-softmmu
+
+TARGET_ARCH=hexagon
+TARGET_XML_FILES=hexagon-core.xml hexagon-hvx.xml
+TARGET_LONG_BITS=32
+TARGET_NOT_USING_LEGACY_LDST_PHYS_API=y
+TARGET_NOT_USING_LEGACY_NATIVE_ENDIAN_API=y
+TARGET_NEED_FDT=y
diff --git a/docs/about/deprecated.rst b/docs/about/deprecated.rst
index 97750f5..cf3e1a0 100644
--- a/docs/about/deprecated.rst
+++ b/docs/about/deprecated.rst
@@ -249,6 +249,13 @@
 and is currently unmaintained. The machine is scheduled to be removed as it
 appears to have no users.
 
+``memory-encryption`` machine property (since 11.1)
+'''''''''''''''''''''''''''''''''''''''''''''''''''
+
+Use ``confidential-guest-support`` instead. The ``memory-encryption`` object
+was an early implementation of memory encryption support in QEMU, but it has
+been superseded by the more comprehensive ``confidential-guest-support``
+object.
 
 Backend options
 ---------------
diff --git a/docs/devel/hexagon-sys.rst b/docs/devel/hexagon-sys.rst
new file mode 100644
index 0000000..92ebc32
--- /dev/null
+++ b/docs/devel/hexagon-sys.rst
@@ -0,0 +1,112 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+.. _Hexagon-System-arch:
+
+Hexagon System Architecture
+===========================
+
+The hexagon architecture has some unique elements which are described here.
+
+Interrupts
+----------
+When interrupts arrive at a Hexagon DSP core, they are priority-steered to
+be handled by an eligible hardware thread with the lowest priority.
+
+Memory
+------
+Each hardware thread has an ``SSR.ASID`` field that contains its Address
+Space Identifier.  This value is catenated with a 32-bit virtual address -
+the MMU can then resolve this extended virtual address to a physical address.
+
+TLBs
+----
+The format of a TLB entry is shown below.
+
+.. note::
+    The Small Core DSPs have a different TLB format which is not yet
+    supported.
+
+.. admonition:: Diagram
+
+ .. code:: text
+
+             6                   5                   4               3
+       3 2 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
+      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+      |v|g|x|A|A|             |                                       |
+      |a|l|P|1|0|     ASID    |             Virtual Page              |
+      |l|b| | | |             |                                       |
+      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+         3                   2                   1                   0
+       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
+      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+      | | | | |       |                                             | |
+      |x|w|r|u|Cacheab|               Physical Page                 |S|
+      | | | | |       |                                             | |
+      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+
+* ASID: the address-space identifier
+* A1, A0: the behavior of these cache line attributes are not modeled by QEMU.
+* xP: the extra-physical bit is the most significant physical address bit.
+* S: the S bit and the LSBs of the physical page indicate the page size
+* val: this is the 'valid' bit, when set it indicates that page matching
+  should consider this entry.
+
+.. list-table:: Page sizes
+   :widths: 25 25 50
+   :header-rows: 1
+
+   * - S-bit
+     - Phys page LSBs
+     - Page size
+   * - 1
+     - N/A
+     - 4kb
+   * - 0
+     - 0b1
+     - 16kb
+   * - 0
+     - 0b10
+     - 64kb
+   * - 0
+     - 0b100
+     - 256kb
+   * - 0
+     - 0b1000
+     - 1MB
+   * - 0
+     - 0b10000
+     - 4MB
+   * - 0
+     - 0b100000
+     - 16MB
+
+* glb: if the global bit is set, the ASID is not considered when matching
+  TLBs.
+* Cacheab: the cacheability attributes of TLBs are not modeled, these bits
+  are ignored.
+* RWX: read-, write-, execute-, enable bits.  Indicates if user programs
+  are permitted to read/write/execute the given page.
+* U: indicates if user programs can access this page.
+
+Scheduler
+---------
+The Hexagon system architecture has a feature to assist the guest OS
+task scheduler.  The guest OS can enable this feature by setting
+``SCHEDCFG.EN``.  The ``BESTWAIT`` register is programmed by the guest OS
+to indicate the priority of the highest priority task waiting to run on a
+hardware thread.  The reschedule interrupt is triggered when any hardware
+thread's priority in ``STID.PRIO`` is worse than the ``BESTWAIT``.  When
+it is triggered, the ``BESTWAIT.PRIO`` value is reset to 0x1ff.
+
+HVX Coprocessor
+---------------
+The Supervisor Status Register field ``SSR.XA`` binds a DSP hardware thread
+to one of the eight possible HVX contexts.  The guest OS is responsible for
+managing this resource.
+
+.. seealso::
+
+    ``target/hexagon/README`` in the QEMU source tree for more info about Hexagon.
diff --git a/docs/devel/index-internals.rst b/docs/devel/index-internals.rst
index 198f155..b89bab9 100644
--- a/docs/devel/index-internals.rst
+++ b/docs/devel/index-internals.rst
@@ -14,6 +14,7 @@
    block-coroutine-wrapper
    clocks
    ebpf_rss
+   hexagon-sys
    migration/index
    multi-process
    reset
diff --git a/docs/system/hexagon/cdsp.rst b/docs/system/hexagon/cdsp.rst
new file mode 100644
index 0000000..2375292
--- /dev/null
+++ b/docs/system/hexagon/cdsp.rst
@@ -0,0 +1,12 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+Compute DSP
+===========
+
+A Hexagon CDSP is designed as a computation offload device for an SoC.  The
+``V66G_1024`` machine contains:
+
+* L2VIC interrupt controller
+* QTimer timer device
+
+This machine will support any Hexagon CPU, but will default to ``v66``.
diff --git a/docs/system/hexagon/emulation.rst b/docs/system/hexagon/emulation.rst
new file mode 100644
index 0000000..36f54f0
--- /dev/null
+++ b/docs/system/hexagon/emulation.rst
@@ -0,0 +1,15 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+.. _Hexagon Emulation:
+
+Hexagon CPU architecture support
+================================
+
+QEMU's TCG emulation includes support for v65, v66, v67, v68, v69, v71, v73.
+It also has support for the following architecture extensions:
+
+- HVX (Hexagon Vector eXtensions)
+
+For information on the specifics of the HVX extension, please refer
+to the `Qualcomm Hexagon V73 HVX Programmer's Reference Manual
+<https://docs.qualcomm.com/bundle/publicresource/80-N2040-53.pdf>`_.
diff --git a/docs/system/introduction.rst b/docs/system/introduction.rst
index bec7291..8d9ef61 100644
--- a/docs/system/introduction.rst
+++ b/docs/system/introduction.rst
@@ -1,6 +1,15 @@
 Introduction
 ============
 
+.. note::
+   QEMU supports many different target CPU architectures and many
+   different machine types for each architecture. Options, properties,
+   and command lines that work for one architecture or machine type
+   will not necessarily work on another. The examples shown in this
+   manual are specific to the architecture and machine type being
+   demonstrated. When in doubt, consult the documentation for your
+   specific target architecture and machine type.
+
 .. _Accelerators:
 
 Virtualisation Accelerators
diff --git a/docs/system/target-hexagon.rst b/docs/system/target-hexagon.rst
new file mode 100644
index 0000000..416b8f7
--- /dev/null
+++ b/docs/system/target-hexagon.rst
@@ -0,0 +1,103 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+.. _Hexagon-System-emulator:
+
+Hexagon System emulator
+-----------------------
+
+Use the ``qemu-system-hexagon`` executable to simulate a 32-bit Hexagon
+machine.
+
+Hexagon Machines
+================
+
+Hexagon DSPs are suited to various functions and generally appear in a
+"DSP subsystem" of a larger system-on-chip (SoC).
+
+Hexagon DSPs are often included in a subsystem that looks like the diagram
+below.  Instructions are loaded into DDR before the DSP is brought out of
+reset and the first instructions are fetched from DDR via the EVB/reset vector.
+
+In a real system, a TBU/SMMU would normally arbitrate AXI accesses but
+we don't have a need to model that for QEMU.
+
+Hexagon DSP cores use simultaneous multithreading (SMT) with as many as 8
+hardware threads.
+
+.. admonition:: Diagram
+
+ .. code:: text
+
+              AHB (local) bus                     AXI (global) bus
+                    │                                 │
+                    │                                 │
+       ┌─────────┐  │       ┌─────────────────┐       │
+       │ L2VIC   ├──┤       │                 │       │
+       │         ├──┼───────►                 ├───────┤
+       └─────▲───┘  │       │   Hexagon DSP   │       │
+             │      │       │                 │       │        ┌─────┐
+             │      │       │    N threads    │       │        │ DDR │
+             │      ├───────┤                 │       │        │     │
+        ┌────┴──┐   │       │                 │       ├────────┤     │
+        │QTimer ├───┤       │                 │       │        │     │
+        │       │   │       │                 │       │        │     │
+        └───────┘   │       │   ┌─────────┐   │       │        │     │
+                    │       │  ┌─────────┐│   │       │        │     │
+        ┌───────┐   │       │  │  HVX xM ││   │       │        │     │
+        │QDSP6SS├───┤       │  │         │┘   │       │        │     │
+        └───────┘   │       │  └─────────┘    │       │        └─────┘
+                    │       │                 │       │
+        ┌───────┐   │       └─────────────────┘       │
+        │  CSR  ├───┤
+        └───────┘   │   ┌──────┐   ┌───────────┐
+                    │   │ TCM  │   │   VTCM    │
+                        │      │   │           │
+                        └──────┘   │           │
+                                   │           │
+                                   │           │
+                                   │           │
+                                   └───────────┘
+
+Components
+----------
+Other than l2vic and HVX, the components below are not implemented in QEMU.
+
+* L2VIC: the L2 vectored interrupt controller.  Supports 1024 input
+  interrupts, edge- or level-triggered.  The core ISA has system registers
+  ``VID``, ``VID1`` which read through to the L2VIC device.
+* QTimer: ARMSSE-based programmable timer device. Its interrupts are
+  wired to the L2VIC.  System registers ``TIMER``, ``UTIMER`` read
+  through to the QTimer device.
+* QDSP6SS: DSP subsystem features, accessible to the entire SoC, including
+  DSP NMI, watchdog, reset, etc.
+* CSR: Configuration/Status Registers.
+* TCM: DSP-exclusive tightly-coupled memory.  This memory can be used for
+  DSPs when isolated from DDR and in some bootstrapping modes.
+* VTCM: DSP-exclusive vector tightly-coupled memory.  This memory is accessed
+  by some HVX instructions.
+* HVX: the vector coprocessor supports 64 and 128-byte vector registers.
+  64-byte mode is not implemented in QEMU.
+
+
+Bootstrapping
+-------------
+Hexagon systems do not generally have access to a block device.  So, for
+QEMU the typical use case involves loading a binary or ELF file into memory
+and executing from the indicated start address::
+
+    $ qemu-system-hexagon -kernel ./prog -append 'arg1 arg2'
+
+Semihosting
+-----------
+Hexagon supports a semihosting interface similar to other architectures'.
+The ``trap0`` instruction can activate these semihosting calls so that the
+guest software can access the host console and filesystem.  Semihosting
+is not yet implemented in QEMU hexagon.
+
+
+Hexagon Features
+================
+.. toctree::
+   hexagon/emulation
+   hexagon/cdsp
+
diff --git a/docs/system/targets.rst b/docs/system/targets.rst
index 5b12858..5ebdd0f 100644
--- a/docs/system/targets.rst
+++ b/docs/system/targets.rst
@@ -30,3 +30,4 @@
    target-sparc64
    target-i386
    target-xtensa
+   target-hexagon
diff --git a/fsdev/file-op-9p.h b/fsdev/file-op-9p.h
index e8d0661..1d93114 100644
--- a/fsdev/file-op-9p.h
+++ b/fsdev/file-op-9p.h
@@ -81,6 +81,11 @@
 
 #define V9FS_SEC_MASK               0x0000003C
 
+/*
+ * Limits the maximum amount of simultaneously open xattr FIDs to prevent
+ * host memory exhaustion (as each xattr FID contains a xattr value buffer).
+ */
+#define V9FS_MAX_XATTR_DEFAULT  1024
 
 typedef struct FileOperations FileOperations;
 typedef struct XattrOperations XattrOperations;
@@ -96,6 +101,8 @@
     FsThrottle fst;
     mode_t fmode;
     mode_t dmode;
+    /* temporary storage for parse_opts only */
+    uint32_t max_xattr;
 } FsDriverEntry;
 
 struct FsContext {
@@ -109,6 +116,10 @@
     void *private;
     mode_t fmode;
     mode_t dmode;
+    /* max. amount of simultaneously open xattr FIDs */
+    uint32_t xattr_fid_limit;
+    /* current amount of open xattr FIDs */
+    uint32_t xattr_fid_count;
 };
 
 struct V9fsPath {
diff --git a/fsdev/qemu-fsdev-opts.c b/fsdev/qemu-fsdev-opts.c
index 07a18c6..c2c1e83 100644
--- a/fsdev/qemu-fsdev-opts.c
+++ b/fsdev/qemu-fsdev-opts.c
@@ -46,6 +46,9 @@
         }, {
             .name = "dmode",
             .type = QEMU_OPT_NUMBER,
+        }, {
+            .name = "max_xattr",
+            .type = QEMU_OPT_NUMBER,
         },
 
         THROTTLE_OPTS,
@@ -92,6 +95,9 @@
         }, {
             .name = "dmode",
             .type = QEMU_OPT_NUMBER,
+        }, {
+            .name = "max_xattr",
+            .type = QEMU_OPT_NUMBER,
         },
 
         { /*End of list */ }
diff --git a/fsdev/qemu-fsdev.c b/fsdev/qemu-fsdev.c
index 57877da..f97103c 100644
--- a/fsdev/qemu-fsdev.c
+++ b/fsdev/qemu-fsdev.c
@@ -45,7 +45,7 @@
 static QTAILQ_HEAD(, FsDriverListEntry) fsdriver_entries =
     QTAILQ_HEAD_INITIALIZER(fsdriver_entries);
 
-#define COMMON_FS_DRIVER_OPTIONS "id", "fsdriver", "readonly"
+#define COMMON_FS_DRIVER_OPTIONS "id", "fsdriver", "readonly", "max_xattr"
 
 static FsDriverTable FsDrivers[] = {
     {
diff --git a/hw/9pfs/9p-local.c b/hw/9pfs/9p-local.c
index aa48306..ee592b6 100644
--- a/hw/9pfs/9p-local.c
+++ b/hw/9pfs/9p-local.c
@@ -775,8 +775,11 @@
 {
     if (fid_type == P9_FID_DIR) {
         return dirfd(fs->dir.stream);
-    } else {
+    } else if (fid_type == P9_FID_FILE) {
         return fs->fd;
+    } else {
+        errno = EBADF;
+        return -1;
     }
 }
 
@@ -1527,6 +1530,15 @@
     const char *path = qemu_opt_get(opts, "path");
     const char *multidevs = qemu_opt_get(opts, "multidevs");
 
+    uint64_t val = qemu_opt_get_number(opts, "max_xattr",
+                                       V9FS_MAX_XATTR_DEFAULT);
+    if (val > UINT32_MAX) {
+        error_setg(errp, "max_xattr value '%s' too large",
+                   qemu_opt_get(opts, "max_xattr"));
+        return -1;
+    }
+    fse->max_xattr = val;
+
     if (!sec_model) {
         error_setg(errp, "security_model property not set");
         error_append_security_model_hint(errp);
diff --git a/hw/9pfs/9p-synth.c b/hw/9pfs/9p-synth.c
index b3743f6..3b3654b 100644
--- a/hw/9pfs/9p-synth.c
+++ b/hw/9pfs/9p-synth.c
@@ -25,6 +25,8 @@
 #include "qemu/rcu_queue.h"
 #include "qemu/cutils.h"
 #include "system/qtest.h"
+#include "qapi/error.h"
+#include "qemu/option.h"
 
 /* Root node for synth file system */
 static V9fsSynthNode synth_root = {
@@ -475,15 +477,15 @@
                                 const char *name, void *value,
                                 size_t size, int flags)
 {
-    errno = ENOTSUP;
-    return -1;
+    /* pretend it worked */
+    return 0;
 }
 
 static int synth_lremovexattr(FsContext *ctx,
                                    V9fsPath *path, const char *name)
 {
-    errno = ENOTSUP;
-    return -1;
+    /* pretend it worked */
+    return 0;
 }
 
 static int synth_name_to_path(FsContext *ctx, V9fsPath *dir_path,
@@ -563,6 +565,19 @@
     return 1;
 }
 
+/* transmits internal xattr counter to client */
+static ssize_t v9fs_synth_read_xattr_count(void *buf, int len, off_t offset,
+                                           void *arg)
+{
+    FsContext *ctx = arg;
+    size_t local_count = ctx->xattr_fid_count;
+    if (len < (int)sizeof(size_t)) {
+        return -ENOSPC;
+    }
+    memcpy(buf, &local_count, sizeof(size_t));
+    return sizeof(size_t);
+}
+
 static int synth_init(FsContext *ctx, Error **errp)
 {
     QLIST_INIT(&synth_root.child);
@@ -624,17 +639,45 @@
                 g_free(name);
             }
         }
+
+        /* Directory for internal statistic queries */
+        {
+            V9fsSynthNode *stat_dir = NULL;
+            ret = qemu_v9fs_synth_mkdir(NULL, 0755, "stat", &stat_dir);
+            assert(!ret);
+
+            /* File for internal xattr count query */
+            ret = qemu_v9fs_synth_add_file(stat_dir, 0444, "xattr_count",
+                                           v9fs_synth_read_xattr_count,
+                                           NULL, ctx);
+            assert(!ret);
+        }
     }
 
     return 0;
 }
 
+static int synth_parse_opts(QemuOpts *opts, FsDriverEntry *fse, Error **errp)
+{
+    uint64_t val = qemu_opt_get_number(opts, "max_xattr",
+                                       V9FS_MAX_XATTR_DEFAULT);
+    if (val > UINT32_MAX) {
+        error_setg(errp, "max_xattr value '%s' too large",
+                   qemu_opt_get(opts, "max_xattr"));
+        return -1;
+    }
+    fse->max_xattr = val;
+
+    return 0;
+}
+
 static bool synth_has_valid_file_handle(int fid_type, V9fsFidOpenState *fs)
 {
     return false;
 }
 
 FileOperations synth_ops = {
+    .parse_opts   = synth_parse_opts,
     .init         = synth_init,
     .lstat        = synth_lstat,
     .readlink     = synth_readlink,
diff --git a/hw/9pfs/9p.c b/hw/9pfs/9p.c
index b486cce..3119f01 100644
--- a/hw/9pfs/9p.c
+++ b/hw/9pfs/9p.c
@@ -265,6 +265,31 @@
     return str->size;
 }
 
+static int xattr_fid_count_inc(V9fsPDU *pdu)
+{
+    V9fsState *s = pdu->s;
+
+    if (s->ctx.xattr_fid_limit > 0 &&
+        s->ctx.xattr_fid_count >= s->ctx.xattr_fid_limit) {
+        error_report_once("9pfs: xattr_fid_count limit exceeded "
+                          "(configurable by option 'max_xattr').");
+        return -ENOSPC;
+    }
+    s->ctx.xattr_fid_count++;
+    return 0;
+}
+
+static void xattr_fid_count_decr(V9fsPDU *pdu)
+{
+    V9fsState *s = pdu->s;
+
+    if (s->ctx.xattr_fid_count > 0) {
+        s->ctx.xattr_fid_count--;
+    } else {
+        error_report_once("9pfs: xattr_fid_count underflow detected");
+    }
+}
+
 /*
  * returns 0 if fid got re-opened, 1 if not, < 0 on error
  */
@@ -397,6 +422,7 @@
         }
     } else if (fidp->fid_type == P9_FID_XATTR) {
         retval = v9fs_xattr_fid_clunk(pdu, fidp);
+        xattr_fid_count_decr(pdu);
     }
     v9fs_path_free(&fidp->path);
     g_free(fidp);
@@ -634,6 +660,14 @@
         fidp->clunked = true;
         put_fid(pdu, fidp);
     }
+
+    /*
+     * Explicitly reset the xattr FID counter.
+     *
+     * free_fid() already decrements the counter for each P9_FID_XATTR, so the
+     * counter should already be zero, hence this is just a defensive measure.
+     */
+    s->ctx.xattr_fid_count = 0;
 }
 
 #define P9_QID_TYPE_DIR         0x80
@@ -1469,6 +1503,16 @@
         goto out;
     }
 
+    /* cap msize to transport's theoretical limit */
+    if (s->transport->msize_limit) {
+        size_t limit = s->transport->msize_limit(s);
+        if (s->msize > limit) {
+            s->msize = limit;
+            warn_report_once("9p: client msize capped to %zu (transport limit)",
+                             limit);
+        }
+    }
+
     /* 8192 is the default msize of Linux clients */
     if (s->msize <= 8192 && !(s->ctx.export_flags & V9FS_NO_PERF_WARN)) {
         warn_report_once(
@@ -2261,10 +2305,15 @@
         err = -ENOENT;
         goto out_nofid;
     }
+    if (!fid_has_valid_file_handle(pdu->s, fidp)) {
+        err = -EBADF;
+        goto out;
+    }
     err = v9fs_co_fsync(pdu, fidp, datasync);
     if (!err) {
         err = offset;
     }
+out:
     put_fid(pdu, fidp);
 out_nofid:
     pdu_complete(pdu, err);
@@ -2669,6 +2718,7 @@
     uint32_t max_count;
     V9fsPDU *pdu = opaque;
     V9fsState *s = pdu->s;
+    size_t max_resp_sz;
 
     retval = pdu_unmarshal(pdu, offset, "dqd", &fid,
                            &initial_offset, &max_count);
@@ -2677,9 +2727,28 @@
     }
     trace_v9fs_readdir(pdu->tag, pdu->id, fid, initial_offset, max_count);
 
+    max_resp_sz = s->msize;
+
+    /*
+     * Constrain max_count to transport's current, actual response buffer size.
+     * A bad client might provide a response buffer < msize.
+     */
+    if (s->transport->response_buffer_size) {
+        size_t buf_size = s->transport->response_buffer_size(pdu);
+        if (max_resp_sz > buf_size) {
+            max_resp_sz = buf_size;
+        }
+    }
+
     /* Enough space for a R_readdir header: size[4] Rreaddir tag[2] count[4] */
-    if (max_count > s->msize - 11) {
-        max_count = s->msize - 11;
+    if (max_resp_sz > 11) {
+        max_resp_sz -= 11;
+    } else {
+        max_resp_sz = 0;
+    }
+
+    if (max_count > max_resp_sz) {
+        max_count = max_resp_sz;
         warn_report_once(
             "9p: bad client: T_readdir with count > msize - 11"
         );
@@ -3576,6 +3645,10 @@
     }
     /* do we need to sync the file? */
     if (donttouch_stat(&v9stat)) {
+        if (!fid_has_valid_file_handle(s, fidp)) {
+            err = -EBADF;
+            goto out;
+        }
         err = v9fs_co_fsync(pdu, fidp, 0);
         goto out;
     }
@@ -3835,6 +3908,10 @@
         err = -ENOENT;
         goto out_nofid;
     }
+    if (!fid_has_valid_file_handle(pdu->s, fidp)) {
+        err = -EBADF;
+        goto out;
+    }
     err = v9fs_co_fstat(pdu, fidp, &stbuf);
     if (err < 0) {
         goto out;
@@ -3880,6 +3957,10 @@
         err = -ENOENT;
         goto out_nofid;
     }
+    if (!fid_has_valid_file_handle(pdu->s, fidp)) {
+        err = -EBADF;
+        goto out;
+    }
     err = v9fs_co_fstat(pdu, fidp, &stbuf);
     if (err < 0) {
         goto out;
@@ -3993,6 +4074,14 @@
             clunk_fid(s, xattr_fidp->fid);
             goto out;
         }
+
+        /* Check xattr FID limit */
+        err = xattr_fid_count_inc(pdu);
+        if (err < 0) {
+            clunk_fid(s, xattr_fidp->fid);
+            goto out;
+        }
+
         /*
          * Read the xattr value
          */
@@ -4000,6 +4089,7 @@
         xattr_fidp->fid_type = P9_FID_XATTR;
         xattr_fidp->fs.xattr.xattrwalk_fid = true;
         xattr_fidp->fs.xattr.value = g_malloc0(size);
+
         if (size) {
             err = v9fs_co_llistxattr(pdu, &xattr_fidp->path,
                                      xattr_fidp->fs.xattr.value,
@@ -4026,6 +4116,14 @@
             clunk_fid(s, xattr_fidp->fid);
             goto out;
         }
+
+        /* Check xattr FID limit */
+        err = xattr_fid_count_inc(pdu);
+        if (err < 0) {
+            clunk_fid(s, xattr_fidp->fid);
+            goto out;
+        }
+
         /*
          * Read the xattr value
          */
@@ -4033,6 +4131,7 @@
         xattr_fidp->fid_type = P9_FID_XATTR;
         xattr_fidp->fs.xattr.xattrwalk_fid = true;
         xattr_fidp->fs.xattr.value = g_malloc0(size);
+
         if (size) {
             err = v9fs_co_lgetxattr(pdu, &xattr_fidp->path,
                                     &name, xattr_fidp->fs.xattr.value,
@@ -4134,6 +4233,12 @@
         goto out_put_fid;
     }
 
+    /* Check xattr FID limit */
+    err = xattr_fid_count_inc(pdu);
+    if (err < 0) {
+        goto out_put_fid;
+    }
+
     /* Make the file fid point to xattr */
     xattr_fidp = file_fidp;
     xattr_fidp->fid_type = P9_FID_XATTR;
@@ -4395,6 +4500,10 @@
 
     s->reclaiming = false;
 
+    /* init xattr FID limit from fsdev config */
+    s->ctx.xattr_fid_limit = fse->max_xattr;
+    s->ctx.xattr_fid_count = 0;
+
     rc = 0;
 out:
     if (rc) {
diff --git a/hw/9pfs/9p.h b/hw/9pfs/9p.h
index b2df659..1a30966 100644
--- a/hw/9pfs/9p.h
+++ b/hw/9pfs/9p.h
@@ -481,6 +481,8 @@
     void        (*init_out_iov_from_pdu)(V9fsPDU *pdu, struct iovec **piov,
                                          unsigned int *pniov, size_t size);
     void        (*push_and_notify)(V9fsPDU *pdu);
+    size_t      (*msize_limit)(V9fsState *s);
+    size_t      (*response_buffer_size)(V9fsPDU *pdu);
 };
 
 #endif
diff --git a/hw/9pfs/virtio-9p-device.c b/hw/9pfs/virtio-9p-device.c
index 9f70e23..50dc930 100644
--- a/hw/9pfs/virtio-9p-device.c
+++ b/hw/9pfs/virtio-9p-device.c
@@ -192,12 +192,29 @@
     *pniov = elem->out_num;
 }
 
+static size_t virtio_9p_msize_limit(V9fsState *s)
+{
+    const size_t guestPageSize = 4096;
+    return (VIRTQUEUE_MAX_SIZE - 2) * guestPageSize;
+}
+
+static size_t virtio_9p_response_buffer_size(V9fsPDU *pdu)
+{
+    V9fsState *s = pdu->s;
+    V9fsVirtioState *v = container_of(s, V9fsVirtioState, state);
+    VirtQueueElement *elem = v->elems[pdu->idx];
+
+    return iov_size(elem->in_sg, elem->in_num);
+}
+
 static const V9fsTransport virtio_9p_transport = {
     .pdu_vmarshal = virtio_pdu_vmarshal,
     .pdu_vunmarshal = virtio_pdu_vunmarshal,
     .init_in_iov_from_pdu = virtio_init_in_iov_from_pdu,
     .init_out_iov_from_pdu = virtio_init_out_iov_from_pdu,
     .push_and_notify = virtio_9p_push_and_notify,
+    .msize_limit = virtio_9p_msize_limit,
+    .response_buffer_size = virtio_9p_response_buffer_size,
 };
 
 static void virtio_9p_device_realize(DeviceState *dev, Error **errp)
diff --git a/hw/9pfs/xen-9p-backend.c b/hw/9pfs/xen-9p-backend.c
index ca0fff5..24c90d9 100644
--- a/hw/9pfs/xen-9p-backend.c
+++ b/hw/9pfs/xen-9p-backend.c
@@ -250,12 +250,43 @@
     qemu_bh_schedule(ring->bh);
 }
 
+static size_t xen_9p_msize_limit(V9fsState *s)
+{
+    Xen9pfsDev *xen_9pfs = container_of(s, Xen9pfsDev, state);
+    size_t limit;
+    int i;
+
+    if (!xen_9pfs->num_rings) {
+        return 0;
+    }
+
+    limit = XEN_FLEX_RING_SIZE(xen_9pfs->rings[0].ring_order);
+    for (i = 1; i < xen_9pfs->num_rings; i++) {
+        limit = MIN(limit, XEN_FLEX_RING_SIZE(xen_9pfs->rings[i].ring_order));
+    }
+
+    return limit;
+}
+
+static size_t xen_9pfs_response_buffer_size(V9fsPDU *pdu)
+{
+    Xen9pfsDev *priv = container_of(pdu->s, Xen9pfsDev, state);
+    Xen9pfsRing *ring = &priv->rings[pdu->tag % priv->num_rings];
+    struct iovec in_sg[2];
+    int num;
+
+    xen_9pfs_in_sg(ring, in_sg, &num, pdu->idx, 0);
+    return iov_size(in_sg, num);
+}
+
 static const V9fsTransport xen_9p_transport = {
     .pdu_vmarshal = xen_9pfs_pdu_vmarshal,
     .pdu_vunmarshal = xen_9pfs_pdu_vunmarshal,
     .init_in_iov_from_pdu = xen_9pfs_init_in_iov_from_pdu,
     .init_out_iov_from_pdu = xen_9pfs_init_out_iov_from_pdu,
     .push_and_notify = xen_9pfs_push_and_notify,
+    .msize_limit = xen_9p_msize_limit,
+    .response_buffer_size = xen_9pfs_response_buffer_size,
 };
 
 static int xen_9pfs_init(struct XenLegacyDevice *xendev)
diff --git a/hw/Kconfig b/hw/Kconfig
index c109f55..c92ca2b 100644
--- a/hw/Kconfig
+++ b/hw/Kconfig
@@ -53,6 +53,7 @@
 source cpu/Kconfig
 source alpha/Kconfig
 source avr/Kconfig
+source hexagon/Kconfig
 source hppa/Kconfig
 source i386/Kconfig
 source loongarch/Kconfig
diff --git a/hw/core/machine.c b/hw/core/machine.c
index 9a10e45..5b97df6 100644
--- a/hw/core/machine.c
+++ b/hw/core/machine.c
@@ -497,6 +497,8 @@
     Object *cgs =
         object_resolve_path_component(object_get_objects_root(), value);
 
+    warn_report("memory-encryption is deprecated, use confidential-guest-support instead");
+
     if (!cgs) {
         error_setg(errp, "No such memory encryption object '%s'", value);
         return;
diff --git a/hw/hexagon/Kconfig b/hw/hexagon/Kconfig
new file mode 100644
index 0000000..52065ab
--- /dev/null
+++ b/hw/hexagon/Kconfig
@@ -0,0 +1,14 @@
+config HEX_DSP
+    bool
+    default y
+    depends on HEXAGON
+
+config HEX_VIRT
+    bool
+    default y
+    depends on HEX_DSP && FDT
+    select DEVICE_TREE
+    select VIRTIO_MMIO
+    select PL011
+    select VIRTIO_BLK
+    select VIRTIO_SCSI
diff --git a/hw/hexagon/hexagon_dsp.c b/hw/hexagon/hexagon_dsp.c
new file mode 100644
index 0000000..aa49399
--- /dev/null
+++ b/hw/hexagon/hexagon_dsp.c
@@ -0,0 +1,225 @@
+/*
+ * Hexagon DSP Subsystem emulation.  This represents a generic DSP
+ * subsystem with few peripherals, like the Compute DSP.
+ *
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+
+#include "qemu/osdep.h"
+#include "qemu/units.h"
+#include "system/address-spaces.h"
+#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/core/loader.h"
+#include "qapi/error.h"
+#include "qemu/error-report.h"
+#include "qemu/log.h"
+#include "elf.h"
+#include "cpu.h"
+#include "migration/cpu.h"
+#include "system/system.h"
+#include "target/hexagon/internal.h"
+#include "system/physmem.h"
+#include "system/reset.h"
+
+#include "machine_cfg_v66g_1024.h.inc"
+
+#define TYPE_HEXAGON_DSP_MACHINE "hexagon-dsp-machine"
+OBJECT_DECLARE_SIMPLE_TYPE(HexagonDspMachineState, HEXAGON_DSP_MACHINE)
+
+struct HexagonDspMachineState {
+    HexagonCommonMachineState parent_obj;
+
+    hwaddr isdb_secure_flag;
+    hwaddr isdb_trusted_flag;
+};
+
+static HexagonDspMachineState *current_dms;
+
+static void hex_symbol_callback(const char *st_name, int st_info,
+                                uint64_t st_value, uint64_t st_size)
+{
+    if (!g_strcmp0("isdb_secure_flag", st_name)) {
+        current_dms->isdb_secure_flag = st_value;
+    }
+    if (!g_strcmp0("isdb_trusted_flag", st_name)) {
+        current_dms->isdb_trusted_flag = st_value;
+    }
+}
+
+/* Board init.  */
+static struct hexagon_board_boot_info hexagon_binfo;
+
+static void hexagon_load_kernel(HexagonDspMachineState *dms, HexagonCPU *cpu)
+{
+    uint64_t pentry;
+    long kernel_size;
+
+    current_dms = dms;
+    kernel_size = load_elf_ram_sym(hexagon_binfo.kernel_filename, NULL, NULL,
+                      NULL, &pentry, NULL, NULL,
+                      &hexagon_binfo.kernel_elf_flags, 0, EM_HEXAGON, 0, 0,
+                      &address_space_memory, false, hex_symbol_callback);
+    current_dms = NULL;
+
+    if (kernel_size <= 0) {
+        error_report("no kernel file '%s'",
+            hexagon_binfo.kernel_filename);
+        exit(1);
+    }
+
+    qdev_prop_set_uint32(DEVICE(cpu), "exec-start-addr", pentry);
+}
+
+static void hexagon_init_bootstrap(HexagonDspMachineState *dms, HexagonCPU *cpu)
+{
+    MachineState *machine = MACHINE(dms);
+
+    if (machine->kernel_filename) {
+        uint32_t mem = 1;
+
+        hexagon_load_kernel(dms, cpu);
+        if (dms->isdb_secure_flag) {
+            physical_memory_write(dms->isdb_secure_flag,
+                                     &mem, sizeof(mem));
+        }
+        if (dms->isdb_trusted_flag) {
+            physical_memory_write(dms->isdb_trusted_flag,
+                                     &mem, sizeof(mem));
+        }
+    }
+}
+
+static void do_cpu_reset(void *opaque)
+{
+    HexagonCPU *cpu = opaque;
+    CPUState *cs = CPU(cpu);
+    cpu_reset(cs);
+}
+
+static void hexagon_common_init(MachineState *machine, Rev_t rev,
+                                const struct hexagon_machine_config *m_cfg)
+{
+    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) {
+        hexagon_binfo.ram_size = machine->ram_size;
+        hexagon_binfo.kernel_filename = machine->kernel_filename;
+    }
+
+    machine->enable_graphics = 0;
+
+    address_space = get_system_memory();
+
+    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);
+
+    for (int i = 0; i < machine->smp.cpus; i++) {
+        HexagonCPU *cpu = HEXAGON_CPU(object_new(machine->cpu_type));
+        qemu_register_reset(do_cpu_reset, cpu);
+
+        /*
+         * CPU #0 is the only CPU running at boot, others must be
+         * explicitly enabled via start instruction.
+         */
+        qdev_prop_set_bit(DEVICE(cpu), "start-powered-off", (i != 0));
+        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);
+    }
+
+    rom_add_blob_fixed_as("config_table.rom", &m_cfg->cfgtable,
+                          sizeof(m_cfg->cfgtable), m_cfg->cfgbase,
+                          &address_space_memory);
+}
+
+static void init_mc(MachineClass *mc)
+{
+    mc->block_default_type = IF_SD;
+    mc->default_ram_size = 4 * GiB;
+    mc->no_parallel = 1;
+    mc->no_floppy = 1;
+    mc->no_cdrom = 1;
+    mc->no_serial = 1;
+    mc->is_default = false;
+    mc->max_cpus = 8;
+}
+
+/* ----------------------------------------------------------------- */
+/* Core-specific configuration settings are defined below this line. */
+/* Config table values defined in machine_configs.h.inc              */
+/* ----------------------------------------------------------------- */
+
+static void v66g_1024_config_init(MachineState *machine)
+{
+    hexagon_common_init(machine, v66_rev, &v66g_1024);
+}
+
+static void v66g_1024_init(ObjectClass *oc, const void *data)
+{
+    MachineClass *mc = MACHINE_CLASS(oc);
+
+    mc->desc = "Hexagon V66G_1024";
+    mc->init = v66g_1024_config_init;
+    init_mc(mc);
+    mc->is_default = true;
+    mc->default_cpu_type = TYPE_HEXAGON_CPU_V66;
+    mc->default_cpus = 4;
+}
+
+static const TypeInfo hexagon_machine_types[] = {
+    {
+        .name = TYPE_HEXAGON_COMMON_MACHINE,
+        .parent = TYPE_MACHINE,
+        .instance_size = sizeof(HexagonCommonMachineState),
+        .abstract = true,
+    },
+    {
+        .name = TYPE_HEXAGON_DSP_MACHINE,
+        .parent = TYPE_HEXAGON_COMMON_MACHINE,
+        .instance_size = sizeof(HexagonDspMachineState),
+        .abstract = true,
+    },
+    {
+        .name = MACHINE_TYPE_NAME("V66G_1024"),
+        .parent = TYPE_HEXAGON_DSP_MACHINE,
+        .class_init = v66g_1024_init,
+    },
+};
+
+DEFINE_TYPES(hexagon_machine_types)
diff --git a/hw/hexagon/hexagon_globalreg.c b/hw/hexagon/hexagon_globalreg.c
new file mode 100644
index 0000000..b5e5913
--- /dev/null
+++ b/hw/hexagon/hexagon_globalreg.c
@@ -0,0 +1,316 @@
+/*
+ * Hexagon Global Registers
+ *
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/hexagon/hexagon.h"
+#include "hw/hexagon/hexagon_globalreg.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/core/sysbus.h"
+#include "hw/core/resettable.h"
+#include "migration/vmstate.h"
+#include "qom/object.h"
+#include "target/hexagon/cpu.h"
+#include "target/hexagon/hex_regs.h"
+#include "qemu/log.h"
+#include "trace.h"
+#include "qapi/error.h"
+
+#define IMMUTABLE (~0)
+#define INVALID_REG_VAL 0xdeadbeef
+
+static const char *hex_sreg_names[] = {
+    [HEX_SREG_SGP0] = "sgp0",
+    [HEX_SREG_SGP1] = "sgp1",
+    [HEX_SREG_STID] = "stid",
+    [HEX_SREG_ELR] = "elr",
+    [HEX_SREG_BADVA0] = "badva0",
+    [HEX_SREG_BADVA1] = "badva1",
+    [HEX_SREG_SSR] = "ssr",
+    [HEX_SREG_CCR] = "ccr",
+    [HEX_SREG_HTID] = "htid",
+    [HEX_SREG_BADVA] = "badva",
+    [HEX_SREG_IMASK] = "imask",
+    [HEX_SREG_GEVB] = "gevb",
+    [HEX_SREG_EVB] = "evb",
+    [HEX_SREG_MODECTL] = "modectl",
+    [HEX_SREG_SYSCFG] = "syscfg",
+    [HEX_SREG_IPENDAD] = "ipendad",
+    [HEX_SREG_VID] = "vid",
+    [HEX_SREG_VID1] = "vid1",
+    [HEX_SREG_BESTWAIT] = "bestwait",
+    [HEX_SREG_IEL] = "iel",
+    [HEX_SREG_SCHEDCFG] = "schedcfg",
+    [HEX_SREG_IAHL] = "iahl",
+    [HEX_SREG_CFGBASE] = "cfgbase",
+    [HEX_SREG_DIAG] = "diag",
+    [HEX_SREG_REV] = "rev",
+    [HEX_SREG_PCYCLELO] = "pcyclelo",
+    [HEX_SREG_PCYCLEHI] = "pcyclehi",
+    [HEX_SREG_ISDBST] = "isdbst",
+    [HEX_SREG_ISDBCFG0] = "isdbcfg0",
+    [HEX_SREG_ISDBCFG1] = "isdbcfg1",
+    [HEX_SREG_LIVELOCK] = "livelock",
+    [HEX_SREG_BRKPTPC0] = "brkptpc0",
+    [HEX_SREG_BRKPTCFG0] = "brkptcfg0",
+    [HEX_SREG_BRKPTPC1] = "brkptpc1",
+    [HEX_SREG_BRKPTCFG1] = "brkptcfg1",
+    [HEX_SREG_ISDBMBXIN] = "isdbmbxin",
+    [HEX_SREG_ISDBMBXOUT] = "isdbmbxout",
+    [HEX_SREG_ISDBEN] = "isdben",
+    [HEX_SREG_ISDBGPR] = "isdbgpr",
+    [HEX_SREG_PMUCNT4] = "pmucnt4",
+    [HEX_SREG_PMUCNT5] = "pmucnt5",
+    [HEX_SREG_PMUCNT6] = "pmucnt6",
+    [HEX_SREG_PMUCNT7] = "pmucnt7",
+    [HEX_SREG_PMUCNT0] = "pmucnt0",
+    [HEX_SREG_PMUCNT1] = "pmucnt1",
+    [HEX_SREG_PMUCNT2] = "pmucnt2",
+    [HEX_SREG_PMUCNT3] = "pmucnt3",
+    [HEX_SREG_PMUEVTCFG] = "pmuevtcfg",
+    [HEX_SREG_PMUSTID0] = "pmustid0",
+    [HEX_SREG_PMUEVTCFG1] = "pmuevtcfg1",
+    [HEX_SREG_PMUSTID1] = "pmustid1",
+    [HEX_SREG_TIMERLO] = "timerlo",
+    [HEX_SREG_TIMERHI] = "timerhi",
+    [HEX_SREG_PMUCFG] = "pmucfg",
+    [HEX_SREG_S59] = "s59",
+    [HEX_SREG_S60] = "s60",
+    [HEX_SREG_S61] = "s61",
+    [HEX_SREG_S62] = "s62",
+    [HEX_SREG_S63] = "s63",
+};
+
+static const char *get_sreg_name(uint32_t reg)
+{
+    if (reg < ARRAY_SIZE(hex_sreg_names) && hex_sreg_names[reg]) {
+        return hex_sreg_names[reg];
+    }
+    return "UNKNOWN";
+}
+
+/* Global system register mutability masks */
+static const uint32_t global_sreg_immut_masks[NUM_SREGS] = {
+    [HEX_SREG_EVB] = 0x000000ff,
+    [HEX_SREG_MODECTL] = IMMUTABLE,
+    [HEX_SREG_SYSCFG] = 0x80001c00,
+    [HEX_SREG_IPENDAD] = IMMUTABLE,
+    [HEX_SREG_VID] = 0xfc00fc00,
+    [HEX_SREG_VID1] = 0xfc00fc00,
+    [HEX_SREG_BESTWAIT] = 0xfffffe00,
+    [HEX_SREG_IAHL] = 0x00000000,
+    [HEX_SREG_SCHEDCFG] = 0xfffffee0,
+    [HEX_SREG_CFGBASE] = IMMUTABLE,
+    [HEX_SREG_DIAG] = 0x00000000,
+    [HEX_SREG_REV] = IMMUTABLE,
+    [HEX_SREG_ISDBST] = IMMUTABLE,
+    [HEX_SREG_ISDBCFG0] = 0xe0000000,
+    [HEX_SREG_BRKPTPC0] = 0x00000003,
+    [HEX_SREG_BRKPTCFG0] = 0xfc007000,
+    [HEX_SREG_BRKPTPC1] = 0x00000003,
+    [HEX_SREG_BRKPTCFG1] = 0xfc007000,
+    [HEX_SREG_ISDBMBXIN] = IMMUTABLE,
+    [HEX_SREG_ISDBMBXOUT] = 0x00000000,
+    [HEX_SREG_ISDBEN] = 0xfffffffe,
+    [HEX_SREG_TIMERLO] = IMMUTABLE,
+    [HEX_SREG_TIMERHI] = IMMUTABLE,
+};
+
+static void hexagon_globalreg_init(Object *obj)
+{
+    HexagonGlobalRegState *s = HEXAGON_GLOBALREG(obj);
+
+    memset(s->regs, 0, sizeof(s->regs));
+}
+
+static inline uint32_t apply_write_mask(uint32_t new_val, uint32_t cur_val,
+                                        uint32_t reg_mask)
+{
+    if (reg_mask) {
+        return (new_val & ~reg_mask) | (cur_val & reg_mask);
+    }
+    return new_val;
+}
+
+uint32_t hexagon_globalreg_read(HexagonGlobalRegState *s, uint32_t reg,
+                                uint32_t htid)
+{
+    uint32_t value;
+
+    if (!s) {
+        return 0;
+    }
+    g_assert(reg < NUM_SREGS);
+    g_assert(reg >= HEX_SREG_GLB_START);
+
+    value = s->regs[reg];
+
+    trace_hexagon_globalreg_read(htid, get_sreg_name(reg), value);
+    return value;
+}
+
+void hexagon_globalreg_write(HexagonGlobalRegState *s, uint32_t reg,
+                             uint32_t value, uint32_t htid)
+{
+    if (!s) {
+        return;
+    }
+    g_assert(reg < NUM_SREGS);
+    g_assert(reg >= HEX_SREG_GLB_START);
+    s->regs[reg] = value;
+    trace_hexagon_globalreg_write(htid, get_sreg_name(reg), value);
+}
+
+uint32_t hexagon_globalreg_masked_value(HexagonGlobalRegState *s, uint32_t reg,
+                                        uint32_t value)
+{
+    uint32_t reg_mask;
+
+    if (!s) {
+        return value;
+    }
+    g_assert(reg < NUM_SREGS);
+    g_assert(reg >= HEX_SREG_GLB_START);
+    reg_mask = global_sreg_immut_masks[reg];
+    return reg_mask == IMMUTABLE ?
+            s->regs[reg] :
+            apply_write_mask(value, s->regs[reg], reg_mask);
+}
+
+void hexagon_globalreg_write_masked(HexagonGlobalRegState *s, uint32_t reg,
+                                    uint32_t value)
+{
+    if (!s) {
+        return;
+    }
+    s->regs[reg] = hexagon_globalreg_masked_value(s, reg, value);
+}
+
+uint64_t hexagon_globalreg_get_pcycle_base(HexagonGlobalRegState *s)
+{
+    g_assert(s);
+    return s->g_pcycle_base;
+}
+
+void hexagon_globalreg_set_pcycle_base(HexagonGlobalRegState *s,
+                                       uint64_t value)
+{
+    g_assert(s);
+    s->g_pcycle_base = value;
+}
+
+static void do_hexagon_globalreg_reset(HexagonGlobalRegState *s)
+{
+    uint32_t isdben_val = 0;
+
+    g_assert(s);
+    memset(s->regs, 0, sizeof(s->regs));
+
+    s->g_pcycle_base = 0;
+
+    s->regs[HEX_SREG_EVB] = s->boot_evb;
+    s->regs[HEX_SREG_CFGBASE] = HEXAGON_CFG_ADDR_BASE(s->config_table_addr);
+    s->regs[HEX_SREG_REV] = s->dsp_rev;
+
+    if (s->isdben_etm_enable) {
+        isdben_val |= (1 << 0);  /* ETM enable bit */
+    }
+    if (s->isdben_dfd_enable) {
+        isdben_val |= (1 << 1);  /* DFD enable bit */
+    }
+    if (s->isdben_trusted) {
+        isdben_val |= (1 << 2);  /* Trusted bit */
+    }
+    if (s->isdben_secure) {
+        isdben_val |= (1 << 3);  /* Secure bit */
+    }
+    s->regs[HEX_SREG_ISDBEN] = isdben_val;
+    s->regs[HEX_SREG_MODECTL] = 0x1;
+
+    /*
+     * These register indices are placeholders in these arrays
+     * and their actual values are synthesized from state elsewhere.
+     * We can initialize these with invalid values so that if we
+     * mistakenly generate reads, they will look obviously wrong.
+     */
+    s->regs[HEX_SREG_PCYCLELO] = INVALID_REG_VAL;
+    s->regs[HEX_SREG_PCYCLEHI] = INVALID_REG_VAL;
+    s->regs[HEX_SREG_TIMERLO] = INVALID_REG_VAL;
+    s->regs[HEX_SREG_TIMERHI] = INVALID_REG_VAL;
+    s->regs[HEX_SREG_PMUCNT0] = INVALID_REG_VAL;
+    s->regs[HEX_SREG_PMUCNT1] = INVALID_REG_VAL;
+    s->regs[HEX_SREG_PMUCNT2] = INVALID_REG_VAL;
+    s->regs[HEX_SREG_PMUCNT3] = INVALID_REG_VAL;
+    s->regs[HEX_SREG_PMUCNT4] = INVALID_REG_VAL;
+    s->regs[HEX_SREG_PMUCNT5] = INVALID_REG_VAL;
+    s->regs[HEX_SREG_PMUCNT6] = INVALID_REG_VAL;
+    s->regs[HEX_SREG_PMUCNT7] = INVALID_REG_VAL;
+}
+
+static void hexagon_globalreg_reset_hold(Object *obj, ResetType type)
+{
+    HexagonGlobalRegState *s = HEXAGON_GLOBALREG(obj);
+    do_hexagon_globalreg_reset(s);
+}
+
+static const VMStateDescription vmstate_hexagon_globalreg = {
+    .name = "hexagon_globalreg",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (const VMStateField[]){
+        VMSTATE_UINT32_ARRAY(regs, HexagonGlobalRegState, NUM_SREGS),
+        VMSTATE_UINT64(g_pcycle_base, HexagonGlobalRegState),
+        VMSTATE_UINT32(boot_evb, HexagonGlobalRegState),
+        VMSTATE_UINT64(config_table_addr, HexagonGlobalRegState),
+        VMSTATE_UINT32(dsp_rev, HexagonGlobalRegState),
+        VMSTATE_BOOL(isdben_etm_enable, HexagonGlobalRegState),
+        VMSTATE_BOOL(isdben_dfd_enable, HexagonGlobalRegState),
+        VMSTATE_BOOL(isdben_trusted, HexagonGlobalRegState),
+        VMSTATE_BOOL(isdben_secure, HexagonGlobalRegState),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static const Property hexagon_globalreg_properties[] = {
+    DEFINE_PROP_UINT32("boot-evb", HexagonGlobalRegState, boot_evb, 0x0),
+    DEFINE_PROP_UINT64("config-table-addr", HexagonGlobalRegState,
+                       config_table_addr, 0xffffffffULL),
+    DEFINE_PROP_UINT32("dsp-rev", HexagonGlobalRegState, dsp_rev, 0),
+    DEFINE_PROP_BOOL("isdben-etm-enable", HexagonGlobalRegState,
+                     isdben_etm_enable, false),
+    DEFINE_PROP_BOOL("isdben-dfd-enable", HexagonGlobalRegState,
+                     isdben_dfd_enable, false),
+    DEFINE_PROP_BOOL("isdben-trusted", HexagonGlobalRegState,
+                     isdben_trusted, false),
+    DEFINE_PROP_BOOL("isdben-secure", HexagonGlobalRegState,
+                     isdben_secure, false),
+};
+
+static void hexagon_globalreg_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+    rc->phases.hold = hexagon_globalreg_reset_hold;
+    dc->vmsd = &vmstate_hexagon_globalreg;
+    dc->user_creatable = false;
+    device_class_set_props(dc, hexagon_globalreg_properties);
+}
+
+static const TypeInfo hexagon_globalreg_info = {
+    .name = TYPE_HEXAGON_GLOBALREG,
+    .parent = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(HexagonGlobalRegState),
+    .instance_init = hexagon_globalreg_init,
+    .class_init = hexagon_globalreg_class_init,
+};
+
+static void hexagon_globalreg_register_types(void)
+{
+    type_register_static(&hexagon_globalreg_info);
+}
+
+type_init(hexagon_globalreg_register_types)
diff --git a/hw/hexagon/hexagon_tlb.c b/hw/hexagon/hexagon_tlb.c
new file mode 100644
index 0000000..b6d4aff
--- /dev/null
+++ b/hw/hexagon/hexagon_tlb.c
@@ -0,0 +1,467 @@
+/*
+ * Hexagon TLB QOM Device
+ *
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "hw/hexagon/hexagon_tlb.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/core/resettable.h"
+#include "migration/vmstate.h"
+#include "monitor/monitor.h"
+#include "qapi/error.h"
+#include "exec/page-protection.h"
+#include "exec/target_page.h"
+#include "target/hexagon/cpu.h"
+#include "target/hexagon/cpu_bits.h"
+
+/* PTE (TLB entry) field extraction */
+#define GET_PTE_PPD(entry)   extract64((entry),  0, 24)
+#define GET_PTE_C(entry)     extract64((entry), 24,  4)
+#define GET_PTE_U(entry)     extract64((entry), 28,  1)
+#define GET_PTE_R(entry)     extract64((entry), 29,  1)
+#define GET_PTE_W(entry)     extract64((entry), 30,  1)
+#define GET_PTE_X(entry)     extract64((entry), 31,  1)
+#define GET_PTE_VPN(entry)   extract64((entry), 32, 20)
+#define GET_PTE_ASID(entry)  extract64((entry), 52,  7)
+#define GET_PTE_ATR0(entry)  extract64((entry), 59,  1)
+#define GET_PTE_ATR1(entry)  extract64((entry), 60,  1)
+#define GET_PTE_PA35(entry)  extract64((entry), 61,  1)
+#define GET_PTE_G(entry)     extract64((entry), 62,  1)
+#define GET_PTE_V(entry)     extract64((entry), 63,  1)
+
+/* PPD (physical page descriptor) */
+static inline uint64_t GET_PPD(uint64_t entry)
+{
+    return GET_PTE_PPD(entry) | (GET_PTE_PA35(entry) << 24);
+}
+
+#define NO_ASID      (1 << 8)
+
+typedef enum {
+    PGSIZE_4K,
+    PGSIZE_16K,
+    PGSIZE_64K,
+    PGSIZE_256K,
+    PGSIZE_1M,
+    PGSIZE_4M,
+    PGSIZE_16M,
+    PGSIZE_64M,
+    PGSIZE_256M,
+    PGSIZE_1G,
+} tlb_pgsize_t;
+
+#define NUM_PGSIZE_TYPES (PGSIZE_1G + 1)
+
+static const char *pgsize_str[NUM_PGSIZE_TYPES] = {
+    "4K",
+    "16K",
+    "64K",
+    "256K",
+    "1M",
+    "4M",
+    "16M",
+    "64M",
+    "256M",
+    "1G",
+};
+
+#define INVALID_MASK 0xffffffffLL
+
+static const uint64_t encmask_2_mask[] = {
+    0x0fffLL,                           /* 4k,   0000 */
+    0x3fffLL,                           /* 16k,  0001 */
+    0xffffLL,                           /* 64k,  0010 */
+    0x3ffffLL,                          /* 256k, 0011 */
+    0xfffffLL,                          /* 1m,   0100 */
+    0x3fffffLL,                         /* 4m,   0101 */
+    0xffffffLL,                         /* 16m,  0110 */
+    0x3ffffffLL,                        /* 64m,  0111 */
+    0xfffffffLL,                        /* 256m, 1000 */
+    0x3fffffffLL,                       /* 1g,   1001 */
+    INVALID_MASK,                       /* RSVD, 1010 */
+};
+
+static inline tlb_pgsize_t hex_tlb_pgsize_type(uint64_t entry)
+{
+    if (entry == 0) {
+        qemu_log_mask(CPU_LOG_MMU, "%s: Supplied TLB entry was 0!\n",
+                      __func__);
+        return 0;
+    }
+    tlb_pgsize_t size = ctz64(entry);
+    g_assert(size < NUM_PGSIZE_TYPES);
+    return size;
+}
+
+static inline uint64_t hex_tlb_page_size_bytes(uint64_t entry)
+{
+    return 1ull << (qemu_target_page_bits() + 2 * hex_tlb_pgsize_type(entry));
+}
+
+static inline uint64_t hex_tlb_phys_page_num(uint64_t entry)
+{
+    uint32_t ppd = GET_PPD(entry);
+    return ppd >> 1;
+}
+
+static inline uint64_t hex_tlb_phys_addr(uint64_t entry)
+{
+    uint64_t pagemask = encmask_2_mask[hex_tlb_pgsize_type(entry)];
+    uint64_t pagenum = hex_tlb_phys_page_num(entry);
+    uint64_t PA = (pagenum << qemu_target_page_bits()) & (~pagemask);
+    return PA;
+}
+
+static inline uint64_t hex_tlb_virt_addr(uint64_t entry)
+{
+    return (uint64_t)GET_PTE_VPN(entry) << qemu_target_page_bits();
+}
+
+bool hexagon_tlb_dump_entry(Monitor *mon, uint64_t entry)
+{
+    if (GET_PTE_V(entry)) {
+        uint64_t PA = hex_tlb_phys_addr(entry);
+        uint64_t VA = hex_tlb_virt_addr(entry);
+        monitor_printf(mon, "0x%016" PRIx64 ": ", entry);
+        monitor_printf(mon, "V:%" PRId64 " G:%" PRId64
+                       " A1:%" PRId64 " A0:%" PRId64,
+                       GET_PTE_V(entry),
+                       GET_PTE_G(entry),
+                       GET_PTE_ATR1(entry),
+                       GET_PTE_ATR0(entry));
+        monitor_printf(mon, " ASID:0x%02" PRIx64 " VA:0x%08" PRIx64,
+                       GET_PTE_ASID(entry), VA);
+        monitor_printf(mon,
+                       " X:%" PRId64 " W:%" PRId64 " R:%" PRId64
+                       " U:%" PRId64 " C:%" PRId64,
+                       GET_PTE_X(entry),
+                       GET_PTE_W(entry),
+                       GET_PTE_R(entry),
+                       GET_PTE_U(entry),
+                       GET_PTE_C(entry));
+        monitor_printf(mon, " PA:0x%09" PRIx64 " SZ:%s (0x%" PRIx64 ")",
+                       PA, pgsize_str[hex_tlb_pgsize_type(entry)],
+                       hex_tlb_page_size_bytes(entry));
+        monitor_printf(mon, "\n");
+        return true;
+    }
+
+    /* Not valid */
+    return false;
+}
+
+static inline bool hex_tlb_entry_match_noperm(uint64_t entry, uint32_t asid,
+                                              uint64_t VA)
+{
+    if (GET_PTE_V(entry)) {
+        if (GET_PTE_G(entry)) {
+            /* Global entry - ignore ASID */
+        } else if (asid != NO_ASID) {
+            uint32_t tlb_asid = GET_PTE_ASID(entry);
+            if (tlb_asid != asid) {
+                return false;
+            }
+        }
+
+        uint64_t page_size = hex_tlb_page_size_bytes(entry);
+        uint64_t page_start =
+            ROUND_DOWN(hex_tlb_virt_addr(entry), page_size);
+        if (page_start <= VA && VA < page_start + page_size) {
+            return true;
+        }
+    }
+    return false;
+}
+
+static inline void hex_tlb_entry_get_perm(uint64_t entry,
+                                          MMUAccessType access_type,
+                                          int mmu_idx, int *prot,
+                                          int32_t *excp, int *cause_code)
+{
+    bool perm_x = GET_PTE_X(entry);
+    bool perm_w = GET_PTE_W(entry);
+    bool perm_r = GET_PTE_R(entry);
+    bool perm_u = GET_PTE_U(entry);
+    bool user_idx = mmu_idx == MMU_USER_IDX;
+
+    if (mmu_idx == MMU_KERNEL_IDX) {
+        *prot = PAGE_VALID | PAGE_READ | PAGE_WRITE | PAGE_EXEC;
+        return;
+    }
+
+    *prot = PAGE_VALID;
+    switch (access_type) {
+    case MMU_INST_FETCH:
+        if (user_idx && !perm_u) {
+            *excp = HEX_EVENT_PRECISE;
+            *cause_code = HEX_CAUSE_FETCH_NO_UPAGE;
+        } else if (!perm_x) {
+            *excp = HEX_EVENT_PRECISE;
+            *cause_code = HEX_CAUSE_FETCH_NO_XPAGE;
+        }
+        break;
+    case MMU_DATA_LOAD:
+        if (user_idx && !perm_u) {
+            *excp = HEX_EVENT_PRECISE;
+            *cause_code = HEX_CAUSE_PRIV_NO_UREAD;
+        } else if (!perm_r) {
+            *excp = HEX_EVENT_PRECISE;
+            *cause_code = HEX_CAUSE_PRIV_NO_READ;
+        }
+        break;
+    case MMU_DATA_STORE:
+        if (user_idx && !perm_u) {
+            *excp = HEX_EVENT_PRECISE;
+            *cause_code = HEX_CAUSE_PRIV_NO_UWRITE;
+        } else if (!perm_w) {
+            *excp = HEX_EVENT_PRECISE;
+            *cause_code = HEX_CAUSE_PRIV_NO_WRITE;
+        }
+        break;
+    }
+
+    if (!user_idx || perm_u) {
+        if (perm_x) {
+            *prot |= PAGE_EXEC;
+        }
+        if (perm_r) {
+            *prot |= PAGE_READ;
+        }
+        if (perm_w) {
+            *prot |= PAGE_WRITE;
+        }
+    }
+}
+
+static inline bool hex_tlb_entry_match(uint64_t entry, uint8_t asid,
+                                       uint32_t VA,
+                                       MMUAccessType access_type, hwaddr *PA,
+                                       int *prot, uint64_t *size,
+                                       int32_t *excp, int *cause_code,
+                                       int mmu_idx)
+{
+    if (hex_tlb_entry_match_noperm(entry, asid, VA)) {
+        hex_tlb_entry_get_perm(entry, access_type, mmu_idx, prot, excp,
+                               cause_code);
+        *PA = hex_tlb_phys_addr(entry);
+        *size = hex_tlb_page_size_bytes(entry);
+        return true;
+    }
+    return false;
+}
+
+static bool hex_tlb_is_match(uint64_t entry1, uint64_t entry2,
+                             bool consider_gbit)
+{
+    bool valid1 = GET_PTE_V(entry1);
+    bool valid2 = GET_PTE_V(entry2);
+    uint64_t size1 = hex_tlb_page_size_bytes(entry1);
+    uint64_t vaddr1 = ROUND_DOWN(hex_tlb_virt_addr(entry1), size1);
+    uint64_t size2 = hex_tlb_page_size_bytes(entry2);
+    uint64_t vaddr2 = ROUND_DOWN(hex_tlb_virt_addr(entry2), size2);
+    int asid1 = GET_PTE_ASID(entry1);
+    int asid2 = GET_PTE_ASID(entry2);
+    bool gbit1 = GET_PTE_G(entry1);
+    bool gbit2 = GET_PTE_G(entry2);
+
+    if (!valid1 || !valid2) {
+        return false;
+    }
+
+    if (((vaddr1 <= vaddr2) && (vaddr2 < (vaddr1 + size1))) ||
+        ((vaddr2 <= vaddr1) && (vaddr1 < (vaddr2 + size2)))) {
+        if (asid1 == asid2) {
+            return true;
+        }
+        if ((consider_gbit && gbit1) || gbit2) {
+            return true;
+        }
+    }
+    return false;
+}
+
+/* Public API */
+
+uint64_t hexagon_tlb_read(HexagonTLBState *tlb, uint32_t index)
+{
+    g_assert(index < tlb->num_entries);
+    return tlb->entries[index];
+}
+
+void hexagon_tlb_write(HexagonTLBState *tlb, uint32_t index, uint64_t value)
+{
+    g_assert(index < tlb->num_entries);
+    tlb->entries[index] = value;
+}
+
+bool hexagon_tlb_find_match(HexagonTLBState *tlb, uint32_t asid,
+                            uint32_t VA, MMUAccessType access_type,
+                            hwaddr *PA, int *prot, uint64_t *size,
+                            int32_t *excp, int *cause_code, int mmu_idx)
+{
+    *PA = 0;
+    *prot = 0;
+    *size = 0;
+    *excp = 0;
+    *cause_code = 0;
+
+    for (uint32_t i = 0; i < tlb->num_entries; i++) {
+        if (hex_tlb_entry_match(tlb->entries[i], asid, VA, access_type,
+                                PA, prot, size, excp, cause_code, mmu_idx)) {
+            return true;
+        }
+    }
+    return false;
+}
+
+uint32_t hexagon_tlb_lookup(HexagonTLBState *tlb, uint32_t asid,
+                            uint32_t VA, int *cause_code)
+{
+    uint32_t not_found = 0x80000000;
+    uint32_t idx = not_found;
+
+    for (uint32_t i = 0; i < tlb->num_entries; i++) {
+        uint64_t entry = tlb->entries[i];
+        if (hex_tlb_entry_match_noperm(entry, asid, VA)) {
+            if (idx != not_found) {
+                *cause_code = HEX_CAUSE_IMPRECISE_MULTI_TLB_MATCH;
+                break;
+            }
+            idx = i;
+        }
+    }
+
+    if (idx == not_found) {
+        qemu_log_mask(CPU_LOG_MMU,
+                      "%s: 0x%" PRIx32 ", 0x%08" PRIx32 " => NOT FOUND\n",
+                      __func__, asid, VA);
+    } else {
+        qemu_log_mask(CPU_LOG_MMU,
+                      "%s: 0x%" PRIx32 ", 0x%08" PRIx32 " => %d\n",
+                      __func__, asid, VA, idx);
+    }
+
+    return idx;
+}
+
+/*
+ * Return codes:
+ * 0 or positive             index of match
+ * -1                        multiple matches
+ * -2                        no match
+ */
+int hexagon_tlb_check_overlap(HexagonTLBState *tlb, uint64_t entry,
+                              uint64_t index)
+{
+    int matches = 0;
+    int last_match = 0;
+
+    for (uint32_t i = 0; i < tlb->num_entries; i++) {
+        if (hex_tlb_is_match(entry, tlb->entries[i], false)) {
+            matches++;
+            last_match = i;
+        }
+    }
+
+    if (matches == 1) {
+        return last_match;
+    }
+    if (matches == 0) {
+        return -2;
+    }
+    return -1;
+}
+
+void hexagon_tlb_dump(Monitor *mon, HexagonTLBState *tlb)
+{
+    for (uint32_t i = 0; i < tlb->num_entries; i++) {
+        hexagon_tlb_dump_entry(mon, tlb->entries[i]);
+    }
+}
+
+uint32_t hexagon_tlb_get_num_entries(HexagonTLBState *tlb)
+{
+    return tlb->num_entries;
+}
+
+/* QOM lifecycle */
+
+static void hexagon_tlb_init(Object *obj)
+{
+}
+
+static void hexagon_tlb_realize(DeviceState *dev, Error **errp)
+{
+    HexagonTLBState *s = HEXAGON_TLB(dev);
+
+    if (s->num_entries == 0 || s->num_entries > MAX_TLB_ENTRIES) {
+        error_setg(errp, "Invalid TLB num-entries: %" PRIu32,
+                   s->num_entries);
+        return;
+    }
+    s->entries = g_new0(uint64_t, s->num_entries);
+}
+
+static void hexagon_tlb_unrealize(DeviceState *dev)
+{
+    HexagonTLBState *s = HEXAGON_TLB(dev);
+    g_free(s->entries);
+    s->entries = NULL;
+}
+
+static void hexagon_tlb_reset_hold(Object *obj, ResetType type)
+{
+    HexagonTLBState *s = HEXAGON_TLB(obj);
+    if (s->entries) {
+        memset(s->entries, 0, sizeof(uint64_t) * s->num_entries);
+    }
+}
+
+static const VMStateDescription vmstate_hexagon_tlb = {
+    .name = "hexagon-tlb",
+    .version_id = 0,
+    .minimum_version_id = 0,
+    .fields = (const VMStateField[]) {
+        VMSTATE_UINT32(num_entries, HexagonTLBState),
+        VMSTATE_VARRAY_UINT32_ALLOC(entries, HexagonTLBState, num_entries,
+                                    0, vmstate_info_uint64, uint64_t),
+        VMSTATE_END_OF_LIST()
+    },
+};
+
+static const Property hexagon_tlb_properties[] = {
+    DEFINE_PROP_UINT32("num-entries", HexagonTLBState, num_entries,
+                       MAX_TLB_ENTRIES),
+};
+
+static void hexagon_tlb_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+    dc->realize = hexagon_tlb_realize;
+    dc->unrealize = hexagon_tlb_unrealize;
+    rc->phases.hold = hexagon_tlb_reset_hold;
+    dc->vmsd = &vmstate_hexagon_tlb;
+    dc->user_creatable = false;
+    device_class_set_props(dc, hexagon_tlb_properties);
+}
+
+static const TypeInfo hexagon_tlb_info = {
+    .name = TYPE_HEXAGON_TLB,
+    .parent = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(HexagonTLBState),
+    .instance_init = hexagon_tlb_init,
+    .class_init = hexagon_tlb_class_init,
+};
+
+static void hexagon_tlb_register_types(void)
+{
+    type_register_static(&hexagon_tlb_info);
+}
+
+type_init(hexagon_tlb_register_types)
diff --git a/hw/hexagon/machine_cfg_sa8775_cdsp0.h.inc b/hw/hexagon/machine_cfg_sa8775_cdsp0.h.inc
new file mode 100644
index 0000000..442cbe3
--- /dev/null
+++ b/hw/hexagon/machine_cfg_sa8775_cdsp0.h.inc
@@ -0,0 +1,64 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+static const struct hexagon_machine_config SA8775P_cdsp0 = {
+    .cfgbase =         0x24000000 + 0x180000,
+    .l2tcm_size =      0x00000000,
+    .l2vic_base =      0x26300000 + 0x90000,
+    .l2vic_size =      0x00001000,
+    .csr_base =        0x26300000,
+    .qtmr_region =     0x26300000 + 0xA1000,
+    .cfgtable = {
+        .l2tcm_base = 0x00002400,
+        .reserved0 = 0x00000000,
+        .subsystem_base = 0x00002638,
+        .etm_base = 0x00002419,
+        .l2cfg_base = 0x0000241a,
+        .reserved1 = 0x0000241b,
+        .l1s0_base = 0x00002500,
+        .axi2_lowaddr = 0x00000000,
+        .streamer_base = 0x00000000,
+        .reserved2 = 0x00000000,
+        .fastl2vic_base = 0x0000241e,
+        .jtlb_size_entries = 0x00000080,
+        .coproc_present = 0x00000001,
+        .ext_contexts = 0x00000004,
+        .vtcm_base = 0x00002500,
+        .vtcm_size_kb = 0x00002000,
+        .l2tag_size = 0x00000400,
+        .l2ecomem_size = 0x00000000,
+        .thread_enable_mask = 0x0000003f,
+        .eccreg_base = 0x0000241f,
+        .l2line_size = 0x00000080,
+        .tiny_core = 0x00000000,
+        .l2itcm_size = 0x00000000,
+        .l2itcm_base = 0x00002400,
+        .reserved3 = 0x00000000,
+        .dtm_present = 0x00000000,
+        .dma_version = 0x00000003,
+        .hvx_vec_log_length = 0x00000007,
+        .core_id = 0x00000000,
+        .core_count = 0x00000000,
+        .coproc2_reg0 = 0x00000040,
+        .coproc2_reg1 = 0x00000020,
+        .v2x_mode = 0x00000001,
+        .coproc2_reg2 = 0x00000008,
+        .coproc2_reg3 = 0x00000020,
+        .coproc2_reg4 = 0x00000000,
+        .coproc2_reg5 = 0x00000002,
+        .coproc2_reg6 = 0x00000016,
+        .coproc2_reg7 = 0x00000006,
+        .acd_preset = 0x00000001,
+        .mnd_preset = 0x00000000,
+        .l1d_size_kb = 0x00000010,
+        .l1i_size_kb = 0x00000020,
+        .l1d_write_policy = 0x00000002,
+        .vtcm_bank_width = 0x00000080,
+        .reserved4 = 0x00000001,
+        .reserved5 = 0x00000000,
+        .reserved6 = 0x00000003,
+        .coproc2_cvt_mpy_size = 0x0000000a,
+        .consistency_domain = 0x000000e0,
+        .capacity_domain = 0x00000080,
+        .axi3_lowaddr = 0x00000000,
+    },
+};
diff --git a/hw/hexagon/machine_cfg_v66g_1024.h.inc b/hw/hexagon/machine_cfg_v66g_1024.h.inc
new file mode 100644
index 0000000..cc4d89b
--- /dev/null
+++ b/hw/hexagon/machine_cfg_v66g_1024.h.inc
@@ -0,0 +1,64 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+static const struct hexagon_machine_config v66g_1024 = {
+    .cfgbase =        0xd8180000,
+    .l2tcm_size =     0x00000000,
+    .l2vic_base =     0xfc910000,
+    .l2vic_size =     0x00001000,
+    .csr_base =       0xfc900000,
+    .qtmr_region =    0xfc921000,
+    .cfgtable = {
+        .l2tcm_base = 0x0000d800,
+        .reserved0 = 0x0000d400,
+        .subsystem_base = 0x0000fc90,
+        .etm_base = 0x0000d805,
+        .l2cfg_base = 0x0000d81a,
+        .reserved1 = 0x00000000,
+        .l1s0_base = 0x0000d820,
+        .axi2_lowaddr = 0x00003000,
+        .streamer_base = 0x00000000,
+        .reserved2 = 0x0000d819,
+        .fastl2vic_base = 0x0000d81e,
+        .jtlb_size_entries = 0x00000080,
+        .coproc_present = 0x00000001,
+        .ext_contexts = 0x00000004,
+        .vtcm_base = 0x0000d820,
+        .vtcm_size_kb = 0x00000100,
+        .l2tag_size = 0x00000400,
+        .l2ecomem_size = 0x00000400,
+        .thread_enable_mask = 0x0000000f,
+        .eccreg_base = 0x0000d81f,
+        .l2line_size = 0x00000080,
+        .tiny_core = 0x00000000,
+        .l2itcm_size = 0x00000000,
+        .l2itcm_base = 0x0000d820,
+        .reserved3 = 0x00000000,
+        .dtm_present = 0x00000000,
+        .dma_version = 0x00000000,
+        .hvx_vec_log_length = 0x00000080,
+        .core_id = 0x00000000,
+        .core_count = 0x00000000,
+        .coproc2_reg0 = 0x00000000,
+        .coproc2_reg1 = 0x00000000,
+        .v2x_mode = 0x00000000,
+        .coproc2_reg2 = 0x00000000,
+        .coproc2_reg3 = 0x00000000,
+        .coproc2_reg4 = 0x00000000,
+        .coproc2_reg5 = 0x00000000,
+        .coproc2_reg6 = 0x00000000,
+        .coproc2_reg7 = 0x00000000,
+        .acd_preset = 0x00000000,
+        .mnd_preset = 0x00000000,
+        .l1d_size_kb = 0x00000000,
+        .l1i_size_kb = 0x00000000,
+        .l1d_write_policy = 0x00000000,
+        .vtcm_bank_width = 0x00000000,
+        .reserved4 = 0x00000000,
+        .reserved5 = 0x00000000,
+        .reserved6 = 0x00000000,
+        .coproc2_cvt_mpy_size = 0x00000000,
+        .consistency_domain = 0x00000000,
+        .capacity_domain = 0x00000000,
+        .axi3_lowaddr = 0x00000000,
+    },
+};
diff --git a/hw/hexagon/machine_cfg_v68n_1024.h.inc b/hw/hexagon/machine_cfg_v68n_1024.h.inc
new file mode 100644
index 0000000..0c79689
--- /dev/null
+++ b/hw/hexagon/machine_cfg_v68n_1024.h.inc
@@ -0,0 +1,64 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+static const struct hexagon_machine_config v68n_1024 = {
+    .cfgbase =           0xde000000,
+    .l2tcm_size =        0x00000000,
+    .l2vic_base =        0xfc910000,
+    .l2vic_size =        0x00001000,
+    .csr_base =          0xfc900000,
+    .qtmr_region =       0xfc921000,
+    .cfgtable = {
+        .l2tcm_base = 0x0000d800,
+        .reserved0 = 0x00000000,
+        .subsystem_base = 0x0000fc90,
+        .etm_base = 0x0000d819,
+        .l2cfg_base = 0x0000d81a,
+        .reserved1 = 0x00000000,
+        .l1s0_base = 0x0000d840,
+        .axi2_lowaddr = 0x00003000,
+        .streamer_base = 0x0000d81c,
+        .reserved2 = 0x0000d81d,
+        .fastl2vic_base = 0x0000d81e,
+        .jtlb_size_entries = 0x00000080,
+        .coproc_present = 0x00000001,
+        .ext_contexts = 0x00000004,
+        .vtcm_base = 0x0000d840,
+        .vtcm_size_kb = 0x00001000,
+        .l2tag_size = 0x00000400,
+        .l2ecomem_size = 0x00000400,
+        .thread_enable_mask = 0x0000003f,
+        .eccreg_base = 0x0000d81f,
+        .l2line_size = 0x00000080,
+        .tiny_core = 0x00000000,
+        .l2itcm_size = 0x00000000,
+        .l2itcm_base = 0x0000d820,
+        .reserved3 = 0x00000000,
+        .dtm_present = 0x00000000,
+        .dma_version = 0x00000001,
+        .hvx_vec_log_length = 0x00000007,
+        .core_id = 0x00000000,
+        .core_count = 0x00000000,
+        .coproc2_reg0 = 0x00000040,
+        .coproc2_reg1 = 0x00000020,
+        .v2x_mode = 0x1f1f1f1f,
+        .coproc2_reg2 = 0x1f1f1f1f,
+        .coproc2_reg3 = 0x1f1f1f1f,
+        .coproc2_reg4 = 0x1f1f1f1f,
+        .coproc2_reg5 = 0x1f1f1f1f,
+        .coproc2_reg6 = 0x1f1f1f1f,
+        .coproc2_reg7 = 0x1f1f1f1f,
+        .acd_preset = 0x1f1f1f1f,
+        .mnd_preset = 0x1f1f1f1f,
+        .l1d_size_kb = 0x1f1f1f1f,
+        .l1i_size_kb = 0x1f1f1f1f,
+        .l1d_write_policy = 0x1f1f1f1f,
+        .vtcm_bank_width = 0x1f1f1f1f,
+        .reserved4 = 0x1f1f1f1f,
+        .reserved5 = 0x1f1f1f1f,
+        .reserved6 = 0x1f1f1f1f,
+        .coproc2_cvt_mpy_size = 0x1f1f1f1f,
+        .consistency_domain = 0x1f1f1f1f,
+        .capacity_domain = 0x1f1f1f1f,
+        .axi3_lowaddr = 0x1f1f1f1f,
+    },
+};
diff --git a/hw/hexagon/meson.build b/hw/hexagon/meson.build
new file mode 100644
index 0000000..bade3a3
--- /dev/null
+++ b/hw/hexagon/meson.build
@@ -0,0 +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('hexagon_dsp.c'))
+hexagon_ss.add(when: 'CONFIG_HEX_VIRT', if_true: files('virt.c'))
+
+hw_arch += {'hexagon': hexagon_ss}
diff --git a/hw/hexagon/trace-events b/hw/hexagon/trace-events
new file mode 100644
index 0000000..5d623ed
--- /dev/null
+++ b/hw/hexagon/trace-events
@@ -0,0 +1,3 @@
+# Hexagon global register access
+hexagon_globalreg_read(uint32_t htid, const char *reg_name, uint32_t value) "htid=%u reg=%s value=0x%x"
+hexagon_globalreg_write(uint32_t htid, const char *reg_name, uint32_t value) "htid=%u reg=%s value=0x%x"
diff --git a/hw/hexagon/trace.h b/hw/hexagon/trace.h
new file mode 100644
index 0000000..9e0b39f
--- /dev/null
+++ b/hw/hexagon/trace.h
@@ -0,0 +1,2 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#include "trace/trace-hw_hexagon.h"
diff --git a/hw/hexagon/virt.c b/hw/hexagon/virt.c
new file mode 100644
index 0000000..a363899
--- /dev/null
+++ b/hw/hexagon/virt.c
@@ -0,0 +1,347 @@
+/*
+ * Hexagon virt emulation
+ *
+ * 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/virt.h"
+#include "elf.h"
+#include "hw/char/pl011.h"
+#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/core/loader.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/core/qdev-clock.h"
+#include "hw/core/register.h"
+#include "qemu/error-report.h"
+#include "qemu/guest-random.h"
+#include "qemu/units.h"
+#include "machine_cfg_v68n_1024.h.inc"
+#include "system/address-spaces.h"
+#include "system/device_tree.h"
+#include "system/reset.h"
+#include "system/system.h"
+#include <libfdt.h>
+
+enum {
+    VIRT_UART0,
+    VIRT_FDT,
+};
+
+static const MemMapEntry base_memmap[] = {
+    [VIRT_UART0] = { 0x10000000, 0x00000200 },
+    [VIRT_FDT] = { 0x99800000, 0x00400000 },
+};
+
+
+static void create_fdt(HexagonVirtMachineState *vms)
+{
+    MachineState *ms = MACHINE(vms);
+    void *fdt = create_device_tree(&vms->fdt_size);
+    uint8_t rng_seed[32];
+
+    if (!fdt) {
+        error_report("create_device_tree() failed");
+        exit(1);
+    }
+
+    ms->fdt = fdt;
+
+    qemu_fdt_setprop_cell(fdt, "/", "#address-cells", 0x2);
+    qemu_fdt_setprop_cell(fdt, "/", "#size-cells", 0x1);
+    qemu_fdt_setprop_string(fdt, "/", "model", "hexagon-virt,qemu");
+    qemu_fdt_setprop_string(fdt, "/", "compatible", "qcom,sm8150");
+
+    qemu_fdt_add_subnode(fdt, "/soc");
+    qemu_fdt_setprop_cell(fdt, "/soc", "#address-cells", 0x2);
+    qemu_fdt_setprop_cell(fdt, "/soc", "#size-cells", 0x1);
+    qemu_fdt_setprop(fdt, "/soc", "ranges", NULL, 0);
+
+    qemu_fdt_add_subnode(fdt, "/chosen");
+    qemu_guest_getrandom_nofail(rng_seed, sizeof(rng_seed));
+    qemu_fdt_setprop(fdt, "/chosen", "rng-seed", rng_seed, sizeof(rng_seed));
+}
+
+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");
+
+        assert(sizeof(m_cfg->cfgtable.vtcm_base) == sizeof(uint32_t));
+        qemu_fdt_setprop_cells(ms->fdt, "/soc/vtcm", "reg", 0,
+                               m_cfg->cfgtable.vtcm_base << 16,
+                               vtcm_size_bytes);
+    }
+
+    if (m_cfg->cfgtable.ext_contexts > 0) {
+        qemu_fdt_add_subnode(ms->fdt, "/soc/hvx");
+        qemu_fdt_setprop_string(ms->fdt, "/soc/hvx", "compatible",
+                                "qcom,hexagon-hvx");
+        qemu_fdt_setprop_cells(ms->fdt, "/soc/hvx", "qcom,hvx-max-ctxts",
+                               m_cfg->cfgtable.ext_contexts);
+        qemu_fdt_setprop_cells(ms->fdt, "/soc/hvx", "qcom,hvx-vlength",
+                               m_cfg->cfgtable.hvx_vec_log_length);
+    }
+}
+
+static int32_t fdt_add_clocks(const HexagonVirtMachineState *vms)
+{
+    MachineState *ms = MACHINE(vms);
+    int32_t clk_phandle = qemu_fdt_alloc_phandle(ms->fdt);
+
+    qemu_fdt_add_subnode(ms->fdt, "/apb-pclk");
+    qemu_fdt_setprop_string(ms->fdt, "/apb-pclk", "compatible", "fixed-clock");
+    qemu_fdt_setprop_cell(ms->fdt, "/apb-pclk", "#clock-cells", 0x0);
+    qemu_fdt_setprop_cell(ms->fdt, "/apb-pclk", "clock-frequency", 24000000);
+    qemu_fdt_setprop_string(ms->fdt, "/apb-pclk", "clock-output-names",
+                            "clk24mhz");
+    qemu_fdt_setprop_cell(ms->fdt, "/apb-pclk", "phandle", clk_phandle);
+
+    return clk_phandle;
+}
+
+static void fdt_add_uart(const HexagonVirtMachineState *vms, int uart,
+                         int32_t clk_phandle)
+{
+    char *nodename;
+    hwaddr base = base_memmap[uart].base;
+    hwaddr size = base_memmap[uart].size;
+    assert(uart == 0);
+    const char compat[] = "arm,pl011\0arm,primecell";
+    const char clocknames[] = "uartclk\0apb_pclk";
+    MachineState *ms = MACHINE(vms);
+    DeviceState *dev;
+    SysBusDevice *s;
+
+    dev = qdev_new(TYPE_PL011);
+    s = SYS_BUS_DEVICE(dev);
+    qdev_prop_set_chr(dev, "chardev", serial_hd(0));
+    qdev_connect_clock_in(dev, "clk", vms->apb_clk);
+    sysbus_realize_and_unref(s, &error_fatal);
+    sysbus_mmio_map(s, 0, base);
+
+    nodename = g_strdup_printf("/pl011@%" PRIx64, base);
+    qemu_fdt_add_subnode(ms->fdt, nodename);
+
+    /* 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_cells(ms->fdt, nodename, "clocks", clk_phandle,
+                           clk_phandle);
+    qemu_fdt_setprop(ms->fdt, nodename, "clock-names", clocknames,
+                     sizeof(clocknames));
+
+    qemu_fdt_setprop_string(ms->fdt, "/chosen", "stdout-path", nodename);
+    qemu_fdt_add_subnode(ms->fdt, "/aliases");
+    qemu_fdt_setprop_string(ms->fdt, "/aliases", "serial0", nodename);
+
+    g_free(nodename);
+}
+
+static void fdt_add_cpu_nodes(const HexagonVirtMachineState *vms)
+{
+    MachineState *ms = MACHINE(vms);
+    qemu_fdt_add_subnode(ms->fdt, "/cpus");
+    qemu_fdt_setprop_cell(ms->fdt, "/cpus", "#address-cells", 0x1);
+    qemu_fdt_setprop_cell(ms->fdt, "/cpus", "#size-cells", 0x0);
+
+    /* cpu nodes */
+    for (int num = ms->smp.cpus - 1; num >= 0; num--) {
+        char *nodename = g_strdup_printf("/cpus/cpu@%d", num);
+        qemu_fdt_add_subnode(ms->fdt, nodename);
+        qemu_fdt_setprop_string(ms->fdt, nodename, "device_type", "cpu");
+        qemu_fdt_setprop_cell(ms->fdt, nodename, "reg", num);
+        qemu_fdt_setprop_cell(ms->fdt, nodename, "phandle",
+                              qemu_fdt_alloc_phandle(ms->fdt));
+        g_free(nodename);
+    }
+}
+
+
+
+static void virt_instance_init(Object *obj)
+{
+    HexagonVirtMachineState *vms = HEXAGON_VIRT_MACHINE(obj);
+
+    create_fdt(vms);
+}
+
+void hexagon_load_fdt(const HexagonVirtMachineState *vms)
+{
+    MachineState *ms = MACHINE(vms);
+    hwaddr fdt_addr = base_memmap[VIRT_FDT].base;
+    uint32_t fdtsize = vms->fdt_size;
+
+    g_assert(fdtsize <= base_memmap[VIRT_FDT].size);
+    /* copy in the device tree */
+    rom_add_blob_fixed_as("fdt", ms->fdt, fdtsize, fdt_addr,
+                          &address_space_memory);
+    qemu_register_reset_nosnapshotload(
+        qemu_fdt_randomize_seeds,
+        rom_ptr_for_as(&address_space_memory, fdt_addr, fdtsize));
+}
+
+static uint64_t load_kernel(const HexagonVirtMachineState *vms)
+{
+    MachineState *ms = MACHINE(vms);
+    uint64_t entry = 0;
+    if (load_elf_ram_sym(ms->kernel_filename, NULL, NULL, NULL, &entry, NULL,
+                         NULL, NULL, 0, EM_HEXAGON, 0, 0, &address_space_memory,
+                         false, NULL) > 0) {
+        return entry;
+    }
+    error_report("error loading '%s'", ms->kernel_filename);
+    exit(1);
+}
+
+static uint64_t load_bios(HexagonVirtMachineState *vms)
+{
+    MachineState *ms = MACHINE(vms);
+    uint64_t bios_addr = 0x0;  /* Load BIOS at reset vector address 0x0 */
+    int bios_size;
+
+    bios_size = load_image_targphys(ms->firmware ?: "",
+                                    bios_addr, 64 * 1024, NULL);
+    if (bios_size < 0) {
+        error_report("Could not load BIOS '%s'", ms->firmware ?: "");
+        exit(1);
+    }
+
+    return bios_addr;  /* Return entry point at address 0x0 */
+}
+
+static void do_cpu_reset(void *opaque)
+{
+    HexagonCPU *cpu = opaque;
+    CPUState *cs = CPU(cpu);
+    cpu_reset(cs);
+}
+
+static void virt_init(MachineState *ms)
+{
+    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;
+
+    qemu_fdt_setprop_string(ms->fdt, "/chosen", "bootargs", ms->kernel_cmdline);
+
+    vms->sys = get_system_memory();
+
+    /* Create APB clock for peripherals */
+    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);
+
+    if (m_cfg->l2tcm_size) {
+        memory_region_init_ram(&vms->tcm, NULL, "tcm.ram", m_cfg->l2tcm_size,
+                               &error_fatal);
+        memory_region_add_subregion(vms->sys, m_cfg->cfgtable.l2tcm_base << 16,
+                                    &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);
+
+    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);
+
+    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);
+            } 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), "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);
+    }
+    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);
+
+    hexagon_load_fdt(vms);
+}
+
+
+static void virt_class_init(ObjectClass *oc, const void *data)
+{
+    MachineClass *mc = MACHINE_CLASS(oc);
+
+    mc->desc = "Hexagon Virtual Machine";
+    mc->init = virt_init;
+    mc->default_cpu_type = HEXAGON_CPU_TYPE_NAME("v68");
+    mc->default_ram_size = 4 * GiB;
+    mc->max_cpus = 8;
+    mc->default_cpus = 8;
+    mc->is_default = false;
+    mc->default_kernel_irqchip_split = false;
+    mc->block_default_type = IF_VIRTIO;
+    mc->default_boot_order = NULL;
+    mc->no_cdrom = 1;
+    mc->numa_mem_supported = false;
+    mc->default_nic = "virtio-mmio-bus";
+}
+
+
+static const TypeInfo virt_machine_types[] = { {
+    .name = TYPE_HEXAGON_VIRT_MACHINE,
+    .parent = TYPE_HEXAGON_COMMON_MACHINE,
+    .instance_size = sizeof(HexagonVirtMachineState),
+    .class_init = virt_class_init,
+    .instance_init = virt_instance_init,
+} };
+
+DEFINE_TYPES(virt_machine_types)
diff --git a/hw/meson.build b/hw/meson.build
index ef65ba5..7fa81db 100644
--- a/hw/meson.build
+++ b/hw/meson.build
@@ -3,6 +3,7 @@
 subdir('arm')
 subdir('avr')
 subdir('hppa')
+subdir('hexagon')
 subdir('xenpv') # i386 uses it
 subdir('i386')
 subdir('loongarch')
diff --git a/hw/misc/ivshmem-pci.c b/hw/misc/ivshmem-pci.c
index c987eeb..536475e 100644
--- a/hw/misc/ivshmem-pci.c
+++ b/hw/misc/ivshmem-pci.c
@@ -26,7 +26,7 @@
 #include "hw/core/qdev-properties-system.h"
 #include "hw/pci/msi.h"
 #include "hw/pci/msix.h"
-#include "system/kvm.h"
+#include "system/accel-irq.h"
 #include "migration/blocker.h"
 #include "migration/vmstate.h"
 #include "qemu/error-report.h"
@@ -424,19 +424,19 @@
                                      Error **errp)
 {
     PCIDevice *pdev = PCI_DEVICE(s);
-    KVMRouteChange c;
+    AccelRouteChange c;
     int ret;
 
     IVSHMEM_DPRINTF("ivshmem_add_kvm_msi_virq vector:%d\n", vector);
     assert(!s->msi_vectors[vector].pdev);
 
-    c = kvm_irqchip_begin_route_changes(kvm_state);
+    c = accel_irqchip_begin_route_changes();
     ret = kvm_irqchip_add_msi_route(&c, vector, pdev);
     if (ret < 0) {
         error_setg(errp, "kvm_irqchip_add_msi_route failed");
         return;
     }
-    kvm_irqchip_commit_route_changes(&c);
+    accel_irqchip_commit_route_changes(&c);
 
     s->msi_vectors[vector].virq = ret;
     s->msi_vectors[vector].pdev = pdev;
diff --git a/hw/vfio/pci.c b/hw/vfio/pci.c
index 7d57eb4..4d822b9 100644
--- a/hw/vfio/pci.c
+++ b/hw/vfio/pci.c
@@ -38,6 +38,7 @@
 #include "qemu/module.h"
 #include "qemu/range.h"
 #include "qemu/units.h"
+#include "system/accel-irq.h"
 #include "system/kvm.h"
 #include "system/runstate.h"
 #include "pci.h"
@@ -50,7 +51,7 @@
 #include "vfio-helpers.h"
 
 /* Protected by BQL */
-static KVMRouteChange vfio_route_change;
+static AccelRouteChange vfio_route_change;
 
 static void vfio_disable_interrupts(VFIOPCIDevice *vdev);
 static void vfio_mmap_set_enabled(VFIOPCIDevice *vdev, bool enabled);
@@ -688,9 +689,9 @@
             if (vdev->defer_kvm_irq_routing) {
                 vfio_pci_add_kvm_msi_virq(vdev, vector, nr, true);
             } else {
-                vfio_route_change = kvm_irqchip_begin_route_changes(kvm_state);
+                vfio_route_change = accel_irqchip_begin_route_changes();
                 vfio_pci_add_kvm_msi_virq(vdev, vector, nr, true);
-                kvm_irqchip_commit_route_changes(&vfio_route_change);
+                accel_irqchip_commit_route_changes(&vfio_route_change);
                 vfio_connect_kvm_msi_virq(vector, nr);
             }
         }
@@ -789,7 +790,7 @@
 {
     assert(!vdev->defer_kvm_irq_routing);
     vdev->defer_kvm_irq_routing = true;
-    vfio_route_change = kvm_irqchip_begin_route_changes(kvm_state);
+    vfio_route_change = accel_irqchip_begin_route_changes();
 }
 
 void vfio_pci_commit_kvm_msi_virq_batch(VFIOPCIDevice *vdev)
@@ -799,7 +800,7 @@
     assert(vdev->defer_kvm_irq_routing);
     vdev->defer_kvm_irq_routing = false;
 
-    kvm_irqchip_commit_route_changes(&vfio_route_change);
+    accel_irqchip_commit_route_changes(&vfio_route_change);
 
     for (i = 0; i < vdev->nr_vectors; i++) {
         vfio_connect_kvm_msi_virq(&vdev->msi_vectors[i], i);
diff --git a/hw/virtio/virtio-pci.c b/hw/virtio/virtio-pci.c
index fd97195..6f5db5f 100644
--- a/hw/virtio/virtio-pci.c
+++ b/hw/virtio/virtio-pci.c
@@ -869,7 +869,7 @@
     int ret;
 
     if (irqfd->users == 0) {
-        KVMRouteChange c = kvm_irqchip_begin_route_changes(kvm_state);
+        AccelRouteChange c = accel_irqchip_begin_route_changes();
         ret = accel_irqchip_add_msi_route(&c, vector, &proxy->pci_dev);
         if (ret < 0) {
             return ret;
@@ -2669,4 +2669,3 @@
 }
 
 type_init(virtio_pci_register_types)
-
diff --git a/include/accel/accel-route.h b/include/accel/accel-route.h
new file mode 100644
index 0000000..07fac27
--- /dev/null
+++ b/include/accel/accel-route.h
@@ -0,0 +1,17 @@
+/*
+ * Accelerator MSI route change tracking
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef ACCEL_ROUTE_H
+#define ACCEL_ROUTE_H
+
+#include "qemu/accel.h"
+
+typedef struct AccelRouteChange {
+    AccelState *accel;
+    int changes;
+} AccelRouteChange;
+
+#endif /* ACCEL_ROUTE_H */
diff --git a/include/hw/hexagon/hexagon.h b/include/hw/hexagon/hexagon.h
new file mode 100644
index 0000000..1034b09
--- /dev/null
+++ b/include/hw/hexagon/hexagon.h
@@ -0,0 +1,161 @@
+/*
+ * Hexagon Baseboard System emulation.
+ *
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+
+#ifndef HW_HEXAGON_H
+#define HW_HEXAGON_H
+
+#include "system/memory.h"
+#include "hw/core/boards.h"
+
+struct hexagon_board_boot_info {
+    uint64_t ram_size;
+    const char *kernel_filename;
+    uint32_t kernel_elf_flags;
+};
+
+typedef enum {
+    unknown_rev = 0,
+    v66_rev = 0xa666,
+    v67_rev = 0x2667,
+    v68_rev = 0x8d68,
+    v69_rev = 0x8c69,
+    v71_rev = 0x8c71,
+    v73_rev = 0x8c73,
+    v73m_rev = 0xcc73,
+} Rev_t;
+#define HEXAGON_LATEST_REV v73
+#define HEXAGON_LATEST_REV_UPPER V73
+
+/*
+ * Config table address bases represent bits [35:16].
+ */
+#define HEXAGON_CFG_ADDR_BASE(addr) (((addr) >> 16) & 0x0fffff)
+
+#define HEXAGON_CFGSPACE_ENTRIES (128)
+
+union hexagon_config_table {
+    struct {
+        /* Base address of L2TCM space */
+        uint32_t l2tcm_base;
+        uint32_t reserved0;
+        /* Base address of subsystem space */
+        uint32_t subsystem_base;
+        /* Base address of ETM space */
+        uint32_t etm_base;
+        /* Base address of L2 configuration space */
+        uint32_t l2cfg_base;
+        uint32_t reserved1;
+        /* Base address of L1S */
+        uint32_t l1s0_base;
+        /* Base address of AXI2 */
+        uint32_t axi2_lowaddr;
+        /* Base address of streamer base */
+        uint32_t streamer_base;
+        uint32_t reserved2;
+        /* Base address of fast L2VIC */
+        uint32_t fastl2vic_base;
+        /* Number of entries in JTLB */
+        uint32_t jtlb_size_entries;
+        /* Coprocessor type */
+        uint32_t coproc_present;
+        /* Number of extension execution contexts available */
+        uint32_t ext_contexts;
+        /* Base address of Hexagon Vector Tightly Coupled Memory (VTCM) */
+        uint32_t vtcm_base;
+        /* Size of VTCM (in KB) */
+        uint32_t vtcm_size_kb;
+        /* L2 tag size */
+        uint32_t l2tag_size;
+        /* Amount of physical L2 memory in released version */
+        uint32_t l2ecomem_size;
+        /* Hardware threads available on the core */
+        uint32_t thread_enable_mask;
+        /* Base address of the ECC registers */
+        uint32_t eccreg_base;
+        /* L2 line size */
+        uint32_t l2line_size;
+        /* Small Core processor (also implies audio extension) */
+        uint32_t tiny_core;
+        /* Size of L2TCM */
+        uint32_t l2itcm_size;
+        /* Base address of L2-ITCM */
+        uint32_t l2itcm_base;
+        uint32_t reserved3;
+        /* DTM is present */
+        uint32_t dtm_present;
+        /* Version of the DMA */
+        uint32_t dma_version;
+        /* Native HVX vector length in log of bytes */
+        uint32_t hvx_vec_log_length;
+        /* Core ID of the multi-core */
+        uint32_t core_id;
+        /* Number of multi-core cores */
+        uint32_t core_count;
+        uint32_t coproc2_reg0;
+        uint32_t coproc2_reg1;
+        /* Supported HVX vector length */
+        uint32_t v2x_mode;
+        uint32_t coproc2_reg2;
+        uint32_t coproc2_reg3;
+        uint32_t coproc2_reg4;
+        uint32_t coproc2_reg5;
+        uint32_t coproc2_reg6;
+        uint32_t coproc2_reg7;
+        /* Voltage droop mitigation technique parameter */
+        uint32_t acd_preset;
+        /* Voltage droop mitigation technique parameter */
+        uint32_t mnd_preset;
+        /* L1 data cache size (in KB) */
+        uint32_t l1d_size_kb;
+        /* L1 instruction cache size in (KB) */
+        uint32_t l1i_size_kb;
+        /* L1 data cache write policy: see HexagonL1WritePolicy */
+        uint32_t l1d_write_policy;
+        /* VTCM bank width  */
+        uint32_t vtcm_bank_width;
+        uint32_t reserved4;
+        uint32_t reserved5;
+        uint32_t reserved6;
+        uint32_t coproc2_cvt_mpy_size;
+        uint32_t consistency_domain;
+        uint32_t capacity_domain;
+        uint32_t axi3_lowaddr;
+        uint32_t coproc2_int8_subcolumns;
+        uint32_t corecfg_present;
+        uint32_t coproc2_fp16_acc_exp;
+        uint32_t AXIM2_secondary_base;
+    };
+    uint32_t raw[HEXAGON_CFGSPACE_ENTRIES];
+};
+
+struct hexagon_machine_config {
+    /* Base address of config table */
+    uint32_t cfgbase;
+    /* Size of L2 TCM */
+    uint32_t l2tcm_size;
+    /* Base address of L2VIC */
+    uint32_t l2vic_base;
+    /* Size of L2VIC region */
+    uint32_t l2vic_size;
+    /* QTimer csr base */
+    uint32_t csr_base;
+    uint32_t qtmr_region;
+    union hexagon_config_table cfgtable;
+};
+
+#define TYPE_HEXAGON_COMMON_MACHINE "hexagon-common-machine"
+OBJECT_DECLARE_SIMPLE_TYPE(HexagonCommonMachineState, HEXAGON_COMMON_MACHINE)
+
+struct HexagonCommonMachineState {
+    MachineState parent_obj;
+
+    MemoryRegion ram;
+    MemoryRegion cfgtable_rom;
+};
+
+#endif
diff --git a/include/hw/hexagon/hexagon_globalreg.h b/include/hw/hexagon/hexagon_globalreg.h
new file mode 100644
index 0000000..9500998
--- /dev/null
+++ b/include/hw/hexagon/hexagon_globalreg.h
@@ -0,0 +1,55 @@
+/*
+ * Hexagon Global Registers QOM Object
+ *
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HEXAGON_GLOBALREG_H
+#define HEXAGON_GLOBALREG_H
+
+#include "hw/core/qdev.h"
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+#include "target/hexagon/cpu.h"
+
+#define TYPE_HEXAGON_GLOBALREG "hexagon-globalreg"
+OBJECT_DECLARE_SIMPLE_TYPE(HexagonGlobalRegState, HEXAGON_GLOBALREG)
+
+struct HexagonGlobalRegState {
+    SysBusDevice parent_obj;
+
+    /* Array of system registers */
+    uint32_t regs[NUM_SREGS];
+
+    /* Global performance cycle counter base */
+    uint64_t g_pcycle_base;
+
+    /* Properties for global register reset values */
+    uint32_t boot_evb;           /* Boot Exception Vector Base (HEX_SREG_EVB) */
+    uint64_t config_table_addr;  /* Configuration table base */
+    uint32_t dsp_rev;           /* DSP revision register (HEX_SREG_REV) */
+
+    /* ISDB properties */
+    bool isdben_etm_enable;     /* ISDB ETM enable bit */
+    bool isdben_dfd_enable;     /* ISDB DFD enable bit */
+    bool isdben_trusted;        /* ISDB trusted mode bit */
+    bool isdben_secure;         /* ISDB secure mode bit */
+};
+
+/* Public interface functions */
+uint32_t hexagon_globalreg_read(HexagonGlobalRegState *s, uint32_t reg,
+                                uint32_t htid);
+void hexagon_globalreg_write(HexagonGlobalRegState *s, uint32_t reg,
+                             uint32_t value, uint32_t htid);
+uint32_t hexagon_globalreg_masked_value(HexagonGlobalRegState *s, uint32_t reg,
+                                        uint32_t value);
+void hexagon_globalreg_write_masked(HexagonGlobalRegState *s, uint32_t reg,
+                                    uint32_t value);
+
+/* Global performance cycle counter access */
+uint64_t hexagon_globalreg_get_pcycle_base(HexagonGlobalRegState *s);
+void hexagon_globalreg_set_pcycle_base(HexagonGlobalRegState *s,
+                                       uint64_t value);
+
+#endif /* HEXAGON_GLOBALREG_H */
diff --git a/include/hw/hexagon/hexagon_tlb.h b/include/hw/hexagon/hexagon_tlb.h
new file mode 100644
index 0000000..90d9ed8
--- /dev/null
+++ b/include/hw/hexagon/hexagon_tlb.h
@@ -0,0 +1,46 @@
+/*
+ * Hexagon TLB QOM Device
+ *
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_HEXAGON_TLB_H
+#define HW_HEXAGON_TLB_H
+
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+#include "exec/hwaddr.h"
+#include "exec/mmu-access-type.h"
+#include "monitor/monitor.h"
+#define TYPE_HEXAGON_TLB "hexagon-tlb"
+OBJECT_DECLARE_SIMPLE_TYPE(HexagonTLBState, HEXAGON_TLB)
+
+struct HexagonTLBState {
+    SysBusDevice parent_obj;
+
+    uint32_t num_entries;
+    uint64_t *entries;
+};
+
+uint64_t hexagon_tlb_read(HexagonTLBState *tlb, uint32_t index);
+void hexagon_tlb_write(HexagonTLBState *tlb, uint32_t index, uint64_t value);
+
+bool hexagon_tlb_find_match(HexagonTLBState *tlb, uint32_t asid,
+                            uint32_t VA, MMUAccessType access_type,
+                            hwaddr *PA, int *prot, uint64_t *size,
+                            int32_t *excp, int *cause_code, int mmu_idx);
+
+uint32_t hexagon_tlb_lookup(HexagonTLBState *tlb, uint32_t asid,
+                            uint32_t VA, int *cause_code);
+
+int hexagon_tlb_check_overlap(HexagonTLBState *tlb, uint64_t entry,
+                              uint64_t index);
+
+void hexagon_tlb_dump(Monitor *mon, HexagonTLBState *tlb);
+
+bool hexagon_tlb_dump_entry(Monitor *mon, uint64_t entry);
+
+uint32_t hexagon_tlb_get_num_entries(HexagonTLBState *tlb);
+
+#endif /* HW_HEXAGON_TLB_H */
diff --git a/include/hw/hexagon/virt.h b/include/hw/hexagon/virt.h
new file mode 100644
index 0000000..fcb4776
--- /dev/null
+++ b/include/hw/hexagon/virt.h
@@ -0,0 +1,30 @@
+/*
+ * Definitions for hexagon virt board.
+ *
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_HEXAGONVIRT_H
+#define HW_HEXAGONVIRT_H
+
+#include "hw/hexagon/hexagon.h"
+#include "target/hexagon/cpu.h"
+
+struct HexagonVirtMachineState {
+    HexagonCommonMachineState parent_obj;
+
+    int fdt_size;
+    MemoryRegion *sys;
+    MemoryRegion tcm;
+    MemoryRegion vtcm;
+    MemoryRegion bios;
+    Clock *apb_clk;
+};
+
+void hexagon_load_fdt(const struct HexagonVirtMachineState *vms);
+
+#define TYPE_HEXAGON_VIRT_MACHINE MACHINE_TYPE_NAME("virt")
+OBJECT_DECLARE_SIMPLE_TYPE(HexagonVirtMachineState, HEXAGON_VIRT_MACHINE)
+
+#endif /* HW_HEXAGONVIRT_H */
diff --git a/include/hw/hyperv/hvgdk_mini.h b/include/hw/hyperv/hvgdk_mini.h
index c3a8f33..f8838a3 100644
--- a/include/hw/hyperv/hvgdk_mini.h
+++ b/include/hw/hyperv/hvgdk_mini.h
@@ -9,9 +9,25 @@
 
 #define MSHV_IOCTL  0xB8
 
+/* Hyper-V specific model specific registers (MSRs) */
+
+/* HV_X64_SYNTHETIC_MSR */
+#define HV_X64_MSR_GUEST_OS_ID      0x40000000
+#define HV_X64_MSR_HYPERCALL        0x40000001
+#define HV_X64_MSR_VP_INDEX         0x40000002
+#define HV_X64_MSR_RESET            0x40000003
+#define HV_X64_MSR_VP_RUNTIME       0x40000010
+#define HV_X64_MSR_TIME_REF_COUNT   0x40000020
+#define HV_X64_MSR_REFERENCE_TSC    0x40000021
+#define HV_X64_MSR_TSC_FREQUENCY    0x40000022
+#define HV_X64_MSR_APIC_FREQUENCY   0x40000023
+
 typedef enum hv_register_name {
     /* Pending Interruption Register */
     HV_REGISTER_PENDING_INTERRUPTION = 0x00010002,
+    HV_REGISTER_INTERRUPT_STATE      = 0x00010003,
+    HV_REGISTER_PENDING_EVENT0       = 0x00010004,
+    HV_REGISTER_PENDING_EVENT1       = 0x00010005,
 
     /* X64 User-Mode Registers */
     HV_X64_REGISTER_RAX     = 0x00020000,
@@ -154,10 +170,22 @@
     HV_X64_REGISTER_SPEC_CTRL       = 0x00080084,
     HV_X64_REGISTER_TSC_ADJUST      = 0x00080096,
 
+    /* CET / Shadow Stack */
+    HV_X64_REGISTER_U_XSS                    = 0x0008008B,
+    HV_X64_REGISTER_U_CET                    = 0x0008008C,
+    HV_X64_REGISTER_S_CET                    = 0x0008008D,
+    HV_X64_REGISTER_SSP                      = 0x0008008E,
+    HV_X64_REGISTER_PL0_SSP                  = 0x0008008F,
+    HV_X64_REGISTER_PL1_SSP                  = 0x00080090,
+    HV_X64_REGISTER_PL2_SSP                  = 0x00080091,
+    HV_X64_REGISTER_PL3_SSP                  = 0x00080092,
+    HV_X64_REGISTER_INTERRUPT_SSP_TABLE_ADDR = 0x00080093,
+
     /* Other MSRs */
     HV_X64_REGISTER_MSR_IA32_MISC_ENABLE = 0x000800A0,
 
     /* Misc */
+    HV_X64_REGISTER_HYPERCALL       = 0x00090001,
     HV_REGISTER_GUEST_OS_ID         = 0x00090002,
     HV_REGISTER_REFERENCE_TSC       = 0x00090017,
 
@@ -454,6 +482,8 @@
     struct hv_register_assoc elements[];
 } QEMU_PACKED hv_input_set_vp_registers;
 
+#define MSHV_VP_MAX_REGISTERS   128
+
 union hv_interrupt_control {
     uint64_t as_uint64;
     struct {
@@ -474,6 +504,109 @@
     uint16_t rsvd_z1;
 } QEMU_PACKED;
 
+/* Flags for dirty mask of hv_vp_register_page */
+enum hv_x64_register_class_type {
+    HV_X64_REGISTER_CLASS_GENERAL = 0,
+    HV_X64_REGISTER_CLASS_IP = 1,
+    HV_X64_REGISTER_CLASS_XMM = 2,
+    HV_X64_REGISTER_CLASS_SEGMENT = 3,
+    HV_X64_REGISTER_CLASS_FLAGS = 4,
+};
+
+#define HV_VP_REGISTER_PAGE_MAX_VECTOR_COUNT  7
+
+union hv_vp_register_page_interrupt_vectors {
+    uint64_t as_uint64;
+    struct {
+        uint8_t vector_count;
+        uint8_t vector[HV_VP_REGISTER_PAGE_MAX_VECTOR_COUNT];
+    };
+};
+
+struct hv_vp_register_page {
+    uint16_t version;
+    uint8_t isvalid;
+    uint8_t rsvdz;
+    uint32_t dirty;
+
+    union {
+        struct {
+            /* General purpose registers (HV_X64_REGISTER_CLASS_GENERAL) */
+            union {
+                struct {
+                    uint64_t rax;
+                    uint64_t rcx;
+                    uint64_t rdx;
+                    uint64_t rbx;
+                    uint64_t rsp;
+                    uint64_t rbp;
+                    uint64_t rsi;
+                    uint64_t rdi;
+                    uint64_t r8;
+                    uint64_t r9;
+                    uint64_t r10;
+                    uint64_t r11;
+                    uint64_t r12;
+                    uint64_t r13;
+                    uint64_t r14;
+                    uint64_t r15;
+                } QEMU_PACKED;
+
+                uint64_t gp_registers[16];
+            };
+            /* Instruction pointer (HV_X64_REGISTER_CLASS_IP) */
+            uint64_t rip;
+            /* Flags (HV_X64_REGISTER_CLASS_FLAGS) */
+            uint64_t rflags;
+        } QEMU_PACKED;
+
+        uint64_t registers[18];
+    };
+    uint8_t reserved[8];
+    /* Volatile XMM registers (HV_X64_REGISTER_CLASS_XMM) */
+    union {
+        struct {
+            struct hv_u128 xmm0;
+            struct hv_u128 xmm1;
+            struct hv_u128 xmm2;
+            struct hv_u128 xmm3;
+            struct hv_u128 xmm4;
+            struct hv_u128 xmm5;
+        } QEMU_PACKED;
+
+        struct hv_u128 xmm_registers[6];
+    };
+    /* Segment registers (HV_X64_REGISTER_CLASS_SEGMENT) */
+    union {
+        struct {
+            struct hv_x64_segment_register es;
+            struct hv_x64_segment_register cs;
+            struct hv_x64_segment_register ss;
+            struct hv_x64_segment_register ds;
+            struct hv_x64_segment_register fs;
+            struct hv_x64_segment_register gs;
+        } QEMU_PACKED;
+
+        struct hv_x64_segment_register segment_registers[6];
+    };
+    /* Misc. control registers (cannot be set via this interface) */
+    uint64_t cr0;
+    uint64_t cr3;
+    uint64_t cr4;
+    uint64_t cr8;
+    uint64_t efer;
+    uint64_t dr7;
+    union hv_x64_pending_interruption_register pending_interruption;
+    union hv_x64_interrupt_state_register interrupt_state;
+    uint64_t instruction_emulation_hints;
+    uint64_t xfem;
+
+    uint8_t reserved1[0x100];
+
+    /* Interrupts injected as part of HvCallDispatchVp. */
+    union hv_vp_register_page_interrupt_vectors interrupt_vectors;
+} QEMU_PACKED;
+
 /* /dev/mshv */
 #define MSHV_CREATE_PARTITION   _IOW(MSHV_IOCTL, 0x00, struct mshv_create_partition)
 #define MSHV_CREATE_VP          _IOW(MSHV_IOCTL, 0x01, struct mshv_create_vp)
diff --git a/include/hw/hyperv/hvhdk.h b/include/hw/hyperv/hvhdk.h
index 41af743..9e6dcb2 100644
--- a/include/hw/hyperv/hvhdk.h
+++ b/include/hw/hyperv/hvhdk.h
@@ -11,6 +11,16 @@
 
 #define HV_PARTITION_SYNTHETIC_PROCESSOR_FEATURES_BANKS 1
 
+struct hv_input_get_partition_property {
+    uint64_t partition_id;
+    uint32_t property_code; /* enum hv_partition_property_code */
+    uint32_t padding;
+} QEMU_PACKED;
+
+struct hv_output_get_partition_property {
+    uint64_t property_value;
+} QEMU_PACKED;
+
 struct hv_input_set_partition_property {
     uint64_t partition_id;
     uint32_t property_code; /* enum hv_partition_property_code */
@@ -161,6 +171,191 @@
     };
 };
 
+union hv_partition_processor_xsave_features {
+    struct {
+        uint64_t xsave_support:1;
+        uint64_t xsaveopt_support:1;
+        uint64_t avx_support:1;
+        uint64_t avx2_support:1;
+        uint64_t fma_support:1;
+        uint64_t mpx_support:1;
+        uint64_t avx512_support:1;
+        uint64_t avx512_dq_support:1;
+        uint64_t avx512_cd_support:1;
+        uint64_t avx512_bw_support:1;
+        uint64_t avx512_vl_support:1;
+        uint64_t xsave_comp_support:1;
+        uint64_t xsave_supervisor_support:1;
+        uint64_t xcr1_support:1;
+        uint64_t avx512_bitalg_support:1;
+        uint64_t avx512_i_fma_support:1;
+        uint64_t avx512_v_bmi_support:1;
+        uint64_t avx512_v_bmi2_support:1;
+        uint64_t avx512_vnni_support:1;
+        uint64_t gfni_support:1;
+        uint64_t vaes_support:1;
+        uint64_t avx512_v_popcntdq_support:1;
+        uint64_t vpclmulqdq_support:1;
+        uint64_t avx512_bf16_support:1;
+        uint64_t avx512_vp2_intersect_support:1;
+        uint64_t avx512_fp16_support:1;
+        uint64_t xfd_support:1;
+        uint64_t amx_tile_support:1;
+        uint64_t amx_bf16_support:1;
+        uint64_t amx_int8_support:1;
+        uint64_t avx_vnni_support:1;
+        uint64_t avx_ifma_support:1;
+        uint64_t avx_ne_convert_support:1;
+        uint64_t avx_vnni_int8_support:1;
+        uint64_t avx_vnni_int16_support:1;
+        uint64_t avx10_1_256_support:1;
+        uint64_t avx10_1_512_support:1;
+        uint64_t amx_fp16_support:1;
+        uint64_t reserved1:26;
+    };
+    uint64_t as_uint64;
+};
+
+#define HV_PARTITION_PROCESSOR_FEATURES_BANKS 2
+#define HV_PARTITION_PROCESSOR_FEATURES_RESERVEDBANK1_BITFIELD_COUNT 4
+
+
+union hv_partition_processor_features {
+    uint64_t as_uint64[HV_PARTITION_PROCESSOR_FEATURES_BANKS];
+    struct {
+        uint64_t sse3_support:1;
+        uint64_t lahf_sahf_support:1;
+        uint64_t ssse3_support:1;
+        uint64_t sse4_1_support:1;
+        uint64_t sse4_2_support:1;
+        uint64_t sse4a_support:1;
+        uint64_t xop_support:1;
+        uint64_t pop_cnt_support:1;
+        uint64_t cmpxchg16b_support:1;
+        uint64_t altmovcr8_support:1;
+        uint64_t lzcnt_support:1;
+        uint64_t mis_align_sse_support:1;
+        uint64_t mmx_ext_support:1;
+        uint64_t amd3dnow_support:1;
+        uint64_t extended_amd3dnow_support:1;
+        uint64_t page_1gb_support:1;
+        uint64_t aes_support:1;
+        uint64_t pclmulqdq_support:1;
+        uint64_t pcid_support:1;
+        uint64_t fma4_support:1;
+        uint64_t f16c_support:1;
+        uint64_t rd_rand_support:1;
+        uint64_t rd_wr_fs_gs_support:1;
+        uint64_t smep_support:1;
+        uint64_t enhanced_fast_string_support:1;
+        uint64_t bmi1_support:1;
+        uint64_t bmi2_support:1;
+        uint64_t hle_support_deprecated:1;
+        uint64_t rtm_support_deprecated:1;
+        uint64_t movbe_support:1;
+        uint64_t npiep1_support:1;
+        uint64_t dep_x87_fpu_save_support:1;
+        uint64_t rd_seed_support:1;
+        uint64_t adx_support:1;
+        uint64_t intel_prefetch_support:1;
+        uint64_t smap_support:1;
+        uint64_t hle_support:1;
+        uint64_t rtm_support:1;
+        uint64_t rdtscp_support:1;
+        uint64_t clflushopt_support:1;
+        uint64_t clwb_support:1;
+        uint64_t sha_support:1;
+        uint64_t x87_pointers_saved_support:1;
+        uint64_t invpcid_support:1;
+        uint64_t ibrs_support:1;
+        uint64_t stibp_support:1;
+        uint64_t ibpb_support:1;
+        uint64_t unrestricted_guest_support:1;
+        uint64_t mdd_support:1;
+        uint64_t fast_short_rep_mov_support:1;
+        uint64_t l1dcache_flush_support:1;
+        uint64_t rdcl_no_support:1;
+        uint64_t ibrs_all_support:1;
+        uint64_t skip_l1df_support:1;
+        uint64_t ssb_no_support:1;
+        uint64_t rsb_a_no_support:1;
+        uint64_t virt_spec_ctrl_support:1;
+        uint64_t rd_pid_support:1;
+        uint64_t umip_support:1;
+        uint64_t mbs_no_support:1;
+        uint64_t mb_clear_support:1;
+        uint64_t taa_no_support:1;
+        uint64_t tsx_ctrl_support:1;
+        uint64_t reserved_bank0:1;
+
+        /* N.B. Begin bank 1 processor features. */
+        uint64_t a_count_m_count_support:1;
+        uint64_t tsc_invariant_support:1;
+        uint64_t cl_zero_support:1;
+        uint64_t rdpru_support:1;
+        uint64_t la57_support:1;
+        uint64_t mbec_support:1;
+        uint64_t nested_virt_support:1;
+        uint64_t psfd_support:1;
+        uint64_t cet_ss_support:1;
+        uint64_t cet_ibt_support:1;
+        uint64_t vmx_exception_inject_support:1;
+        uint64_t enqcmd_support:1;
+        uint64_t umwait_tpause_support:1;
+        uint64_t movdiri_support:1;
+        uint64_t movdir64b_support:1;
+        uint64_t cldemote_support:1;
+        uint64_t serialize_support:1;
+        uint64_t tsc_deadline_tmr_support:1;
+        uint64_t tsc_adjust_support:1;
+        uint64_t fzl_rep_movsb:1;
+        uint64_t fs_rep_stosb:1;
+        uint64_t fs_rep_cmpsb:1;
+        uint64_t tsx_ld_trk_support:1;
+        uint64_t vmx_ins_outs_exit_info_support:1;
+        uint64_t hlat_support:1;
+        uint64_t sbdr_ssdp_no_support:1;
+        uint64_t fbsdp_no_support:1;
+        uint64_t psdp_no_support:1;
+        uint64_t fb_clear_support:1;
+        uint64_t btc_no_support:1;
+        uint64_t ibpb_rsb_flush_support:1;
+        uint64_t stibp_always_on_support:1;
+        uint64_t perf_global_ctrl_support:1;
+        uint64_t npt_execute_only_support:1;
+        uint64_t npt_ad_flags_support:1;
+        uint64_t npt1_gb_page_support:1;
+        uint64_t amd_processor_topology_node_id_support:1;
+        uint64_t local_machine_check_support:1;
+        uint64_t extended_topology_leaf_fp256_amd_support:1;
+        uint64_t gds_no_support:1;
+        uint64_t cmpccxadd_support:1;
+        uint64_t tsc_aux_virtualization_support:1;
+        uint64_t rmp_query_support:1;
+        uint64_t bhi_no_support:1;
+        uint64_t bhi_dis_support:1;
+        uint64_t prefetch_i_support:1;
+        uint64_t sha512_support:1;
+        uint64_t mitigation_ctrl_support:1;
+        uint64_t rfds_no_support:1;
+        uint64_t rfds_clear_support:1;
+        uint64_t sm3_support:1;
+        uint64_t sm4_support:1;
+        uint64_t secure_avic_support:1;
+        uint64_t guest_intercept_ctrl_support:1;
+        uint64_t sbpb_supported:1;
+        uint64_t ibpb_br_type_supported:1;
+        uint64_t srso_no_supported:1;
+        uint64_t srso_user_kernel_no_supported:1;
+        uint64_t vrew_clear_supported:1;
+        uint64_t tsa_l1_no_supported:1;
+        uint64_t tsa_sq_no_supported:1;
+        uint64_t lass_support:1;
+        uint64_t idle_hlt_intercept_support:1;
+        uint64_t msr_list_support:1;
+    } QEMU_PACKED;
+};
+
 enum hv_translate_gva_result_code {
     HV_TRANSLATE_GVA_SUCCESS                    = 0,
 
diff --git a/include/system/accel-irq.h b/include/system/accel-irq.h
index a2caa06..fc94c54 100644
--- a/include/system/accel-irq.h
+++ b/include/system/accel-irq.h
@@ -25,9 +25,11 @@
     return mshv_msi_via_irqfd_enabled() || kvm_irqchip_is_split();
 }
 
-int accel_irqchip_add_msi_route(KVMRouteChange *c, int vector, PCIDevice *dev);
+AccelRouteChange accel_irqchip_begin_route_changes(void);
+int accel_irqchip_add_msi_route(AccelRouteChange *c, int vector,
+                                PCIDevice *dev);
 int accel_irqchip_update_msi_route(int vector, MSIMessage msg, PCIDevice *dev);
-void accel_irqchip_commit_route_changes(KVMRouteChange *c);
+void accel_irqchip_commit_route_changes(AccelRouteChange *c);
 void accel_irqchip_commit_routes(void);
 void accel_irqchip_release_virq(int virq);
 int accel_irqchip_add_irqfd_notifier_gsi(EventNotifier *n, EventNotifier *rn,
diff --git a/include/system/kvm.h b/include/system/kvm.h
index 5fa33ed..cdd1856 100644
--- a/include/system/kvm.h
+++ b/include/system/kvm.h
@@ -18,6 +18,7 @@
 
 #include "exec/memattrs.h"
 #include "qemu/accel.h"
+#include "accel/accel-route.h"
 #include "qom/object.h"
 
 #ifdef COMPILING_PER_TARGET
@@ -183,11 +184,6 @@
 typedef struct Notifier Notifier;
 typedef struct NotifierWithReturn NotifierWithReturn;
 
-typedef struct KVMRouteChange {
-     KVMState *s;
-     int changes;
-} KVMRouteChange;
-
 /* external API */
 
 unsigned int kvm_get_max_memslots(void);
@@ -466,7 +462,7 @@
 
 /**
  * kvm_irqchip_add_msi_route - Add MSI route for specific vector
- * @c:      KVMRouteChange instance.
+ * @c:      AccelRouteChange instance.
  * @vector: which vector to add. This can be either MSI/MSIX
  *          vector. The function will automatically detect whether
  *          MSI/MSIX is enabled, and fetch corresponding MSI
@@ -475,24 +471,11 @@
  *          as @NULL, an empty MSI message will be inited.
  * @return: virq (>=0) when success, errno (<0) when failed.
  */
-int kvm_irqchip_add_msi_route(KVMRouteChange *c, int vector, PCIDevice *dev);
+int kvm_irqchip_add_msi_route(AccelRouteChange *c, int vector, PCIDevice *dev);
 int kvm_irqchip_update_msi_route(KVMState *s, int virq, MSIMessage msg,
                                  PCIDevice *dev);
 void kvm_irqchip_commit_routes(KVMState *s);
 
-static inline KVMRouteChange kvm_irqchip_begin_route_changes(KVMState *s)
-{
-    return (KVMRouteChange) { .s = s, .changes = 0 };
-}
-
-static inline void kvm_irqchip_commit_route_changes(KVMRouteChange *c)
-{
-    if (c->changes) {
-        kvm_irqchip_commit_routes(c->s);
-        c->changes = 0;
-    }
-}
-
 int kvm_irqchip_get_virq(KVMState *s);
 void kvm_irqchip_release_virq(KVMState *s, int virq);
 
diff --git a/include/system/mshv.h b/include/system/mshv.h
index 75286ba..46fe3fc 100644
--- a/include/system/mshv.h
+++ b/include/system/mshv.h
@@ -21,6 +21,7 @@
 #include "qapi/qapi-types-common.h"
 #include "system/memory.h"
 #include "accel/accel-ops.h"
+#include "accel/accel-route.h"
 
 #ifdef COMPILING_PER_TARGET
 #ifdef CONFIG_MSHV
@@ -32,6 +33,7 @@
 #endif
 
 #define MSHV_MAX_MSI_ROUTES 4096
+#define MSHV_MIN_ALLOCATED_MSI_ROUTES 64
 
 #define MSHV_PAGE_SHIFT 12
 
@@ -44,7 +46,13 @@
 #define mshv_msi_via_irqfd_enabled() mshv_enabled()
 #endif
 
+#define TYPE_MSHV_ACCEL ACCEL_CLASS_NAME("mshv")
+
 typedef struct MshvState MshvState;
+
+DECLARE_INSTANCE_CHECKER(MshvState, MSHV_STATE,
+                         TYPE_MSHV_ACCEL)
+
 extern MshvState *mshv_state;
 
 /* interrupt */
@@ -52,12 +60,16 @@
                            uint32_t vp_index, bool logical_destination_mode,
                            bool level_triggered);
 
-int mshv_irqchip_add_msi_route(int vector, PCIDevice *dev);
+int mshv_irqchip_add_msi_route(AccelRouteChange *c, int vector, PCIDevice *dev);
 int mshv_irqchip_update_msi_route(int virq, MSIMessage msg, PCIDevice *dev);
-void mshv_irqchip_commit_routes(void);
-void mshv_irqchip_release_virq(int virq);
+void mshv_irqchip_release_virq(MshvState *s, int virq);
+void mshv_irqchip_commit_routes(MshvState *s);
 int mshv_irqchip_add_irqfd_notifier_gsi(const EventNotifier *n,
                                         const EventNotifier *rn, int virq);
 int mshv_irqchip_remove_irqfd_notifier_gsi(const EventNotifier *n, int virq);
+void mshv_init_irq_routing(MshvState *s);
+
+/* cpuid */
+uint32_t mshv_get_supported_cpuid(uint32_t func, uint32_t idx, int reg);
 
 #endif
diff --git a/include/system/mshv_int.h b/include/system/mshv_int.h
index 35386c4..ba30738 100644
--- a/include/system/mshv_int.h
+++ b/include/system/mshv_int.h
@@ -14,8 +14,25 @@
 #ifndef QEMU_MSHV_INT_H
 #define QEMU_MSHV_INT_H
 
+#include "hw/hyperv/hvhdk.h"
+
 #define MSHV_MSR_ENTRIES_COUNT 64
 
+/*
+ * Interruption-type encoding, used by the hypervisor in
+ * hv_x64_pending_interruption_register.interruption_type
+ * See TLFS 6.0 section 7.9.2, p55
+ * https://learn.microsoft.com/en-us/virtualization/hyper-v-on-windows/tlfs/tlfs
+ */
+#define MSHV_HV_INTERRUPTION_TYPE_EXT_INT     0
+#define MSHV_HV_INTERRUPTION_TYPE_NMI         2
+#define MSHV_HV_INTERRUPTION_TYPE_HW_EXC      3
+#define MSHV_HV_INTERRUPTION_TYPE_SW_INT      4
+#define MSHV_HV_INTERRUPTION_TYPE_PRIV_SW_EXC 5
+#define MSHV_HV_INTERRUPTION_TYPE_SW_EXC      6
+
+#define MSHV_DIRTY_PAGES_BATCH_SIZE 0x10000
+
 typedef struct hyperv_message hv_message;
 
 typedef struct MshvHvCallArgs {
@@ -48,6 +65,12 @@
     int nr_as;
     MshvAddressSpace *as;
     int fd;
+    /* irqchip routing */
+    struct mshv_user_irq_table *irq_routes;
+    int nr_allocated_irq_routes;
+    unsigned long *used_gsi_bitmap;
+    unsigned int gsi_count;
+    union hv_partition_processor_features processor_features;
 };
 
 typedef struct MshvMsiControl {
@@ -58,19 +81,6 @@
 #define mshv_vcpufd(cpu) (cpu->accel->cpufd)
 
 /* cpu */
-typedef struct MshvFPU {
-    uint8_t fpr[8][16];
-    uint16_t fcw;
-    uint16_t fsw;
-    uint8_t ftwx;
-    uint8_t pad1;
-    uint16_t last_opcode;
-    uint64_t last_ip;
-    uint64_t last_dp;
-    uint8_t xmm[16][16];
-    uint32_t mxcsr;
-    uint32_t pad2;
-} MshvFPU;
 
 typedef enum MshvVmExit {
     MshvVmExitIgnore   = 0,
@@ -81,19 +91,20 @@
 void mshv_init_mmio_emu(void);
 int mshv_create_vcpu(int vm_fd, uint8_t vp_index, int *cpu_fd);
 void mshv_remove_vcpu(int vm_fd, int cpu_fd);
-int mshv_configure_vcpu(const CPUState *cpu, const MshvFPU *fpu, uint64_t xcr0);
-int mshv_get_standard_regs(CPUState *cpu);
-int mshv_get_special_regs(CPUState *cpu);
+int mshv_configure_vcpu(const CPUState *cpu);
 int mshv_run_vcpu(int vm_fd, CPUState *cpu, hv_message *msg, MshvVmExit *exit);
-int mshv_load_regs(CPUState *cpu);
-int mshv_store_regs(CPUState *cpu);
 int mshv_set_generic_regs(const CPUState *cpu, const hv_register_assoc *assocs,
                           size_t n_regs);
-int mshv_arch_put_registers(const CPUState *cpu);
+int mshv_get_generic_regs(CPUState *cpu, hv_register_assoc *assocs,
+                          size_t n_regs);
+int mshv_arch_store_vcpu_state(const CPUState *cpu);
+int mshv_arch_load_vcpu_state(CPUState *cpu);
 void mshv_arch_init_vcpu(CPUState *cpu);
 void mshv_arch_destroy_vcpu(CPUState *cpu);
 void mshv_arch_amend_proc_features(
     union hv_partition_synthetic_processor_features *features);
+void mshv_arch_disable_partition_proc_features(
+     union hv_partition_processor_features *disabled_features);
 int mshv_arch_post_init_vm(int vm_fd);
 
 typedef struct mshv_root_hvcall mshv_root_hvcall;
@@ -113,24 +124,13 @@
                          bool is_secure_mode);
 void mshv_set_phys_mem(MshvMemoryListener *mml, MemoryRegionSection *section,
                        bool add);
+void mshv_log_sync(MemoryListener *listener, MemoryRegionSection *section);
+bool mshv_log_global_start(MemoryListener *listener, Error **errp);
+void mshv_log_global_stop(MemoryListener *listener);
 
 /* msr */
-typedef struct MshvMsrEntry {
-  uint32_t index;
-  uint32_t reserved;
-  uint64_t data;
-} MshvMsrEntry;
-
-typedef struct MshvMsrEntries {
-    MshvMsrEntry entries[MSHV_MSR_ENTRIES_COUNT];
-    uint32_t nmsrs;
-} MshvMsrEntries;
-
-int mshv_configure_msr(const CPUState *cpu, const MshvMsrEntry *msrs,
-                       size_t n_msrs);
-
-/* interrupt */
-void mshv_init_msicontrol(void);
-int mshv_reserve_ioapic_msi_routes(int vm_fd);
+int mshv_init_msrs(const CPUState *cpu);
+int mshv_get_msrs(CPUState *cpu);
+int mshv_set_msrs(const CPUState *cpu);
 
 #endif
diff --git a/linux-user/hexagon/cpu_loop.c b/linux-user/hexagon/cpu_loop.c
index 9464246..0958c51 100644
--- a/linux-user/hexagon/cpu_loop.c
+++ b/linux-user/hexagon/cpu_loop.c
@@ -22,6 +22,7 @@
 #include "qemu.h"
 #include "user-internals.h"
 #include "user/cpu_loop.h"
+#include "target/hexagon/internal.h"
 #include "signal-common.h"
 #include "internal.h"
 
@@ -60,6 +61,28 @@
                 env->gpr[0] = ret;
             }
             break;
+        case HEX_EVENT_PRECISE:
+            switch (env->cause_code) {
+            case HEX_CAUSE_FETCH_NO_UPAGE:
+            case HEX_CAUSE_PRIV_NO_UREAD:
+            case HEX_CAUSE_PRIV_NO_UWRITE:
+            force_sig_fault(TARGET_SIGSEGV, TARGET_SEGV_MAPERR,
+                    env->gpr[HEX_REG_PC]);
+
+            break;
+            case HEX_CAUSE_PRIV_USER_NO_GINSN:
+            case HEX_CAUSE_PRIV_USER_NO_SINSN:
+            case HEX_CAUSE_INVALID_PACKET:
+            force_sig_fault(TARGET_SIGILL, TARGET_ILL_ILLOPC,
+                    env->gpr[HEX_REG_PC]);
+            break;
+            default:
+                EXCP_DUMP(env, "\nqemu: unhandled CPU precise exception "
+                    "cause code 0x%x - aborting\n",
+                    env->cause_code);
+                exit(EXIT_FAILURE);
+            }
+            break;
         case HEX_CAUSE_PC_NOT_ALIGNED:
             force_sig_fault(TARGET_SIGBUS, TARGET_BUS_ADRALN,
                             env->gpr[HEX_REG_R31]);
diff --git a/meson.build b/meson.build
index 0d1df06..164328d 100644
--- a/meson.build
+++ b/meson.build
@@ -3601,6 +3601,7 @@
     'hw/display',
     'hw/dma',
     'hw/fsi',
+    'hw/hexagon',
     'hw/hyperv',
     'hw/i2c',
     'hw/i3c',
diff --git a/python/qemu/machine/machine.py b/python/qemu/machine/machine.py
index ebb58d5..3670c8c 100644
--- a/python/qemu/machine/machine.py
+++ b/python/qemu/machine/machine.py
@@ -292,8 +292,8 @@ def _load_io_log(self) -> None:
                 self._iolog = iolog.read()
 
     @property
-    def _base_args(self) -> List[str]:
-        args = ['-display', 'none', '-vga', 'none']
+    def _harness_args(self) -> List[str]:
+        args: List[str] = []
 
         if self._qmp_set:
             if self._sock_pair:
@@ -306,12 +306,17 @@ def _base_args(self) -> List[str]:
                 moncdev = f"socket,id=mon,path={self._monitor_address}"
             args.extend(['-chardev', moncdev, '-mon',
                          'chardev=mon,mode=control'])
+        return args
 
-        if self._machine is not None:
-            args.extend(['-machine', self._machine])
+    def _console_args(self, interactive: bool = False) -> List[str]:
+        args: List[str] = []
+        # redirect pre_console_index serials to null
         for _ in range(self._console_index):
             args.extend(['-serial', 'null'])
-        if self._console_set:
+
+        if interactive:
+            args.extend(['-serial', 'mon:stdio'])
+        elif self._console_set:
             assert self._cons_sock_pair is not None
             fd = self._cons_sock_pair[0].fileno()
             chardev = f"socket,id=console,fd={fd}"
@@ -324,6 +329,13 @@ def _base_args(self) -> List[str]:
         return args
 
     @property
+    def _base_args(self) -> List[str]:
+        args: List[str] = ['-display', 'none', '-vga', 'none']
+        if self._machine is not None:
+            args.extend(['-machine', self._machine])
+        return args
+
+    @property
     def args(self) -> List[str]:
         """Returns the list of arguments given to the QEMU binary."""
         return self._args
@@ -366,6 +378,8 @@ def _pre_launch(self) -> None:
         self._qemu_full_args = tuple(chain(
             self._wrapper,
             [self._binary],
+            self._harness_args,
+            self._console_args(),
             self._base_args,
             self._args
         ))
@@ -474,6 +488,14 @@ def _launch(self) -> None:
         """
         self._pre_launch()
         LOG.debug('VM launch command: %r', ' '.join(self._qemu_full_args))
+        # Log a simplified, developer-runnable command:
+        # Exclude harness-managed infrastructure args (harness_args)
+        # and wrapper.
+        debug_cmd = [self._binary]
+        debug_cmd.extend(self._console_args(interactive=True))
+        debug_cmd.extend(self._base_args)
+        debug_cmd.extend(self._args)
+        LOG.debug('Developer-runnable command: %r', ' '.join(debug_cmd))
 
         # Cleaning up of this subprocess is guaranteed by _do_shutdown.
         # pylint: disable=consider-using-with
diff --git a/qemu-options.hx b/qemu-options.hx
index c799286..e44b47d 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -36,7 +36,8 @@
     "                dea-key-wrap=on|off controls support for DEA key wrapping (default=on)\n"
     "                suppress-vmdesc=on|off disables self-describing migration (default=off)\n"
     "                nvdimm=on|off controls NVDIMM support (default=off)\n"
-    "                memory-encryption=<id> memory encryption object to use (default=none)\n"
+    "                confidential-guest-support=<id> specifies confidential guest support object (default=none)\n"
+    "                memory-encryption=<id> (memory-encryption is the alias of confidential-guest-support, recommend to use confidential-guest-support)\n"
     "                hmat=on|off controls ACPI HMAT support (default=off)\n"
     "                spcr=on|off controls ACPI SPCR support (default=on)\n"
 #ifdef CONFIG_POSIX
@@ -100,8 +101,12 @@
     ``nvdimm=on|off``
         Enables or disables NVDIMM support. The default is off.
 
+    ``confidential-guest-support=<id>``
+        confidential guest support object to use. The default is none.
+
     ``memory-encryption=<id>``
-        Memory encryption object to use. The default is none.
+        The alias of ``confidential-guest-support``. Recommend to use
+        confidential-guest-support.
 
     ``hmat=on|off``
         Enables or disables ACPI Heterogeneous Memory Attribute Table
@@ -1935,19 +1940,19 @@
 
 DEF("fsdev", HAS_ARG, QEMU_OPTION_fsdev,
     "-fsdev local,id=id,path=path,security_model=mapped-xattr|mapped-file|passthrough|none\n"
-    " [,writeout=immediate][,readonly=on][,fmode=fmode][,dmode=dmode]\n"
+    " [,writeout=immediate][,readonly=on][,fmode=fmode][,dmode=dmode][,max_xattr=max]\n"
     " [[,throttling.bps-total=b]|[[,throttling.bps-read=r][,throttling.bps-write=w]]]\n"
     " [[,throttling.iops-total=i]|[[,throttling.iops-read=r][,throttling.iops-write=w]]]\n"
     " [[,throttling.bps-total-max=bm]|[[,throttling.bps-read-max=rm][,throttling.bps-write-max=wm]]]\n"
     " [[,throttling.iops-total-max=im]|[[,throttling.iops-read-max=irm][,throttling.iops-write-max=iwm]]]\n"
     " [[,throttling.iops-size=is]]\n"
-    "-fsdev synth,id=id\n",
+    "-fsdev synth,id=id[,max_xattr=max]\n",
     QEMU_ARCH_ALL)
 
 SRST
-``-fsdev local,id=id,path=path,security_model=security_model [,writeout=writeout][,readonly=on][,fmode=fmode][,dmode=dmode] [,throttling.option=value[,throttling.option=value[,...]]]``
+``-fsdev local,id=id,path=path,security_model=security_model [,writeout=writeout][,readonly=on][,fmode=fmode][,dmode=dmode][,max_xattr=max] [,throttling.option=value[,throttling.option=value[,...]]]``
   \ 
-``-fsdev synth,id=id[,readonly=on]``
+``-fsdev synth,id=id[,readonly=on][,max_xattr=max]``
     Define a new file system device. Valid options are:
 
     ``local``
@@ -2021,6 +2026,12 @@
         Let every is bytes of a request count as a new request for iops
         throttling purposes.
 
+    ``max_xattr=max``
+        Specifies the maximum number of concurrent xattr FIDs allowed for
+        this export. The default is 1024. Set to 0 for allowing an infinite
+        number of xattr FIDs. This limit prevents host memory exhaustion
+        attacks by capping the number of simultaneous xattr FIDs.
+
     -fsdev option is used along with -device driver "virtio-9p-...".
 
 ``-device virtio-9p-type,fsdev=id,mount_tag=mount_tag``
@@ -2040,14 +2051,14 @@
 
 DEF("virtfs", HAS_ARG, QEMU_OPTION_virtfs,
     "-virtfs local,path=path,mount_tag=tag,security_model=mapped-xattr|mapped-file|passthrough|none\n"
-    "        [,id=id][,writeout=immediate][,readonly=on][,fmode=fmode][,dmode=dmode][,multidevs=remap|forbid|warn]\n"
-    "-virtfs synth,mount_tag=tag[,id=id][,readonly=on]\n",
+    "        [,id=id][,writeout=immediate][,readonly=on][,fmode=fmode][,dmode=dmode][,multidevs=remap|forbid|warn][,max_xattr=max]\n"
+    "-virtfs synth,mount_tag=tag[,id=id][,readonly=on][,max_xattr=max]\n",
     QEMU_ARCH_ALL)
 
 SRST
-``-virtfs local,path=path,mount_tag=mount_tag ,security_model=security_model[,writeout=writeout][,readonly=on] [,fmode=fmode][,dmode=dmode][,multidevs=multidevs]``
+``-virtfs local,path=path,mount_tag=mount_tag ,security_model=security_model[,writeout=writeout][,readonly=on] [,fmode=fmode][,dmode=dmode][,multidevs=multidevs][,max_xattr=max]``
   \ 
-``-virtfs synth,mount_tag=mount_tag``
+``-virtfs synth,mount_tag=mount_tag[,max_xattr=max]``
     Define a new virtual filesystem device and expose it to the guest using
     a virtio-9p-device (a.k.a. 9pfs), which essentially means that a certain
     directory on host is made directly accessible by guest as a pass-through
@@ -2113,6 +2124,12 @@
         Specifies the tag name to be used by the guest to mount this
         export point.
 
+    ``max_xattr=max``
+        Specifies the maximum number of concurrent xattr FIDs allowed for
+        this export. The default is 1024. Set to 0 for allowing an infinite
+        number of xattr FIDs. This limit prevents host memory exhaustion
+        attacks by capping the number of simultaneous xattr FIDs.
+
     ``multidevs=remap|forbid|warn``
         Specifies how to deal with multiple devices being shared with
         the same 9p export in order to avoid file ID collisions on guest.
@@ -6364,9 +6381,50 @@
              # |qemu_system_x86| \\
                  ...... \\
                  -object sev-guest,id=sev0,cbitpos=47,reduced-phys-bits=1 \\
-                 -machine ...,memory-encryption=sev0 \\
+                 -machine ...,confidential-guest-support=sev0 \\
                  .....
 
+    ``-object tdx-guest,id=id,[attributes=attrs,sept-ve-disable=on|off,mrconfigid=sha384_digest,mrowner=sha384_digest,mrownerconfig=sha384_digest,quote-generation-socket=socketaddr]``
+        Create an Intel Trusted Domain eXtensions (TDX) guest object, which is
+        the type of ``confidential-guest-support`` object. When pass the object
+        ID to machine's ``confidential-guest-support`` property, it can create
+        a TDX guest.
+
+        The ``attributes`` property is a 64-bit integer, which specifies the
+        TD attributes of the TD.
+
+        The ``sept-ve-disable`` property controls the bit 28 of TD attributes
+        specifically. When it's on, the EPT violation conversion to #VE on
+        guest access of PENDING pages is disabled. Some guest OS (e.g., Linux
+        TD guest) may require this to be set, otherwise they refuse to boot.
+        The default value is on.
+
+        The ``mrconfigid`` property is base64 encoded SHA384 digest, which
+        provides the ID for non-owner-defined configuration of the guest TD,
+        e.g., run-time or OS configuration. The default value is all zeros.
+
+        The ``mrowner`` property is base64 encoded SHA384 digest, which
+        provides the ID for guest TD's owner. The default value is all zeros.
+
+        The ``mrownerconfig`` property is base64 encoded SHA384 digest, which
+        provides the ID for owner-defined configuration of the guest TD, e.g.,
+        the configuration specific to the workload rather than the run-time of
+        OS. The default value is all zeros.
+
+        The ``quote-generation-socket`` property specifies the socket address
+        of the Quote Generation Service (QGS). QGS is a daemon running on the
+        host. QEMU forwards the <GetQuote> request from TD guest to QGS and
+        sents the reply (which contains generated QUOTE on success) from QGS
+        to guest TD.
+
+        .. parsed-literal::
+
+             # |qemu_system_x86| \\
+                 ...... \\
+                 -object '{"qom-type":"tdx-guest","id":"tdx","quote-generation-socket":{"type":"unix","path":"/var/run/qgs.socket"}}' \\
+                 -machine ...,confidential-guest-support=tdx \\
+                 ......
+
     ``-object igvm-cfg,file=file``
         Create an IGVM configuration object that defines the initial state
         of the guest using a file in that conforms to the Independent Guest
diff --git a/system/qdev-monitor.c b/system/qdev-monitor.c
index dfc95a0..00fed79 100644
--- a/system/qdev-monitor.c
+++ b/system/qdev-monitor.c
@@ -71,7 +71,7 @@
                               QEMU_ARCH_SPARC | \
                               QEMU_ARCH_XTENSA)
 #define QEMU_ARCH_VIRTIO_CCW (QEMU_ARCH_S390X)
-#define QEMU_ARCH_VIRTIO_MMIO (QEMU_ARCH_M68K)
+#define QEMU_ARCH_VIRTIO_MMIO (QEMU_ARCH_M68K | QEMU_ARCH_HEXAGON)
 
 /* Please keep this table sorted by typename. */
 static const QDevAlias qdev_alias_table[] = {
diff --git a/system/runstate.c b/system/runstate.c
index 18e585b..08acf80 100644
--- a/system/runstate.c
+++ b/system/runstate.c
@@ -706,7 +706,7 @@
                           " error code: 0x%" PRIx32 " error message:\"%s\"\n",
                           info->u.tdx.error_code, message);
             g_free(message);
-            if (info->u.tdx.gpa != -1ull) {
+            if (info->u.tdx.has_gpa) {
                 qemu_log_mask(LOG_GUEST_ERROR, "Additional error information "
                               "can be found at gpa page: 0x%" PRIx64 "\n",
                               info->u.tdx.gpa);
diff --git a/system/vl.c b/system/vl.c
index 1c0da7d..1d14e2e 100644
--- a/system/vl.c
+++ b/system/vl.c
@@ -3260,7 +3260,7 @@
                 QemuOpts *fsdev;
                 QemuOpts *device;
                 const char *writeout, *sock_fd, *socket, *path, *security_model,
-                           *multidevs;
+                           *multidevs, *max_xattr_str;
 
                 olist = qemu_find_opts("virtfs");
                 if (!olist) {
@@ -3324,6 +3324,11 @@
                 if (multidevs) {
                     qemu_opt_set(fsdev, "multidevs", multidevs, &error_abort);
                 }
+                max_xattr_str = qemu_opt_get(opts, "max_xattr");
+                if (max_xattr_str) {
+                    qemu_opt_set(fsdev, "max_xattr", max_xattr_str,
+                                 &error_abort);
+                }
                 device = qemu_opts_create(qemu_find_opts("device"), NULL, 0,
                                           &error_abort);
                 qemu_opt_set(device, "driver", "virtio-9p-pci", &error_abort);
diff --git a/target/Kconfig b/target/Kconfig
index 3c73e3b..0288a3f 100644
--- a/target/Kconfig
+++ b/target/Kconfig
@@ -16,6 +16,7 @@
 source sparc/Kconfig
 source tricore/Kconfig
 source xtensa/Kconfig
+source hexagon/Kconfig
 
 config TARGET_BIG_ENDIAN
     bool
diff --git a/target/hexagon/Kconfig b/target/hexagon/Kconfig
new file mode 100644
index 0000000..7e556f3
--- /dev/null
+++ b/target/hexagon/Kconfig
@@ -0,0 +1,2 @@
+config HEXAGON
+    bool
diff --git a/target/hexagon/arch.c b/target/hexagon/arch.c
index e17e714..0a400bf 100644
--- a/target/hexagon/arch.c
+++ b/target/hexagon/arch.c
@@ -208,6 +208,11 @@
  * model it in qemu user mode.
  */
 #define RAISE_FP_EXCEPTION   do {} while (0)
+#else
+ /*
+  * To be implemented.
+  */
+#define RAISE_FP_EXCEPTION   do { g_assert_not_reached(); } while (0)
 #endif
 
 #define SOFTFLOAT_TEST_FLAG(FLAG, MYF, MYE) \
diff --git a/target/hexagon/attribs_def.h.inc b/target/hexagon/attribs_def.h.inc
index 9e3a05f..6c55063 100644
--- a/target/hexagon/attribs_def.h.inc
+++ b/target/hexagon/attribs_def.h.inc
@@ -52,6 +52,9 @@
 DEF_ATTRIB(REGWRSIZE_8B, "Memory width is 8 bytes", "", "")
 DEF_ATTRIB(MEMLIKE, "Memory-like instruction", "", "")
 DEF_ATTRIB(MEMLIKE_PACKET_RULES, "follows Memory-like packet rules", "", "")
+DEF_ATTRIB(CACHEOP, "Cache operation", "", "")
+DEF_ATTRIB(COPBYADDRESS, "Cache operation by address", "", "")
+DEF_ATTRIB(COPBYIDX, "Cache operation by index", "", "")
 DEF_ATTRIB(RELEASE, "Releases a lock", "", "")
 DEF_ATTRIB(ACQUIRE, "Acquires a lock", "", "")
 
@@ -101,7 +104,9 @@
 
 /* access to implicit registers */
 DEF_ATTRIB(IMPLICIT_WRITES_LR, "Writes the link register", "", "UREG.LR")
+DEF_ATTRIB(IMPLICIT_READS_PC, "Reads the program counter", "UREG.PC", "")
 DEF_ATTRIB(IMPLICIT_WRITES_SP, "Writes the stack pointer", "", "UREG.SP")
+DEF_ATTRIB(IMPLICIT_READS_SP, "Reads the stack pointer", "UREG.SP", "")
 DEF_ATTRIB(IMPLICIT_WRITES_FP, "Writes the frame pointer", "", "UREG.FP")
 DEF_ATTRIB(IMPLICIT_WRITES_LC0, "Writes loop count for loop 0", "", "UREG.LC0")
 DEF_ATTRIB(IMPLICIT_WRITES_LC1, "Writes loop count for loop 1", "", "UREG.LC1")
@@ -111,13 +116,19 @@
 DEF_ATTRIB(IMPLICIT_WRITES_P1, "Writes Predicate 1", "", "UREG.P1")
 DEF_ATTRIB(IMPLICIT_WRITES_P2, "Writes Predicate 1", "", "UREG.P2")
 DEF_ATTRIB(IMPLICIT_WRITES_P3, "May write Predicate 3", "", "UREG.P3")
-DEF_ATTRIB(IMPLICIT_READS_PC, "Reads the PC register", "", "")
-DEF_ATTRIB(IMPLICIT_READS_P0, "Reads the P0 register", "", "")
-DEF_ATTRIB(IMPLICIT_READS_P1, "Reads the P1 register", "", "")
-DEF_ATTRIB(IMPLICIT_READS_P2, "Reads the P2 register", "", "")
-DEF_ATTRIB(IMPLICIT_READS_P3, "Reads the P3 register", "", "")
+DEF_ATTRIB(IMPLICIT_READS_P0, "Reads Predicate 0", "UREG.P0", "")
+DEF_ATTRIB(IMPLICIT_READS_P1, "Reads Predicate 1", "UREG.P1", "")
+DEF_ATTRIB(IMPLICIT_READS_P3, "Reads Predicate 3", "UREG.P3", "")
 DEF_ATTRIB(IMPLICIT_WRITES_USR, "May write USR", "", "")
-DEF_ATTRIB(IMPLICIT_READS_SP, "Reads the SP register", "", "")
+DEF_ATTRIB(IMPLICIT_WRITES_CCR, "Writes CCR register", "", "UREG.CCR")
+DEF_ATTRIB(IMPLICIT_WRITES_GOSP, "Writes GOSP register", "", "UREG.GOSP")
+DEF_ATTRIB(IMPLICIT_WRITES_SSR, "Writes SSR register", "", "UREG.SSR")
+DEF_ATTRIB(IMPLICIT_WRITES_SGP0, "Writes SGP0 register", "", "UREG.SGP0")
+DEF_ATTRIB(IMPLICIT_WRITES_SGP1, "Writes SGP1 register", "", "UREG.SGP1")
+DEF_ATTRIB(IMPLICIT_WRITES_IMASK_ANYTHREAD,
+    "Writes IMASK for any thread", "", "")
+DEF_ATTRIB(IMPLICIT_WRITES_STID_PRIO_ANYTHREAD,
+    "Writes STID priority for any thread", "", "")
 DEF_ATTRIB(COMMUTES, "The operation is communitive", "", "")
 DEF_ATTRIB(DEALLOCRET, "dealloc_return", "", "")
 DEF_ATTRIB(DEALLOCFRAME, "deallocframe", "", "")
@@ -137,9 +148,14 @@
 DEF_ATTRIB(RESTRICT_NOSLOT1, "No slot 1 instruction in parallel", "", "")
 DEF_ATTRIB(RESTRICT_PREFERSLOT0, "Try to encode into slot 0", "", "")
 DEF_ATTRIB(RESTRICT_PACKET_AXOK, "May exist with A-type or X-type", "", "")
+DEF_ATTRIB(RESTRICT_SLOT1_AOK, "Slot 1 is allowed", "", "")
 
 DEF_ATTRIB(ICOP, "Instruction cache op", "", "")
 
+DEF_ATTRIB(EXCEPTION_SWI, "Software interrupt exception", "", "")
+DEF_ATTRIB(DMA, "DMA instruction", "", "")
+DEF_ATTRIB(NO_TIMING_LOG, "Does not get logged to the timing model", "", "")
+
 DEF_ATTRIB(HWLOOP0_END, "Ends HW loop0", "", "")
 DEF_ATTRIB(HWLOOP1_END, "Ends HW loop1", "", "")
 DEF_ATTRIB(RET_TYPE, "return type", "", "")
@@ -151,6 +167,10 @@
 
 DEF_ATTRIB(L2FETCH, "Instruction is l2fetch type", "", "")
 
+DEF_ATTRIB(DCTAGOP, "Data cache tag operation", "", "")
+DEF_ATTRIB(ICTAGOP, "Instruction cache tag operation", "", "")
+DEF_ATTRIB(L2TAGOP, "L2 cache tag operation", "", "")
+
 DEF_ATTRIB(ICINVA, "icinva", "", "")
 DEF_ATTRIB(DCCLEANINVA, "dccleaninva", "", "")
 
@@ -166,6 +186,9 @@
 DEF_ATTRIB(NOTE_NVSLOT0, "Can execute only in slot 0 (ST)", "", "")
 DEF_ATTRIB(NOTE_NOVP, "Cannot be paired with a HVX permute instruction", "", "")
 DEF_ATTRIB(NOTE_VA_UNARY, "Combined with HVX ALU op (must be unary)", "", "")
+DEF_ATTRIB(NOTE_SLOT1_AOK, "Slot 1 is allowed", "", "")
+DEF_ATTRIB(NOTE_GUEST, "Guest mode instruction", "", "")
+DEF_ATTRIB(NOTE_BADTAG_UNDEF, "Bad tag results in undefined behavior", "", "")
 
 /* V6 MMVector Notes for Documentation */
 DEF_ATTRIB(NOTE_SHIFT_RESOURCE, "Uses the HVX shift resource.", "", "")
diff --git a/target/hexagon/cpu-param.h b/target/hexagon/cpu-param.h
index 1f0f22a..bfe9a86 100644
--- a/target/hexagon/cpu-param.h
+++ b/target/hexagon/cpu-param.h
@@ -18,7 +18,7 @@
 #ifndef HEXAGON_CPU_PARAM_H
 #define HEXAGON_CPU_PARAM_H
 
-#define TARGET_PAGE_BITS 16     /* 64K pages */
+#define TARGET_PAGE_BITS 12     /* 4K pages */
 
 #define TARGET_VIRT_ADDR_SPACE_BITS 32
 
diff --git a/target/hexagon/cpu.c b/target/hexagon/cpu.c
index 2c53f2c..42d93e5 100644
--- a/target/hexagon/cpu.c
+++ b/target/hexagon/cpu.c
@@ -16,16 +16,34 @@
  */
 
 #include "qemu/osdep.h"
+#include "qemu/log.h"
 #include "qemu/qemu-print.h"
 #include "cpu.h"
 #include "internal.h"
+#include "exec/cputlb.h"
 #include "exec/translation-block.h"
 #include "qapi/error.h"
 #include "hw/core/qdev-properties.h"
 #include "fpu/softfloat-helpers.h"
+#include "hw/hexagon/hexagon_tlb.h"
 #include "tcg/tcg.h"
 #include "exec/gdbstub.h"
 #include "accel/tcg/cpu-ops.h"
+#include "cpu_helper.h"
+#include "hex_mmu.h"
+
+#ifndef CONFIG_USER_ONLY
+#include "macros.h"
+#include "sys_macros.h"
+#include "accel/tcg/cpu-ldst.h"
+#include "qemu/main-loop.h"
+#include "hex_interrupts.h"
+#include "hexswi.h"
+#include "exec/cpu-interrupt.h"
+#include "exec/page-protection.h"
+#include "exec/target_page.h"
+#include "hw/hexagon/hexagon_globalreg.h"
+#endif
 
 static ObjectClass *hexagon_cpu_class_by_name(const char *cpu_model)
 {
@@ -43,6 +61,14 @@
 }
 
 static const Property hexagon_cpu_properties[] = {
+#ifndef CONFIG_USER_ONLY
+    DEFINE_PROP_LINK("tlb", HexagonCPU, tlb, TYPE_HEXAGON_TLB,
+                     HexagonTLBState *),
+    DEFINE_PROP_UINT32("exec-start-addr", HexagonCPU, boot_addr, 0xffffffff),
+    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),
@@ -60,6 +86,35 @@
   "c24", "c25", "c26", "c27", "c28",  "c29", "c30", "c31",
 };
 
+#ifndef CONFIG_USER_ONLY
+const char * const hexagon_sregnames[] = {
+    "sgp0",       "sgp1",       "stid",       "elr",        "badva0",
+    "badva1",     "ssr",        "ccr",        "htid",       "badva",
+    "imask",      "gevb",       "vwctrl",     "s13",        "s14",
+    "s15",        "evb",        "modectl",    "syscfg",     "segment",
+    "ipendad",    "vid",        "vid1",       "bestwait",   "s24",
+    "schedcfg",   "s26",        "cfgbase",    "diag",       "rev",
+    "pcyclelo",   "pcyclehi",   "isdbst",     "isdbcfg0",   "isdbcfg1",
+    "livelock",   "brkptpc0",   "brkptcfg0",  "brkptpc1",   "brkptcfg1",
+    "isdbmbxin",  "isdbmbxout", "isdben",     "isdbgpr",    "pmucnt4",
+    "pmucnt5",    "pmucnt6",    "pmucnt7",    "pmucnt0",    "pmucnt1",
+    "pmucnt2",    "pmucnt3",    "pmuevtcfg",  "pmustid0",   "pmuevtcfg1",
+    "pmustid1",   "timerlo",    "timerhi",    "pmucfg",     "s59",
+    "s60",        "s61",        "s62",        "s63",
+};
+
+G_STATIC_ASSERT(NUM_SREGS == ARRAY_SIZE(hexagon_sregnames));
+
+const char * const hexagon_gregnames[] = {
+    "gelr",       "gsr",       "gosp",      "gbadva",    "gcommit1t",
+    "gcommit2t",  "gcommit3t", "gcommit4t", "gcommit5t", "gcommit6t",
+    "gpcycle1t",  "gpcycle2t", "gpcycle3t", "gpcycle4t", "gpcycle5t",
+    "gpcycle6t",  "gpmucnt4",  "gpmucnt5",  "gpmucnt6",  "gpmucnt7",
+    "gcommit7t",  "gcommit8t", "gpcycle7t", "gpcycle8t", "gpcyclelo",
+    "gpcyclehi",  "gpmucnt0",  "gpmucnt1",  "gpmucnt2",  "gpmucnt3",
+    "g30",        "g31",
+};
+#endif
 /*
  * One of the main debugging techniques is to use "-d cpu" and compare against
  * LLDB output when single stepping.  However, the target and qemu put the
@@ -110,6 +165,14 @@
                  hexagon_regnames[regnum], value);
 }
 
+#ifndef CONFIG_USER_ONLY
+static void print_t_sreg(FILE *f, const CPUHexagonState *env, int regnum)
+{
+    qemu_fprintf(f, "  %s = 0x" TARGET_FMT_lx "\n",
+                 hexagon_sregnames[regnum], env->t_sreg[regnum]);
+}
+#endif
+
 static void print_vreg(FILE *f, CPUHexagonState *env, int regnum,
                        bool skip_if_zero)
 {
@@ -209,8 +272,7 @@
     qemu_fprintf(f, "  cs0 = 0x00000000\n");
     qemu_fprintf(f, "  cs1 = 0x00000000\n");
 #else
-    print_reg(f, env, HEX_REG_CAUSE);
-    print_reg(f, env, HEX_REG_BADVA);
+    print_t_sreg(f, env, HEX_SREG_BADVA);
     print_reg(f, env, HEX_REG_CS0);
     print_reg(f, env, HEX_REG_CS1);
 #endif
@@ -261,6 +323,14 @@
         hexagon_raise_exception_err(env, HEX_CAUSE_PC_NOT_ALIGNED, 0);
     }
 
+#ifndef CONFIG_USER_ONLY
+    hex_flags = FIELD_DP32(hex_flags, TB_FLAGS, MMU_INDEX,
+                           cpu_mmu_index(env_cpu(env), false));
+    hex_flags = FIELD_DP32(hex_flags, TB_FLAGS, PCYCLE_ENABLED, 1);
+#else
+    hex_flags = FIELD_DP32(hex_flags, TB_FLAGS, MMU_INDEX, MMU_USER_IDX);
+#endif
+
     return (TCGTBCPUState){ .pc = pc, .flags = hex_flags };
 }
 
@@ -271,6 +341,36 @@
     cpu_env(cs)->gpr[HEX_REG_PC] = tb->pc;
 }
 
+#ifndef CONFIG_USER_ONLY
+bool hexagon_thread_is_enabled(CPUHexagonState *env)
+{
+    HexagonCPU *cpu = env_archcpu(env);
+    uint32_t modectl;
+    uint32_t thread_enabled_mask;
+    bool E_bit;
+
+    if (!cpu->globalregs) {
+        return true;
+    }
+    modectl =
+        hexagon_globalreg_read(cpu->globalregs, HEX_SREG_MODECTL,
+                               env->threadId);
+    thread_enabled_mask = GET_FIELD(MODECTL_E, modectl);
+    E_bit = thread_enabled_mask & (0x1 << env->threadId);
+
+    return E_bit;
+}
+
+static bool hexagon_cpu_has_work(CPUState *cs)
+{
+    CPUHexagonState *env = cpu_env(cs);
+
+    return hexagon_thread_is_enabled(env) &&
+        (cs->interrupt_request & (CPU_INTERRUPT_HARD | CPU_INTERRUPT_SWI
+            | CPU_INTERRUPT_K0_UNLOCK | CPU_INTERRUPT_TLB_UNLOCK));
+}
+#endif
+
 static void hexagon_restore_state_to_opc(CPUState *cs,
                                          const TranslationBlock *tb,
                                          const uint64_t *data)
@@ -278,11 +378,36 @@
     cpu_env(cs)->gpr[HEX_REG_PC] = data[0];
 }
 
+
+#ifndef CONFIG_USER_ONLY
+void hexagon_cpu_soft_reset(CPUHexagonState *env)
+{
+    HexagonCPU *cpu;
+
+    BQL_LOCK_GUARD();
+    env->t_sreg[HEX_SREG_SSR] = 0;
+    hexagon_ssr_set_cause(env, HEX_CAUSE_RESET);
+
+    cpu = env_archcpu(env);
+    if (cpu->globalregs) {
+        uint32_t evb =
+            hexagon_globalreg_read(cpu->globalregs, HEX_SREG_EVB,
+                                   env->threadId);
+        env->gpr[HEX_REG_PC] = evb;
+    } else {
+        env->gpr[HEX_REG_PC] = cpu->boot_addr;
+    }
+}
+#endif
+
 static void hexagon_cpu_reset_hold(Object *obj, ResetType type)
 {
     CPUState *cs = CPU(obj);
     HexagonCPUClass *mcc = HEXAGON_CPU_GET_CLASS(obj);
     CPUHexagonState *env = cpu_env(cs);
+#ifndef CONFIG_USER_ONLY
+    HexagonCPU *cpu = HEXAGON_CPU(cs);
+#endif
 
     if (mcc->parent_phases.hold) {
         mcc->parent_phases.hold(obj, type);
@@ -292,6 +417,22 @@
     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);
+#ifndef CONFIG_USER_ONLY
+    memset(env->t_sreg, 0, sizeof(uint32_t) * NUM_SREGS);
+    memset(env->greg, 0, sizeof(uint32_t) * NUM_GREGS);
+    env->wait_next_pc = 0;
+    env->tlb_lock_state = HEX_LOCK_UNLOCKED;
+    env->k0_lock_state = HEX_LOCK_UNLOCKED;
+    env->tlb_lock_count = 0;
+    env->k0_lock_count = 0;
+    env->next_PC = 0;
+
+    env->t_sreg[HEX_SREG_HTID] = cpu->htid;
+    env->threadId = cpu->htid;
+    hexagon_cpu_soft_reset(env);
+    env->cause_code = HEX_EVENT_NONE;
+    env->gpr[HEX_REG_PC] = cpu->boot_addr;
+#endif
 }
 
 static void hexagon_cpu_disas_set_info(const CPUState *cs,
@@ -319,21 +460,263 @@
                              hexagon_hvx_gdb_write_register,
                              gdb_find_static_feature("hexagon-hvx.xml"));
 
-    qemu_init_vcpu(cs);
-    cpu_reset(cs);
+#ifndef CONFIG_USER_ONLY
+    if (!HEXAGON_CPU(dev)->tlb) {
+        error_setg(errp, "hexagon cpu requires 'tlb' link property to be set");
+        return;
+    }
+#endif
 
+    qemu_init_vcpu(cs);
+
+    cpu_reset(cs);
     mcc->parent_realize(dev, errp);
 }
 
 static int hexagon_cpu_mmu_index(CPUState *cs, bool ifetch)
 {
+#ifndef CONFIG_USER_ONLY
+    CPUHexagonState *env = cpu_env(cs);
+    HexagonCPU *cpu = HEXAGON_CPU(cs);
+    int cpu_mode;
+
+    BQL_LOCK_GUARD();
+    if (cpu->globalregs) {
+        uint32_t syscfg =
+            hexagon_globalreg_read(cpu->globalregs, HEX_SREG_SYSCFG,
+                                   env->threadId);
+        uint8_t mmuen = GET_SYSCFG_FIELD(SYSCFG_MMUEN, syscfg);
+        if (!mmuen) {
+            return MMU_KERNEL_IDX;
+        }
+    }
+
+    cpu_mode = get_cpu_mode(env);
+    if (cpu_mode == HEX_CPU_MODE_MONITOR) {
+        return MMU_KERNEL_IDX;
+    } else if (cpu_mode == HEX_CPU_MODE_GUEST) {
+        return MMU_GUEST_IDX;
+    }
+#endif
+
     return MMU_USER_IDX;
 }
 
+#ifndef CONFIG_USER_ONLY
+static void hexagon_cpu_set_irq(void *opaque, int irq, int level)
+{
+    HexagonCPU *cpu = HEXAGON_CPU(opaque);
+    CPUState *cs = CPU(cpu);
+    CPUHexagonState *env = cpu_env(cs);
+
+    switch (irq) {
+    case HEXAGON_CPU_IRQ_0 ... HEXAGON_CPU_IRQ_7:
+        qemu_log_mask(CPU_LOG_INT, "%s: irq %d, level %d\n",
+                      __func__, irq, level);
+        if (level) {
+            hex_raise_interrupts(env, 1 << irq, CPU_INTERRUPT_HARD);
+        }
+        break;
+    default:
+        g_assert_not_reached();
+    }
+}
+#endif
+
 static void hexagon_cpu_init(Object *obj)
 {
+#ifndef CONFIG_USER_ONLY
+    HexagonCPU *cpu = HEXAGON_CPU(obj);
+    qdev_init_gpio_in(DEVICE(cpu), hexagon_cpu_set_irq, 8);
+#endif
 }
 
+#ifndef CONFIG_USER_ONLY
+static bool get_physical_address(CPUHexagonState *env, hwaddr *phys, int *prot,
+                                 uint64_t *size, int32_t *excp,
+                                 uint32_t address,
+                                 MMUAccessType access_type, int mmu_idx)
+
+{
+    if (hexagon_cpu_mmu_enabled(env)) {
+        return hex_tlb_find_match(env, address, access_type, phys, prot, size,
+                                  excp, mmu_idx);
+    } else {
+        *phys = address & 0xFFFFFFFF;
+        *prot = PAGE_VALID | PAGE_READ | PAGE_WRITE | PAGE_EXEC;
+        *size = TARGET_PAGE_SIZE;
+        return true;
+    }
+}
+
+/* qemu seems to only want to know about TARGET_PAGE_SIZE pages */
+static void find_qemu_subpage(vaddr *addr, hwaddr *phys, uint64_t page_size)
+{
+    vaddr page_start = *addr & ~((vaddr)(page_size - 1));
+    vaddr offset = ((*addr - page_start) / TARGET_PAGE_SIZE) * TARGET_PAGE_SIZE;
+    *addr = page_start + offset;
+    *phys += offset;
+}
+
+static hwaddr hexagon_cpu_get_phys_addr_debug(CPUState *cs, vaddr addr)
+{
+    CPUHexagonState *env = cpu_env(cs);
+    hwaddr phys_addr;
+    int prot;
+    uint64_t page_size = 0;
+    int32_t excp = 0;
+    int mmu_idx = MMU_KERNEL_IDX;
+
+    if (get_physical_address(env, &phys_addr, &prot, &page_size, &excp,
+                             addr, 0, mmu_idx)) {
+        find_qemu_subpage(&addr, &phys_addr, page_size);
+        return phys_addr;
+    }
+
+    return -1;
+}
+
+
+#define INVALID_BADVA 0xbadabada
+
+static void set_badva_regs(CPUHexagonState *env, uint32_t VA, int slot,
+                           MMUAccessType access_type)
+{
+    env->t_sreg[HEX_SREG_BADVA] = VA;
+
+    if (access_type == MMU_INST_FETCH || slot == 0) {
+        env->t_sreg[HEX_SREG_BADVA0] = VA;
+        env->t_sreg[HEX_SREG_BADVA1] = INVALID_BADVA;
+        SET_SSR_FIELD(env, SSR_V0, 1);
+        SET_SSR_FIELD(env, SSR_V1, 0);
+        SET_SSR_FIELD(env, SSR_BVS, 0);
+    } else if (slot == 1) {
+        env->t_sreg[HEX_SREG_BADVA0] = INVALID_BADVA;
+        env->t_sreg[HEX_SREG_BADVA1] = VA;
+        SET_SSR_FIELD(env, SSR_V0, 0);
+        SET_SSR_FIELD(env, SSR_V1, 1);
+        SET_SSR_FIELD(env, SSR_BVS, 1);
+    } else {
+        g_assert_not_reached();
+    }
+}
+
+static void raise_tlbmiss_exception(CPUState *cs, uint32_t VA, int slot,
+                                    MMUAccessType access_type)
+{
+    CPUHexagonState *env = cpu_env(cs);
+
+    set_badva_regs(env, VA, slot, access_type);
+
+    switch (access_type) {
+    case MMU_INST_FETCH:
+        cs->exception_index = HEX_EVENT_TLB_MISS_X;
+        if ((VA & ~TARGET_PAGE_MASK) == 0) {
+            env->cause_code = HEX_CAUSE_TLBMISSX_CAUSE_NEXTPAGE;
+        } else {
+            env->cause_code = HEX_CAUSE_TLBMISSX_CAUSE_NORMAL;
+        }
+        break;
+    case MMU_DATA_LOAD:
+        cs->exception_index = HEX_EVENT_TLB_MISS_RW;
+        env->cause_code = HEX_CAUSE_TLBMISSRW_CAUSE_READ;
+        break;
+    case MMU_DATA_STORE:
+        cs->exception_index = HEX_EVENT_TLB_MISS_RW;
+        env->cause_code = HEX_CAUSE_TLBMISSRW_CAUSE_WRITE;
+        break;
+    }
+}
+
+static void raise_perm_exception(CPUState *cs, uint32_t VA, int slot,
+                                 MMUAccessType access_type, int32_t excp)
+{
+    CPUHexagonState *env = cpu_env(cs);
+
+    set_badva_regs(env, VA, slot, access_type);
+    cs->exception_index = excp;
+}
+
+static const char *access_type_names[] = { "MMU_DATA_LOAD ", "MMU_DATA_STORE",
+                                           "MMU_INST_FETCH" };
+
+static const char *mmu_idx_names[] = { "MMU_USER_IDX", "MMU_GUEST_IDX",
+                                       "MMU_KERNEL_IDX" };
+
+static bool hexagon_tlb_fill(CPUState *cs, vaddr address, int size,
+                             MMUAccessType access_type, int mmu_idx, bool probe,
+                             uintptr_t retaddr)
+{
+    CPUHexagonState *env = cpu_env(cs);
+    int slot = 0;
+    hwaddr phys;
+    int prot = 0;
+    uint64_t page_size = 0;
+    int32_t excp = 0;
+    bool ret = 0;
+
+    qemu_log_mask(
+        CPU_LOG_MMU,
+        "%s: tid = 0x%" PRIx32 ", pc = 0x%08" PRIx32
+        ", vaddr = 0x%08" VADDR_PRIx ", size = %d, %s,\tprobe = %d, %s\n",
+        __func__, env->threadId, env->gpr[HEX_REG_PC], address, size,
+        access_type_names[access_type], probe, mmu_idx_names[mmu_idx]);
+    ret = get_physical_address(env, &phys, &prot, &page_size, &excp, address,
+                               access_type, mmu_idx);
+    if (ret) {
+        if (!excp) {
+            find_qemu_subpage(&address, &phys, page_size);
+            tlb_set_page(cs, address, phys, prot, mmu_idx, TARGET_PAGE_SIZE);
+            return ret;
+        }
+        if (probe) {
+            return false;
+        }
+        raise_perm_exception(cs, address, slot, access_type, excp);
+        do_raise_exception(env, cs->exception_index, env->gpr[HEX_REG_PC],
+                           retaddr);
+    }
+    if (probe) {
+        return false;
+    }
+    raise_tlbmiss_exception(cs, address, slot, access_type);
+    do_raise_exception(env, cs->exception_index, env->gpr[HEX_REG_PC], retaddr);
+}
+
+#include "hw/core/sysemu-cpu-ops.h"
+
+static const struct SysemuCPUOps hexagon_sysemu_ops = {
+    .has_work = hexagon_cpu_has_work,
+    .get_phys_addr_debug = hexagon_cpu_get_phys_addr_debug,
+};
+
+static bool hexagon_cpu_exec_interrupt(CPUState *cs, int interrupt_request)
+{
+    CPUHexagonState *env = cpu_env(cs);
+    if (interrupt_request & CPU_INTERRUPT_TLB_UNLOCK) {
+        cs->halted = false;
+        cpu_reset_interrupt(cs, CPU_INTERRUPT_TLB_UNLOCK);
+        return true;
+    }
+    if (interrupt_request & CPU_INTERRUPT_K0_UNLOCK) {
+        cs->halted = false;
+        cpu_reset_interrupt(cs, CPU_INTERRUPT_K0_UNLOCK);
+        return true;
+    }
+    if (interrupt_request & (CPU_INTERRUPT_HARD | CPU_INTERRUPT_SWI)) {
+        return hex_check_interrupts(env);
+    }
+    return false;
+}
+
+static vaddr hexagon_pointer_wrap(CPUState *cs, int mmu_idx,
+                                  vaddr result, vaddr base)
+{
+    return result;
+}
+
+#endif
+
 static const TCGCPUOps hexagon_tcg_ops = {
     /* MTTCG not yet supported: require strict ordering */
     .guest_default_memory_order = TCG_MO_ALL,
@@ -344,6 +727,14 @@
     .synchronize_from_tb = hexagon_cpu_synchronize_from_tb,
     .restore_state_to_opc = hexagon_restore_state_to_opc,
     .mmu_index = hexagon_cpu_mmu_index,
+#ifndef CONFIG_USER_ONLY
+    .cpu_exec_interrupt = hexagon_cpu_exec_interrupt,
+    .pointer_wrap = hexagon_pointer_wrap,
+    .cpu_exec_reset = cpu_reset,
+    .tlb_fill = hexagon_tlb_fill,
+    .cpu_exec_halt = hexagon_cpu_has_work,
+    .do_interrupt = hexagon_cpu_do_interrupt,
+#endif /* !CONFIG_USER_ONLY */
 };
 
 static void hexagon_cpu_class_init(ObjectClass *c, const void *data)
@@ -369,9 +760,34 @@
     cc->gdb_stop_before_watchpoint = true;
     cc->gdb_core_xml_file = "hexagon-core.xml";
     cc->disas_set_info = hexagon_cpu_disas_set_info;
+#ifndef CONFIG_USER_ONLY
+    cc->sysemu_ops = &hexagon_sysemu_ops;
+    dc->vmsd = &vmstate_hexagon_cpu;
+#endif
+#ifdef CONFIG_TCG
     cc->tcg_ops = &hexagon_tcg_ops;
+#endif
 }
 
+#ifndef CONFIG_USER_ONLY
+uint32_t hexagon_greg_read(CPUHexagonState *env, uint32_t reg)
+{
+    if (reg <= HEX_GREG_G3) {
+        return env->greg[reg];
+    }
+    switch (reg) {
+    case HEX_GREG_GPCYCLELO:
+        return hexagon_get_sys_pcycle_count_low(env);
+    case HEX_GREG_GPCYCLEHI:
+        return hexagon_get_sys_pcycle_count_high(env);
+    default:
+        qemu_log_mask(LOG_UNIMP, "reading greg %" PRId32
+                " not yet supported.\n", reg);
+        return 0;
+    }
+}
+#endif
+
 static void hexagon_cpu_class_base_init(ObjectClass *c, const void *data)
 {
     HexagonCPUClass *mcc = HEXAGON_CPU_CLASS(c);
diff --git a/target/hexagon/cpu.h b/target/hexagon/cpu.h
index 7c09c97..7694fd9 100644
--- a/target/hexagon/cpu.h
+++ b/target/hexagon/cpu.h
@@ -20,6 +20,16 @@
 
 #include "fpu/softfloat-types.h"
 
+#ifndef CONFIG_USER_ONLY
+#define NUM_GREGS 32
+#define GREG_WRITES_MAX 2
+#define NUM_SREGS 64
+#define SREG_WRITES_MAX 2
+#endif
+
+typedef struct HexagonTLBState HexagonTLBState;
+typedef struct HexagonGlobalRegState HexagonGlobalRegState;
+
 #include "cpu-qom.h"
 #include "exec/cpu-common.h"
 #include "exec/target_long.h"
@@ -28,9 +38,7 @@
 #include "hw/core/registerfields.h"
 #include "qemu/bitmap.h"
 
-#ifndef CONFIG_USER_ONLY
-#error "Hexagon does not support system emulation"
-#endif
+#include "target/hexagon/reg_fields.h"
 
 #define NUM_PREGS 4
 #define TOTAL_PER_THREAD_REGS 64
@@ -40,10 +48,47 @@
 #define REG_WRITES_MAX 32
 #define PRED_WRITES_MAX 5                   /* 4 insns + endloop */
 #define VSTORES_MAX 2
+#define MAX_TLB_ENTRIES 1024
+#define THREADS_MAX 8
 
 #define CPU_RESOLVING_TYPE TYPE_HEXAGON_CPU
+#ifndef CONFIG_USER_ONLY
+#define CPU_INTERRUPT_SWI      CPU_INTERRUPT_TGT_INT_0
+#define CPU_INTERRUPT_K0_UNLOCK CPU_INTERRUPT_TGT_INT_1
+#define CPU_INTERRUPT_TLB_UNLOCK CPU_INTERRUPT_TGT_INT_2
 
-#define MMU_USER_IDX 0
+#define HEX_CPU_MODE_USER    1
+#define HEX_CPU_MODE_GUEST   2
+#define HEX_CPU_MODE_MONITOR 3
+
+#define HEX_EXE_MODE_OFF     1
+#define HEX_EXE_MODE_RUN     2
+#define HEX_EXE_MODE_WAIT    3
+#define HEX_EXE_MODE_DEBUG   4
+#endif
+
+#define MMU_USER_IDX         0
+#ifndef CONFIG_USER_ONLY
+#define MMU_GUEST_IDX        1
+#define MMU_KERNEL_IDX       2
+
+typedef enum {
+    HEX_LOCK_UNLOCKED       = 0,
+    HEX_LOCK_WAITING        = 1,
+    HEX_LOCK_OWNER          = 2,
+    HEX_LOCK_QUEUED        = 3
+} hex_lock_state_t;
+#endif
+
+
+#define HEXAGON_CPU_IRQ_0 0
+#define HEXAGON_CPU_IRQ_1 1
+#define HEXAGON_CPU_IRQ_2 2
+#define HEXAGON_CPU_IRQ_3 3
+#define HEXAGON_CPU_IRQ_4 4
+#define HEXAGON_CPU_IRQ_5 5
+#define HEXAGON_CPU_IRQ_6 6
+#define HEXAGON_CPU_IRQ_7 7
 
 typedef struct {
     target_ulong va;
@@ -77,12 +122,30 @@
 typedef struct CPUArchState {
     target_ulong gpr[TOTAL_PER_THREAD_REGS];
     target_ulong pred[NUM_PREGS];
+    uint32_t cause_code;
 
     /* For comparing with LLDB on target - see adjust_stack_ptrs function */
     target_ulong last_pc_dumped;
     target_ulong stack_start;
 
     uint8_t slot_cancelled;
+
+#ifndef CONFIG_USER_ONLY
+    /* Some system registers are per thread and some are global. */
+    uint32_t t_sreg[NUM_SREGS];
+
+    uint32_t greg[NUM_GREGS];
+    uint32_t wait_next_pc;
+
+    /* This alias of CPUState.cpu_index is used by imported sources: */
+    uint32_t threadId;
+    hex_lock_state_t tlb_lock_state;
+    hex_lock_state_t k0_lock_state;
+    uint32_t tlb_lock_count;
+    uint32_t k0_lock_count;
+    uint64_t t_cycle_count;
+#endif
+    uint32_t next_PC;
     target_ulong new_value_usr;
 
     MemLog mem_log_stores[STORES_MAX];
@@ -130,16 +193,34 @@
     bool lldb_compat;
     target_ulong lldb_stack_adjust;
     bool short_circuit;
+#ifndef CONFIG_USER_ONLY
+    HexagonTLBState *tlb;
+    uint32_t boot_addr;
+    HexagonGlobalRegState *globalregs;
+    uint32_t htid;
+#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)
 
 G_NORETURN void hexagon_raise_exception_err(CPUHexagonState *env,
                                             uint32_t exception,
                                             uintptr_t pc);
 
+#ifndef CONFIG_USER_ONLY
+/*
+ * @return true if the @a thread_env hardware thread is
+ * not stopped.
+ */
+bool hexagon_thread_is_enabled(CPUHexagonState *thread_env);
+uint32_t hexagon_greg_read(CPUHexagonState *env, uint32_t reg);
+void hexagon_cpu_soft_reset(CPUHexagonState *env);
+#endif
+
 typedef HexagonCPU ArchCPU;
 
 void hexagon_translate_init(void);
diff --git a/target/hexagon/cpu_bits.h b/target/hexagon/cpu_bits.h
index aaac6b9..164e74c 100644
--- a/target/hexagon/cpu_bits.h
+++ b/target/hexagon/cpu_bits.h
@@ -25,20 +25,77 @@
 #define PCALIGN_MASK (PCALIGN - 1)
 
 enum hex_event {
-    HEX_EVENT_NONE           = -1,
-    HEX_EVENT_TRAP0          =  0x008,
+    HEX_EVENT_NONE = -1,
+    HEX_EVENT_RESET = 0x0,
+    HEX_EVENT_IMPRECISE = 0x1,
+    HEX_EVENT_PRECISE = 0x2,
+    HEX_EVENT_TLB_MISS_X = 0x4,
+    HEX_EVENT_TLB_MISS_RW = 0x6,
+    HEX_EVENT_TRAP0 = 0x8,
+    HEX_EVENT_TRAP1 = 0x9,
+    HEX_EVENT_FPTRAP = 0xb,
+    HEX_EVENT_DEBUG = 0xc,
+    HEX_EVENT_INT0 = 0x10,
+    HEX_EVENT_INT1 = 0x11,
+    HEX_EVENT_INT2 = 0x12,
+    HEX_EVENT_INT3 = 0x13,
+    HEX_EVENT_INT4 = 0x14,
+    HEX_EVENT_INT5 = 0x15,
+    HEX_EVENT_INT6 = 0x16,
+    HEX_EVENT_INT7 = 0x17,
+    HEX_EVENT_INT8 = 0x18,
+    HEX_EVENT_INT9 = 0x19,
+    HEX_EVENT_INTA = 0x1a,
+    HEX_EVENT_INTB = 0x1b,
+    HEX_EVENT_INTC = 0x1c,
+    HEX_EVENT_INTD = 0x1d,
+    HEX_EVENT_INTE = 0x1e,
+    HEX_EVENT_INTF = 0x1f,
 };
 
 enum hex_cause {
     HEX_CAUSE_NONE = -1,
-    HEX_CAUSE_TRAP0 = 0x172,
-    HEX_CAUSE_FETCH_NO_UPAGE =  0x012,
-    HEX_CAUSE_INVALID_PACKET =  0x015,
-    HEX_CAUSE_INVALID_OPCODE =  0x015,
+    HEX_CAUSE_RESET = 0x000,
+    HEX_CAUSE_BIU_PRECISE = 0x001,
+    HEX_CAUSE_UNSUPPORTED_HVX_64B = 0x002, /* QEMU-specific */
+    HEX_CAUSE_DOUBLE_EXCEPT = 0x003,
+    HEX_CAUSE_TRAP0 = 0x008,
+    HEX_CAUSE_TRAP1 = 0x009,
+    HEX_CAUSE_FETCH_NO_XPAGE = 0x011,
+    HEX_CAUSE_FETCH_NO_UPAGE = 0x012,
+    HEX_CAUSE_INVALID_PACKET = 0x015,
+    HEX_CAUSE_INVALID_OPCODE = 0x015, /* alias: same cause as INVALID_PACKET */
+    HEX_CAUSE_NO_COPROC_ENABLE = 0x016,
+    HEX_CAUSE_NO_COPROC2_ENABLE = 0x018,
+    HEX_CAUSE_PRIV_USER_NO_GINSN = 0x01a,
+    HEX_CAUSE_PRIV_USER_NO_SINSN = 0x01b,
     HEX_CAUSE_REG_WRITE_CONFLICT = 0x01d,
-    HEX_CAUSE_PC_NOT_ALIGNED =  0x01e,
-    HEX_CAUSE_PRIV_NO_UREAD  =  0x024,
-    HEX_CAUSE_PRIV_NO_UWRITE =  0x025,
+    HEX_CAUSE_PC_NOT_ALIGNED = 0x01e,
+    HEX_CAUSE_MISALIGNED_LOAD = 0x020,
+    HEX_CAUSE_MISALIGNED_STORE = 0x021,
+    HEX_CAUSE_PRIV_NO_READ = 0x022,
+    HEX_CAUSE_PRIV_NO_WRITE = 0x023,
+    HEX_CAUSE_PRIV_NO_UREAD = 0x024,
+    HEX_CAUSE_PRIV_NO_UWRITE = 0x025,
+    HEX_CAUSE_COPROC_LDST = 0x026,
+    HEX_CAUSE_STACK_LIMIT = 0x027,
+    HEX_CAUSE_VWCTRL_WINDOW_MISS = 0x029,
+    HEX_CAUSE_IMPRECISE_NMI = 0x043,
+    HEX_CAUSE_IMPRECISE_MULTI_TLB_MATCH = 0x044,
+    HEX_CAUSE_TLBMISSX_CAUSE_NORMAL = 0x060,
+    HEX_CAUSE_TLBMISSX_CAUSE_NEXTPAGE = 0x061,
+    HEX_CAUSE_TLBMISSRW_CAUSE_READ = 0x070,
+    HEX_CAUSE_TLBMISSRW_CAUSE_WRITE = 0x071,
+    HEX_CAUSE_DEBUG_SINGLESTEP = 0x80,
+    HEX_CAUSE_FPTRAP_CAUSE_BADFLOAT = 0x0bf,
+    HEX_CAUSE_INT0 = 0x0c0,
+    HEX_CAUSE_INT1 = 0x0c1,
+    HEX_CAUSE_INT2 = 0x0c2,
+    HEX_CAUSE_INT3 = 0x0c3,
+    HEX_CAUSE_INT4 = 0x0c4,
+    HEX_CAUSE_INT5 = 0x0c5,
+    HEX_CAUSE_INT6 = 0x0c6,
+    HEX_CAUSE_INT7 = 0x0c7,
 };
 
 #define PACKET_WORDS_MAX         4
diff --git a/target/hexagon/cpu_helper.c b/target/hexagon/cpu_helper.c
new file mode 100644
index 0000000..64c5746
--- /dev/null
+++ b/target/hexagon/cpu_helper.c
@@ -0,0 +1,399 @@
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "cpu.h"
+#include "cpu_helper.h"
+#include "system/cpus.h"
+#include "hw/core/boards.h"
+#include "hw/hexagon/hexagon.h"
+#include "hw/hexagon/hexagon_globalreg.h"
+#include "hex_interrupts.h"
+#include "hex_mmu.h"
+#include "system/runstate.h"
+#include "exec/cpu-interrupt.h"
+#include "exec/target_page.h"
+#include "accel/tcg/cpu-ldst.h"
+#include "exec/cputlb.h"
+#include "qemu/log.h"
+#include "tcg/tcg-op.h"
+#include "internal.h"
+#include "macros.h"
+#include "sys_macros.h"
+#include "arch.h"
+
+
+uint32_t hexagon_get_pmu_counter(CPUHexagonState *cur_env, int index)
+{
+    g_assert_not_reached();
+}
+
+uint64_t hexagon_get_sys_pcycle_count(CPUHexagonState *env)
+{
+    uint64_t total = 0;
+    CPUState *cs;
+
+    g_assert(bql_locked());
+    CPU_FOREACH(cs) {
+        CPUHexagonState *thread_env = cpu_env(cs);
+        total += thread_env->t_cycle_count;
+    }
+    return total;
+}
+
+uint32_t hexagon_get_sys_pcycle_count_high(CPUHexagonState *env)
+{
+    return (uint32_t)(hexagon_get_sys_pcycle_count(env) >> 32);
+}
+
+uint32_t hexagon_get_sys_pcycle_count_low(CPUHexagonState *env)
+{
+    return (uint32_t)(hexagon_get_sys_pcycle_count(env));
+}
+
+void hexagon_set_sys_pcycle_count_high(CPUHexagonState *env, uint32_t val)
+{
+    uint64_t old;
+
+    g_assert(bql_locked());
+    old = hexagon_get_sys_pcycle_count(env);
+    old = deposit64(old, 32, 32, val);
+    hexagon_set_sys_pcycle_count(env, old);
+}
+
+void hexagon_set_sys_pcycle_count_low(CPUHexagonState *env, uint32_t val)
+{
+    uint64_t old;
+
+    g_assert(bql_locked());
+    old = hexagon_get_sys_pcycle_count(env);
+    old = deposit64(old, 0, 32, val);
+    hexagon_set_sys_pcycle_count(env, old);
+}
+
+void hexagon_set_sys_pcycle_count(CPUHexagonState *env, uint64_t val)
+{
+    CPUState *cs;
+    uint64_t total;
+    int num_threads;
+    int64_t delta, per_thread, remainder;
+
+    g_assert(bql_locked());
+    total = hexagon_get_sys_pcycle_count(env);
+
+    /* Count active threads */
+    num_threads = 0;
+    CPU_FOREACH(cs) {
+        num_threads++;
+    }
+    g_assert(num_threads > 0);
+
+    /*
+     * Distribute the delta evenly across all threads.
+     * Any remainder goes to the calling thread.
+     */
+    delta = (int64_t)(val - total);
+    per_thread = delta / num_threads;
+    remainder = delta - per_thread * num_threads;
+
+    CPU_FOREACH(cs) {
+        CPUHexagonState *thread_env = cpu_env(cs);
+        thread_env->t_cycle_count += per_thread;
+    }
+    env->t_cycle_count += remainder;
+}
+
+static void hexagon_resume_thread(CPUHexagonState *env)
+{
+    CPUState *cs = env_cpu(env);
+    clear_wait_mode(env);
+    /*
+     * The wait instruction keeps the PC pointing to itself
+     * so that it has an opportunity to check for interrupts.
+     *
+     * When we come out of wait mode, adjust the PC to the
+     * next executable instruction.
+     */
+    env->gpr[HEX_REG_PC] = env->wait_next_pc;
+    cs = env_cpu(env);
+    ASSERT_DIRECT_TO_GUEST_UNSET(env, cs->exception_index);
+    cs->halted = false;
+    cs->exception_index = HEX_EVENT_NONE;
+    qemu_cpu_kick(cs);
+}
+
+void hexagon_resume_threads(CPUHexagonState *current_env, uint32_t mask)
+{
+    CPUState *cs;
+    CPUHexagonState *env;
+
+    g_assert(bql_locked());
+    CPU_FOREACH(cs) {
+        env = cpu_env(cs);
+        g_assert(env->threadId < THREADS_MAX);
+        if ((mask & (0x1 << env->threadId))) {
+            if (get_exe_mode(env) == HEX_EXE_MODE_WAIT) {
+                hexagon_resume_thread(env);
+            }
+        }
+    }
+}
+
+void hexagon_modify_ssr(CPUHexagonState *env, uint32_t new, uint32_t old)
+{
+    bool old_EX, old_UM, old_GM, old_IE;
+    bool new_EX, new_UM, new_GM, new_IE;
+    uint8_t old_asid, new_asid;
+
+    g_assert(bql_locked());
+
+    old_EX = GET_SSR_FIELD(SSR_EX, old);
+    old_UM = GET_SSR_FIELD(SSR_UM, old);
+    old_GM = GET_SSR_FIELD(SSR_GM, old);
+    old_IE = GET_SSR_FIELD(SSR_IE, old);
+    new_EX = GET_SSR_FIELD(SSR_EX, new);
+    new_UM = GET_SSR_FIELD(SSR_UM, new);
+    new_GM = GET_SSR_FIELD(SSR_GM, new);
+    new_IE = GET_SSR_FIELD(SSR_IE, new);
+
+    if ((old_EX != new_EX) ||
+        (old_UM != new_UM) ||
+        (old_GM != new_GM)) {
+        hex_mmu_mode_change(env);
+    }
+
+    old_asid = GET_SSR_FIELD(SSR_ASID, old);
+    new_asid = GET_SSR_FIELD(SSR_ASID, new);
+    if (new_asid != old_asid) {
+        CPUState *cs = env_cpu(env);
+        tlb_flush(cs);
+    }
+
+    /* See if the interrupts have been enabled or we have exited EX mode */
+    if ((new_IE && !old_IE) ||
+        (!new_EX && old_EX)) {
+        hex_interrupt_update(env);
+    }
+}
+
+void clear_wait_mode(CPUHexagonState *env)
+{
+    HexagonCPU *cpu;
+    uint32_t modectl, thread_wait_mask;
+
+    g_assert(bql_locked());
+
+    cpu = env_archcpu(env);
+    if (cpu->globalregs) {
+        modectl =
+            hexagon_globalreg_read(cpu->globalregs, HEX_SREG_MODECTL,
+                                   env->threadId);
+        thread_wait_mask = GET_FIELD(MODECTL_W, modectl);
+        thread_wait_mask &= ~(0x1 << env->threadId);
+        SET_SYSTEM_FIELD(env, HEX_SREG_MODECTL, MODECTL_W, thread_wait_mask);
+    }
+}
+
+void hexagon_ssr_set_cause(CPUHexagonState *env, uint32_t cause)
+{
+    uint32_t old, new;
+
+    g_assert(bql_locked());
+
+    old = env->t_sreg[HEX_SREG_SSR];
+    SET_SYSTEM_FIELD(env, HEX_SREG_SSR, SSR_EX, 1);
+    SET_SYSTEM_FIELD(env, HEX_SREG_SSR, SSR_CAUSE, cause);
+    new = env->t_sreg[HEX_SREG_SSR];
+
+    hexagon_modify_ssr(env, new, old);
+}
+
+
+int get_exe_mode(CPUHexagonState *env)
+{
+    HexagonCPU *cpu;
+    uint32_t modectl, thread_enabled_mask, thread_wait_mask;
+    uint32_t isdbst, debugmode;
+    bool E_bit, W_bit, D_bit;
+
+    g_assert(bql_locked());
+
+    cpu = env_archcpu(env);
+    modectl = cpu->globalregs ?
+        hexagon_globalreg_read(cpu->globalregs, HEX_SREG_MODECTL,
+                               env->threadId) : 0;
+    thread_enabled_mask = GET_FIELD(MODECTL_E, modectl);
+    E_bit = thread_enabled_mask & (0x1 << env->threadId);
+    thread_wait_mask = GET_FIELD(MODECTL_W, modectl);
+    W_bit = thread_wait_mask & (0x1 << env->threadId);
+    isdbst = cpu->globalregs ?
+        hexagon_globalreg_read(cpu->globalregs, HEX_SREG_ISDBST,
+                               env->threadId) : 0;
+    debugmode = GET_FIELD(ISDBST_DEBUGMODE, isdbst);
+    D_bit = debugmode & (0x1 << env->threadId);
+
+    if (!D_bit && !W_bit && !E_bit) {
+        return HEX_EXE_MODE_OFF;
+    }
+    if (!D_bit && !W_bit && E_bit) {
+        return HEX_EXE_MODE_RUN;
+    }
+    if (!D_bit && W_bit && E_bit) {
+        return HEX_EXE_MODE_WAIT;
+    }
+    if (D_bit && !W_bit && E_bit) {
+        return HEX_EXE_MODE_DEBUG;
+    }
+    g_assert_not_reached();
+}
+
+static uint32_t set_enable_mask(CPUHexagonState *env)
+{
+    HexagonCPU *cpu;
+    uint32_t modectl, thread_enabled_mask;
+
+    g_assert(bql_locked());
+
+    cpu = env_archcpu(env);
+    if (!cpu->globalregs) {
+        return 0;
+    }
+    modectl =
+        hexagon_globalreg_read(cpu->globalregs, HEX_SREG_MODECTL,
+                               env->threadId);
+    thread_enabled_mask = GET_FIELD(MODECTL_E, modectl);
+    thread_enabled_mask |= 0x1 << env->threadId;
+    SET_SYSTEM_FIELD(env, HEX_SREG_MODECTL, MODECTL_E, thread_enabled_mask);
+    return thread_enabled_mask;
+}
+
+static uint32_t clear_enable_mask(CPUHexagonState *env)
+{
+    HexagonCPU *cpu;
+    uint32_t modectl, thread_enabled_mask;
+
+    g_assert(bql_locked());
+
+    cpu = env_archcpu(env);
+    if (!cpu->globalregs) {
+        return 0;
+    }
+    modectl =
+        hexagon_globalreg_read(cpu->globalregs, HEX_SREG_MODECTL,
+                               env->threadId);
+    thread_enabled_mask = GET_FIELD(MODECTL_E, modectl);
+    thread_enabled_mask &= ~(0x1 << env->threadId);
+    SET_SYSTEM_FIELD(env, HEX_SREG_MODECTL, MODECTL_E, thread_enabled_mask);
+    return thread_enabled_mask;
+}
+static void do_start_thread(CPUState *cs, run_on_cpu_data tbd)
+{
+    CPUHexagonState *env;
+
+    BQL_LOCK_GUARD();
+
+    env = cpu_env(cs);
+
+    hexagon_cpu_soft_reset(env);
+
+    set_enable_mask(env);
+
+    cs->halted = 0;
+    cs->exception_index = HEX_EVENT_NONE;
+    cpu_resume(cs);
+}
+
+void hexagon_start_threads(CPUHexagonState *current_env, uint32_t mask)
+{
+    CPUState *cs;
+    CPU_FOREACH(cs) {
+        CPUHexagonState *env = cpu_env(cs);
+        if (!(mask & (0x1 << env->threadId))) {
+            continue;
+        }
+
+        if (current_env->threadId != env->threadId) {
+            async_safe_run_on_cpu(cs, do_start_thread, RUN_ON_CPU_NULL);
+        }
+    }
+}
+
+/*
+ * When we have all threads stopped, the return
+ * value to the shell is register 2 from thread 0.
+ */
+static uint32_t get_thread0_r2(void)
+{
+    CPUState *cs;
+    CPU_FOREACH(cs) {
+        CPUHexagonState *thread = cpu_env(cs);
+        if (thread->threadId == 0) {
+            return thread->gpr[2];
+        }
+    }
+    g_assert_not_reached();
+}
+
+void hexagon_stop_thread(CPUHexagonState *env)
+{
+    uint32_t thread_enabled_mask;
+    CPUState *cs;
+
+    BQL_LOCK_GUARD();
+
+    thread_enabled_mask = clear_enable_mask(env);
+    cs = env_cpu(env);
+    cpu_interrupt(cs, CPU_INTERRUPT_HALT);
+    if (!thread_enabled_mask) {
+        /* All threads are stopped, request shutdown */
+        qemu_system_shutdown_request_with_code(
+            SHUTDOWN_CAUSE_GUEST_SHUTDOWN, get_thread0_r2());
+    }
+}
+
+static int sys_in_monitor_mode_ssr(uint32_t ssr)
+{
+    if ((GET_SSR_FIELD(SSR_EX, ssr) != 0) ||
+        ((GET_SSR_FIELD(SSR_EX, ssr) == 0) &&
+         (GET_SSR_FIELD(SSR_UM, ssr) == 0))) {
+        return 1;
+    }
+    return 0;
+}
+
+static int sys_in_guest_mode_ssr(uint32_t ssr)
+{
+    if ((GET_SSR_FIELD(SSR_EX, ssr) == 0) &&
+        (GET_SSR_FIELD(SSR_UM, ssr) != 0) &&
+        (GET_SSR_FIELD(SSR_GM, ssr) != 0)) {
+        return 1;
+    }
+    return 0;
+}
+
+static int sys_in_user_mode_ssr(uint32_t ssr)
+{
+    if ((GET_SSR_FIELD(SSR_EX, ssr) == 0) &&
+        (GET_SSR_FIELD(SSR_UM, ssr) != 0) &&
+        (GET_SSR_FIELD(SSR_GM, ssr) == 0)) {
+        return 1;
+    }
+    return 0;
+}
+
+int get_cpu_mode(CPUHexagonState *env)
+{
+    uint32_t ssr = env->t_sreg[HEX_SREG_SSR];
+
+    if (sys_in_monitor_mode_ssr(ssr)) {
+        return HEX_CPU_MODE_MONITOR;
+    } else if (sys_in_guest_mode_ssr(ssr)) {
+        return HEX_CPU_MODE_GUEST;
+    } else if (sys_in_user_mode_ssr(ssr)) {
+        return HEX_CPU_MODE_USER;
+    }
+    return HEX_CPU_MODE_MONITOR;
+}
diff --git a/target/hexagon/cpu_helper.h b/target/hexagon/cpu_helper.h
new file mode 100644
index 0000000..d176750
--- /dev/null
+++ b/target/hexagon/cpu_helper.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HEXAGON_CPU_HELPER_H
+#define HEXAGON_CPU_HELPER_H
+
+uint32_t hexagon_get_pmu_counter(CPUHexagonState *cur_env, int index);
+void hexagon_modify_ssr(CPUHexagonState *env, uint32_t new, uint32_t old);
+int get_cpu_mode(CPUHexagonState *env);
+int get_exe_mode(CPUHexagonState *env);
+void clear_wait_mode(CPUHexagonState *env);
+void hexagon_ssr_set_cause(CPUHexagonState *env, uint32_t cause);
+void hexagon_start_threads(CPUHexagonState *env, uint32_t mask);
+void hexagon_stop_thread(CPUHexagonState *env);
+void hexagon_resume_threads(CPUHexagonState *env, uint32_t mask);
+uint64_t hexagon_get_sys_pcycle_count(CPUHexagonState *env);
+uint32_t hexagon_get_sys_pcycle_count_high(CPUHexagonState *env);
+uint32_t hexagon_get_sys_pcycle_count_low(CPUHexagonState *env);
+void hexagon_set_sys_pcycle_count_high(CPUHexagonState *env, uint32_t val);
+void hexagon_set_sys_pcycle_count_low(CPUHexagonState *env, uint32_t val);
+void hexagon_set_sys_pcycle_count(CPUHexagonState *env, uint64_t val);
+
+#endif
diff --git a/target/hexagon/decode.c b/target/hexagon/decode.c
index 1595451..6eddcca 100644
--- a/target/hexagon/decode.c
+++ b/target/hexagon/decode.c
@@ -185,6 +185,8 @@
     if ((GET_ATTRIB(opcode, A_JUMP)) ||
         (GET_ATTRIB(opcode, A_CALL)) ||
         (opcode == J2_trap0) ||
+        (opcode == J2_trap1) ||
+        (opcode == J2_rte) ||
         (opcode == J2_pause)) {
         /* Exception to A_JUMP attribute */
         if (opcode == J4_hintjumpr) {
@@ -363,6 +365,18 @@
             break;
         }
     }
+    /*
+     * And at the very very very end, move any RTE's, since they update
+     * user/supervisor mode.
+     */
+#if !defined(CONFIG_USER_ONLY)
+    for (i = 0; i < last_insn; i++) {
+        if (packet->insn[i].opcode == J2_rte) {
+            decode_send_insn_to(packet, i, last_insn);
+            break;
+        }
+    }
+#endif
 }
 
 static void
diff --git a/target/hexagon/gen_analyze_funcs.py b/target/hexagon/gen_analyze_funcs.py
index fdefd5b..44bb22e 100755
--- a/target/hexagon/gen_analyze_funcs.py
+++ b/target/hexagon/gen_analyze_funcs.py
@@ -22,7 +22,6 @@
 import string
 import hex_common
 
-
 ##
 ## Generate the code to analyze the instruction
 ##     For A2_add: Rd32=add(Rs32,Rt32), { RdV=RsV+RtV;}
@@ -42,6 +41,13 @@ def gen_analyze_func(f, tag, regs, imms):
     f.write(f"static void analyze_{tag}(DisasContext *ctx)\n")
     f.write("{\n")
 
+    if hex_common.tag_ignore(tag):
+        f.write("}\n\n")
+        return
+
+    if hex_common.is_sysemu_tag(tag):
+        f.write("#ifndef CONFIG_USER_ONLY\n")
+
     f.write("    Insn *insn G_GNUC_UNUSED = ctx->insn;\n")
     if (hex_common.is_hvx_insn(tag)):
         if hex_common.has_hvx_helper(tag):
@@ -58,7 +64,8 @@ def gen_analyze_func(f, tag, regs, imms):
     for regno, register in enumerate(regs):
         reg_type, reg_id = register
         reg = hex_common.get_register(tag, reg_type, reg_id)
-        reg.decl_reg_num(f, regno)
+        if reg.is_read() or reg.is_written():
+            reg.decl_reg_num(f, regno)
 
     ## Analyze the register reads
     for regno, register in enumerate(regs):
@@ -78,6 +85,9 @@ def gen_analyze_func(f, tag, regs, imms):
 
     f.write("    mark_implicit_writes(ctx);\n")
 
+    if hex_common.is_sysemu_tag(tag):
+        f.write("#endif /* !CONFIG_USER_ONLY */\n")
+
     f.write("}\n\n")
 
 
diff --git a/target/hexagon/gen_helper_funcs.py b/target/hexagon/gen_helper_funcs.py
index 6206104..1629ebc 100755
--- a/target/hexagon/gen_helper_funcs.py
+++ b/target/hexagon/gen_helper_funcs.py
@@ -104,30 +104,24 @@ def main():
     tagimms = hex_common.get_tagimms()
 
     with open(args.out, "w") as f:
-        for tag in hex_common.tags:
-            ## Skip the priv instructions
-            if "A_PRIV" in hex_common.attribdict[tag]:
-                continue
-            ## Skip the guest instructions
-            if "A_GUEST" in hex_common.attribdict[tag]:
-                continue
-            ## Skip the floating point instructions
-            if "A_FPOP" in hex_common.attribdict[tag]:
-                continue
-            ## Skip the diag instructions
-            if tag == "Y6_diag":
-                continue
-            if tag == "Y6_diag0":
-                continue
-            if tag == "Y6_diag1":
+        for tag in hex_common.get_user_tags():
+            if hex_common.tag_ignore(tag):
                 continue
             if hex_common.skip_qemu_helper(tag):
                 continue
             if hex_common.is_idef_parser_enabled(tag):
                 continue
-
             gen_helper_function(f, tag, tagregs, tagimms)
 
+        f.write("#if !defined(CONFIG_USER_ONLY)\n")
+        for tag in hex_common.get_sys_tags():
+            if hex_common.skip_qemu_helper(tag):
+                continue
+            if hex_common.is_idef_parser_enabled(tag):
+                continue
+            gen_helper_function(f, tag, tagregs, tagimms)
+        f.write("#endif\n")
+
 
 if __name__ == "__main__":
     main()
diff --git a/target/hexagon/gen_helper_protos.py b/target/hexagon/gen_helper_protos.py
index 77f8e0a..59c8bdd 100755
--- a/target/hexagon/gen_helper_protos.py
+++ b/target/hexagon/gen_helper_protos.py
@@ -59,19 +59,8 @@ def main():
     tagimms = hex_common.get_tagimms()
 
     with open(args.out, "w") as f:
-        for tag in hex_common.tags:
-            ## Skip the priv instructions
-            if "A_PRIV" in hex_common.attribdict[tag]:
-                continue
-            ## Skip the guest instructions
-            if "A_GUEST" in hex_common.attribdict[tag]:
-                continue
-            ## Skip the diag instructions
-            if tag == "Y6_diag":
-                continue
-            if tag == "Y6_diag0":
-                continue
-            if tag == "Y6_diag1":
+        for tag in hex_common.get_user_tags():
+            if hex_common.tag_ignore(tag):
                 continue
 
             if hex_common.skip_qemu_helper(tag):
@@ -81,6 +70,18 @@ def main():
 
             gen_helper_prototype(f, tag, tagregs, tagimms)
 
+        f.write("#if !defined(CONFIG_USER_ONLY)\n")
+        for tag in hex_common.get_sys_tags():
+            if hex_common.tag_ignore(tag):
+                continue
+            if hex_common.skip_qemu_helper(tag):
+                continue
+            if hex_common.is_idef_parser_enabled(tag):
+                continue
+
+            gen_helper_prototype(f, tag, tagregs, tagimms)
+        f.write("#endif\n")
+
 
 if __name__ == "__main__":
     main()
diff --git a/target/hexagon/gen_idef_parser_funcs.py b/target/hexagon/gen_idef_parser_funcs.py
index 2f6e826..32bce9b 100644
--- a/target/hexagon/gen_idef_parser_funcs.py
+++ b/target/hexagon/gen_idef_parser_funcs.py
@@ -60,6 +60,8 @@ def main():
         f.write('#include "macros.h.inc"\n\n')
 
         for tag in hex_common.tags:
+            if hex_common.tag_ignore(tag):
+                continue
             ## Skip the priv instructions
             if "A_PRIV" in hex_common.attribdict[tag]:
                 continue
diff --git a/target/hexagon/gen_op_attribs.py b/target/hexagon/gen_op_attribs.py
index bbbb02d..94dd1f8 100755
--- a/target/hexagon/gen_op_attribs.py
+++ b/target/hexagon/gen_op_attribs.py
@@ -38,7 +38,7 @@ def main():
     ##     Generate all the attributes associated with each instruction
     ##
     with open(args.out, "w") as f:
-        for tag in hex_common.tags:
+        for tag in hex_common.get_all_tags():
             f.write(
                 f"OP_ATTRIB({tag},ATTRIBS("
                 f'{",".join(sorted(hex_common.attribdict[tag]))}))\n'
diff --git a/target/hexagon/gen_opcodes_def.py b/target/hexagon/gen_opcodes_def.py
index 94a19ff..17ba3f9 100755
--- a/target/hexagon/gen_opcodes_def.py
+++ b/target/hexagon/gen_opcodes_def.py
@@ -37,7 +37,10 @@ def main():
     ##     Generate a list of all the opcodes
     ##
     with open(args.out, "w") as f:
-        for tag in hex_common.tags:
+        for tag in hex_common.get_user_tags():
+            f.write(f"OPCODE({tag}),\n")
+
+        for tag in hex_common.get_sys_tags():
             f.write(f"OPCODE({tag}),\n")
 
 
diff --git a/target/hexagon/gen_tcg.h b/target/hexagon/gen_tcg.h
index 0159e5c..a28af0c 100644
--- a/target/hexagon/gen_tcg.h
+++ b/target/hexagon/gen_tcg.h
@@ -488,6 +488,7 @@
 
 /* dczeroa clears the 32 byte cache line at the address given */
 #define fGEN_TCG_Y2_dczeroa(SHORTCODE) SHORTCODE
+#define fGEN_TCG_Y2_dczeroa_nt(SHORTCODE) SHORTCODE
 
 /* In linux-user mode, these are not modelled, suppress compiler warning */
 #define fGEN_TCG_Y2_dcinva(SHORTCODE) \
@@ -1112,6 +1113,9 @@
                            RdV, tcg_constant_tl(0)); \
     } while (0)
 
+#define fGEN_TCG_Y2_break(SHORTCODE)
+#define fGEN_TCG_J2_unpause(SHORTCODE)
+
 #define fGEN_TCG_J2_pause(SHORTCODE) \
     do { \
         uiV = uiV; \
@@ -1321,6 +1325,11 @@
         RsV = RsV; \
         uiV = uiV; \
     } while (0)
+#define fGEN_TCG_Y2_dcfetchbo_nt(SHORTCODE) \
+    do { \
+        RsV = RsV; \
+        uiV = uiV; \
+    } while (0)
 
 #define fGEN_TCG_L2_loadw_aq(SHORTCODE)                 SHORTCODE
 #define fGEN_TCG_L4_loadd_aq(SHORTCODE)                 SHORTCODE
@@ -1340,13 +1349,6 @@
 #define fGEN_TCG_S2_storew_rl_st_vi(SHORTCODE)          SHORTCODE
 #define fGEN_TCG_S4_stored_rl_st_vi(SHORTCODE)          SHORTCODE
 
-#define fGEN_TCG_J2_trap0(SHORTCODE) \
-    do { \
-        uiV = uiV; \
-        tcg_gen_movi_tl(hex_gpr[HEX_REG_PC], ctx->pkt.pc); \
-        TCGv excp = tcg_constant_tl(HEX_EVENT_TRAP0); \
-        gen_helper_raise_exception(tcg_env, excp); \
-    } while (0)
 #endif
 
 #define fGEN_TCG_A2_nop(SHORTCODE) do { } while (0)
diff --git a/target/hexagon/gen_tcg_funcs.py b/target/hexagon/gen_tcg_funcs.py
index e7f90a0..6d5d99c 100755
--- a/target/hexagon/gen_tcg_funcs.py
+++ b/target/hexagon/gen_tcg_funcs.py
@@ -21,7 +21,7 @@
 import re
 import string
 import hex_common
-
+from textwrap import dedent
 
 ##
 ## Generate the TCG code to call the helper
@@ -49,6 +49,18 @@ def gen_tcg_func(f, tag, regs, imms):
 
     f.write("    Insn *insn G_GNUC_UNUSED = ctx->insn;\n")
 
+    if "A_PRIV" in hex_common.attribdict[tag]:
+        f.write(dedent("""\
+#ifdef CONFIG_USER_ONLY
+    hex_gen_exception_end_tb(ctx, HEX_CAUSE_PRIV_USER_NO_SINSN);
+#else
+"""))
+    if "A_GUEST" in hex_common.attribdict[tag]:
+        f.write(dedent("""\
+#ifdef CONFIG_USER_ONLY
+    hex_gen_exception_end_tb(ctx, HEX_CAUSE_PRIV_USER_NO_GINSN);
+#else
+"""))
     if hex_common.need_ea(tag):
         f.write("    TCGv EA G_GNUC_UNUSED = tcg_temp_new();\n")
 
@@ -100,6 +112,11 @@ def gen_tcg_func(f, tag, regs, imms):
         if reg.is_written():
             reg.gen_write(f, tag)
 
+    if (
+        "A_PRIV" in hex_common.attribdict[tag]
+        or "A_GUEST" in hex_common.attribdict[tag]
+    ):
+        f.write("#endif   /* CONFIG_USER_ONLY */\n")
     f.write("}\n\n")
 
 
@@ -124,18 +141,10 @@ def main():
             f.write('#include "idef-generated-emitter.h.inc"\n\n')
 
         for tag in hex_common.tags:
-            ## Skip the priv instructions
-            if "A_PRIV" in hex_common.attribdict[tag]:
-                continue
-            ## Skip the guest instructions
-            if "A_GUEST" in hex_common.attribdict[tag]:
-                continue
-            ## Skip the diag instructions
-            if tag == "Y6_diag":
-                continue
-            if tag == "Y6_diag0":
-                continue
-            if tag == "Y6_diag1":
+            if hex_common.tag_ignore(tag):
+                f.write(f"static void generate_{tag}"
+                        f"(DisasContext *ctx)\n")
+                f.write("{\n}\n\n")
                 continue
 
             gen_def_tcg_func(f, tag, tagregs, tagimms)
diff --git a/target/hexagon/gen_tcg_sys.h b/target/hexagon/gen_tcg_sys.h
new file mode 100644
index 0000000..af50322
--- /dev/null
+++ b/target/hexagon/gen_tcg_sys.h
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HEXAGON_GEN_TCG_SYS_H
+#define HEXAGON_GEN_TCG_SYS_H
+
+/* System mode instructions */
+#define fGEN_TCG_Y2_swi(SHORTCODE) \
+    gen_helper_swi(tcg_env, RsV)
+
+#define fGEN_TCG_Y2_cswi(SHORTCODE) \
+    gen_helper_cswi(tcg_env, RsV)
+
+#define fGEN_TCG_Y2_ciad(SHORTCODE) \
+    gen_helper_ciad(tcg_env, RsV)
+
+#define fGEN_TCG_Y4_siad(SHORTCODE) \
+    gen_helper_siad(tcg_env, RsV)
+
+#define fGEN_TCG_Y2_iassignw(SHORTCODE) \
+    gen_helper_iassignw(tcg_env, RsV)
+
+#define fGEN_TCG_Y2_iassignr(SHORTCODE) \
+    gen_helper_iassignr(RdV, tcg_env, RsV)
+
+#define fGEN_TCG_Y2_getimask(SHORTCODE) \
+    gen_helper_getimask(RdV, tcg_env, RsV)
+
+#define fGEN_TCG_Y2_setimask(SHORTCODE) \
+    gen_helper_setimask(tcg_env, PtV, RsV)
+
+#define fGEN_TCG_Y2_setprio(SHORTCODE) \
+    gen_helper_setprio(tcg_env, PtV, RsV)
+
+#define fGEN_TCG_Y2_crswap0(SHORTCODE) \
+    do { \
+        TCGv_i32 tmp = tcg_temp_new_i32(); \
+        tcg_gen_mov_tl(tmp, RxV); \
+        tcg_gen_mov_tl(RxV, hex_t_sreg[HEX_SREG_SGP0]); \
+        if (ctx->need_commit) { \
+            tcg_gen_mov_tl(ctx->t_sreg_new_value[HEX_SREG_SGP0], tmp); \
+        } else { \
+            tcg_gen_mov_tl(hex_t_sreg[HEX_SREG_SGP0], tmp); \
+        } \
+    } while (0)
+
+#define fGEN_TCG_Y4_crswap1(SHORTCODE) \
+    do { \
+        TCGv_i32 tmp = tcg_temp_new_i32(); \
+        tcg_gen_mov_tl(tmp, RxV); \
+        tcg_gen_mov_tl(RxV, hex_t_sreg[HEX_SREG_SGP1]); \
+        if (ctx->need_commit) { \
+            tcg_gen_mov_tl(ctx->t_sreg_new_value[HEX_SREG_SGP1], tmp); \
+        } else { \
+            tcg_gen_mov_tl(hex_t_sreg[HEX_SREG_SGP1], tmp); \
+        } \
+    } while (0)
+
+#define fGEN_TCG_Y4_crswap10(SHORTCODE) \
+    do { \
+        TCGv_i64 tmp = tcg_temp_new_i64(); \
+        tcg_gen_mov_i64(tmp, RxxV); \
+        tcg_gen_concat_i32_i64(RxxV, \
+                               hex_t_sreg[HEX_SREG_SGP0], \
+                               hex_t_sreg[HEX_SREG_SGP1]); \
+        if (ctx->need_commit) { \
+            tcg_gen_extrl_i64_i32( \
+                ctx->t_sreg_new_value[HEX_SREG_SGP0], tmp); \
+            tcg_gen_extrh_i64_i32( \
+                ctx->t_sreg_new_value[HEX_SREG_SGP1], tmp); \
+        } else { \
+            tcg_gen_extrl_i64_i32(hex_t_sreg[HEX_SREG_SGP0], tmp); \
+            tcg_gen_extrh_i64_i32(hex_t_sreg[HEX_SREG_SGP1], tmp); \
+        } \
+    } while (0)
+
+#define fGEN_TCG_Y2_wait(SHORTCODE) \
+    do { \
+        RsV = RsV; \
+        gen_helper_wait(tcg_env, tcg_constant_tl(ctx->pkt.pc)); \
+    } while (0)
+
+#define fGEN_TCG_Y2_resume(SHORTCODE) \
+    gen_helper_resume(tcg_env, RsV)
+
+#define fGEN_TCG_Y2_start(SHORTCODE) \
+    gen_helper_start(tcg_env, RsV)
+
+#define fGEN_TCG_Y2_stop(SHORTCODE) \
+    do { \
+        RsV = RsV; \
+        gen_helper_stop(tcg_env); \
+    } while (0)
+
+#define fGEN_TCG_Y2_tfrscrr(SHORTCODE) \
+    tcg_gen_mov_tl(RdV, SsV)
+
+#define fGEN_TCG_Y2_tfrsrcr(SHORTCODE) \
+    tcg_gen_mov_tl(SdV, RsV)
+
+#define fGEN_TCG_Y4_tfrscpp(SHORTCODE) \
+    tcg_gen_mov_i64(RddV, SssV)
+
+#define fGEN_TCG_Y4_tfrspcp(SHORTCODE) \
+    tcg_gen_mov_i64(SddV, RssV)
+
+#define fGEN_TCG_G4_tfrgcrr(SHORTCODE) \
+    tcg_gen_mov_tl(RdV, GsV)
+
+#define fGEN_TCG_G4_tfrgrcr(SHORTCODE) \
+    tcg_gen_mov_tl(GdV, RsV)
+
+#define fGEN_TCG_G4_tfrgcpp(SHORTCODE) \
+    tcg_gen_mov_i64(RddV, GssV)
+
+#define fGEN_TCG_G4_tfrgpcp(SHORTCODE) \
+    tcg_gen_mov_i64(GddV, RssV)
+
+
+/*
+ * rte (return from exception)
+ *     Clear the EX bit in SSR
+ *     Jump to ELR
+ */
+#define fGEN_TCG_J2_rte(SHORTCODE) \
+    do { \
+        TCGv_i32 new_ssr = tcg_temp_new_i32(); \
+        tcg_gen_deposit_tl(new_ssr, hex_t_sreg[HEX_SREG_SSR], \
+                           tcg_constant_tl(0), \
+                           reg_field_info[SSR_EX].offset, \
+                           reg_field_info[SSR_EX].width); \
+        gen_log_sreg_write(ctx, HEX_SREG_SSR, new_ssr); \
+        gen_jumpr(ctx, hex_t_sreg[HEX_SREG_ELR]); \
+    } while (0)
+
+#endif
diff --git a/target/hexagon/genptr.c b/target/hexagon/genptr.c
index 5d5adac..1ebc747 100644
--- a/target/hexagon/genptr.c
+++ b/target/hexagon/genptr.c
@@ -23,6 +23,7 @@
 #include "exec/helper-gen.h"
 #include "insn.h"
 #include "opcodes.h"
+#include "sys_macros.h"
 #include "translate.h"
 #define QEMU_GENERATE       /* Used internally by macros.h */
 #include "macros.h"
@@ -30,6 +31,10 @@
 #undef QEMU_GENERATE
 #include "gen_tcg.h"
 #include "gen_tcg_hvx.h"
+#ifndef CONFIG_USER_ONLY
+#include "gen_tcg_sys.h"
+#endif
+
 #include "genptr.h"
 
 TCGv gen_read_reg(TCGv result, int num)
@@ -120,6 +125,156 @@
     }
 }
 
+#ifndef CONFIG_USER_ONLY
+G_GNUC_UNUSED
+static bool greg_writable(int rnum, bool pair)
+{
+    if (pair) {
+        if (rnum < HEX_GREG_G3) {
+            return true;
+        }
+        qemu_log_mask(LOG_UNIMP,
+                "Warning: ignoring write to guest register pair G%d:%d\n",
+                rnum + 1, rnum);
+    } else {
+        if (rnum <= HEX_GREG_G3) {
+            return true;
+        }
+        qemu_log_mask(LOG_UNIMP,
+                "Warning: ignoring write to guest register G%d\n", rnum);
+    }
+    return false;
+}
+
+G_GNUC_UNUSED
+static void check_greg_impl(int rnum, bool pair)
+{
+    if (pair && (!greg_implemented(rnum) || !greg_implemented(rnum + 1))) {
+        qemu_log_mask(LOG_UNIMP,
+                "Warning: guest register pair G%d:%d is unimplemented or "
+                "reserved. Read will yield 0.\n",
+                rnum + 1, rnum);
+    } else if (!pair && !greg_implemented(rnum)) {
+        qemu_log_mask(LOG_UNIMP,
+                "Warning: guest register G%d is unimplemented or reserved."
+                " Read will yield 0.\n", rnum);
+    }
+}
+
+G_GNUC_UNUSED
+static inline void gen_log_greg_write(DisasContext *ctx, int rnum, TCGv_i32 val)
+{
+    tcg_gen_mov_i32(ctx->greg_new_value[rnum], val);
+}
+
+G_GNUC_UNUSED
+static void gen_log_greg_write_pair(DisasContext *ctx, int rnum, TCGv_i64 val)
+{
+    TCGv_i32 val32 = tcg_temp_new_i32();
+
+    /* Low word */
+    tcg_gen_extrl_i64_i32(val32, val);
+    gen_log_greg_write(ctx, rnum, val32);
+
+    /* High word */
+    tcg_gen_extrh_i64_i32(val32, val);
+    gen_log_greg_write(ctx, rnum + 1, val32);
+}
+
+static const uint32_t sreg_immut_masks[NUM_SREGS] = {
+    [HEX_SREG_STID] = 0xff00ff00,
+    [HEX_SREG_ELR] = 0x00000003,
+    [HEX_SREG_SSR] = 0x00008000,
+    [HEX_SREG_CCR] = 0x10e0ff24,
+    [HEX_SREG_HTID] = IMMUTABLE,
+    [HEX_SREG_IMASK] = 0xffff0000,
+    [HEX_SREG_GEVB] = 0x000000ff,
+};
+
+G_GNUC_UNUSED
+static void gen_log_sreg_write(DisasContext *ctx, int rnum, TCGv_i32 val)
+{
+    const uint32_t reg_mask = sreg_immut_masks[rnum];
+
+    if (reg_mask != IMMUTABLE) {
+        if (rnum < HEX_SREG_GLB_START) {
+            gen_masked_reg_write(val, hex_t_sreg[rnum], reg_mask);
+            if (ctx->need_commit || rnum == HEX_SREG_SSR) {
+                tcg_gen_mov_i32(ctx->t_sreg_new_value[rnum], val);
+            } else {
+                tcg_gen_mov_i32(hex_t_sreg[rnum], val);
+            }
+        } else {
+            gen_helper_sreg_write_masked(tcg_env, tcg_constant_i32(rnum), val);
+        }
+    }
+}
+
+G_GNUC_UNUSED
+static void gen_log_sreg_write_pair(DisasContext *ctx, int rnum, TCGv_i64 val)
+{
+    TCGv_i32 val32 = tcg_temp_new_i32();
+
+    /* Low word */
+    tcg_gen_extrl_i64_i32(val32, val);
+    gen_log_sreg_write(ctx, rnum, val32);
+
+    /* High word */
+    tcg_gen_extrh_i64_i32(val32, val);
+    gen_log_sreg_write(ctx, rnum + 1, val32);
+}
+
+G_GNUC_UNUSED
+static void gen_read_sreg(TCGv_i32 dst, int reg_num)
+{
+    if (reg_num >= HEX_SREG_GLB_START || reg_num == HEX_SREG_BADVA) {
+        gen_helper_sreg_read(dst, tcg_env, tcg_constant_i32(reg_num));
+    } else {
+        tcg_gen_mov_i32(dst, hex_t_sreg[reg_num]);
+    }
+}
+
+G_GNUC_UNUSED
+static void gen_read_sreg_pair(TCGv_i64 dst, int reg_num)
+{
+    if (reg_num < HEX_SREG_GLB_START) {
+        if (reg_num + 1 == HEX_SREG_BADVA) {
+            TCGv_i32 badva = tcg_temp_new_i32();
+            gen_helper_sreg_read(badva, tcg_env,
+                                 tcg_constant_i32(HEX_SREG_BADVA));
+            tcg_gen_concat_i32_i64(dst, hex_t_sreg[reg_num], badva);
+        } else {
+            tcg_gen_concat_i32_i64(dst, hex_t_sreg[reg_num],
+                                        hex_t_sreg[reg_num + 1]);
+        }
+    } else {
+        gen_helper_sreg_read_pair(dst, tcg_env, tcg_constant_i32(reg_num));
+    }
+}
+
+G_GNUC_UNUSED
+static void gen_read_greg(TCGv_i32 dst, int reg_num)
+{
+    if (reg_num <= HEX_GREG_G3) {
+        tcg_gen_mov_i32(dst, hex_greg[reg_num]);
+    } else {
+        gen_helper_greg_read(dst, tcg_env, tcg_constant_i32(reg_num));
+    }
+}
+
+G_GNUC_UNUSED
+static void gen_read_greg_pair(TCGv_i64 dst, int reg_num)
+{
+    if (reg_num == HEX_GREG_G0 || reg_num == HEX_GREG_G2) {
+        tcg_gen_concat_i32_i64(dst, hex_greg[reg_num],
+                                    hex_greg[reg_num + 1]);
+    } else {
+        gen_helper_greg_read_pair(dst, tcg_env, tcg_constant_i32(reg_num));
+    }
+}
+#endif
+
+
 void gen_pred_write(DisasContext *ctx, int pnum, TCGv val)
 {
     TCGv pred = get_result_pred(ctx, pnum);
@@ -738,26 +893,30 @@
     tcg_gen_qemu_ld_i64(frame, EA, ctx->mem_idx, MO_LE | MO_UQ);
 }
 
-#ifndef CONFIG_HEXAGON_IDEF_PARSER
 /* Stack overflow check */
-static void gen_framecheck(TCGv EA, int framesize)
+void gen_framecheck(DisasContext *ctx, TCGv_i32 addr, TCGv_i32 ea)
 {
-    /* Not modelled in linux-user mode */
-    /* Placeholder for system mode */
 #ifndef CONFIG_USER_ONLY
-    g_assert_not_reached();
+    TCGLabel *ok = gen_new_label();
+    tcg_gen_brcond_i32(TCG_COND_GEU, addr, hex_gpr[HEX_REG_FRAMELIMIT], ok);
+    gen_helper_raise_stack_overflow(tcg_env,
+                                   tcg_constant_i32(ctx->insn->slot), ea);
+    gen_set_label(ok);
 #endif
 }
 
+#ifndef CONFIG_HEXAGON_IDEF_PARSER
 static void gen_allocframe(DisasContext *ctx, TCGv r29, int framesize)
 {
     TCGv r30 = get_result_gpr(ctx, HEX_REG_FP);
+    TCGv_i32 new_r29 = tcg_temp_new_i32();
     TCGv_i64 frame;
     tcg_gen_addi_tl(r30, r29, -8);
     frame = gen_frame_scramble();
     gen_store8(tcg_env, r30, frame, ctx->insn->slot);
-    gen_framecheck(r30, framesize);
-    tcg_gen_subi_tl(r29, r30, framesize);
+    tcg_gen_subi_tl(new_r29, r30, framesize);
+    gen_framecheck(ctx, new_r29, hex_gpr[HEX_REG_PC]);
+    tcg_gen_mov_tl(r29, new_r29);
 }
 
 static void gen_deallocframe(DisasContext *ctx, TCGv_i64 r31_30, TCGv r30)
diff --git a/target/hexagon/helper.h b/target/hexagon/helper.h
index 6445682..033e561 100644
--- a/target/hexagon/helper.h
+++ b/target/hexagon/helper.h
@@ -18,7 +18,7 @@
 #include "internal.h"
 #include "helper_protos_generated.h.inc"
 
-DEF_HELPER_FLAGS_2(raise_exception, TCG_CALL_NO_RETURN, noreturn, env, i32)
+DEF_HELPER_FLAGS_3(raise_exception, TCG_CALL_NO_RETURN, noreturn, env, i32, i32)
 DEF_HELPER_2(commit_store, void, env, int)
 DEF_HELPER_3(gather_store, void, env, i32, int)
 DEF_HELPER_1(commit_hvx_stores, void, env)
@@ -107,3 +107,28 @@
 DEF_HELPER_2(probe_pkt_scalar_store_s0, void, env, int)
 DEF_HELPER_2(probe_hvx_stores, void, env, int)
 DEF_HELPER_2(probe_pkt_scalar_hvx_stores, void, env, int)
+
+#if !defined(CONFIG_USER_ONLY)
+DEF_HELPER_3(raise_stack_overflow, void, env, i32, i32)
+DEF_HELPER_2(swi, void, env, i32)
+DEF_HELPER_2(cswi, void, env, i32)
+DEF_HELPER_2(ciad, void, env, i32)
+DEF_HELPER_2(siad, void, env, i32)
+DEF_HELPER_2(iassignw, void, env, i32)
+DEF_HELPER_2(iassignr, i32, env, i32)
+DEF_HELPER_2(getimask, i32, env, i32)
+DEF_HELPER_3(setimask, void, env, i32, i32)
+DEF_HELPER_2(sreg_read, i32, env, i32)
+DEF_HELPER_2(sreg_read_pair, i64, env, i32)
+DEF_HELPER_2(greg_read, i32, env, i32)
+DEF_HELPER_2(greg_read_pair, i64, env, i32)
+DEF_HELPER_3(sreg_write_masked, void, env, i32, i32)
+DEF_HELPER_3(setprio, void, env, i32, i32)
+DEF_HELPER_2(start, void, env, i32)
+DEF_HELPER_1(stop, void, env)
+DEF_HELPER_2(wait, void, env, i32)
+DEF_HELPER_2(resume, void, env, i32)
+DEF_HELPER_1(resched, void, env)
+DEF_HELPER_3(modify_ssr, void, env, i32, i32)
+DEF_HELPER_1(pending_interrupt, void, env)
+#endif
diff --git a/target/hexagon/hex_common.py b/target/hexagon/hex_common.py
index e37d5a5..d91a653 100755
--- a/target/hexagon/hex_common.py
+++ b/target/hexagon/hex_common.py
@@ -33,6 +33,41 @@
 overrides = {}  # tags with helper overrides
 idef_parser_enabled = {}  # tags enabled for idef-parser
 
+
+def is_sysemu_tag(tag):
+    return bool(attribdict[tag] & {"A_PRIV", "A_GUEST"})
+
+
+def tag_ignore(tag):
+    tag_skips = (
+        "Y6_diag",
+        "Y6_diag0",
+        "Y6_diag1",
+    )
+    attr_skips = {
+        "A_FAKEINSN",
+        "A_MAPPING",
+        "A_CONDMAPPING",
+    }
+    return tag in tag_skips or attribdict[tag] & attr_skips
+
+
+def get_sys_tags():
+    return sorted(
+        tag for tag in frozenset(tags) if is_sysemu_tag(tag)
+    )
+
+
+def get_user_tags():
+    return sorted(
+        tag for tag in frozenset(tags) if not is_sysemu_tag(tag)
+    )
+
+
+def get_all_tags():
+    return get_user_tags() + get_sys_tags()
+
+
 # We should do this as a hash for performance,
 # but to keep order let's keep it as a list.
 def uniquify(seq):
@@ -93,8 +128,11 @@ def calculate_attribs():
     add_qemu_macro_attrib("fTRAP", "A_IMPLICIT_READS_PC")
     add_qemu_macro_attrib("fSET_OVERFLOW", "A_IMPLICIT_WRITES_USR")
     add_qemu_macro_attrib("fSET_LPCFG", "A_IMPLICIT_WRITES_USR")
+    add_qemu_macro_attrib("fCLEAR_RTE_EX", "A_IMPLICIT_WRITES_SSR")
     add_qemu_macro_attrib("fLOAD", "A_SCALAR_LOAD")
     add_qemu_macro_attrib("fSTORE", "A_SCALAR_STORE")
+    add_qemu_macro_attrib("fSET_K0_LOCK", "A_IMPLICIT_READS_PC")
+    add_qemu_macro_attrib("fSET_TLB_LOCK", "A_IMPLICIT_READS_PC")
     add_qemu_macro_attrib('fLSBNEW0', 'A_IMPLICIT_READS_P0')
     add_qemu_macro_attrib('fLSBNEW0NOT', 'A_IMPLICIT_READS_P0')
     add_qemu_macro_attrib('fREAD_P0', 'A_IMPLICIT_READS_P0')
@@ -215,7 +253,9 @@ 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_IMPLICIT_WRITES_USR" in attribdict[tag])
+            "A_IMPLICIT_WRITES_USR" in attribdict[tag] or
+            "A_PRIV" in attribdict[tag] or
+            "J2_trap" in tag)
 
 
 def need_slot(tag):
@@ -224,6 +264,9 @@ def need_slot(tag):
         and "A_CVI_GATHER" not in attribdict[tag]
         and ("A_STORE" in attribdict[tag]
              or "A_LOAD" in attribdict[tag])
+        and tag != "L4_loadw_phys"
+        and tag != "L6_memcpy"
+        and tag != "Y6_dmlink"
     ):
         return 1
     else:
@@ -247,7 +290,9 @@ def need_next_PC(tag):
 
 
 def need_pkt_has_multi_cof(tag):
-    return "A_COF" in attribdict[tag]
+    if attribdict[tag] & {"A_JUMP", "A_CALL"}:
+        return tag != "J4_hintjumpr"
+    return False
 
 
 def need_pkt_need_commit(tag):
@@ -367,12 +412,16 @@ def helper_proto_type(self):
         return "s32"
     def helper_arg_type(self):
         return "int32_t"
+    def is_pair(self):
+        return False
 
 class Pair(Scalar):
     def helper_proto_type(self):
         return "s64"
     def helper_arg_type(self):
         return "int64_t"
+    def is_pair(self):
+        return True
 
 class Hvx:
     def is_scalar_reg(self):
@@ -1008,6 +1057,125 @@ def analyze_write(self, f, tag, regno):
             ctx_log_qreg_write(ctx, {self.reg_num}, insn_has_hvx_helper);
         """))
 
+class GuestRegister(Register):
+    pass
+
+class GuestDest(GuestRegister, Single, Dest):
+    def decl_tcg(self, f, tag, regno):
+        self.decl_reg_num(f, regno)
+        f.write(code_fmt(f"""\
+            TCGv_i32 {self.reg_tcg()} = tcg_temp_new_i32();
+        """))
+    def gen_write(self, f, tag):
+        f.write(code_fmt(f"""\
+            gen_log_greg_write(ctx, {self.reg_num}, {self.reg_tcg()});
+        """))
+    def analyze_write(self, f, tag, regno):
+        f.write(code_fmt(f"""\
+            ctx_log_greg_write(ctx, {self.reg_num});
+        """))
+
+class GuestSource(GuestRegister, Single, OldSource):
+    def decl_reg_num(self, f, regno):
+        f.write(code_fmt(f"""\
+            const int {self.reg_num} G_GNUC_UNUSED = insn->regno[{regno}];
+        """))
+    def decl_tcg(self, f, tag, regno):
+        self.decl_reg_num(f, regno)
+        f.write(code_fmt(f"""\
+            TCGv_i32 {self.reg_tcg()} = tcg_temp_new_i32();
+            gen_read_greg({self.reg_tcg()}, {self.reg_num});
+        """))
+    def analyze_read(self, f, regno):
+        pass
+
+class GuestPairDest(GuestRegister, Pair, Dest):
+    def decl_tcg(self, f, tag, regno):
+        self.decl_reg_num(f, regno)
+        f.write(code_fmt(f"""\
+            TCGv_i64 {self.reg_tcg()} = tcg_temp_new_i64();
+        """))
+    def gen_write(self, f, tag):
+        f.write(code_fmt(f"""\
+            gen_log_greg_write_pair(ctx, {self.reg_num}, {self.reg_tcg()});
+        """))
+    def analyze_write(self, f, tag, regno):
+        f.write(code_fmt(f"""\
+            ctx_log_greg_write_pair(ctx, {self.reg_num});
+        """))
+
+class GuestPairSource(GuestRegister, Pair, OldSource):
+    def decl_reg_num(self, f, regno):
+        f.write(code_fmt(f"""\
+            const int {self.reg_num} G_GNUC_UNUSED = insn->regno[{regno}];
+        """))
+    def decl_tcg(self, f, tag, regno):
+        self.decl_reg_num(f, regno)
+        f.write(code_fmt(f"""\
+            TCGv_i64 {self.reg_tcg()} = tcg_temp_new_i64();
+            gen_read_greg_pair({self.reg_tcg()}, {self.reg_num});
+        """))
+    def analyze_read(self, f, regno):
+        pass
+
+class SystemDest(Register, Single, Dest):
+    def decl_tcg(self, f, tag, regno):
+        self.decl_reg_num(f, regno)
+        f.write(code_fmt(f"""\
+            TCGv_i32 {self.reg_tcg()} = tcg_temp_new_i32();
+        """))
+    def gen_write(self, f, tag):
+        f.write(code_fmt(f"""\
+            gen_log_sreg_write(ctx, {self.reg_num}, {self.reg_tcg()});
+        """))
+    def analyze_write(self, f, tag, regno):
+        f.write(code_fmt(f"""\
+            ctx_log_sreg_write(ctx, {self.reg_num});
+        """))
+
+class SystemSource(Register, Single, OldSource):
+    def decl_reg_num(self, f, regno):
+        f.write(code_fmt(f"""\
+            const int {self.reg_num} G_GNUC_UNUSED = insn->regno[{regno}];
+        """))
+    def decl_tcg(self, f, tag, regno):
+        self.decl_reg_num(f, regno)
+        f.write(code_fmt(f"""\
+            TCGv_i32 {self.reg_tcg()} = tcg_temp_new_i32();
+            gen_read_sreg({self.reg_tcg()}, {self.reg_num});
+        """))
+    def analyze_read(self, f, regno):
+        pass
+
+class SystemPairDest(Register, Pair, Dest):
+    def decl_tcg(self, f, tag, regno):
+        self.decl_reg_num(f, regno)
+        f.write(code_fmt(f"""\
+            TCGv_i64 {self.reg_tcg()} = tcg_temp_new_i64();
+        """))
+    def gen_write(self, f, tag):
+        f.write(code_fmt(f"""\
+            gen_log_sreg_write_pair(ctx, {self.reg_num}, {self.reg_tcg()});
+        """))
+    def analyze_write(self, f, tag, regno):
+        f.write(code_fmt(f"""\
+            ctx_log_sreg_write_pair(ctx, {self.reg_num});
+        """))
+
+class SystemPairSource(Register, Pair, OldSource):
+    def decl_reg_num(self, f, regno):
+        f.write(code_fmt(f"""\
+            const int {self.reg_num} G_GNUC_UNUSED = insn->regno[{regno}];
+        """))
+    def decl_tcg(self, f, tag, regno):
+        self.decl_reg_num(f, regno)
+        f.write(code_fmt(f"""\
+            TCGv_i64 {self.reg_tcg()} = tcg_temp_new_i64();
+            gen_read_sreg_pair({self.reg_tcg()}, {self.reg_num});
+        """))
+    def analyze_read(self, f, regno):
+        pass
+
 def init_registers():
     regs = {
         GprDest("R", "d"),
@@ -1054,6 +1222,16 @@ def init_registers():
         QRegSource("Q", "u"),
         QRegSource("Q", "v"),
         QRegReadWrite("Q", "x"),
+
+        # system regs
+        GuestDest("G", "d"),
+        GuestSource("G", "s"),
+        GuestPairDest("G", "dd"),
+        GuestPairSource("G", "ss"),
+        SystemDest("S", "d"),
+        SystemSource("S", "s"),
+        SystemPairDest("S", "dd"),
+        SystemPairSource("S", "ss"),
     }
     for reg in regs:
         registers[f"{reg.regtype}{reg.regid}"] = reg
@@ -1197,6 +1375,7 @@ def parse_common_args(desc):
     parser.add_argument("semantics", help="semantics file")
     parser.add_argument("overrides", help="overrides file")
     parser.add_argument("overrides_vec", help="vector overrides file")
+    parser.add_argument("overrides_sys", help="system overrides file")
     parser.add_argument("out", help="output file")
     parser.add_argument("--idef-parser",
                         help="file of instructions translated by idef-parser")
@@ -1204,6 +1383,7 @@ def parse_common_args(desc):
     read_semantics_file(args.semantics)
     read_overrides_file(args.overrides)
     read_overrides_file(args.overrides_vec)
+    read_overrides_file(args.overrides_sys)
     if args.idef_parser:
         read_idef_parser_enabled_file(args.idef_parser)
     calculate_attribs()
diff --git a/target/hexagon/hex_interrupts.c b/target/hexagon/hex_interrupts.c
new file mode 100644
index 0000000..3534481
--- /dev/null
+++ b/target/hexagon/hex_interrupts.c
@@ -0,0 +1,371 @@
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/main-loop.h"
+#include "cpu.h"
+#include "cpu_helper.h"
+#include "exec/cpu-interrupt.h"
+#include "hex_interrupts.h"
+#include "macros.h"
+#include "sys_macros.h"
+#include "system/cpus.h"
+#include "hw/hexagon/hexagon_globalreg.h"
+
+static bool hex_is_qualified_for_int(CPUHexagonState *env, int int_num);
+
+static bool get_syscfg_gie(CPUHexagonState *env)
+{
+    HexagonCPU *cpu = env_archcpu(env);
+    uint32_t syscfg =
+        hexagon_globalreg_read(cpu->globalregs, HEX_SREG_SYSCFG,
+                               env->threadId);
+    return GET_SYSCFG_FIELD(SYSCFG_GIE, syscfg);
+}
+
+static bool get_ssr_ex(CPUHexagonState *env)
+{
+    uint32_t ssr = env->t_sreg[HEX_SREG_SSR];
+    return GET_SSR_FIELD(SSR_EX, ssr);
+}
+
+static bool get_ssr_ie(CPUHexagonState *env)
+{
+    uint32_t ssr = env->t_sreg[HEX_SREG_SSR];
+    return GET_SSR_FIELD(SSR_IE, ssr);
+}
+
+/* Do these together so we only have to call hexagon_modify_ssr once */
+static void set_ssr_ex_cause(CPUHexagonState *env, int ex, uint32_t cause)
+{
+    uint32_t old, new;
+
+    old = env->t_sreg[HEX_SREG_SSR];
+    SET_SYSTEM_FIELD(env, HEX_SREG_SSR, SSR_EX, ex);
+    SET_SYSTEM_FIELD(env, HEX_SREG_SSR, SSR_CAUSE, cause);
+    new = env->t_sreg[HEX_SREG_SSR];
+    hexagon_modify_ssr(env, new, old);
+}
+
+static bool get_iad_bit(CPUHexagonState *env, int int_num)
+{
+    HexagonCPU *cpu = env_archcpu(env);
+    uint32_t ipendad =
+        hexagon_globalreg_read(cpu->globalregs, HEX_SREG_IPENDAD,
+                               env->threadId);
+    uint32_t iad = GET_FIELD(IPENDAD_IAD, ipendad);
+    return extract32(iad, int_num, 1);
+}
+
+static void set_iad_bit(CPUHexagonState *env, int int_num, int val)
+{
+    HexagonCPU *cpu = env_archcpu(env);
+    uint32_t ipendad =
+        hexagon_globalreg_read(cpu->globalregs, HEX_SREG_IPENDAD,
+                               env->threadId);
+    uint32_t iad = GET_FIELD(IPENDAD_IAD, ipendad);
+    iad = deposit32(iad, int_num, 1, val);
+    fSET_FIELD(ipendad, IPENDAD_IAD, iad);
+    hexagon_globalreg_write(cpu->globalregs, HEX_SREG_IPENDAD,
+                            ipendad, env->threadId);
+}
+
+static uint32_t get_ipend(CPUHexagonState *env)
+{
+    HexagonCPU *cpu = env_archcpu(env);
+    uint32_t ipendad =
+        hexagon_globalreg_read(cpu->globalregs, HEX_SREG_IPENDAD,
+                               env->threadId);
+    return GET_FIELD(IPENDAD_IPEND, ipendad);
+}
+
+static inline bool get_ipend_bit(CPUHexagonState *env, int int_num)
+{
+    HexagonCPU *cpu = env_archcpu(env);
+    uint32_t ipendad =
+        hexagon_globalreg_read(cpu->globalregs, HEX_SREG_IPENDAD,
+                               env->threadId);
+    uint32_t ipend = GET_FIELD(IPENDAD_IPEND, ipendad);
+    return extract32(ipend, int_num, 1);
+}
+
+static void clear_ipend(CPUHexagonState *env, uint32_t mask)
+{
+    HexagonCPU *cpu = env_archcpu(env);
+    uint32_t ipendad =
+        hexagon_globalreg_read(cpu->globalregs, HEX_SREG_IPENDAD,
+                               env->threadId);
+    uint32_t ipend = GET_FIELD(IPENDAD_IPEND, ipendad);
+    ipend &= ~mask;
+    fSET_FIELD(ipendad, IPENDAD_IPEND, ipend);
+    hexagon_globalreg_write(cpu->globalregs, HEX_SREG_IPENDAD,
+                            ipendad, env->threadId);
+}
+
+static void set_ipend(CPUHexagonState *env, uint32_t mask)
+{
+    HexagonCPU *cpu = env_archcpu(env);
+    uint32_t ipendad =
+        hexagon_globalreg_read(cpu->globalregs, HEX_SREG_IPENDAD,
+                               env->threadId);
+    uint32_t ipend = GET_FIELD(IPENDAD_IPEND, ipendad);
+    ipend |= mask;
+    fSET_FIELD(ipendad, IPENDAD_IPEND, ipend);
+    hexagon_globalreg_write(cpu->globalregs, HEX_SREG_IPENDAD,
+                            ipendad, env->threadId);
+}
+
+static void set_ipend_bit(CPUHexagonState *env, int int_num, int val)
+{
+    HexagonCPU *cpu = env_archcpu(env);
+    uint32_t ipendad =
+        hexagon_globalreg_read(cpu->globalregs, HEX_SREG_IPENDAD,
+                               env->threadId);
+    uint32_t ipend = GET_FIELD(IPENDAD_IPEND, ipendad);
+    ipend = deposit32(ipend, int_num, 1, val);
+    fSET_FIELD(ipendad, IPENDAD_IPEND, ipend);
+    hexagon_globalreg_write(cpu->globalregs, HEX_SREG_IPENDAD,
+                            ipendad, env->threadId);
+}
+
+static bool get_imask_bit(CPUHexagonState *env, int int_num)
+{
+    uint32_t imask = env->t_sreg[HEX_SREG_IMASK];
+    return extract32(imask, int_num, 1);
+}
+
+static uint32_t get_prio(CPUHexagonState *env)
+{
+    uint32_t stid = env->t_sreg[HEX_SREG_STID];
+    return extract32(stid, reg_field_info[STID_PRIO].offset,
+                     reg_field_info[STID_PRIO].width);
+}
+
+static void set_elr(CPUHexagonState *env, uint32_t val)
+{
+    env->t_sreg[HEX_SREG_ELR] = val;
+}
+
+static bool get_schedcfgen(CPUHexagonState *env)
+{
+    HexagonCPU *cpu = env_archcpu(env);
+    uint32_t schedcfg =
+        hexagon_globalreg_read(cpu->globalregs, HEX_SREG_SCHEDCFG,
+                               env->threadId);
+    return extract32(schedcfg, reg_field_info[SCHEDCFG_EN].offset,
+                     reg_field_info[SCHEDCFG_EN].width);
+}
+
+static bool is_lowest_prio(CPUHexagonState *env, int int_num)
+{
+    uint32_t my_prio = get_prio(env);
+    CPUState *cs;
+
+    CPU_FOREACH(cs) {
+        CPUHexagonState *hex_env = cpu_env(cs);
+        if (!hex_is_qualified_for_int(hex_env, int_num)) {
+            continue;
+        }
+
+        /* Note that lower values indicate *higher* priority */
+        if (my_prio < get_prio(hex_env)) {
+            return false;
+        }
+    }
+    return true;
+}
+
+static bool hex_is_qualified_for_int(CPUHexagonState *env, int int_num)
+{
+    bool syscfg_gie = get_syscfg_gie(env);
+    bool iad = get_iad_bit(env, int_num);
+    bool ssr_ie = get_ssr_ie(env);
+    bool ssr_ex = get_ssr_ex(env);
+    bool imask = get_imask_bit(env, int_num);
+
+    return syscfg_gie && !iad && ssr_ie && !ssr_ex && !imask;
+}
+
+static void clear_pending_locks(CPUHexagonState *env)
+{
+    g_assert(bql_locked());
+    if (env->k0_lock_state == HEX_LOCK_WAITING) {
+        env->k0_lock_state = HEX_LOCK_UNLOCKED;
+    }
+    if (env->tlb_lock_state == HEX_LOCK_WAITING) {
+        env->tlb_lock_state = HEX_LOCK_UNLOCKED;
+    }
+}
+
+static bool should_not_exec(CPUHexagonState *env)
+{
+    return (get_exe_mode(env) == HEX_EXE_MODE_WAIT);
+}
+
+static void restore_state(CPUHexagonState *env, bool int_accepted)
+{
+    CPUState *cs = env_cpu(env);
+    cpu_reset_interrupt(cs, CPU_INTERRUPT_HARD | CPU_INTERRUPT_SWI);
+    if (!int_accepted && should_not_exec(env)) {
+        cpu_interrupt(cs, CPU_INTERRUPT_HALT);
+    }
+}
+
+static void hex_accept_int(CPUHexagonState *env, int int_num)
+{
+    CPUState *cs = env_cpu(env);
+    HexagonCPU *cpu = env_archcpu(env);
+    uint32_t evb =
+        hexagon_globalreg_read(cpu->globalregs, HEX_SREG_EVB,
+                               env->threadId);
+    const int exe_mode = get_exe_mode(env);
+    const bool in_wait_mode = exe_mode == HEX_EXE_MODE_WAIT;
+
+    set_ipend_bit(env, int_num, 0);
+    set_iad_bit(env, int_num, 1);
+    set_ssr_ex_cause(env, 1, HEX_CAUSE_INT0 | int_num);
+    cs->exception_index = HEX_EVENT_INT0 + int_num;
+    env->cause_code = HEX_EVENT_INT0 + int_num;
+    clear_pending_locks(env);
+    if (in_wait_mode) {
+        qemu_log_mask(CPU_LOG_INT,
+            "%s: thread " TARGET_FMT_ld " resuming, exiting WAIT mode\n",
+            __func__, env->threadId);
+        set_elr(env, env->wait_next_pc);
+        clear_wait_mode(env);
+        cs->halted = false;
+    } else if (env->k0_lock_state == HEX_LOCK_WAITING) {
+        g_assert_not_reached();
+    } else {
+        set_elr(env, env->gpr[HEX_REG_PC]);
+    }
+    env->gpr[HEX_REG_PC] = evb | (cs->exception_index << 2);
+    if (get_ipend(env) == 0) {
+        restore_state(env, true);
+    }
+}
+
+
+bool hex_check_interrupts(CPUHexagonState *env)
+{
+    CPUState *cs = env_cpu(env);
+    bool int_handled = false;
+    bool ssr_ex = get_ssr_ex(env);
+    int max_ints = 32;
+    bool schedcfgen;
+
+    /* Early exit if nothing pending */
+    if (get_ipend(env) == 0) {
+        restore_state(env, false);
+        return false;
+    }
+
+    BQL_LOCK_GUARD();
+    /* Only check priorities when schedcfgen is set */
+    schedcfgen = get_schedcfgen(env);
+    for (int i = 0; i < max_ints; i++) {
+        if (!get_iad_bit(env, i) && get_ipend_bit(env, i)) {
+            bool syscfg_gie, iad, ssr_ie, imask;
+
+            qemu_log_mask(CPU_LOG_INT,
+                          "%s: thread[" TARGET_FMT_ld "] "
+                          "pc = 0x" TARGET_FMT_lx
+                          " found int %d\n",
+                          __func__, env->threadId,
+                          env->gpr[HEX_REG_PC], i);
+            if (hex_is_qualified_for_int(env, i) &&
+                (!schedcfgen || is_lowest_prio(env, i))) {
+                qemu_log_mask(CPU_LOG_INT,
+                              "%s: thread[" TARGET_FMT_ld "] int %d handled_\n",
+                              __func__, env->threadId, i);
+                hex_accept_int(env, i);
+                int_handled = true;
+                break;
+            }
+            syscfg_gie = get_syscfg_gie(env);
+            iad = get_iad_bit(env, i);
+            ssr_ie = get_ssr_ie(env);
+            imask = get_imask_bit(env, i);
+
+            qemu_log_mask(CPU_LOG_INT,
+                          "%s: thread[" TARGET_FMT_ld "] "
+                          "int %d not handled, qualified: %d, "
+                          "schedcfg_en: %d, low prio %d\n",
+                          __func__, env->threadId, i,
+                          hex_is_qualified_for_int(env, i), schedcfgen,
+                          is_lowest_prio(env, i));
+
+            qemu_log_mask(CPU_LOG_INT,
+                          "%s: thread[" TARGET_FMT_ld "] "
+                          "int %d not handled, GIE %d, iad %d, "
+                          "SSR:IE %d, SSR:EX: %d, imask bit %d\n",
+                          __func__, env->threadId, i, syscfg_gie, iad, ssr_ie,
+                          ssr_ex, imask);
+        }
+    }
+
+    /*
+     * If we didn't handle the interrupt and it wasn't
+     * because we were in EX state, then we won't be able
+     * to execute the interrupt on this CPU unless something
+     * changes in the CPU state.  Clear the interrupt_request bits
+     * while preserving the IPEND bits, and we can re-assert the
+     * interrupt_request bit(s) when we execute one of those instructions.
+     */
+    if (!int_handled && !ssr_ex) {
+        restore_state(env, int_handled);
+    } else if (int_handled) {
+        assert(!cs->halted);
+    }
+
+    return int_handled;
+}
+
+void hex_clear_interrupts(CPUHexagonState *env, uint32_t mask, uint32_t type)
+{
+    if (mask == 0) {
+        return;
+    }
+
+    /*
+     * Notify all CPUs that the interrupt has happened
+     */
+    BQL_LOCK_GUARD();
+    clear_ipend(env, mask);
+    hex_interrupt_update(env);
+}
+
+void hex_raise_interrupts(CPUHexagonState *env, uint32_t mask, uint32_t type)
+{
+    g_assert(bql_locked());
+    if (mask == 0) {
+        return;
+    }
+
+    /*
+     * Notify all CPUs that the interrupt has happened
+     */
+    set_ipend(env, mask);
+    hex_interrupt_update(env);
+}
+
+void hex_interrupt_update(CPUHexagonState *env)
+{
+    CPUState *cs;
+
+    g_assert(bql_locked());
+    if (get_ipend(env) != 0) {
+        CPU_FOREACH(cs) {
+            CPUHexagonState *hex_env = cpu_env(cs);
+            const int exe_mode = get_exe_mode(hex_env);
+            if (exe_mode != HEX_EXE_MODE_OFF) {
+                cpu_interrupt(cs, CPU_INTERRUPT_SWI);
+                cpu_resume(cs);
+            }
+        }
+    }
+}
diff --git a/target/hexagon/hex_interrupts.h b/target/hexagon/hex_interrupts.h
new file mode 100644
index 0000000..6b6f540
--- /dev/null
+++ b/target/hexagon/hex_interrupts.h
@@ -0,0 +1,15 @@
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HEX_INTERRUPTS_H
+#define HEX_INTERRUPTS_H
+
+bool hex_check_interrupts(CPUHexagonState *env);
+void hex_clear_interrupts(CPUHexagonState *env, uint32_t mask, uint32_t type);
+void hex_raise_interrupts(CPUHexagonState *env, uint32_t mask, uint32_t type);
+void hex_interrupt_update(CPUHexagonState *env);
+
+#endif
diff --git a/target/hexagon/hex_mmu.c b/target/hexagon/hex_mmu.c
new file mode 100644
index 0000000..81d64a6
--- /dev/null
+++ b/target/hexagon/hex_mmu.c
@@ -0,0 +1,270 @@
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/main-loop.h"
+#include "qemu/qemu-print.h"
+#include "cpu.h"
+#include "system/cpus.h"
+#include "internal.h"
+#include "exec/cpu-interrupt.h"
+#include "cpu_helper.h"
+#include "exec/cputlb.h"
+#include "hex_mmu.h"
+#include "macros.h"
+#include "sys_macros.h"
+#include "hw/hexagon/hexagon_tlb.h"
+#include "hw/hexagon/hexagon_globalreg.h"
+
+static inline void hex_log_tlbw(uint32_t index, uint64_t entry)
+{
+    qemu_log_mask(CPU_LOG_MMU,
+                  "tlbw[%03" PRIu32 "]: 0x%016" PRIx64 "\n",
+                  index, entry);
+}
+
+void hex_tlbw(CPUHexagonState *env, uint32_t index, uint64_t value)
+{
+    uint32_t myidx = fTLB_NONPOW2WRAP(fTLB_IDXMASK(index));
+    HexagonTLBState *tlb = env_archcpu(env)->tlb;
+    uint64_t old_entry = hexagon_tlb_read(tlb, myidx);
+
+    bool old_entry_valid = extract64(old_entry, 63, 1);
+    if (old_entry_valid && hexagon_cpu_mmu_enabled(env)) {
+        CPUState *cs = env_cpu(env);
+        tlb_flush(cs);
+    }
+    hexagon_tlb_write(tlb, myidx, value);
+    hex_log_tlbw(myidx, value);
+}
+
+void hex_mmu_on(CPUHexagonState *env)
+{
+    CPUState *cs = env_cpu(env);
+    qemu_log_mask(CPU_LOG_MMU, "Hexagon MMU turned on!\n");
+    tlb_flush(cs);
+}
+
+void hex_mmu_off(CPUHexagonState *env)
+{
+    CPUState *cs = env_cpu(env);
+    qemu_log_mask(CPU_LOG_MMU, "Hexagon MMU turned off!\n");
+    tlb_flush(cs);
+}
+
+void hex_mmu_mode_change(CPUHexagonState *env)
+{
+    qemu_log_mask(CPU_LOG_MMU, "Hexagon mode change!\n");
+    CPUState *cs = env_cpu(env);
+    tlb_flush(cs);
+}
+
+bool hex_tlb_find_match(CPUHexagonState *env, uint32_t VA,
+                        MMUAccessType access_type, hwaddr *PA, int *prot,
+                        uint64_t *size, int32_t *excp, int mmu_idx)
+{
+    HexagonCPU *cpu = env_archcpu(env);
+    uint32_t ssr = env->t_sreg[HEX_SREG_SSR];
+    uint8_t asid = GET_SSR_FIELD(SSR_ASID, ssr);
+    int cause_code = 0;
+
+    bool found = hexagon_tlb_find_match(cpu->tlb, asid, VA, access_type,
+                                        PA, prot, size, excp, &cause_code,
+                                        mmu_idx);
+    if (cause_code) {
+        env->cause_code = cause_code;
+    }
+    return found;
+}
+
+/* Called from tlbp instruction */
+uint32_t hex_tlb_lookup(CPUHexagonState *env, uint32_t ssr, uint32_t VA)
+{
+    HexagonCPU *cpu = env_archcpu(env);
+    uint8_t asid = GET_SSR_FIELD(SSR_ASID, ssr);
+    int cause_code = 0;
+
+    uint32_t result = hexagon_tlb_lookup(cpu->tlb, asid, VA, &cause_code);
+    if (cause_code) {
+        env->cause_code = cause_code;
+    }
+    return result;
+}
+
+/*
+ * Return codes:
+ * 0 or positive             index of match
+ * -1                        multiple matches
+ * -2                        no match
+ */
+int hex_tlb_check_overlap(CPUHexagonState *env, uint64_t entry, uint64_t index)
+{
+    HexagonCPU *cpu = env_archcpu(env);
+    return hexagon_tlb_check_overlap(cpu->tlb, entry, index);
+}
+
+void dump_mmu(Monitor *mon, CPUHexagonState *env)
+{
+    HexagonCPU *cpu = env_archcpu(env);
+    hexagon_tlb_dump(mon, cpu->tlb);
+}
+
+static inline void print_thread(const char *str, CPUState *cs)
+{
+    g_assert(bql_locked());
+    CPUHexagonState *thread = cpu_env(cs);
+    bool is_stopped = cpu_is_stopped(cs);
+    int exe_mode = get_exe_mode(thread);
+    hex_lock_state_t lock_state = thread->tlb_lock_state;
+    qemu_log_mask(CPU_LOG_MMU,
+           "%s: threadId = %" PRIu32 ": %s,"
+           " exe_mode = %s, tlb_lock_state = %s\n",
+           str,
+           thread->threadId,
+           is_stopped ? "stopped" : "running",
+           exe_mode == HEX_EXE_MODE_OFF ? "off" :
+           exe_mode == HEX_EXE_MODE_RUN ? "run" :
+           exe_mode == HEX_EXE_MODE_WAIT ? "wait" :
+           exe_mode == HEX_EXE_MODE_DEBUG ? "debug" :
+           "unknown",
+           lock_state == HEX_LOCK_UNLOCKED ? "unlocked" :
+           lock_state == HEX_LOCK_WAITING ? "waiting" :
+           lock_state == HEX_LOCK_OWNER ? "owner" :
+           "unknown");
+}
+
+static inline void print_thread_states(const char *str)
+{
+    CPUState *cs;
+    CPU_FOREACH(cs) {
+        print_thread(str, cs);
+    }
+}
+
+void hex_tlb_lock(CPUHexagonState *env)
+{
+    qemu_log_mask(CPU_LOG_MMU, "hex_tlb_lock: " TARGET_FMT_ld "\n",
+                  env->threadId);
+    BQL_LOCK_GUARD();
+    g_assert((env->tlb_lock_count == 0) || (env->tlb_lock_count == 1));
+
+    HexagonCPU *cpu = env_archcpu(env);
+    uint32_t syscfg = cpu->globalregs ?
+        hexagon_globalreg_read(cpu->globalregs, HEX_SREG_SYSCFG,
+                               env->threadId) : 0;
+    uint8_t tlb_lock = GET_SYSCFG_FIELD(SYSCFG_TLBLOCK, syscfg);
+    if (tlb_lock) {
+        if (env->tlb_lock_state == HEX_LOCK_QUEUED) {
+            env->next_PC += 4;
+            env->tlb_lock_count++;
+            env->tlb_lock_state = HEX_LOCK_OWNER;
+            SET_SYSCFG_FIELD(env, SYSCFG_TLBLOCK, 1);
+            return;
+        }
+        if (env->tlb_lock_state == HEX_LOCK_OWNER) {
+            qemu_log_mask(CPU_LOG_MMU | LOG_GUEST_ERROR,
+                          "Double tlblock at PC: 0x%" PRIx32
+                          ", thread may hang\n",
+                          env->next_PC);
+            env->next_PC += 4;
+            CPUState *cs = env_cpu(env);
+            cpu_interrupt(cs, CPU_INTERRUPT_HALT);
+            return;
+        }
+        env->tlb_lock_state = HEX_LOCK_WAITING;
+        CPUState *cs = env_cpu(env);
+        cpu_interrupt(cs, CPU_INTERRUPT_HALT);
+    } else {
+        env->next_PC += 4;
+        env->tlb_lock_count++;
+        env->tlb_lock_state = HEX_LOCK_OWNER;
+        SET_SYSCFG_FIELD(env, SYSCFG_TLBLOCK, 1);
+    }
+
+    if (qemu_loglevel_mask(CPU_LOG_MMU)) {
+        qemu_log_mask(CPU_LOG_MMU, "Threads after hex_tlb_lock:\n");
+        print_thread_states("\tThread");
+    }
+}
+
+void hex_tlb_unlock(CPUHexagonState *env)
+{
+    BQL_LOCK_GUARD();
+    g_assert((env->tlb_lock_count == 0) || (env->tlb_lock_count == 1));
+
+    /* Nothing to do if the TLB isn't locked by this thread */
+    HexagonCPU *cpu = env_archcpu(env);
+    uint32_t syscfg = cpu->globalregs ?
+        hexagon_globalreg_read(cpu->globalregs, HEX_SREG_SYSCFG,
+                               env->threadId) : 0;
+    uint8_t tlb_lock = GET_SYSCFG_FIELD(SYSCFG_TLBLOCK, syscfg);
+    if ((tlb_lock == 0) ||
+        (env->tlb_lock_state != HEX_LOCK_OWNER)) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "thread %" PRIu32 " attempted to tlbunlock"
+                      " without having the lock, tlb_lock state = %u\n",
+                      env->threadId, (unsigned)env->tlb_lock_state);
+        g_assert(env->tlb_lock_state != HEX_LOCK_WAITING);
+        return;
+    }
+
+    env->tlb_lock_count--;
+    env->tlb_lock_state = HEX_LOCK_UNLOCKED;
+    SET_SYSCFG_FIELD(env, SYSCFG_TLBLOCK, 0);
+
+    /* Look for a thread to unlock */
+    unsigned int this_threadId = env->threadId;
+    CPUHexagonState *unlock_thread = NULL;
+    CPUState *cs;
+    CPU_FOREACH(cs) {
+        CPUHexagonState *thread = cpu_env(cs);
+
+        /*
+         * The hardware implements round-robin fairness, so we look for threads
+         * starting at env->threadId + 1 and incrementing modulo the number of
+         * threads.
+         *
+         * To implement this, we check if thread is a earlier in the modulo
+         * sequence than unlock_thread.
+         *     if unlock thread is higher than this thread
+         *         thread must be between this thread and unlock_thread
+         *     else
+         *         thread higher than this thread is ahead of unlock_thread
+         *         thread must be lower then unlock thread
+         */
+        if (thread->tlb_lock_state == HEX_LOCK_WAITING) {
+            if (!unlock_thread) {
+                unlock_thread = thread;
+            } else if (unlock_thread->threadId > this_threadId) {
+                if (this_threadId < thread->threadId &&
+                    thread->threadId < unlock_thread->threadId) {
+                    unlock_thread = thread;
+                }
+            } else {
+                if (thread->threadId > this_threadId) {
+                    unlock_thread = thread;
+                }
+                if (thread->threadId < unlock_thread->threadId) {
+                    unlock_thread = thread;
+                }
+            }
+        }
+    }
+    if (unlock_thread) {
+        cs = env_cpu(unlock_thread);
+        print_thread("\tWaiting thread found", cs);
+        unlock_thread->tlb_lock_state = HEX_LOCK_QUEUED;
+        SET_SYSCFG_FIELD(unlock_thread, SYSCFG_TLBLOCK, 1);
+        cpu_interrupt(cs, CPU_INTERRUPT_TLB_UNLOCK);
+    }
+
+    if (qemu_loglevel_mask(CPU_LOG_MMU)) {
+        qemu_log_mask(CPU_LOG_MMU, "Threads after hex_tlb_unlock:\n");
+        print_thread_states("\tThread");
+    }
+
+}
diff --git a/target/hexagon/hex_mmu.h b/target/hexagon/hex_mmu.h
new file mode 100644
index 0000000..4f556c7
--- /dev/null
+++ b/target/hexagon/hex_mmu.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HEXAGON_MMU_H
+#define HEXAGON_MMU_H
+
+#include "cpu.h"
+#include "monitor/monitor.h"
+
+extern void hex_tlbw(CPUHexagonState *env, uint32_t index, uint64_t value);
+extern uint32_t hex_tlb_lookup(CPUHexagonState *env, uint32_t ssr, uint32_t VA);
+extern void hex_mmu_on(CPUHexagonState *env);
+extern void hex_mmu_off(CPUHexagonState *env);
+extern void hex_mmu_mode_change(CPUHexagonState *env);
+extern bool hex_tlb_find_match(CPUHexagonState *env, uint32_t VA,
+                               MMUAccessType access_type, hwaddr *PA, int *prot,
+                               uint64_t *size, int32_t *excp, int mmu_idx);
+extern int hex_tlb_check_overlap(CPUHexagonState *env, uint64_t entry,
+                                 uint64_t index);
+extern void hex_tlb_lock(CPUHexagonState *env);
+extern void hex_tlb_unlock(CPUHexagonState *env);
+void dump_mmu(Monitor *mon, CPUHexagonState *env);
+#endif
diff --git a/target/hexagon/hex_regs.h b/target/hexagon/hex_regs.h
index bddfc28..214b7b8 100644
--- a/target/hexagon/hex_regs.h
+++ b/target/hexagon/hex_regs.h
@@ -81,4 +81,121 @@
     HEX_REG_UTIMERHI          = 63,
 };
 
+#ifndef CONFIG_USER_ONLY
+
+#define HEX_GREG_VALUES \
+  DECL_HEX_GREG(G0,         0) \
+  DECL_HEX_GREG(GELR,       0) \
+  DECL_HEX_GREG(G1,         1) \
+  DECL_HEX_GREG(GSR,        1) \
+  DECL_HEX_GREG(G2,         2) \
+  DECL_HEX_GREG(GOSP,       2) \
+  DECL_HEX_GREG(G3,         3) \
+  DECL_HEX_GREG(GBADVA,     3) \
+  DECL_HEX_GREG(GCYCLE_1T,  10) \
+  DECL_HEX_GREG(GCYCLE_2T,  11) \
+  DECL_HEX_GREG(GCYCLE_3T,  12) \
+  DECL_HEX_GREG(GCYCLE_4T,  13) \
+  DECL_HEX_GREG(GCYCLE_5T,  14) \
+  DECL_HEX_GREG(GCYCLE_6T,  15) \
+  DECL_HEX_GREG(GPMUCNT4,   16) \
+  DECL_HEX_GREG(GPMUCNT5,   17) \
+  DECL_HEX_GREG(GPMUCNT6,   18) \
+  DECL_HEX_GREG(GPMUCNT7,   19) \
+  DECL_HEX_GREG(GPCYCLELO,  24) \
+  DECL_HEX_GREG(GPCYCLEHI,  25) \
+  DECL_HEX_GREG(GPMUCNT0,   26) \
+  DECL_HEX_GREG(GPMUCNT1,   27) \
+  DECL_HEX_GREG(GPMUCNT2,   28) \
+  DECL_HEX_GREG(GPMUCNT3,   29) \
+  DECL_HEX_GREG_DONE
+
+#define DECL_HEX_GREG_DONE
+#define DECL_HEX_GREG(name, val) HEX_GREG_ ##name = val,
+enum hex_greg {
+    HEX_GREG_VALUES
+};
+#undef DECL_HEX_GREG
+#undef DECL_HEX_GREG_DONE
+
+#define DECL_HEX_GREG_DONE 0
+#define DECL_HEX_GREG(_, val) (1 << val) |
+static inline bool greg_implemented(enum hex_greg greg)
+{
+#if NUM_GREGS > 32
+#error "NUM_GREGS too large for greg_implemented(): update `impl_bitmap`"
+#endif
+    static int32_t impl_bitmap = HEX_GREG_VALUES;
+    return impl_bitmap & (1 << greg);
+}
+#undef DECL_HEX_GREG
+#undef DECL_HEX_GREG_DONE
+
+#endif /* CONFIG_USER_ONLY */
+
+#ifndef CONFIG_USER_ONLY
+enum {
+    HEX_SREG_SGP0 = 0,
+    HEX_SREG_SGP1 = 1,
+    HEX_SREG_STID = 2,
+    HEX_SREG_ELR = 3,
+    HEX_SREG_BADVA0 = 4,
+    HEX_SREG_BADVA1 = 5,
+    HEX_SREG_SSR = 6,
+    HEX_SREG_CCR = 7,
+    HEX_SREG_HTID = 8,
+    HEX_SREG_BADVA = 9,
+    HEX_SREG_IMASK = 10,
+    HEX_SREG_GEVB  = 11,
+    HEX_SREG_GLB_START = 16,
+    HEX_SREG_EVB = 16,
+    HEX_SREG_MODECTL = 17,
+    HEX_SREG_SYSCFG = 18,
+    HEX_SREG_IPENDAD = 20,
+    HEX_SREG_VID = 21,
+    HEX_SREG_VID1 = 22,
+    HEX_SREG_BESTWAIT = 23,
+    HEX_SREG_IEL = 24,
+    HEX_SREG_SCHEDCFG = 25,
+    HEX_SREG_IAHL = 26,
+    HEX_SREG_CFGBASE = 27,
+    HEX_SREG_DIAG = 28,
+    HEX_SREG_REV = 29,
+    HEX_SREG_PCYCLELO = 30,
+    HEX_SREG_PCYCLEHI = 31,
+    HEX_SREG_ISDBST = 32,
+    HEX_SREG_ISDBCFG0 = 33,
+    HEX_SREG_ISDBCFG1 = 34,
+    HEX_SREG_LIVELOCK = 35,
+    HEX_SREG_BRKPTPC0 = 36,
+    HEX_SREG_BRKPTCFG0 = 37,
+    HEX_SREG_BRKPTPC1 = 38,
+    HEX_SREG_BRKPTCFG1 = 39,
+    HEX_SREG_ISDBMBXIN = 40,
+    HEX_SREG_ISDBMBXOUT = 41,
+    HEX_SREG_ISDBEN = 42,
+    HEX_SREG_ISDBGPR = 43,
+    HEX_SREG_PMUCNT4 = 44,
+    HEX_SREG_PMUCNT5 = 45,
+    HEX_SREG_PMUCNT6 = 46,
+    HEX_SREG_PMUCNT7 = 47,
+    HEX_SREG_PMUCNT0 = 48,
+    HEX_SREG_PMUCNT1 = 49,
+    HEX_SREG_PMUCNT2 = 50,
+    HEX_SREG_PMUCNT3 = 51,
+    HEX_SREG_PMUEVTCFG = 52,
+    HEX_SREG_PMUSTID0 = 53,
+    HEX_SREG_PMUEVTCFG1 = 54,
+    HEX_SREG_PMUSTID1 = 55,
+    HEX_SREG_TIMERLO = 56,
+    HEX_SREG_TIMERHI = 57,
+    HEX_SREG_PMUCFG = 58,
+    HEX_SREG_S59 = 59,
+    HEX_SREG_S60 = 60,
+    HEX_SREG_S61 = 61,
+    HEX_SREG_S62 = 62,
+    HEX_SREG_S63 = 63,
+};
+#endif /* !CONFIG_USER_ONLY */
+
 #endif
diff --git a/target/hexagon/hexswi.c b/target/hexagon/hexswi.c
new file mode 100644
index 0000000..43c373e
--- /dev/null
+++ b/target/hexagon/hexswi.c
@@ -0,0 +1,271 @@
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "cpu.h"
+#include "cpu_helper.h"
+#include "exec/helper-proto.h"
+#include "qemu/log.h"
+#include "qemu/main-loop.h"
+#include "arch.h"
+#include "internal.h"
+#include "macros.h"
+#include "sys_macros.h"
+#include "accel/tcg/cpu-loop.h"
+#include "tcg/tcg-op.h"
+#include "hex_mmu.h"
+#include "hexswi.h"
+#include "hw/hexagon/hexagon_globalreg.h"
+
+#ifdef CONFIG_USER_ONLY
+#error "This file is only used in system emulation"
+#endif
+
+static void set_addresses(CPUHexagonState *env, uint32_t pc_offset,
+                          uint32_t exception_index)
+
+{
+    HexagonCPU *cpu = env_archcpu(env);
+    uint32_t evb = cpu->globalregs ?
+        hexagon_globalreg_read(cpu->globalregs, HEX_SREG_EVB,
+                               env->threadId) :
+        cpu->boot_addr;
+    env->t_sreg[HEX_SREG_ELR] = env->gpr[HEX_REG_PC] + pc_offset;
+    env->gpr[HEX_REG_PC] = evb | (exception_index << 2);
+}
+
+static const char *event_name[] = {
+    [HEX_EVENT_RESET] = "HEX_EVENT_RESET",
+    [HEX_EVENT_IMPRECISE] = "HEX_EVENT_IMPRECISE",
+    [HEX_EVENT_PRECISE] = "HEX_EVENT_PRECISE",
+    [HEX_EVENT_TLB_MISS_X] = "HEX_EVENT_TLB_MISS_X",
+    [HEX_EVENT_TLB_MISS_RW] = "HEX_EVENT_TLB_MISS_RW",
+    [HEX_EVENT_TRAP0] = "HEX_EVENT_TRAP0",
+    [HEX_EVENT_TRAP1] = "HEX_EVENT_TRAP1",
+    [HEX_EVENT_FPTRAP] = "HEX_EVENT_FPTRAP",
+    [HEX_EVENT_DEBUG] = "HEX_EVENT_DEBUG",
+    [HEX_EVENT_INT0] = "HEX_EVENT_INT0",
+    [HEX_EVENT_INT1] = "HEX_EVENT_INT1",
+    [HEX_EVENT_INT2] = "HEX_EVENT_INT2",
+    [HEX_EVENT_INT3] = "HEX_EVENT_INT3",
+    [HEX_EVENT_INT4] = "HEX_EVENT_INT4",
+    [HEX_EVENT_INT5] = "HEX_EVENT_INT5",
+    [HEX_EVENT_INT6] = "HEX_EVENT_INT6",
+    [HEX_EVENT_INT7] = "HEX_EVENT_INT7",
+    [HEX_EVENT_INT8] = "HEX_EVENT_INT8",
+    [HEX_EVENT_INT9] = "HEX_EVENT_INT9",
+    [HEX_EVENT_INTA] = "HEX_EVENT_INTA",
+    [HEX_EVENT_INTB] = "HEX_EVENT_INTB",
+    [HEX_EVENT_INTC] = "HEX_EVENT_INTC",
+    [HEX_EVENT_INTD] = "HEX_EVENT_INTD",
+    [HEX_EVENT_INTE] = "HEX_EVENT_INTE",
+    [HEX_EVENT_INTF] = "HEX_EVENT_INTF"
+};
+
+void hexagon_cpu_do_interrupt(CPUState *cs)
+
+{
+    CPUHexagonState *env = cpu_env(cs);
+    uint32_t ssr;
+
+    BQL_LOCK_GUARD();
+
+    qemu_log_mask(CPU_LOG_INT,
+                  "\t%s: event 0x%02x:%s, cause 0x%" PRIx32 "(%" PRIu32 ")\n",
+                  __func__, (unsigned)cs->exception_index,
+                  event_name[cs->exception_index], env->cause_code,
+                  env->cause_code);
+
+    env->llsc_addr = ~0;
+
+    ssr = env->t_sreg[HEX_SREG_SSR];
+    if (GET_SSR_FIELD(SSR_EX, ssr) == 1) {
+        HexagonCPU *cpu = env_archcpu(env);
+        if (cpu->globalregs) {
+            hexagon_globalreg_write(cpu->globalregs, HEX_SREG_DIAG,
+                                    env->cause_code, env->threadId);
+        }
+        env->cause_code = HEX_CAUSE_DOUBLE_EXCEPT;
+        cs->exception_index = HEX_EVENT_PRECISE;
+    }
+
+    switch (cs->exception_index) {
+    case HEX_EVENT_TRAP0:
+        if (env->cause_code == 0) {
+            qemu_log_mask(LOG_UNIMP,
+                          "trap0 is unhandled, no semihosting available\n");
+        }
+
+        hexagon_ssr_set_cause(env, env->cause_code);
+        set_addresses(env, 4, cs->exception_index);
+        break;
+
+    case HEX_EVENT_TRAP1:
+        hexagon_ssr_set_cause(env, env->cause_code);
+        set_addresses(env, 4, cs->exception_index);
+        break;
+
+    case HEX_EVENT_TLB_MISS_X:
+        switch (env->cause_code) {
+        case HEX_CAUSE_TLBMISSX_CAUSE_NORMAL:
+        case HEX_CAUSE_TLBMISSX_CAUSE_NEXTPAGE:
+            qemu_log_mask(CPU_LOG_MMU,
+                          "TLB miss EX exception (0x%02" PRIx32 ") caught: "
+                          "Cause code (0x%" PRIx32 ") "
+                          "TID = 0x%" PRIx32 ", PC = 0x%" PRIx32
+                          ", BADVA = 0x%" PRIx32 "\n",
+                          (uint32_t)cs->exception_index,
+                          env->cause_code, env->threadId,
+                          env->gpr[HEX_REG_PC],
+                          env->t_sreg[HEX_SREG_BADVA]);
+
+            hexagon_ssr_set_cause(env, env->cause_code);
+            set_addresses(env, 0, cs->exception_index);
+            break;
+
+        default:
+            cpu_abort(cs,
+                      "1:Hexagon exception %" PRId32 "/0x%02" PRIx32 ": "
+                      "Unknown cause code %" PRIu32 "/0x%" PRIx32 "\n",
+                      (uint32_t)cs->exception_index,
+                      (uint32_t)cs->exception_index,
+                      env->cause_code,
+                      env->cause_code);
+            break;
+        }
+        break;
+
+    case HEX_EVENT_TLB_MISS_RW:
+        switch (env->cause_code) {
+        case HEX_CAUSE_TLBMISSRW_CAUSE_READ:
+        case HEX_CAUSE_TLBMISSRW_CAUSE_WRITE:
+            qemu_log_mask(CPU_LOG_MMU,
+                          "TLB miss RW exception (0x%02" PRIx32 ") caught: "
+                          "Cause code (0x%" PRIx32 ") "
+                          "TID = 0x%" PRIx32 ", PC = 0x%" PRIx32
+                          ", BADVA = 0x%" PRIx32 "\n",
+                          (uint32_t)cs->exception_index,
+                          env->cause_code, env->threadId,
+                          env->gpr[HEX_REG_PC],
+                          env->t_sreg[HEX_SREG_BADVA]);
+
+            hexagon_ssr_set_cause(env, env->cause_code);
+            set_addresses(env, 0, cs->exception_index);
+            /* env->sreg[HEX_SREG_BADVA] is set when the exception is raised */
+            break;
+
+        default:
+            cpu_abort(cs,
+                      "2:Hexagon exception %" PRId32 "/0x%02" PRIx32 ": "
+                      "Unknown cause code %" PRIu32 "/0x%" PRIx32 "\n",
+                      (uint32_t)cs->exception_index,
+                      (uint32_t)cs->exception_index,
+                      env->cause_code,
+                      env->cause_code);
+            break;
+        }
+        break;
+
+    case HEX_EVENT_FPTRAP:
+        hexagon_ssr_set_cause(env, env->cause_code);
+        set_addresses(env, 0, cs->exception_index);
+        break;
+
+    case HEX_EVENT_DEBUG:
+        hexagon_ssr_set_cause(env, env->cause_code);
+        set_addresses(env, 0, cs->exception_index);
+        qemu_log_mask(LOG_UNIMP, "single-step exception is not handled\n");
+        break;
+
+    case HEX_EVENT_PRECISE:
+        switch (env->cause_code) {
+        case HEX_CAUSE_FETCH_NO_XPAGE:
+        case HEX_CAUSE_FETCH_NO_UPAGE:
+        case HEX_CAUSE_PRIV_NO_READ:
+        case HEX_CAUSE_PRIV_NO_UREAD:
+        case HEX_CAUSE_PRIV_NO_WRITE:
+        case HEX_CAUSE_PRIV_NO_UWRITE:
+        case HEX_CAUSE_MISALIGNED_LOAD:
+        case HEX_CAUSE_MISALIGNED_STORE:
+        case HEX_CAUSE_PC_NOT_ALIGNED:
+            qemu_log_mask(CPU_LOG_MMU,
+                          "MMU permission exception (0x%02" PRIx32 ") caught: "
+                          "Cause code (0x%" PRIx32 ") "
+                          "TID = 0x%" PRIx32 ", PC = 0x%" PRIx32
+                          ", BADVA = 0x%" PRIx32 "\n",
+                          (uint32_t)cs->exception_index,
+                          env->cause_code, env->threadId,
+                          env->gpr[HEX_REG_PC],
+                          env->t_sreg[HEX_SREG_BADVA]);
+
+
+            hexagon_ssr_set_cause(env, env->cause_code);
+            set_addresses(env, 0, cs->exception_index);
+            /* env->sreg[HEX_SREG_BADVA] is set when the exception is raised */
+            break;
+
+        case HEX_CAUSE_DOUBLE_EXCEPT:
+        case HEX_CAUSE_PRIV_USER_NO_SINSN:
+        case HEX_CAUSE_PRIV_USER_NO_GINSN:
+        case HEX_CAUSE_INVALID_OPCODE:
+        case HEX_CAUSE_NO_COPROC_ENABLE:
+        case HEX_CAUSE_NO_COPROC2_ENABLE:
+        case HEX_CAUSE_UNSUPPORTED_HVX_64B:
+        case HEX_CAUSE_REG_WRITE_CONFLICT:
+        case HEX_CAUSE_VWCTRL_WINDOW_MISS:
+            hexagon_ssr_set_cause(env, env->cause_code);
+            set_addresses(env, 0, cs->exception_index);
+            break;
+
+        case HEX_CAUSE_COPROC_LDST:
+            hexagon_ssr_set_cause(env, env->cause_code);
+            set_addresses(env, 0, cs->exception_index);
+            break;
+
+        case HEX_CAUSE_STACK_LIMIT:
+            hexagon_ssr_set_cause(env, env->cause_code);
+            set_addresses(env, 0, cs->exception_index);
+            break;
+
+        default:
+            cpu_abort(cs,
+                      "3:Hexagon exception %" PRId32 "/0x%02" PRIx32 ": "
+                      "Unknown cause code %" PRIu32 "/0x%" PRIx32 "\n",
+                      (uint32_t)cs->exception_index,
+                      (uint32_t)cs->exception_index,
+                      env->cause_code,
+                      env->cause_code);
+            break;
+        }
+        break;
+
+    case HEX_EVENT_IMPRECISE:
+        qemu_log_mask(LOG_UNIMP,
+                "Imprecise exception: this case is not yet handled");
+        break;
+
+    default:
+        qemu_log_mask(LOG_UNIMP,
+                "Hexagon Unsupported exception 0x%02x/0x%" PRIx32 "\n",
+                (unsigned)cs->exception_index, env->cause_code);
+        break;
+    }
+
+    cs->exception_index = HEX_EVENT_NONE;
+}
+
+void register_trap_exception(CPUHexagonState *env, int traptype, int imm,
+                             uint32_t PC)
+{
+    CPUState *cs = env_cpu(env);
+
+    cs->exception_index = (traptype == 0) ? HEX_EVENT_TRAP0 : HEX_EVENT_TRAP1;
+    ASSERT_DIRECT_TO_GUEST_UNSET(env, cs->exception_index);
+
+    env->cause_code = imm;
+    env->gpr[HEX_REG_PC] = PC;
+    cpu_loop_exit(cs);
+}
diff --git a/target/hexagon/hexswi.h b/target/hexagon/hexswi.h
new file mode 100644
index 0000000..48c1ae6
--- /dev/null
+++ b/target/hexagon/hexswi.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HEXSWI_H
+#define HEXSWI_H
+
+
+#include "cpu.h"
+
+void hexagon_cpu_do_interrupt(CPUState *cpu);
+void register_trap_exception(CPUHexagonState *env, int type, int imm,
+                             uint32_t PC);
+
+#endif /* HEXSWI_H */
diff --git a/target/hexagon/idef-parser/idef-parser.y b/target/hexagon/idef-parser/idef-parser.y
index c6f17c6..3cffafa 100644
--- a/target/hexagon/idef-parser/idef-parser.y
+++ b/target/hexagon/idef-parser/idef-parser.y
@@ -404,6 +404,9 @@
                   ;
 
 frame_check : FCHK '(' rvalue ',' rvalue ')' ';'
+              {
+                  gen_framecheck(c, &@1, &$3, &$5);
+              }
             ;
 
 cancel_statement : LOAD_CANCEL
@@ -869,7 +872,7 @@
     context.header_str = g_string_new(NULL);
     context.ternary = g_array_new(FALSE, TRUE, sizeof(Ternary));
     /* Read input file */
-    FILE *input_file = fopen(argv[ARG_INDEX_IDEFS], "r");
+    FILE *input_file = fopen(argv[ARG_INDEX_IDEFS], "rb");
     fseek(input_file, 0L, SEEK_END);
     long input_size = ftell(input_file);
     context.input_buffer = (char *) calloc(input_size + 1, sizeof(char));
diff --git a/target/hexagon/idef-parser/parser-helpers.c b/target/hexagon/idef-parser/parser-helpers.c
index 70bfa64..b942d9e 100644
--- a/target/hexagon/idef-parser/parser-helpers.c
+++ b/target/hexagon/idef-parser/parser-helpers.c
@@ -1731,6 +1731,15 @@
     OUT(c, locp, "}\n");
 }
 
+void gen_framecheck(Context *c, YYLTYPE *locp, HexValue *addr, HexValue *ea)
+{
+    HexValue addr_m = rvalue_materialize(c, locp, addr);
+    HexValue ea_m = rvalue_materialize(c, locp, ea);
+    addr_m = gen_rvalue_truncate(c, locp, &addr_m);
+    ea_m = gen_rvalue_truncate(c, locp, &ea_m);
+    OUT(c, locp, "gen_framecheck(ctx, ", &addr_m, ", ", &ea_m, ");\n");
+}
+
 void gen_load(Context *c, YYLTYPE *locp, HexValue *width,
               HexSignedness signedness, HexValue *ea, HexValue *dst)
 {
diff --git a/target/hexagon/idef-parser/parser-helpers.h b/target/hexagon/idef-parser/parser-helpers.h
index 2087d53..d3dfcec 100644
--- a/target/hexagon/idef-parser/parser-helpers.h
+++ b/target/hexagon/idef-parser/parser-helpers.h
@@ -295,6 +295,8 @@
 
 void gen_load_cancel(Context *c, YYLTYPE *locp);
 
+void gen_framecheck(Context *c, YYLTYPE *locp, HexValue *addr, HexValue *ea);
+
 void gen_load(Context *c, YYLTYPE *locp, HexValue *size,
               HexSignedness signedness, HexValue *ea, HexValue *dst);
 
diff --git a/target/hexagon/imported/encode_pp.def b/target/hexagon/imported/encode_pp.def
index 0cd30a5..1c64495 100644
--- a/target/hexagon/imported/encode_pp.def
+++ b/target/hexagon/imported/encode_pp.def
@@ -382,15 +382,19 @@
 DEF_ENC32(L4_return_tnew_pnt, ICLASS_LD" 011 0 000 sssss PP0010vv ---ddddd")
 DEF_ENC32(L4_return_fnew_pnt, ICLASS_LD" 011 0 000 sssss PP1010vv ---ddddd")
 
-DEF_ENC32(L2_loadw_locked,ICLASS_LD" 001 0 000 sssss PP000--- 000ddddd")
+/** Load Acquire Store Release Encoding **/
 
-
+DEF_ENC32(L4_loadw_phys,      ICLASS_LD" 001 0 000 sssss PP1ttttt -00ddddd")
+DEF_ENC32(L2_loadw_locked,    ICLASS_LD" 001 0 000 sssss PP000--- 000ddddd")
+DEF_ENC32(L4_loadd_locked,    ICLASS_LD" 001 0 000 sssss PP010--- 000ddddd")
 
 DEF_ENC32(L2_loadw_aq,        ICLASS_LD" 001 0 000 sssss PP001--- 000ddddd")
 DEF_ENC32(L4_loadd_aq,        ICLASS_LD" 001 0 000 sssss PP011--- 000ddddd")
 
-DEF_ENC32(R6_release_at_vi,    ICLASS_ST" 000 01 11sssss PP0ttttt --0011dd")
-DEF_ENC32(R6_release_st_vi,   ICLASS_ST" 000 01 11sssss PP0ttttt --1011dd")
+
+DEF_ENC32(S2_storew_locked,   ICLASS_ST" 000 01 01sssss PP-ttttt ----00dd")
+DEF_ENC32(S4_stored_locked,   ICLASS_ST" 000 01 11sssss PP0ttttt ----00dd")
+
 
 DEF_ENC32(S2_storew_rl_at_vi,  ICLASS_ST" 000 01 01sssss PP-ttttt --0010dd")
 DEF_ENC32(S2_storew_rl_st_vi, ICLASS_ST" 000 01 01sssss PP-ttttt --1010dd")
@@ -398,17 +402,15 @@
 DEF_ENC32(S4_stored_rl_at_vi,  ICLASS_ST" 000 01 11sssss PP0ttttt --0010dd")
 DEF_ENC32(S4_stored_rl_st_vi, ICLASS_ST" 000 01 11sssss PP0ttttt --1010dd")
 
-DEF_ENC32(L4_loadd_locked,ICLASS_LD" 001 0 000 sssss PP010--- 000ddddd")
-DEF_EXT_SPACE(EXTRACTW,   ICLASS_LD" 001 0 000 iiiii PP0iiiii -01iiiii")
+DEF_ENC32(R6_release_at_vi,    ICLASS_ST" 000 01 11sssss PP0ttttt --0011dd")
+DEF_ENC32(R6_release_st_vi,   ICLASS_ST" 000 01 11sssss PP0ttttt --1011dd")
+
+DEF_EXT_SPACE(EXTRACTW,   ICLASS_LD" 001 0 000 iiiii PP0iiiii 001iiiii")
 DEF_ENC32(Y2_dcfetchbo,   ICLASS_LD" 010 0 000 sssss PP0--iii iiiiiiii")
 
 
 
 
-
-
-
-
 /*******************************/
 /*                             */
 /*                             */
@@ -488,13 +490,17 @@
 /*                               x bus/cache     */
 /*                                    x store/cache     */
 DEF_ENC32(S2_allocframe,   ICLASS_ST" 000 01 00xxxxx PP000iii iiiiiiii")
-DEF_ENC32(S2_storew_locked,ICLASS_ST" 000 01 01sssss PP-ttttt ----00dd")
-DEF_ENC32(S4_stored_locked,ICLASS_ST" 000 01 11sssss PP0ttttt ----00dd")
+DEF_ENC32(Y5_l2locka,      ICLASS_ST" 000 01 11sssss PP1----- ------dd")
 DEF_ENC32(Y2_dczeroa,      ICLASS_ST" 000 01 10sssss PP0----- --------")
 
 
 DEF_ENC32(Y2_barrier,      ICLASS_ST" 100 00 00----- PP------ 000-----")
 DEF_ENC32(Y2_syncht,       ICLASS_ST" 100 00 10----- PP------ --------")
+DEF_ENC32(Y2_l2kill,       ICLASS_ST" 100 00 01----- PP-000-- --------")
+DEF_ENC32(Y5_l2gunlock,    ICLASS_ST" 100 00 01----- PP-010-- --------")
+DEF_ENC32(Y5_l2gclean,     ICLASS_ST" 100 00 01----- PP-100-- --------")
+DEF_ENC32(Y5_l2gcleaninv,  ICLASS_ST" 100 00 01----- PP-110-- --------")
+DEF_ENC32(Y2_l2cleaninvidx,ICLASS_ST" 100 00 11sssss PP------ --------")
 
 
 
@@ -502,9 +508,28 @@
 DEF_ENC32(Y2_dcinva,       ICLASS_ST" 000 00 01sssss PP------ --------")
 DEF_ENC32(Y2_dccleaninva,  ICLASS_ST" 000 00 10sssss PP------ --------")
 
-DEF_ENC32(Y4_l2fetch,      ICLASS_ST" 011 00 00sssss PP-ttttt 000-----")
+/* Super */
+DEF_ENC32(Y2_dckill,       ICLASS_ST" 001 00 00----- PP------ --------")
+DEF_ENC32(Y2_dccleanidx,   ICLASS_ST" 001 00 01sssss PP------ --------")
+DEF_ENC32(Y2_dcinvidx,     ICLASS_ST" 001 00 10sssss PP------ --------")
+DEF_ENC32(Y2_dccleaninvidx,ICLASS_ST" 001 00 11sssss PP------ --------")
+
+DEF_ENC32(Y2_dctagw       ,ICLASS_ST" 010 00 00sssss PP-ttttt --------")
+DEF_ENC32(Y2_dctagr       ,ICLASS_ST" 010 00 01sssss PP------ ---ddddd")
+
+DEF_ENC32(Y4_l2tagw       ,ICLASS_ST" 010 00 10sssss PP0ttttt --------")
+DEF_ENC32(Y4_l2tagr       ,ICLASS_ST" 010 00 11sssss PP------ ---ddddd")
+
+DEF_ENC32(Y4_l2fetch,          ICLASS_ST" 011 00 00sssss PP-ttttt 000-----")
+DEF_ENC32(Y5_l2cleanidx,   ICLASS_ST" 011 00 01sssss PP------ --------")
+DEF_ENC32(Y5_l2invidx,     ICLASS_ST" 011 00 10sssss PP------ --------")
+DEF_ENC32(Y5_l2unlocka,    ICLASS_ST" 011 00 11sssss PP------ --------")
 DEF_ENC32(Y5_l2fetch,      ICLASS_ST" 011 01 00sssss PP-ttttt --------")
 
+DEF_ENC32(Y6_l2gcleanpa,   ICLASS_ST" 011 01 01----- PP-ttttt --------")
+DEF_ENC32(Y6_l2gcleaninvpa,ICLASS_ST" 011 01 10----- PP-ttttt --------")
+
+
 /*******************************/
 /*                             */
 /*                             */
@@ -547,13 +572,23 @@
 
 DEF_FIELDROW_DESC32(ICLASS_J" 0100 -------- PP------ --------","[#4] (#u8) ")
 DEF_ENC32(J2_trap0,     ICLASS_J" 0100  00------  PP-iiiii  ---iii--")
-DEF_ENC32(J2_pause,     ICLASS_J" 0100  01------  PP-iiiii  ---iii--")
+DEF_ENC32(J2_trap1,     ICLASS_J" 0100  10-xxxxx  PP-iiiii  ---iii--")
+DEF_ENC32(J2_pause,     ICLASS_J" 0100  01----ii  PP-iiiii  ---iii--")
+
+DEF_FIELDROW_DESC32(ICLASS_J" 0101 -------- PP------ --------","[#5] Rd=(Rs) ")
+DEF_ENC32(Y2_icdatar,   ICLASS_J" 0101  101sssss  PP------  ---ddddd")
+DEF_ENC32(Y2_ictagr,    ICLASS_J" 0101  111sssss  PP------  ---ddddd")
+DEF_ENC32(Y2_ictagw,    ICLASS_J" 0101  110sssss  PP0ttttt  --------")
+DEF_ENC32(Y2_icdataw,   ICLASS_J" 0101  110sssss  PP1ttttt  --------")
 
 DEF_FIELDROW_DESC32(ICLASS_J" 0110 -------- PP------ --------","[#6] icop(Rs) ")
 DEF_ENC32(Y2_icinva,    ICLASS_J" 0110  110sssss  PP000---  --------")
+DEF_ENC32(Y2_icinvidx,  ICLASS_J" 0110  110sssss  PP001---  --------")
+DEF_ENC32(Y2_ickill,    ICLASS_J" 0110  110-----  PP010---  --------")
 
 DEF_FIELDROW_DESC32(ICLASS_J" 0111 -------- PP------ --------","[#7] () ")
 DEF_ENC32(Y2_isync,     ICLASS_J" 0111  11000000  PP0---00  00000010")
+DEF_ENC32(J2_rte,       ICLASS_J" 0111  111-----  PP00----  000-----")
 
 /* JUMP */
 DEF_FIELDROW_DESC32(ICLASS_J" 100- -------- PP------ --------","[#8,9] PC=(#r22)")
@@ -738,12 +773,19 @@
 
 DEF_FIELDROW_DESC32(    ICLASS_CR" 0010  --------  PP------  --------","[#2] Cd=Rs ")
 DEF_ENC32(A2_tfrrcr,    ICLASS_CR" 0010  001sssss  PP------  ---ddddd")
+DEF_ENC32(G4_tfrgrcr,   ICLASS_CR" 0010  000sssss  PP------  ---ddddd")
+DEF_ENC32(Y4_trace,     ICLASS_CR" 0010  010sssss  PP------  000-----")
+DEF_ENC32(Y6_diag,      ICLASS_CR" 0010  010sssss  PP------  001-----")
+DEF_ENC32(Y6_diag0,     ICLASS_CR" 0010  010sssss  PP-ttttt  010-----")
+DEF_ENC32(Y6_diag1,     ICLASS_CR" 0010  010sssss  PP-ttttt  011-----")
 
 DEF_FIELDROW_DESC32(    ICLASS_CR" 0011  --------  PP------  --------","[#3] Cdd=Rss ")
 DEF_ENC32(A4_tfrpcp,    ICLASS_CR" 0011  001sssss  PP------  ---ddddd")
+DEF_ENC32(G4_tfrgpcp,   ICLASS_CR" 0011  000sssss  PP------  ---ddddd")
 
 DEF_FIELDROW_DESC32(    ICLASS_CR" 1000  --------  PP------  --------","[#8] Rdd=Css ")
 DEF_ENC32(A4_tfrcpp,    ICLASS_CR" 1000  000sssss  PP------  ---ddddd")
+DEF_ENC32(G4_tfrgcpp,   ICLASS_CR" 1000  001sssss  PP------  ---ddddd")
 
 DEF_FIELDROW_DESC32(    ICLASS_CR" 1001  --------  PP------  --------","[#9] (#r8,#U10)")
 DEF_ENC32(J2_ploop1si,  ICLASS_CR" 1001  101IIIII  PP-iiiii  IIIii-II")
@@ -754,6 +796,7 @@
 
 DEF_FIELDROW_DESC32(    ICLASS_CR" 1010  --------  PP------  --------","[#10] Rd=Cs ")
 DEF_ENC32(A2_tfrcrr,    ICLASS_CR" 1010  000sssss  PP------  ---ddddd")
+DEF_ENC32(G4_tfrgcrr,   ICLASS_CR" 1010  001sssss  PP------  ---ddddd")
 DEF_ENC32(C4_addipc,    ICLASS_CR" 1010  01001001  PP-iiiii  i--ddddd")
 
 
@@ -781,6 +824,64 @@
 
 
 
+/* Supervisor CR ops */
+/* Interrupts */
+DEF_FIELDROW_DESC32(   ICLASS_CR" 0100 -------- PP------  --------","[#4] (Rs,Pt)")
+DEF_ENC32(Y2_swi,      ICLASS_CR" 0100 000sssss PP------ 000-----")
+DEF_ENC32(Y2_cswi,     ICLASS_CR" 0100 000sssss PP------ 001-----")
+DEF_ENC32(Y2_iassignw, ICLASS_CR" 0100 000sssss PP------ 010-----")
+DEF_ENC32(Y2_ciad,     ICLASS_CR" 0100 000sssss PP------ 011-----")
+DEF_ENC32(Y2_setimask, ICLASS_CR" 0100 100sssss PP----tt 000-----")
+DEF_ENC32(Y2_setprio,  ICLASS_CR" 0100 100sssss PP----tt 001-----")
+DEF_ENC32(Y4_siad,     ICLASS_CR" 0100 100sssss PP------ 011-----")
+
+DEF_ENC32(Y2_wait,     ICLASS_CR" 0100 010sssss PP------ 000-----")
+DEF_ENC32(Y2_resume,   ICLASS_CR" 0100 010sssss PP------ 001-----")
+DEF_ENC32(Y2_stop,     ICLASS_CR" 0100 011sssss PP------ 000-----")
+DEF_ENC32(Y2_start,    ICLASS_CR" 0100 011sssss PP------ 001-----")
+DEF_ENC32(Y4_nmi,      ICLASS_CR" 0100 011sssss PP------ 010-----")
+
+DEF_FIELDROW_DESC32(   ICLASS_CR" 0101 -------- PP------  --------","[#5] Rx ")
+DEF_ENC32(Y2_crswap0,  ICLASS_CR" 0101 000xxxxx PP------ --------")
+DEF_ENC32(Y4_crswap1,  ICLASS_CR" 0101 001xxxxx PP------ --------")
+
+DEF_FIELDROW_DESC32(   ICLASS_CR" 0110 -------- PP------  --------","[#6] Rd=(Rs)")
+DEF_ENC32(Y2_getimask, ICLASS_CR" 0110 000sssss PP------ ---ddddd")
+DEF_ENC32(Y2_iassignr, ICLASS_CR" 0110 011sssss PP------ ---ddddd")
+
+DEF_FIELDROW_DESC32(   ICLASS_CR" 0111 -------- PP------  --------","[#7] cr=Rs ")
+DEF_ENC32(Y2_tfrsrcr,  ICLASS_CR" 0111 00-sssss PP------ -ddddddd")
+
+DEF_FIELDROW_DESC32(   ICLASS_CR" 1100 -------- PP------  --------","[#12] ")
+DEF_ENC32(Y2_break,    ICLASS_CR" 1100 001----- PP------ 000-----")
+DEF_ENC32(Y2_tlblock,  ICLASS_CR" 1100 001----- PP------ 001-----")
+DEF_ENC32(Y2_tlbunlock,ICLASS_CR" 1100 001----- PP------ 010-----")
+DEF_ENC32(Y2_k0lock,   ICLASS_CR" 1100 001----- PP------ 011-----")
+DEF_ENC32(Y2_k0unlock, ICLASS_CR" 1100 001----- PP------ 100-----")
+DEF_ENC32(Y2_tlbp,     ICLASS_CR" 1100 100sssss PP------ ---ddddd")
+DEF_ENC32(Y5_tlboc,    ICLASS_CR" 1100 111sssss PP------ ---ddddd")
+DEF_ENC32(Y5_tlbasidi, ICLASS_CR" 1100 101sssss PP------ --------")
+DEF_ENC32(Y2_tlbr,     ICLASS_CR" 1100 010sssss PP------ ---ddddd")
+DEF_ENC32(Y2_tlbw,     ICLASS_CR" 1100 000sssss PP0ttttt --------")
+DEF_ENC32(Y5_ctlbw,    ICLASS_CR" 1100 110sssss PP0ttttt ---ddddd")
+
+DEF_FIELDROW_DESC32(   ICLASS_CR" 1101 -------- PP------  --------","[#13] Rxx ")
+DEF_ENC32(Y4_crswap10, ICLASS_CR" 1101 10-xxxxx PP------ ---00000")
+DEF_ENC32(Y4_tfrspcp,  ICLASS_CR" 1101 00-sssss PP------ -ddddddd")
+
+DEF_FIELDROW_DESC32(   ICLASS_CR" 1110 -------- PP------  --------","[#14] Rd=cr ")
+DEF_ENC32(Y2_tfrscrr,  ICLASS_CR" 1110 1sssssss PP------ ---ddddd")
+
+DEF_FIELDROW_DESC32(   ICLASS_CR" 1111 -------- PP------  --------","[#15] Rdd=Sss ")
+DEF_ENC32(Y4_tfrscpp,  ICLASS_CR" 1111 0sssssss PP------ ---ddddd")
+
+
+
+
+
+
+
+
 /*******************************/
 /*                             */
 /*                             */
diff --git a/target/hexagon/imported/ldst.idef b/target/hexagon/imported/ldst.idef
index 5319817..4e1e5d5 100644
--- a/target/hexagon/imported/ldst.idef
+++ b/target/hexagon/imported/ldst.idef
@@ -203,6 +203,9 @@
 Q6INSN(L4_loadd_locked,"Rdd32=memd_locked(Rs32)", ATTRIBS(A_REGWRSIZE_8B,A_MEMSIZE_8B,A_LOAD,A_RESTRICT_SLOT0ONLY,A_RESTRICT_PACKET_AXOK,A_NOTE_AXOK), "Load double with lock",
 { fEA_REG(RsV); fLOAD_LOCKED(1,8,u,EA,RddV) })
 
+Q6INSN(L4_loadw_phys,"Rd32=memw_phys(Rs32,Rt32)", ATTRIBS(A_REGWRSIZE_4B,A_PRIV,A_RESTRICT_SLOT0ONLY,A_NOTE_PRIV,A_MEMSIZE_4B,A_LOAD,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET), "Load word from physical address",
+{ fLOAD_PHYS(1,4,u,RsV,RtV,RdV); })
+
 Q6INSN(S4_stored_locked,"memd_locked(Rs32,Pd4)=Rtt32", ATTRIBS(A_REGWRSIZE_8B,A_MEMSIZE_8B,A_STORE,A_RESTRICT_SLOT0ONLY,A_RESTRICT_PACKET_AXOK,A_NOTE_AXOK,A_RESTRICT_LATEPRED,A_NOTE_LATEPRED), "Store word with lock",
 { fEA_REG(RsV); fSTORE_LOCKED(1,8,EA,RttV,PdV) })
 
diff --git a/target/hexagon/imported/macros.def b/target/hexagon/imported/macros.def
old mode 100755
new mode 100644
index 4bbcfdd..13eb13c
--- a/target/hexagon/imported/macros.def
+++ b/target/hexagon/imported/macros.def
@@ -354,6 +354,12 @@
 )
 
 DEF_MACRO(
+    fREAD_SSR, /* read SSR register */
+    (READ_RREG(REG_SSR)),          /* behavior */
+    ()
+)
+
+DEF_MACRO(
     fWRITE_LR, /* write lr */
     WRITE_RREG(REG_LR,A),          /* behavior */
     (A_IMPLICIT_WRITES_LR)
@@ -372,12 +378,36 @@
 )
 
 DEF_MACRO(
+    fWRITE_GOSP, /* write gosp */
+    WRITE_RREG(REG_GOSP,A),          /* behavior */
+    (A_IMPLICIT_WRITES_GOSP)
+)
+
+DEF_MACRO(
     fREAD_SP, /* read stack pointer */
     (READ_RREG(REG_SP)),          /* behavior */
     ()
 )
 
 DEF_MACRO(
+    fREAD_GOSP, /* read guest other stack pointer */
+    (READ_RREG(REG_GOSP)),          /* behavior */
+    ()
+)
+
+DEF_MACRO(
+    fREAD_GELR, /* read guest other stack pointer */
+    (READ_RREG(REG_GELR)),          /* behavior */
+    ()
+)
+
+DEF_MACRO(
+    fREAD_GEVB, /* read guest other stack pointer */
+    (READ_RREG(REG_GEVB)),          /* behavior */
+    ()
+)
+
+DEF_MACRO(
     fREAD_CSREG, /* read  CS register */
     (READ_RREG(REG_CSA+N)),          /* behavior */
     ()
@@ -570,6 +600,11 @@
     WRITE_PREG(3,VAL),     /* behavior */
     (A_IMPLICIT_WRITES_P3)
 )
+DEF_MACRO(
+	fWRITE_P3_LATE, /* write Predicate 0 */
+	{WRITE_PREG(3,VAL); fHIDE(MARK_LATE_PRED_WRITE(3))} ,          /* behavior */
+	(A_IMPLICIT_WRITES_P3,A_RESTRICT_LATEPRED)
+)
 
 DEF_MACRO(
     fPART1, /* write Predicate 0 */
@@ -660,6 +695,7 @@
     ((size8s_t)((size2s_t)(A))),
     /* optional attributes */
 )
+
 DEF_MACRO(
     fCAST2_8u, /* macro name */
     ((size8u_t)((size2u_t)(A))),
@@ -1532,18 +1568,137 @@
 /* OS interface and stop/wait               */
 /********************************************/
 
+DEF_MACRO(RUNNABLE_THREADS_MAX,
+    /* */,
+    ()
+)
+
+DEF_MACRO(THREAD_IS_ON,
+    ((PROC->arch_proc_options->thread_enable_mask>>TNUM) & 0x1),
+    ()
+)
+
+DEF_MACRO(THREAD_EN_MASK,
+    ((PROC->arch_proc_options->thread_enable_mask)),
+    ()
+)
+
+
+
+DEF_MACRO(READ_IMASK,
+    /* */,
+    ()
+)
+DEF_MACRO(WRITE_IMASK,
+    /* */,
+    (A_IMPLICIT_WRITES_IMASK_ANYTHREAD)
+)
+
+
+DEF_MACRO(WRITE_PRIO,
+    /* */,
+    (A_IMPLICIT_WRITES_STID_PRIO_ANYTHREAD)
+)
+
+
+DEF_MACRO(DO_IASSIGNW,
+    /* */,
+    (A_IMPLICIT_WRITES_IMASK_ANYTHREAD)
+)
+
+
+
+
+DEF_MACRO(fDO_NMI,
+    /* */,
+)
+
+DEF_MACRO(fDO_TRACE,
+    /* */,
+)
+
+DEF_MACRO(DO_IASSIGNR,
+    /* */,
+    ()
+)
+
+DEF_MACRO(DO_SWI,
+        /* */,
+        (A_EXCEPTION_SWI)
+)
+
+DEF_MACRO(DO_CSWI,
+        LOG_GLOBAL_REG_WRITE(REG_IPEND,GLOBAL_REG_READ(REG_IPEND) & ~((REG) & GLOBAL_REG_READ(REG_IEL)));,
+        ()
+)
+
+DEF_MACRO(DO_CIAD,
+        sys_ciad(thread,VAL); LOG_GLOBAL_REG_WRITE(REG_IAD,GLOBAL_REG_READ(REG_IAD) & ~(VAL));,
+        (A_EXCEPTION_SWI)
+)
+
+DEF_MACRO(DO_SIAD,
+        sys_siad(thread,VAL); LOG_GLOBAL_REG_WRITE(REG_IAD,GLOBAL_REG_READ(REG_IAD) | (VAL));,
+        (A_EXCEPTION_SWI)
+)
+
+DEF_MACRO(fBREAK,
+    /* */,
+    ()
+)
+
 DEF_MACRO(fPAUSE,
     {sys_pause(thread, insn->slot, IMM);},
     ()
 )
 
+
 DEF_MACRO(fTRAP,
-    warn("Trap NPC=%x ",fREAD_NPC());
-    warn("Trap exception, PCYCLE=%lld TYPE=%d NPC=%x IMM=0x%x",thread->processor_ptr->pstats[pcycles],TRAPTYPE,fREAD_NPC(),IMM);
-    register_trap_exception(thread,fREAD_NPC(),TRAPTYPE,IMM);,
+    /* */,
+    (A_EXCEPTION_SWI)
+)
+
+DEF_MACRO(fCLEAR_RTE_EX,
+      /* */,
+      ()
+)
+
+DEF_MACRO(fTLB_LOCK_AVAILABLE,
+    (fREAD_GLOBAL_REG_FIELD(SYSCONF,SYSCFG_TLBLOCK) == 0),
     ()
 )
 
+DEF_MACRO(fK0_LOCK_AVAILABLE,
+    (fREAD_GLOBAL_REG_FIELD(SYSCONF,SYSCFG_K0LOCK) == 0),
+    ()
+)
+
+DEF_MACRO(fSET_TLB_LOCK,
+      {
+      if (fTLB_LOCK_AVAILABLE()) {
+        fLOG_GLOBAL_REG_FIELD(SYSCONF,SYSCFG_TLBLOCK,1);
+      } else {
+        sys_waiting_for_tlb_lock(thread);
+      }
+      },
+      ()
+)
+
+DEF_MACRO(fSET_K0_LOCK,
+      /* */,
+      ()
+)
+
+DEF_MACRO(fCLEAR_TLB_LOCK,
+      /* */,
+      ()
+)
+
+DEF_MACRO(fCLEAR_K0_LOCK,
+      /* */,
+      ()
+)
+
 DEF_MACRO(fALIGN_REG_FIELD_VALUE,
     ((VAL)<<reg_field_info[FIELD].offset),
     /* */
@@ -1554,6 +1709,24 @@
     /* */
 )
 
+DEF_MACRO(fLOG_REG_FIELD,
+    LOG_MASKED_REG_WRITE(thread,REG_##REG,
+    fALIGN_REG_FIELD_VALUE(FIELD,VAL),
+    fGET_REG_FIELD_MASK(FIELD)),
+    ()
+)
+
+DEF_MACRO(fWRITE_GLOBAL_REG_FIELD,
+    /* */,
+)
+
+DEF_MACRO(fLOG_GLOBAL_REG_FIELD,
+    LOG_MASKED_GLOBAL_REG_WRITE(REG_##REG,
+        fALIGN_REG_FIELD_VALUE(FIELD,VAL),
+        fGET_REG_FIELD_MASK(FIELD)),
+    ()
+)
+
 DEF_MACRO(fREAD_REG_FIELD,
     fEXTRACTU_BITS(thread->Regs[REG_##REG],
         reg_field_info[FIELD].width,
@@ -1561,6 +1734,11 @@
     /* ATTRIBS */
 )
 
+DEF_MACRO(fREAD_GLOBAL_REG_FIELD,
+    /* */,
+    /* ATTRIBS */
+)
+
 DEF_MACRO(fGET_FIELD,
     fEXTRACTU_BITS(VAL,
         reg_field_info[FIELD].width,
@@ -1576,6 +1754,174 @@
     /* ATTRIBS */
 )
 
+DEF_MACRO(fSET_RUN_MODE_NOW,
+        /* */,
+)
+
+DEF_MACRO(fIN_DEBUG_MODE,
+    (thread->debug_mode || (fREAD_GLOBAL_REG_FIELD(ISDBST,ISDBST_DEBUGMODE) & 1<<TNUM)),
+    ()
+)
+DEF_MACRO(fIN_DEBUG_MODE_NO_ISDB,
+    (thread->debug_mode),
+    ()
+)
+
+
+DEF_MACRO(fIN_DEBUG_MODE_WARN,
+    {
+        if (fREAD_GLOBAL_REG_FIELD(ISDBST,ISDBST_DEBUGMODE) & 1<<TNUM)
+            warn("In ISDB debug mode, but TB told me to step normally");
+    },
+    ()
+)
+
+DEF_MACRO(fCLEAR_RUN_MODE,
+    {fLOG_GLOBAL_REG_FIELD(MODECTL,MODECTL_E,
+     fREAD_GLOBAL_REG_FIELD(MODECTL,MODECTL_E) & ~(1<<(TNUM)))},
+    /* NOTHING */
+)
+
+DEF_MACRO(fCLEAR_RUN_MODE_NOW,
+    /* */,
+    /* NOTHING */
+)
+
+DEF_MACRO(fGET_RUN_MODE,
+        /* */,
+)
+
+DEF_MACRO(fSET_WAIT_MODE,
+    {fLOG_GLOBAL_REG_FIELD(MODECTL,MODECTL_W,
+    fREAD_GLOBAL_REG_FIELD(MODECTL,MODECTL_W) | 1<<(TNUM))},
+    /* NOTHING */
+)
+
+DEF_MACRO(fCLEAR_WAIT_MODE,
+        /* */,
+)
+
+DEF_MACRO(fGET_WAIT_MODE,
+        /* */,
+)
+
+
+DEF_MACRO(fRESET_THREAD,
+        register_reset_interrupt(T,NUM),
+)
+
+DEF_MACRO(fREAD_CURRENT_EVB,
+    (GLOBAL_REG_READ(REG_EVB)),
+    /* nothing */
+)
+
+DEF_MACRO(fREAD_ELR,
+    READ_RREG(REG_ELR),
+    ()
+)
+
+DEF_MACRO(fPOW2_HELP_ROUNDUP,
+    ((VAL) | ((VAL) >> 1) | ((VAL) >> 2) | ((VAL) >> 4) | ((VAL) >> 8) | ((VAL) >> 16)),
+    ()
+)
+
+DEF_MACRO(fPOW2_ROUNDUP,
+    fPOW2_HELP_ROUNDUP((VAL)-1)+1,
+    ()
+)
+
+DEF_MACRO(fTLB_IDXMASK,
+    /* */,
+    ()
+)
+
+DEF_MACRO(fTLB_NONPOW2WRAP,
+    /* */,
+    /* ATTRIBS */
+)
+
+DEF_MACRO(fTLBW,
+    /* */,
+    /* ATTRIBS */
+)
+
+DEF_MACRO(fTLB_ENTRY_OVERLAP,
+    fHIDE( (sys_check_overlap(thread,VALUE)!=-2) ),
+    /* ATTRIBS */
+)
+
+DEF_MACRO(fTLB_ENTRY_OVERLAP_IDX,
+    fHIDE(sys_check_overlap(thread,VALUE)),
+    /* ATTRIBS */
+)
+
+
+DEF_MACRO(fTLBR,
+    TLB_REG_READ(fTLB_NONPOW2WRAP(fTLB_IDXMASK(INDEX))),
+    /* ATTRIBS */
+)
+
+DEF_MACRO(fTLBP,
+    tlb_lookup(thread,((TLBHI)>>12),((TLBHI)<<12),1),
+    /* attribs */
+)
+
+
+
+DEF_MACRO(READ_SGP0,
+    READ_RREG(REG_SGP),
+    ()
+)
+
+DEF_MACRO(READ_SGP1,
+    READ_RREG(REG_SGP+1),
+    ()
+)
+
+DEF_MACRO(READ_SGP10,
+    READ_RREG_PAIR(REG_SGP),
+    ()
+)
+
+DEF_MACRO(READ_UGP,
+    READ_RREG(REG_UGP),
+)
+
+DEF_MACRO(WRITE_SGP0,
+    WRITE_RREG(REG_SGP,VAL),
+    (A_IMPLICIT_WRITES_SGP0)
+)
+
+DEF_MACRO(WRITE_SGP1,
+    WRITE_RREG(REG_SGP+1,VAL),
+    (A_IMPLICIT_WRITES_SGP1)
+)
+
+DEF_MACRO(WRITE_SGP10,
+    WRITE_RREG_PAIR(REG_SGP,VAL),
+    (A_IMPLICIT_WRITES_SGP0,A_IMPLICIT_WRITES_SGP1)
+)
+
+DEF_MACRO(WRITE_UGP,
+        WRITE_RREG(REG_UGP,VAL),
+)
+
+DEF_MACRO(fSTART,
+    /* */,
+    ()
+)
+
+DEF_MACRO(fRESUME,
+    fLOG_GLOBAL_REG_FIELD(MODECTL,MODECTL_W,
+    fREAD_GLOBAL_REG_FIELD(MODECTL,MODECTL_W) & (~(REG))),
+    ()
+)
+
+DEF_MACRO(fGET_TNUM,
+    thread->threadId,
+    ()
+)
+
 /********************************************/
 /* Cache Management                         */
 /********************************************/
@@ -1602,19 +1948,49 @@
 )
 
 
+DEF_MACRO(fICFETCH,
+    ,
+    ()
+)
+
 DEF_MACRO(fDCFETCH,
     sys_dcfetch(thread, (REG), insn->slot),
     (A_MEMLIKE)
 )
 
 DEF_MACRO(fICINVA,
-    {
-        arch_internal_flush(thread->processor_ptr, 0, 0xffffffff);
-        sys_icinva(thread, (REG),insn->slot);
-    },
+    /* */,
     (A_ICINVA)
 )
 
+DEF_MACRO(fDCTAGR,
+    ({DST=sys_dctagr(thread, INDEX, insn->slot,DSTREGNO);})/* FIXME */,
+    ()
+)
+
+DEF_MACRO(fDCTAGW,
+    (sys_dctagw(thread, INDEX, PART2, insn->slot)),
+    ()
+)
+DEF_MACRO(fICTAGR,
+    ({DST=sys_ictagr(thread, INDEX, insn->slot,REGNO);}),
+    ()
+)
+
+DEF_MACRO(fICDATAR,
+    ({DST=sys_icdatar(thread, INDEX, insn->slot);}),
+    ()
+)
+
+DEF_MACRO(fICTAGW,
+    (sys_ictagw(thread, INDEX, PART2, insn->slot)),
+    ()
+)
+DEF_MACRO(fICDATAW,
+    ({ fHIDE(); }),
+    ()
+)
+
 DEF_MACRO(fL2FETCH,
     sys_l2fetch(thread, ADDR,HEIGHT,WIDTH,STRIDE,FLAGS, insn->slot),
     (A_MEMLIKE,A_L2FETCH)
@@ -1635,6 +2011,12 @@
     (A_MEMLIKE)
 )
 
+DEF_MACRO(fDCINVA,
+    sys_dcinva(thread, (REG)),
+    (A_MEMLIKE)
+)
+
+
 DEF_MACRO(fCHECKFORPRIV,
     {sys_check_privs(thread); if (EXCEPTION_DETECTED) return; },
     ()
@@ -1645,6 +2027,16 @@
     ()
 )
 
+DEF_MACRO(fTAKEN_INTERRUPT_EDGECLEAR,
+        { proc->global_regs[REG_IPEND] &= ~(INT_NUMTOMASK(intnum) & proc->global_regs[REG_IEL]); },
+        ()
+)
+
+DEF_MACRO(fSET_IAD,
+    /* */,
+    ()
+)
+
 DEF_MACRO(fBRANCH_SPECULATE_STALL,
     {
         sys_speculate_branch_stall(thread, insn->slot, JUMP_COND(JUMP_PRED_SET),
@@ -1664,3 +2056,79 @@
     ,
     ()
 )
+
+DEF_MACRO(fIN_MONITOR_MODE,
+    sys_in_monitor_mode(thread),
+    ()
+)
+
+DEF_MACRO(fIN_USER_MODE,
+    sys_in_user_mode(thread),
+    ()
+)
+
+DEF_MACRO(fIN_GUEST_MODE,
+    sys_in_guest_mode(thread),
+    ()
+)
+
+DEF_MACRO(fGRE_ENABLED,
+    fREAD_REG_FIELD(CCR,CCR_GRE),
+    ()
+)
+
+DEF_MACRO(fGTE_ENABLED,
+    fREAD_REG_FIELD(CCR,CCR_GRE),
+    ()
+)
+
+DEF_MACRO(fTRAP1_VIRTINSN,
+    ((fIN_GUEST_MODE())
+     && (fGRE_ENABLED())
+     && (   ((IMM) == 1)
+         || ((IMM) == 3)
+         || ((IMM) == 4)
+         || ((IMM) == 6))),
+    ()
+)
+
+DEF_MACRO(fVIRTINSN_RTE,
+    do {
+        thread->trap1_info = TRAP1_VIRTINSN_RTE;
+        fLOG_REG_FIELD(SSR,SSR_SS,fREAD_REG_FIELD(GSR,GSR_SS));
+        fLOG_REG_FIELD(CCR,CCR_GIE,fREAD_REG_FIELD(GSR,GSR_IE));
+        fLOG_REG_FIELD(SSR,SSR_GM,!fREAD_REG_FIELD(GSR,GSR_UM));
+        fBRANCH((fREAD_GELR() & -4),COF_TYPE_RTE);
+        fINTERNAL_CLEAR_SAMEPAGE();
+    } while (0),
+    (A_IMPLICIT_WRITES_CCR,A_IMPLICIT_WRITES_SSR)
+)
+
+DEF_MACRO(fVIRTINSN_SETIE,
+    do {
+        fLOG_REG_FIELD(CCR,CCR_GIE,(REG) & 1);
+        REG = fREAD_REG_FIELD(CCR,CCR_GIE);
+        thread->trap1_info = TRAP1_VIRTINSN_SETIE;
+    } while (0),
+    (A_IMPLICIT_WRITES_CCR)
+)
+
+DEF_MACRO(fVIRTINSN_GETIE,
+    {
+        thread->trap1_info = TRAP1_VIRTINSN_GETIE;
+        REG = fREAD_REG_FIELD(CCR,CCR_GIE);
+    },
+    ()
+)
+
+DEF_MACRO(fVIRTINSN_SPSWAP,
+    do {
+        if (fREAD_REG_FIELD(GSR,GSR_UM)) {
+            size4u_t TEMP = REG;
+            REG = fREAD_GOSP();
+            fWRITE_GOSP(TEMP);
+            thread->trap1_info = TRAP1_VIRTINSN_SPSWAP;
+        }
+    } while (0),
+    (A_IMPLICIT_WRITES_GOSP)
+)
diff --git a/target/hexagon/imported/system.idef b/target/hexagon/imported/system.idef
index 7c6568e..9e85bed 100644
--- a/target/hexagon/imported/system.idef
+++ b/target/hexagon/imported/system.idef
@@ -25,31 +25,230 @@
 /* User->OS interface                       */
 /********************************************/
 
-Q6INSN(J2_trap0,"trap0(#u8)",ATTRIBS(A_COF),
+Q6INSN(J2_trap0,"trap0(#u8)",ATTRIBS(A_COF,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET),
 "Trap to Operating System",
     fTRAP(0,uiV);
 )
 
-Q6INSN(J2_pause,"pause(#u8)",ATTRIBS(A_COF),
+Q6INSN(J2_trap1,"trap1(Rx32,#u8)",ATTRIBS(A_COF,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET),
+"Trap to Operating System",
+	/*
+	 * Note: if RxV is not written, we get the same as the input.
+	 * Since trap1 is SOLO, this means the register will effectively not be updated
+	 */
+	if (!fTRAP1_VIRTINSN(uiV)) {
+		fTRAP(1,uiV);
+	} else if (uiV == 1) {
+		fVIRTINSN_RTE(uiV,RxV);
+	} else if (uiV == 3) {
+		fVIRTINSN_SETIE(uiV,RxV);
+	} else if (uiV == 4) {
+		fVIRTINSN_GETIE(uiV,RxV);
+	} else if (uiV == 6) {
+		fVIRTINSN_SPSWAP(uiV,RxV);
+	})
+
+Q6INSN(J2_pause,"pause(#u8)",ATTRIBS(A_COF,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET),
 "Enter low-power state for #u8 cycles",{fPAUSE(uiV);})
 
-Q6INSN(Y2_icinva,"icinva(Rs32)",ATTRIBS(A_ICOP,A_ICFLUSHOP),"Instruction Cache Invalidate Address",{fEA_REG(RsV); fICINVA(EA);})
-
-Q6INSN(Y2_isync,"isync",ATTRIBS(),"Memory Synchronization",{fISYNC();})
-Q6INSN(Y2_barrier,"barrier",ATTRIBS(A_RESTRICT_SLOT0ONLY),"Memory Barrier",{fBARRIER();})
-Q6INSN(Y2_syncht,"syncht",ATTRIBS(A_RESTRICT_SLOT0ONLY),"Memory Synchronization",{fSYNCH();})
+Q6INSN(J2_rte,  "rte", ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NO_TIMING_LOG),
+"Return from Exception",
+{
+fHIDE(if((thread->timing_on) && (thread->status & EXEC_STATUS_REPLAY)) { return; })
+fHIDE(CALLBACK(thread->processor_ptr->options->rte_callback,
+      thread->system_ptr,thread->processor_ptr,
+      thread->threadId,0);)
+fCLEAR_RTE_EX();
+fBRANCH(fREAD_ELR(),COF_TYPE_RTE);})
 
 
-Q6INSN(Y2_dcfetchbo,"dcfetch(Rs32+#u11:3)",ATTRIBS(A_RESTRICT_PREFERSLOT0,A_DCFETCH),"Data Cache Prefetch",{fEA_RI(RsV,uiV); fDCFETCH(EA);})
+/********************************************/
+/* Interrupt Management                     */
+/********************************************/
+
+Q6INSN(Y2_swi,"swi(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_AXOK,A_RESTRICT_PACKET_AXOK),"Software Interrupt",{DO_SWI(RsV);})
+Q6INSN(Y2_cswi,"cswi(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_AXOK,A_RESTRICT_PACKET_AXOK),"Cancel Software Interrupt",{DO_CSWI(RsV);})
+Q6INSN(Y2_ciad,"ciad(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_AXOK,A_RESTRICT_PACKET_AXOK),"Re-enable interrupt in IAD",{DO_CIAD(RsV);})
+Q6INSN(Y4_siad,"siad(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_AXOK,A_RESTRICT_PACKET_AXOK),"Disable interrupt in IAD",{DO_SIAD(RsV);})
+Q6INSN(Y2_iassignr,"Rd32=iassignr(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_AXOK,A_RESTRICT_PACKET_AXOK),"Read interrupt to thread assignments",{DO_IASSIGNR(RsV,RdV);})
+Q6INSN(Y2_iassignw,"iassignw(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_AXOK,A_RESTRICT_PACKET_AXOK),"Write interrupt to thread assignments",{DO_IASSIGNW(RsV);})
 
 
-Q6INSN(Y2_dczeroa,"dczeroa(Rs32)",ATTRIBS(A_STORE,A_RESTRICT_SLOT0ONLY,A_DCZEROA),"Zero an aligned 32-byte cacheline",{fEA_REG(RsV); fDCZEROA(EA);})
-Q6INSN(Y2_dccleana,"dccleana(Rs32)",ATTRIBS(A_RESTRICT_SLOT0ONLY,A_DCFLUSHOP),"Data Cache Clean Address",{fEA_REG(RsV); fDCCLEANA(EA);})
-Q6INSN(Y2_dccleaninva,"dccleaninva(Rs32)",ATTRIBS(A_RESTRICT_SLOT0ONLY,A_DCFLUSHOP),"Data Cache Clean and Invalidate Address",{fEA_REG(RsV); fDCCLEANINVA(EA);})
-Q6INSN(Y2_dcinva,"dcinva(Rs32)",ATTRIBS(A_RESTRICT_SLOT0ONLY,A_DCFLUSHOP),"Data Cache Invalidate Address",{fEA_REG(RsV); fDCCLEANINVA(EA);})
+Q6INSN(Y2_getimask,"Rd32=getimask(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_AXOK,A_RESTRICT_PACKET_AXOK),"Read imask register of another thread",
+{RdV = READ_IMASK(RsV & thread->processor_ptr->thread_system_mask); })
+
+Q6INSN(Y2_setimask,"setimask(Pt4,Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_AXOK,A_RESTRICT_PACKET_AXOK),"Change imask register of another thread",
+{fPREDUSE_TIMING();WRITE_IMASK(PtV & thread->processor_ptr->thread_system_mask,RsV); })
 
 
-Q6INSN(Y4_l2fetch,"l2fetch(Rs32,Rt32)",ATTRIBS(A_RESTRICT_SLOT0ONLY),"L2 Cache Prefetch",
+
+/********************************************/
+/* TLB management                           */
+/********************************************/
+
+Q6INSN(Y2_tlbw,"tlbw(Rss32,Rt32)", ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET),
+"Write TLB entry", {fTLBW(RtV,RssV);})
+
+Q6INSN(Y5_ctlbw,"Rd32=ctlbw(Rss32,Rt32)", ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET),
+"Conditional Write TLB entry",
+{
+  if (fTLB_ENTRY_OVERLAP( (1LL<<63) | RssV )) {
+        RdV=fTLB_ENTRY_OVERLAP_IDX( (1LL<<63) | RssV);
+     } else {
+        fTLBW(RtV,RssV);
+        RdV=0x80000000;
+     }
+})
+
+Q6INSN(Y5_tlboc,"Rd32=tlboc(Rss32)", ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET),
+"TLB overlap check",
+{
+  if (fTLB_ENTRY_OVERLAP( (1LL<<63) | RssV )) {
+        RdV=fTLB_ENTRY_OVERLAP_IDX( (1LL<<63) | RssV);
+     } else {
+        RdV=0x80000000;
+     }
+})
+
+Q6INSN(Y2_tlbr,"Rdd32=tlbr(Rs32)", ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET), "Read TLB entry",
+{RddV = fTLBR(RsV);})
+
+Q6INSN(Y2_tlbp,"Rd32=tlbp(Rs32)", ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET), "Probe TLB", {RdV=fTLBP(RsV);})
+
+Q6INSN(Y5_tlbasidi,"tlbinvasid(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET), "Invalidate ASID",
+{
+	fHIDE(int i;)
+    fHIDE(unsigned int NUM_TLB_ENTRIES = NUM_TLB_REGS(thread->processor_ptr);)
+	for (i = 0; i < NUM_TLB_ENTRIES; i++) {
+		if ((fGET_FIELD(fTLBR(i),PTE_G) == 0) &&
+			(fGET_FIELD(fTLBR(i),PTE_ASID) == fEXTRACTU_RANGE(RsV,26,20))) {
+			fTLBW(i,fTLBR(i) & ~(1ULL << 63));
+		}
+	}
+})
+
+Q6INSN(Y2_tlblock,"tlblock", ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_NO_TIMING_LOG), "Lock TLB",
+{fSET_TLB_LOCK();})
+
+Q6INSN(Y2_tlbunlock,"tlbunlock", ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET), "Unlock TLB",
+{fCLEAR_TLB_LOCK();})
+
+Q6INSN(Y2_k0lock,"k0lock", ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_NO_TIMING_LOG), "Lock K0",
+{fSET_K0_LOCK();})
+
+Q6INSN(Y2_k0unlock,"k0unlock", ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET), "Unlock K0",
+{fCLEAR_K0_LOCK();})
+
+/********************************************/
+/* Supervisor Reg Management                */
+/********************************************/
+
+Q6INSN(Y2_crswap0,"crswap(Rx32,sgp0)",ATTRIBS(A_PRIV,A_NOTE_PRIV), "Swap system general pointer 0 with GPR",
+{fHIDE(size4s_t tmp;) tmp = RxV; RxV = READ_SGP0(); WRITE_SGP0(tmp);})
+Q6INSN(Y4_crswap1,"crswap(Rx32,sgp1)",ATTRIBS(A_PRIV,A_NOTE_PRIV), "Swap system general pointer 1 with GPR",
+{fHIDE(size4s_t tmp;) tmp = RxV; RxV = READ_SGP1(); WRITE_SGP1(tmp);})
+
+Q6INSN(Y4_crswap10,"crswap(Rxx32,sgp1:0)",ATTRIBS(A_PRIV,A_NOTE_PRIV), "Swap system general purpose 0/1 with GPR Pair",
+{fHIDE(size8s_t tmp;) tmp = RxxV; RxxV=READ_SGP10(); WRITE_SGP10(tmp);})
+
+Q6INSN(Y2_tfrscrr,"Rd32=Ss128",ATTRIBS(A_PRIV,A_NOTE_PRIV),"Transfer Supervisor Reg to GPR", {RdV=SsV;})
+Q6INSN(Y2_tfrsrcr,"Sd128=Rs32",ATTRIBS(A_PRIV,A_NOTE_PRIV),"Transfer GPR to Supervisor Reg", {SdV=RsV;})
+Q6INSN(Y4_tfrscpp,"Rdd32=Sss128",ATTRIBS(A_PRIV,A_NOTE_PRIV),"Transfer Supervisor Reg to GPR", {RddV=SssV;})
+Q6INSN(Y4_tfrspcp,"Sdd128=Rss32",ATTRIBS(A_PRIV,A_NOTE_PRIV),"Transfer GPR to Supervisor Reg", {SddV=RssV;})
+
+Q6INSN(G4_tfrgcrr,"Rd32=Gs32",ATTRIBS(A_GUEST,A_NOTE_GUEST),"Transfer Guest Reg to GPR", {RdV=GsV;})
+Q6INSN(G4_tfrgrcr,"Gd32=Rs32",ATTRIBS(A_GUEST,A_NOTE_GUEST),"Transfer GPR to Guest Reg", {GdV=RsV;})
+Q6INSN(G4_tfrgcpp,"Rdd32=Gss32",ATTRIBS(A_GUEST,A_NOTE_GUEST),"Transfer Guest Reg to GPR", {RddV=GssV;})
+Q6INSN(G4_tfrgpcp,"Gdd32=Rss32",ATTRIBS(A_GUEST,A_NOTE_GUEST),"Transfer GPR to Guest Reg", {GddV=RssV;})
+
+
+
+Q6INSN(Y2_setprio,"setprio(Pt4,Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV),"Change TID Prio of another thread",
+{fPREDUSE_TIMING();WRITE_PRIO(PtV & thread->processor_ptr->thread_system_mask,RsV); })
+
+
+
+
+/********************************************/
+/* Power Management / Thread on/off         */
+/********************************************/
+Q6INSN(Y6_diag,"diag(Rs32)",ATTRIBS(),"Send value to Diag trace module",{
+})
+Q6INSN(Y6_diag0,"diag0(Rss32,Rtt32)",ATTRIBS(),"Send values of two register to DIAG Trace. Set X=0",{
+})
+Q6INSN(Y6_diag1,"diag1(Rss32,Rtt32)",ATTRIBS(),"Send values of two register to DIAG Trace.  Set X=1",{
+})
+
+
+Q6INSN(Y4_trace,"trace(Rs32)",ATTRIBS(A_NOTE_AXOK,A_RESTRICT_PACKET_AXOK),"Send value to ETM trace",{
+    fDO_TRACE(RsV);
+})
+
+Q6INSN(Y2_stop,"stop(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET),"Stop thread(s)",{
+    fHIDE(RsV=RsV;)
+    if (!fIN_DEBUG_MODE_NO_ISDB(fGET_TNUM())) fCLEAR_RUN_MODE(fGET_TNUM());
+})
+
+Q6INSN(Y4_nmi,"nmi(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_NO_TIMING_LOG),"Raise NMI on thread(s)",{
+    fDO_NMI(RsV);
+})
+
+Q6INSN(Y2_start,"start(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET),"Start thread(s)",fSTART(RsV);)
+
+Q6INSN(Y2_wait,"wait(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_NO_TIMING_LOG),"Make thread(s) wait",{
+    fHIDE(RsV=RsV;)
+    if (!fIN_DEBUG_MODE(fGET_TNUM())) fSET_WAIT_MODE(fGET_TNUM());
+	fIN_DEBUG_MODE_WARN(fGET_TNUM());
+})
+
+Q6INSN(Y2_resume,"resume(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET),"Make thread(s) stop waiting",fRESUME(RsV);)
+
+Q6INSN(Y2_break,"brkpt",ATTRIBS(A_NOTE_NOPACKET,A_RESTRICT_NOPACKET),"Breakpoint",{fBREAK();})
+
+
+/********************************************/
+/* Cache Management                         */
+/********************************************/
+
+Q6INSN(Y2_ictagr,"Rd32=ictagr(Rs32)",ATTRIBS(A_ICOP,A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_CACHEOP,A_COPBYIDX,A_ICTAGOP),"Instruction Cache Tag Read",{fICTAGR(RsV,RdV,RdN);})
+Q6INSN(Y2_ictagw,"ictagw(Rs32,Rt32)",ATTRIBS(A_ICOP,A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_CACHEOP,A_COPBYIDX,A_ICTAGOP),"Instruction Cache Tag Write",{fICTAGW(RsV,RtV);})
+Q6INSN(Y2_icdataw,"icdataw(Rs32,Rt32)",ATTRIBS(A_ICOP,A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_CACHEOP,A_COPBYIDX,A_ICTAGOP),"Instruction Cache Data Write",{fICDATAW(RsV,RtV);})
+Q6INSN(Y2_icdatar,"Rd32=icdatar(Rs32)",ATTRIBS(A_ICOP,A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_CACHEOP,A_COPBYIDX,A_ICTAGOP),"Instruction Cache Data Read",{fICDATAR(RsV, RdV);})
+Q6INSN(Y2_icinva,"icinva(Rs32)",ATTRIBS(A_ICOP,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_CACHEOP,A_COPBYADDRESS,A_ICFLUSHOP),"Instruction Cache Invalidate Address",{fEA_REG(RsV); fICINVA(EA);})
+Q6INSN(Y2_icinvidx,"icinvidx(Rs32)",ATTRIBS(A_ICOP,A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_CACHEOP,A_COPBYIDX,A_ICFLUSHOP),"Instruction Cache Invalidate Index",{fICINVIDX(RsV);})
+Q6INSN(Y2_ickill,"ickill",ATTRIBS(A_ICOP,A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_CACHEOP,A_ICFLUSHOP),"Instruction Cache Invalidate",{fICKILL();})
+
+Q6INSN(Y2_isync,"isync",ATTRIBS(A_NOTE_NOPACKET,A_RESTRICT_NOPACKET),"Memory Synchronization",{fISYNC();})
+Q6INSN(Y2_barrier,"barrier",ATTRIBS(A_NOTE_NOPACKET,A_RESTRICT_SLOT0ONLY,A_RESTRICT_PACKET_AXOK),"Memory Barrier",{fBARRIER();})
+Q6INSN(Y2_syncht,"syncht",ATTRIBS(A_NOTE_NOPACKET,A_RESTRICT_SLOT0ONLY,A_RESTRICT_NOPACKET),"Memory Synchronization",{fSYNCH();})
+
+
+Q6INSN(Y2_dcfetchbo,"dcfetch(Rs32+#u11:3)",ATTRIBS(A_RESTRICT_PREFERSLOT0,A_DCFETCH,A_RESTRICT_NOSLOT1_STORE),"Data Cache Prefetch",{fEA_RI(RsV,uiV); fDCFETCH(EA);})
+Q6INSN(Y2_dckill,"dckill",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_SLOT0ONLY,A_RESTRICT_NOPACKET,A_CACHEOP,A_DCFLUSHOP),"Data Cache Invalidate",{fDCKILL();})
+
+
+Q6INSN(Y2_dczeroa,"dczeroa(Rs32)",ATTRIBS(A_STORE,A_RESTRICT_SLOT1_AOK,A_NOTE_SLOT1_AOK,A_RESTRICT_SLOT0ONLY,A_CACHEOP,A_COPBYADDRESS,A_DCZEROA),"Zero an aligned 32-byte cacheline",{fEA_REG(RsV); fDCZEROA(EA);})
+Q6INSN(Y2_dccleana,"dccleana(Rs32)",ATTRIBS(A_RESTRICT_SLOT1_AOK,A_NOTE_SLOT1_AOK,A_RESTRICT_SLOT0ONLY,A_CACHEOP,A_COPBYADDRESS,A_DCFLUSHOP),"Data Cache Clean Address",{fEA_REG(RsV); fDCCLEANA(EA);})
+Q6INSN(Y2_dccleanidx,"dccleanidx(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_RESTRICT_PACKET_AXOK,A_NOTE_AXOK,A_RESTRICT_SLOT0ONLY,A_CACHEOP,A_COPBYIDX,A_DCFLUSHOP),"Data Cache Clean Index",{fDCCLEANIDX(RsV);})
+Q6INSN(Y2_dccleaninva,"dccleaninva(Rs32)",ATTRIBS(A_RESTRICT_SLOT1_AOK,A_NOTE_SLOT1_AOK,A_RESTRICT_SLOT0ONLY,A_CACHEOP,A_COPBYADDRESS,A_DCFLUSHOP),"Data Cache Clean and Invalidate Address",{fEA_REG(RsV); fDCCLEANINVA(EA);})
+Q6INSN(Y2_dccleaninvidx,"dccleaninvidx(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_RESTRICT_PACKET_AXOK,A_NOTE_AXOK,A_RESTRICT_SLOT0ONLY,A_CACHEOP,A_COPBYIDX,A_DCFLUSHOP),"Data Cache Clean and Invalidate Index",{fDCCLEANINVIDX(RsV);})
+Q6INSN(Y2_dcinva,"dcinva(Rs32)",ATTRIBS(A_RESTRICT_SLOT1_AOK,A_NOTE_SLOT1_AOK,A_RESTRICT_SLOT0ONLY,A_CACHEOP,A_COPBYADDRESS,A_DCFLUSHOP),"Data Cache Invalidate Address",{fEA_REG(RsV); fDCCLEANINVA(EA);})
+Q6INSN(Y2_dcinvidx,"dcinvidx(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_RESTRICT_PACKET_AXOK,A_NOTE_AXOK,A_RESTRICT_SLOT0ONLY,A_CACHEOP,A_COPBYIDX,A_DCFLUSHOP),"Data Cache Invalidate Index",{fDCINVIDX(RsV);})
+Q6INSN(Y2_dctagr,"Rd32=dctagr(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_RESTRICT_PACKET_AXOK,A_NOTE_AXOK,A_RESTRICT_SLOT0ONLY,A_CACHEOP,A_COPBYIDX,A_DCTAGOP),"Data Cache Tag Read",{fDCTAGR(RsV,RdV,RdN);})
+Q6INSN(Y2_dctagw,"dctagw(Rs32,Rt32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_RESTRICT_SLOT0ONLY,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_CACHEOP,A_COPBYIDX,A_DCTAGOP),"Data Cache Tag Write",{fDCTAGW(RsV,RtV);})
+
+
+Q6INSN(Y2_l2kill,"l2kill",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_SLOT0ONLY,A_RESTRICT_NOPACKET,A_CACHEOP,A_L2FLUSHOP),"L2 Cache Invalidate",{fL2KILL();})
+Q6INSN(Y4_l2tagw,"l2tagw(Rs32,Rt32)",ATTRIBS(A_PRIV,A_NOTE_BADTAG_UNDEF,A_NOTE_PRIV,A_RESTRICT_SLOT0ONLY,A_NOTE_NOPACKET,A_RESTRICT_NOPACKET,A_CACHEOP,A_COPBYIDX,A_L2TAGOP),"L2 Cache Tag Write",{fL2TAGW(RsV,RtV);})
+Q6INSN(Y4_l2tagr,"Rd32=l2tagr(Rs32)",ATTRIBS(A_PRIV,A_NOTE_BADTAG_UNDEF,A_NOTE_PRIV,A_NOTE_AXOK,A_RESTRICT_PACKET_AXOK,A_RESTRICT_SLOT0ONLY,A_CACHEOP,A_COPBYIDX,A_L2TAGOP),"L2 Cache Tag Read",{fL2TAGR(RsV,RdV,RdN);})
+
+Q6INSN(Y2_l2cleaninvidx,"l2cleaninvidx(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_AXOK,A_RESTRICT_PACKET_AXOK,A_RESTRICT_SLOT0ONLY,A_CACHEOP,A_COPBYIDX,A_L2FLUSHOP),"L2 Cache Clean and Invalidate Index",{fL2CLEANINVIDX(RsV); })
+Q6INSN(Y5_l2cleanidx,"l2cleanidx(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_AXOK,A_RESTRICT_PACKET_AXOK,A_RESTRICT_SLOT0ONLY,A_CACHEOP,A_COPBYIDX,A_L2FLUSHOP),"L2 Cache Clean by Index",{fL2CLEANIDX(RsV); })
+Q6INSN(Y5_l2invidx,"l2invidx(Rs32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_AXOK,A_RESTRICT_PACKET_AXOK,A_RESTRICT_SLOT0ONLY,A_CACHEOP,A_COPBYIDX,A_L2FLUSHOP),"L2 Cache Invalidate by Index",{fL2INVIDX(RsV); })
+
+
+
+Q6INSN(Y4_l2fetch,"l2fetch(Rs32,Rt32)",ATTRIBS(A_RESTRICT_SLOT0ONLY,A_RESTRICT_PACKET_AXOK,A_NOTE_AXOK),"L2 Cache Prefetch",
 { fL2FETCH(RsV,
            (RtV&0xff), /*height*/
            ((RtV>>8)&0xff), /*width*/
@@ -59,10 +258,28 @@
 
 
 
-Q6INSN(Y5_l2fetch,"l2fetch(Rs32,Rtt32)",ATTRIBS(A_RESTRICT_SLOT0ONLY),"L2 Cache Prefetch",
+Q6INSN(Y5_l2fetch,"l2fetch(Rs32,Rtt32)",ATTRIBS(A_RESTRICT_SLOT0ONLY,A_RESTRICT_PACKET_AXOK,A_NOTE_AXOK),"L2 Cache Prefetch",
 { fL2FETCH(RsV,
            fGETUHALF(0,RttV), /*height*/
            fGETUHALF(1,RttV), /*width*/
            fGETUHALF(2,RttV), /*stride*/
            fGETUHALF(3,RttV)); /*flags*/
 })
+
+Q6INSN(Y5_l2locka,"Pd4=l2locka(Rs32)", ATTRIBS(A_PRIV,A_NOTE_PRIV,A_CACHEOP,A_COPBYADDRESS,A_RESTRICT_SLOT0ONLY,A_RESTRICT_PACKET_AXOK,A_NOTE_AXOK,A_RESTRICT_LATEPRED,A_NOTE_LATEPRED),
+"Lock L2 cache line by address", { fEA_REG(RsV); fL2LOCKA(EA,PdV,PdN); fHIDE(MARK_LATE_PRED_WRITE(PdN)) })
+
+
+Q6INSN(Y5_l2unlocka,"l2unlocka(Rs32)", ATTRIBS(A_PRIV,A_NOTE_PRIV,A_CACHEOP,A_COPBYADDRESS,A_RESTRICT_SLOT0ONLY,A_RESTRICT_PACKET_AXOK,A_NOTE_AXOK), "UnLock L2 cache line by address", { fEA_REG(RsV); fL2UNLOCKA(EA); })
+
+
+
+Q6INSN(Y5_l2gunlock,"l2gunlock",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_SLOT0ONLY,A_RESTRICT_NOPACKET,A_CACHEOP,A_L2FLUSHOP),"L2 Global Unlock",{fL2UNLOCK();})
+
+Q6INSN(Y5_l2gclean,"l2gclean",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_SLOT0ONLY,A_RESTRICT_NOPACKET,A_CACHEOP,A_L2FLUSHOP),"L2 Global Clean",{fL2CLEAN();})
+
+Q6INSN(Y5_l2gcleaninv,"l2gcleaninv",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_SLOT0ONLY,A_RESTRICT_NOPACKET,A_CACHEOP,A_L2FLUSHOP),"L2 Global Clean and Invalidate",{fL2CLEANINV();})
+
+Q6INSN(Y6_l2gcleanpa,"l2gclean(Rtt32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_SLOT0ONLY,A_RESTRICT_NOPACKET,A_CACHEOP,A_L2FLUSHOP),"L2 Global Clean by PA Range",{fL2CLEANPA(RttV);})
+
+Q6INSN(Y6_l2gcleaninvpa,"l2gcleaninv(Rtt32)",ATTRIBS(A_PRIV,A_NOTE_PRIV,A_NOTE_NOPACKET,A_RESTRICT_SLOT0ONLY,A_RESTRICT_NOPACKET,A_CACHEOP,A_L2FLUSHOP),"L2 Global Clean and Invalidate by PA Range",{fL2CLEANINVPA(RttV);})
diff --git a/target/hexagon/internal.h b/target/hexagon/internal.h
index 5fc837a..00b37ae 100644
--- a/target/hexagon/internal.h
+++ b/target/hexagon/internal.h
@@ -30,5 +30,27 @@
 void hexagon_debug(CPUHexagonState *env);
 
 extern const char * const hexagon_regnames[TOTAL_PER_THREAD_REGS];
+#ifndef CONFIG_USER_ONLY
+extern const char * const hexagon_sregnames[];
+extern const char * const hexagon_gregnames[];
+#endif
+
+void G_NORETURN do_raise_exception(CPUHexagonState *env,
+        uint32_t exception,
+        uint32_t PC,
+        uintptr_t retaddr);
+
+#define hexagon_cpu_mmu_enabled(env) ({ \
+    HexagonCPU *cpu = env_archcpu(env); \
+    cpu->globalregs ? \
+        GET_SYSCFG_FIELD(SYSCFG_MMUEN, \
+            hexagon_globalreg_read(cpu->globalregs, \
+                                   HEX_SREG_SYSCFG, (env)->threadId)) : \
+        0; \
+})
+
+#ifndef CONFIG_USER_ONLY
+extern const VMStateDescription vmstate_hexagon_cpu;
+#endif
 
 #endif
diff --git a/target/hexagon/machine.c b/target/hexagon/machine.c
new file mode 100644
index 0000000..2dd9546
--- /dev/null
+++ b/target/hexagon/machine.c
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "migration/vmstate.h"
+#include "cpu.h"
+
+const VMStateDescription vmstate_hexagon_cpu = {
+    .name = "cpu",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (const VMStateField[]) {
+        VMSTATE_UINT32_ARRAY(env.gpr, HexagonCPU, TOTAL_PER_THREAD_REGS),
+        VMSTATE_UINT32_ARRAY(env.pred, HexagonCPU, NUM_PREGS),
+        VMSTATE_UINT32_ARRAY(env.t_sreg, HexagonCPU, NUM_SREGS),
+        VMSTATE_UINT32_ARRAY(env.greg, HexagonCPU, NUM_GREGS),
+        VMSTATE_UINT32(env.next_PC, HexagonCPU),
+        VMSTATE_UINT32(env.tlb_lock_state, HexagonCPU),
+        VMSTATE_UINT32(env.k0_lock_state, HexagonCPU),
+        VMSTATE_UINT32(env.tlb_lock_count, HexagonCPU),
+        VMSTATE_UINT32(env.k0_lock_count, HexagonCPU),
+        VMSTATE_UINT32(env.threadId, HexagonCPU),
+        VMSTATE_UINT32(env.cause_code, HexagonCPU),
+        VMSTATE_UINT32(env.wait_next_pc, HexagonCPU),
+        VMSTATE_UINT64(env.t_cycle_count, HexagonCPU),
+
+        VMSTATE_END_OF_LIST()
+    },
+};
diff --git a/target/hexagon/macros.h b/target/hexagon/macros.h
index eebfe1e..21ab8ae 100644
--- a/target/hexagon/macros.h
+++ b/target/hexagon/macros.h
@@ -538,9 +538,6 @@
 
 #ifdef CONFIG_USER_ONLY
 #define fFRAMECHECK(ADDR, EA) do { } while (0) /* Not modelled in linux-user */
-#else
-/* System mode not implemented yet */
-#define fFRAMECHECK(ADDR, EA)  g_assert_not_reached();
 #endif
 
 #ifdef QEMU_GENERATE
@@ -631,8 +628,18 @@
 #define fCONSTLL(A) A##LL
 #define fECHO(A) (A)
 
-#define fTRAP(TRAPTYPE, IMM) helper_raise_exception(env, HEX_EXCP_TRAP0)
+#ifdef CONFIG_USER_ONLY
+#define fTRAP(TRAPTYPE, IMM) \
+    do { \
+        hexagon_raise_exception_err(env, HEX_EVENT_TRAP0, PC); \
+    } while (0)
+#endif
+
+#define fDO_TRACE(SREG)
+#define fBREAK()
+#define fUNPAUSE()
 #define fPAUSE(IMM)
+#define fDCFETCH(REG)
 
 #define fALIGN_REG_FIELD_VALUE(FIELD, VAL) \
     ((VAL) << reg_field_info[FIELD].offset)
@@ -643,6 +650,16 @@
                    reg_field_info[FIELD].width, \
                    reg_field_info[FIELD].offset)
 
+#define fGET_FIELD(VAL, FIELD) \
+    fEXTRACTU_BITS(VAL, \
+                   reg_field_info[FIELD].width, \
+                   reg_field_info[FIELD].offset)
+#define fSET_FIELD(VAL, FIELD, NEWVAL) \
+    fINSERT_BITS(VAL, \
+                 reg_field_info[FIELD].width, \
+                 reg_field_info[FIELD].offset, \
+                 (NEWVAL))
+
 #ifdef QEMU_GENERATE
 #define fDCZEROA(REG) \
     do { \
@@ -654,5 +671,18 @@
 #define fBRANCH_SPECULATE_STALL(DOTNEWVAL, JUMP_COND, SPEC_DIR, HINTBITNUM, \
                                 STRBITNUM) /* Nothing */
 
+#ifdef CONFIG_USER_ONLY
+/*
+ * This macro can only be true in guest mode.
+ * In user mode, the 4 VIRTINSN's can't be reached
+ */
+#define fTRAP1_VIRTINSN(IMM)       (false)
+#define fVIRTINSN_SPSWAP(IMM, REG) g_assert_not_reached()
+#define fVIRTINSN_GETIE(IMM, REG)  g_assert_not_reached()
+#define fVIRTINSN_SETIE(IMM, REG)  g_assert_not_reached()
+#define fVIRTINSN_RTE(IMM, REG)    g_assert_not_reached()
+#endif
+
+#define fPREDUSE_TIMING()
 
 #endif
diff --git a/target/hexagon/meson.build b/target/hexagon/meson.build
index d169cf7..59cb09c 100644
--- a/target/hexagon/meson.build
+++ b/target/hexagon/meson.build
@@ -20,6 +20,7 @@
 hex_common_py = 'hex_common.py'
 gen_tcg_h = meson.current_source_dir() / 'gen_tcg.h'
 gen_tcg_hvx_h = meson.current_source_dir() / 'gen_tcg_hvx.h'
+gen_tcg_sys_h = meson.current_source_dir() / 'gen_tcg_sys.h'
 idef_parser_dir = meson.current_source_dir() / 'idef-parser'
 
 #
@@ -234,6 +235,7 @@
     command: [python, files('gen_trans_funcs.py'), semantics_generated, '@OUTPUT@'],
 )
 hexagon_ss.add(decodetree_trans_funcs_generated)
+hexagon_softmmu_ss = ss.source_set()
 
 hexagon_ss.add(files(
     'cpu.c',
@@ -252,6 +254,14 @@
     'mmvec/system_ext_mmvec.c',
 ))
 
+hexagon_softmmu_ss.add(files(
+    'cpu_helper.c',
+    'hex_mmu.c',
+    'hex_interrupts.c',
+    'hexswi.c',
+    'machine.c',
+))
+
 #
 # Step 4.5
 # We use flex/bison based idef-parser to generate TCG code for a lot
@@ -261,7 +271,8 @@
 #     idef-generated-enabled-instructions
 #
 idef_parser_enabled = get_option('hexagon_idef_parser')
-if idef_parser_enabled and 'hexagon-linux-user' in target_dirs
+if idef_parser_enabled and ('hexagon-linux-user' in target_dirs or
+                            'hexagon-softmmu' in target_dirs)
     idef_parser_input_generated = custom_target(
         'idef_parser_input.h.inc',
         output: 'idef_parser_input.h.inc',
@@ -270,13 +281,13 @@
         command: [python, files('gen_idef_parser_funcs.py'), semantics_generated, '@OUTPUT@'],
     )
 
-    compiler = meson.get_compiler('c').get_id()
+    compiler = meson.get_compiler('c').cmd_array()
     preprocessed_idef_parser_input_generated = custom_target(
         'idef_parser_input.preprocessed.h.inc',
         output: 'idef_parser_input.preprocessed.h.inc',
         input: idef_parser_input_generated,
         depend_files: [idef_parser_dir / 'macros.h.inc'],
-        command: [compiler, '-x', 'c', '-E', '-I', idef_parser_dir, '-o', '@OUTPUT@', '@INPUT@'],
+        command: compiler + ['-x', 'c', '-E', '-I', idef_parser_dir, '-o', '@OUTPUT@', '@INPUT@'],
     )
 
     flex = generator(
@@ -337,12 +348,12 @@
     # Setup input and dependencies for the next step, this depends on whether or
     # not idef-parser is enabled
     helper_dep = [semantics_generated, idef_generated_tcg_c, idef_generated_tcg]
-    helper_in = [semantics_generated, gen_tcg_h, gen_tcg_hvx_h, '--idef-parser', idef_generated_list]
+    helper_in = [semantics_generated, gen_tcg_h, gen_tcg_hvx_h, gen_tcg_sys_h, '--idef-parser', idef_generated_list]
 else
     # Setup input and dependencies for the next step, this depends on whether or
     # not idef-parser is enabled
     helper_dep = [semantics_generated]
-    helper_in = [semantics_generated, gen_tcg_h, gen_tcg_hvx_h]
+    helper_in = [semantics_generated, gen_tcg_h, gen_tcg_hvx_h, gen_tcg_sys_h]
 endif
 
 #
@@ -356,7 +367,7 @@
     'helper_protos_generated.h.inc',
     output: 'helper_protos_generated.h.inc',
     depends: helper_dep,
-    depend_files: [hex_common_py, gen_tcg_h, gen_tcg_hvx_h],
+    depend_files: [hex_common_py, gen_tcg_h, gen_tcg_hvx_h, gen_tcg_sys_h],
     command: [python, files('gen_helper_protos.py'), helper_in, '@OUTPUT@'],
 )
 hexagon_ss.add(helper_protos_generated)
@@ -365,7 +376,7 @@
     'helper_funcs_generated.c.inc',
     output: 'helper_funcs_generated.c.inc',
     depends: helper_dep,
-    depend_files: [hex_common_py, gen_tcg_h, gen_tcg_hvx_h],
+    depend_files: [hex_common_py, gen_tcg_h, gen_tcg_hvx_h, gen_tcg_sys_h],
     command: [python, files('gen_helper_funcs.py'), helper_in, '@OUTPUT@'],
 )
 hexagon_ss.add(helper_funcs_generated)
@@ -374,7 +385,7 @@
     'tcg_funcs_generated.c.inc',
     output: 'tcg_funcs_generated.c.inc',
     depends: helper_dep,
-    depend_files: [hex_common_py, gen_tcg_h, gen_tcg_hvx_h],
+    depend_files: [hex_common_py, gen_tcg_h, gen_tcg_hvx_h, gen_tcg_sys_h],
     command: [python, files('gen_tcg_funcs.py'), helper_in, '@OUTPUT@'],
 )
 hexagon_ss.add(tcg_funcs_generated)
@@ -383,9 +394,10 @@
     'analyze_funcs_generated.c.inc',
     output: 'analyze_funcs_generated.c.inc',
     depends: helper_dep,
-    depend_files: [hex_common_py, gen_tcg_h, gen_tcg_hvx_h],
+    depend_files: [hex_common_py, gen_tcg_h, gen_tcg_hvx_h, gen_tcg_sys_h],
     command: [python, files('gen_analyze_funcs.py'), helper_in, '@OUTPUT@'],
 )
 hexagon_ss.add(analyze_funcs_generated)
 
 target_arch += {'hexagon': hexagon_ss}
+target_system_arch += {'hexagon': hexagon_softmmu_ss}
diff --git a/target/hexagon/op_helper.c b/target/hexagon/op_helper.c
index 0a94643..125952a 100644
--- a/target/hexagon/op_helper.c
+++ b/target/hexagon/op_helper.c
@@ -20,36 +20,59 @@
 #include "accel/tcg/cpu-ldst.h"
 #include "accel/tcg/cpu-loop.h"
 #include "accel/tcg/probe.h"
+#include "qemu/main-loop.h"
+#include "cpu.h"
 #include "exec/helper-proto.h"
 #include "fpu/softfloat.h"
-#include "cpu.h"
+#include "exec/cpu-interrupt.h"
 #include "internal.h"
 #include "macros.h"
+#include "sys_macros.h"
 #include "arch.h"
 #include "hex_arch_types.h"
 #include "fma_emu.h"
 #include "mmvec/mmvec.h"
 #include "mmvec/macros.h"
 #include "op_helper.h"
+#include "cpu_helper.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 "hex_interrupts.h"
+#include "hexswi.h"
+#endif
 
 #define SF_BIAS        127
 #define SF_MANTBITS    23
 
 /* Exceptions processing helpers */
+G_NORETURN
+void do_raise_exception(CPUHexagonState *env, uint32_t exception,
+                        uint32_t PC, uintptr_t retaddr)
+{
+    CPUState *cs = env_cpu(env);
+    qemu_log_mask(CPU_LOG_INT, "%s: 0x%08" PRIx32 ", @ %08" PRIx32 "\n",
+                  __func__, exception, PC);
+    ASSERT_DIRECT_TO_GUEST_UNSET(env, exception);
+
+    env->gpr[HEX_REG_PC] = PC;
+    cs->exception_index = exception;
+    cpu_loop_exit_restore(cs, retaddr);
+}
+
 G_NORETURN void hexagon_raise_exception_err(CPUHexagonState *env,
                                             uint32_t exception,
                                             uintptr_t pc)
 {
-    CPUState *cs = env_cpu(env);
-    qemu_log_mask(CPU_LOG_INT, "%s: %d\n", __func__, exception);
-    cs->exception_index = exception;
-    cpu_loop_exit_restore(cs, pc);
+    do_raise_exception(env, exception, pc, 0);
 }
 
-G_NORETURN void HELPER(raise_exception)(CPUHexagonState *env, uint32_t excp)
+G_NORETURN void HELPER(raise_exception)(CPUHexagonState *env, uint32_t excp,
+                                        uint32_t PC)
 {
-    hexagon_raise_exception_err(env, excp, 0);
+    hexagon_raise_exception_err(env, excp, PC);
 }
 
 void log_store32(CPUHexagonState *env, target_ulong addr,
@@ -1205,6 +1228,131 @@
     return RxxV;
 }
 
+#ifndef CONFIG_USER_ONLY
+void HELPER(modify_ssr)(CPUHexagonState *env, uint32_t new, uint32_t old)
+{
+    BQL_LOCK_GUARD();
+    hexagon_modify_ssr(env, new, old);
+}
+
+static void hex_k0_lock(CPUHexagonState *env)
+{
+    HexagonCPU *cpu = env_archcpu(env);
+    CPUState *cs = env_cpu(env);
+    target_ulong syscfg;
+
+    BQL_LOCK_GUARD();
+    g_assert((env->k0_lock_count == 0) || (env->k0_lock_count == 1));
+
+    syscfg = cpu->globalregs ?
+        hexagon_globalreg_read(cpu->globalregs, HEX_SREG_SYSCFG,
+                               env->threadId) : 0;
+    if (GET_SYSCFG_FIELD(SYSCFG_K0LOCK, syscfg)) {
+        if (env->k0_lock_state == HEX_LOCK_QUEUED) {
+            env->next_PC += 4;
+            env->k0_lock_count++;
+            env->k0_lock_state = HEX_LOCK_OWNER;
+            SET_SYSCFG_FIELD(env, SYSCFG_K0LOCK, 1);
+            return;
+        }
+        if (env->k0_lock_state == HEX_LOCK_OWNER) {
+            qemu_log_mask(LOG_GUEST_ERROR,
+                          "Double k0lock at PC: 0x%" PRIx32
+                          ", thread may hang\n",
+                          env->next_PC);
+            env->next_PC += 4;
+            cpu_interrupt(cs, CPU_INTERRUPT_HALT);
+            cpu_loop_exit(cs);
+            return;
+        }
+        env->k0_lock_state = HEX_LOCK_WAITING;
+        cpu_interrupt(cs, CPU_INTERRUPT_HALT);
+        cpu_loop_exit(cs);
+    } else {
+        env->next_PC += 4;
+        env->k0_lock_count++;
+        env->k0_lock_state = HEX_LOCK_OWNER;
+        SET_SYSCFG_FIELD(env, SYSCFG_K0LOCK, 1);
+    }
+}
+
+static void hex_k0_unlock(CPUHexagonState *env)
+{
+    HexagonCPU *cpu = env_archcpu(env);
+    unsigned int this_threadId = env->threadId;
+    CPUHexagonState *unlock_thread = NULL;
+    CPUState *cs;
+    target_ulong syscfg;
+
+    BQL_LOCK_GUARD();
+    g_assert((env->k0_lock_count == 0) || (env->k0_lock_count == 1));
+
+    /* Nothing to do if the k0 isn't locked by this thread */
+    syscfg = cpu->globalregs ?
+        hexagon_globalreg_read(cpu->globalregs, HEX_SREG_SYSCFG,
+                               env->threadId) : 0;
+    if ((GET_SYSCFG_FIELD(SYSCFG_K0LOCK, syscfg) == 0) ||
+        (env->k0_lock_state != HEX_LOCK_OWNER)) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "thread %" PRIu32 " attempted to unlock k0 without"
+                      " having the lock, k0_lock state = %u,"
+                      " syscfg:k0 = %" PRIu32 "\n",
+                      env->threadId, (unsigned)env->k0_lock_state,
+                      (uint32_t)GET_SYSCFG_FIELD(SYSCFG_K0LOCK, syscfg));
+        g_assert(env->k0_lock_state != HEX_LOCK_WAITING);
+        return;
+    }
+
+    env->k0_lock_count--;
+    env->k0_lock_state = HEX_LOCK_UNLOCKED;
+    SET_SYSCFG_FIELD(env, SYSCFG_K0LOCK, 0);
+
+    /* Look for a thread to unlock */
+    CPU_FOREACH(cs) {
+        CPUHexagonState *thread = cpu_env(cs);
+
+        /*
+         * The hardware implements round-robin fairness, so we look for threads
+         * starting at env->threadId + 1 and incrementing modulo the number of
+         * threads.
+         *
+         * To implement this, we check if thread is a earlier in the modulo
+         * sequence than unlock_thread.
+         *     if unlock thread is higher than this thread
+         *         thread must be between this thread and unlock_thread
+         *     else
+         *         thread higher than this thread is ahead of unlock_thread
+         *         thread must be lower then unlock thread
+         */
+        if (thread->k0_lock_state == HEX_LOCK_WAITING) {
+            if (!unlock_thread) {
+                unlock_thread = thread;
+            } else if (unlock_thread->threadId > this_threadId) {
+                if (this_threadId < thread->threadId &&
+                    thread->threadId < unlock_thread->threadId) {
+                    unlock_thread = thread;
+                }
+            } else {
+                if (thread->threadId > this_threadId) {
+                    unlock_thread = thread;
+                }
+                if (thread->threadId < unlock_thread->threadId) {
+                    unlock_thread = thread;
+                }
+            }
+        }
+    }
+    if (unlock_thread) {
+        cs = env_cpu(unlock_thread);
+        unlock_thread->k0_lock_state = HEX_LOCK_QUEUED;
+        SET_SYSCFG_FIELD(unlock_thread, SYSCFG_K0LOCK, 1);
+        cpu_interrupt(cs, CPU_INTERRUPT_K0_UNLOCK);
+    }
+
+}
+#endif
+
+
 /* Histogram instructions */
 
 void HELPER(vhist)(CPUHexagonState *env)
@@ -1370,6 +1518,420 @@
     }
 }
 
+#ifndef CONFIG_USER_ONLY
+void HELPER(raise_stack_overflow)(CPUHexagonState *env, uint32_t slot,
+                                  uint32_t badva)
+{
+    /*
+     * Per section 7.3.1 of the V67 Programmer's Reference,
+     * stack limit exception isn't raised in monitor mode.
+     */
+    uint32_t ssr = env->t_sreg[HEX_SREG_SSR];
+    CPUState *cs;
+
+    if (GET_SSR_FIELD(SSR_EX, ssr) ||
+        !GET_SSR_FIELD(SSR_UM, ssr)) {
+        return;
+    }
+
+    cs = env_cpu(env);
+    cs->exception_index = HEX_EVENT_PRECISE;
+    env->cause_code = HEX_CAUSE_STACK_LIMIT;
+    ASSERT_DIRECT_TO_GUEST_UNSET(env, cs->exception_index);
+
+    if (slot == 0) {
+        env->t_sreg[HEX_SREG_BADVA0] = badva;
+        SET_SSR_FIELD(env, SSR_V0, 1);
+        SET_SSR_FIELD(env, SSR_V1, 0);
+        SET_SSR_FIELD(env, SSR_BVS, 0);
+    } else if (slot == 1) {
+        env->t_sreg[HEX_SREG_BADVA1] = badva;
+        SET_SSR_FIELD(env, SSR_V0, 0);
+        SET_SSR_FIELD(env, SSR_V1, 1);
+        SET_SSR_FIELD(env, SSR_BVS, 1);
+    } else {
+        g_assert_not_reached();
+    }
+    cpu_loop_exit_restore(cs, 0);
+}
+
+void HELPER(ciad)(CPUHexagonState *env, uint32_t mask)
+{
+    g_assert_not_reached();
+}
+
+void HELPER(siad)(CPUHexagonState *env, uint32_t mask)
+{
+    uint32_t ipendad;
+    uint32_t iad;
+    HexagonCPU *cpu;
+
+    BQL_LOCK_GUARD();
+    cpu = env_archcpu(env);
+    ipendad = cpu->globalregs ?
+        hexagon_globalreg_read(cpu->globalregs, HEX_SREG_IPENDAD,
+                               env->threadId) : 0;
+    iad = fGET_FIELD(ipendad, IPENDAD_IAD);
+    fSET_FIELD(ipendad, IPENDAD_IAD, iad | mask);
+    if (cpu->globalregs) {
+        hexagon_globalreg_write(cpu->globalregs, HEX_SREG_IPENDAD,
+                                ipendad, env->threadId);
+    }
+    hex_interrupt_update(env);
+}
+
+void HELPER(swi)(CPUHexagonState *env, uint32_t mask)
+{
+    BQL_LOCK_GUARD();
+    hex_raise_interrupts(env, mask, CPU_INTERRUPT_SWI);
+}
+
+void HELPER(cswi)(CPUHexagonState *env, uint32_t mask)
+{
+    BQL_LOCK_GUARD();
+    hex_clear_interrupts(env, mask, CPU_INTERRUPT_SWI);
+}
+
+void HELPER(iassignw)(CPUHexagonState *env, uint32_t src)
+{
+    uint32_t modectl;
+    uint32_t thread_enabled_mask;
+    CPUState *cpu;
+    HexagonCPU *hex_cpu;
+
+    BQL_LOCK_GUARD();
+    hex_cpu = env_archcpu(env);
+    modectl = hex_cpu->globalregs ?
+        hexagon_globalreg_read(hex_cpu->globalregs, HEX_SREG_MODECTL,
+                               env->threadId) : 0;
+    thread_enabled_mask = GET_FIELD(MODECTL_E, modectl);
+
+    CPU_FOREACH(cpu) {
+        CPUHexagonState *thread_env = &(HEXAGON_CPU(cpu)->env);
+        uint32_t thread_id_mask = 0x1 << thread_env->threadId;
+        if (thread_enabled_mask & thread_id_mask) {
+            uint32_t imask = thread_env->t_sreg[HEX_SREG_IMASK];
+            uint32_t intbitpos = (src >> 16) & 0xF;
+            uint32_t val = (src >> thread_env->threadId) & 0x1;
+            imask = deposit32(imask, intbitpos, 1, val);
+            thread_env->t_sreg[HEX_SREG_IMASK] = imask;
+
+            qemu_log_mask(CPU_LOG_INT, "%s: thread " TARGET_FMT_ld
+               ", new imask 0x%" PRIx32 "\n", __func__,
+               thread_env->threadId, imask);
+        }
+    }
+    hex_interrupt_update(env);
+}
+
+uint32_t HELPER(iassignr)(CPUHexagonState *env, uint32_t src)
+{
+    uint32_t modectl;
+    uint32_t thread_enabled_mask;
+    uint32_t intbitpos;
+    uint32_t dest_reg;
+    CPUState *cpu;
+    HexagonCPU *hex_cpu;
+
+    BQL_LOCK_GUARD();
+    hex_cpu = env_archcpu(env);
+    modectl = hex_cpu->globalregs ?
+        hexagon_globalreg_read(hex_cpu->globalregs, HEX_SREG_MODECTL,
+                               env->threadId) : 0;
+    thread_enabled_mask = GET_FIELD(MODECTL_E, modectl);
+    /* src fields are in same position as modectl, but mean different things */
+    intbitpos = GET_FIELD(MODECTL_W, src);
+    dest_reg = 0;
+    CPU_FOREACH(cpu) {
+        CPUHexagonState *thread_env = &(HEXAGON_CPU(cpu)->env);
+        uint32_t thread_id_mask = 0x1 << thread_env->threadId;
+        if (thread_enabled_mask & thread_id_mask) {
+            uint32_t imask = thread_env->t_sreg[HEX_SREG_IMASK];
+            dest_reg |= ((imask >> intbitpos) & 0x1) << thread_env->threadId;
+        }
+    }
+
+    return dest_reg;
+}
+
+void HELPER(start)(CPUHexagonState *env, uint32_t imask)
+{
+    hexagon_start_threads(env, imask);
+}
+
+void HELPER(stop)(CPUHexagonState *env)
+{
+    hexagon_stop_thread(env);
+}
+
+static void set_wait_mode(CPUHexagonState *env)
+{
+    HexagonCPU *cpu;
+    uint32_t modectl;
+    uint32_t thread_wait_mask;
+
+    g_assert(bql_locked());
+
+    cpu = env_archcpu(env);
+    if (!cpu->globalregs) {
+        return;
+    }
+    modectl =
+        hexagon_globalreg_read(cpu->globalregs, HEX_SREG_MODECTL,
+                               env->threadId);
+    thread_wait_mask = GET_FIELD(MODECTL_W, modectl);
+    thread_wait_mask |= 0x1 << env->threadId;
+    SET_SYSTEM_FIELD(env, HEX_SREG_MODECTL, MODECTL_W, thread_wait_mask);
+}
+
+static void hexagon_wait_thread(CPUHexagonState *env, uint32_t PC)
+{
+    CPUState *cs;
+
+    g_assert(bql_locked());
+
+    if (qemu_loglevel_mask(LOG_GUEST_ERROR) &&
+        (env->k0_lock_state != HEX_LOCK_UNLOCKED ||
+         env->tlb_lock_state != HEX_LOCK_UNLOCKED)) {
+        qemu_log("WARNING: executing wait() with acquired lock"
+                 "may lead to deadlock\n");
+    }
+    g_assert(get_exe_mode(env) != HEX_EXE_MODE_WAIT);
+
+    cs = env_cpu(env);
+    /*
+     * The addtion of cpu_has_work is borrowed from arm's wfi helper
+     * and is critical for our stability
+     */
+    if ((cs->exception_index != HEX_EVENT_NONE) ||
+        (cpu_has_work(cs))) {
+        qemu_log_mask(CPU_LOG_INT,
+            "%s: thread %" PRIu32 " skipping WAIT mode, have some work\n",
+            __func__, env->threadId);
+        return;
+    }
+    set_wait_mode(env);
+    env->wait_next_pc = PC + 4;
+
+    cpu_interrupt(cs, CPU_INTERRUPT_HALT);
+}
+
+static inline QEMU_ALWAYS_INLINE void resched(CPUHexagonState *env)
+{
+    uint32_t schedcfg;
+    uint32_t schedcfg_en;
+    int int_number;
+    CPUState *cs;
+    uint32_t lowest_th_prio = 0; /* 0 is highest prio */
+    uint32_t bestwait_reg;
+    uint32_t best_prio;
+    HexagonCPU *cpu;
+
+    BQL_LOCK_GUARD();
+    qemu_log_mask(CPU_LOG_INT, "%s: check resched\n", __func__);
+    cpu = env_archcpu(env);
+    schedcfg = cpu->globalregs ?
+        hexagon_globalreg_read(cpu->globalregs, HEX_SREG_SCHEDCFG,
+                               env->threadId) : 0;
+    schedcfg_en = GET_FIELD(SCHEDCFG_EN, schedcfg);
+    int_number = GET_FIELD(SCHEDCFG_INTNO, schedcfg);
+
+    if (!schedcfg_en) {
+        return;
+    }
+
+    CPU_FOREACH(cs) {
+        HexagonCPU *thread = HEXAGON_CPU(cs);
+        CPUHexagonState *thread_env = &(thread->env);
+        uint32_t th_prio = GET_FIELD(
+            STID_PRIO, thread_env->t_sreg[HEX_SREG_STID]);
+        if (!hexagon_thread_is_enabled(thread_env)) {
+            continue;
+        }
+
+        lowest_th_prio = (lowest_th_prio > th_prio)
+            ? lowest_th_prio
+            : th_prio;
+    }
+
+    bestwait_reg = cpu->globalregs ?
+        hexagon_globalreg_read(cpu->globalregs, HEX_SREG_BESTWAIT,
+                               env->threadId) : 0;
+    best_prio = GET_FIELD(BESTWAIT_PRIO, bestwait_reg);
+
+    /*
+     * If the lowest priority thread is lower priority than the
+     * value in the BESTWAIT register, we must raise the reschedule
+     * interrupt on the lowest priority thread.
+     */
+    if (lowest_th_prio > best_prio) {
+        qemu_log_mask(CPU_LOG_INT,
+                "%s: raising resched int %u,"
+                " cur PC 0x%" PRIx32 "\n",
+                __func__, (unsigned)int_number, env->gpr[HEX_REG_PC]);
+        SET_SYSTEM_FIELD(env, HEX_SREG_BESTWAIT, BESTWAIT_PRIO, ~0);
+        hex_raise_interrupts(env, 1 << int_number, CPU_INTERRUPT_SWI);
+    }
+}
+
+void HELPER(resched)(CPUHexagonState *env)
+{
+    resched(env);
+}
+
+void HELPER(wait)(CPUHexagonState *env, uint32_t PC)
+{
+    BQL_LOCK_GUARD();
+
+    if (!fIN_DEBUG_MODE(env->threadId)) {
+        hexagon_wait_thread(env, PC);
+    }
+}
+
+void HELPER(resume)(CPUHexagonState *env, uint32_t mask)
+{
+    BQL_LOCK_GUARD();
+    hexagon_resume_threads(env, mask);
+}
+
+uint32_t HELPER(getimask)(CPUHexagonState *env, uint32_t tid)
+{
+    CPUState *cs;
+    BQL_LOCK_GUARD();
+    CPU_FOREACH(cs) {
+        HexagonCPU *found_cpu = HEXAGON_CPU(cs);
+        CPUHexagonState *found_env = &found_cpu->env;
+        if (found_env->threadId == tid) {
+            uint32_t imask = found_env->t_sreg[HEX_SREG_IMASK];
+            qemu_log_mask(CPU_LOG_INT, "%s: tid " TARGET_FMT_lx
+                          " imask = 0x%" PRIx32 "\n", __func__,
+                          env->threadId,
+                          (uint32_t)GET_FIELD(IMASK_MASK, imask));
+            return GET_FIELD(IMASK_MASK, imask);
+        }
+    }
+    return 0;
+}
+
+void HELPER(setimask)(CPUHexagonState *env, uint32_t tid, uint32_t imask)
+{
+    CPUState *cs;
+
+    BQL_LOCK_GUARD();
+    CPU_FOREACH(cs) {
+        HexagonCPU *found_cpu = HEXAGON_CPU(cs);
+        CPUHexagonState *found_env = &found_cpu->env;
+
+        if (tid == found_env->threadId) {
+            SET_SYSTEM_FIELD(found_env, HEX_SREG_IMASK, IMASK_MASK, imask);
+            qemu_log_mask(CPU_LOG_INT, "%s: tid " TARGET_FMT_lx
+                          " imask 0x%" PRIx32 "\n",
+                          __func__, found_env->threadId, imask);
+            hex_interrupt_update(found_env);
+            return;
+        }
+    }
+    qemu_log_mask(LOG_GUEST_ERROR,
+                  "setimask used with an invalid tid near PC: 0x%"
+                  PRIx32 "\n", env->next_PC);
+}
+
+void HELPER(sreg_write_masked)(CPUHexagonState *env, uint32_t reg, uint32_t val)
+{
+    BQL_LOCK_GUARD();
+    if (reg < HEX_SREG_GLB_START) {
+        env->t_sreg[reg] = val;
+    } else {
+        HexagonCPU *cpu = env_archcpu(env);
+        if (cpu->globalregs) {
+            hexagon_globalreg_write_masked(cpu->globalregs, reg, val);
+        }
+    }
+}
+
+static inline QEMU_ALWAYS_INLINE uint32_t sreg_read(CPUHexagonState *env,
+                                                    uint32_t reg)
+{
+    HexagonCPU *cpu;
+
+    g_assert(bql_locked());
+    if (reg < HEX_SREG_GLB_START) {
+        return env->t_sreg[reg];
+    }
+    cpu = env_archcpu(env);
+    return cpu->globalregs ?
+        hexagon_globalreg_read(cpu->globalregs, reg, env->threadId) : 0;
+}
+
+uint32_t HELPER(sreg_read)(CPUHexagonState *env, uint32_t reg)
+{
+    BQL_LOCK_GUARD();
+    return sreg_read(env, reg);
+}
+
+uint64_t HELPER(sreg_read_pair)(CPUHexagonState *env, uint32_t reg)
+{
+    BQL_LOCK_GUARD();
+
+    return deposit64((uint64_t) sreg_read(env, reg), 32, 32,
+        sreg_read(env, reg + 1));
+}
+
+uint32_t HELPER(greg_read)(CPUHexagonState *env, uint32_t reg)
+
+{
+    return hexagon_greg_read(env, reg);
+}
+
+uint64_t HELPER(greg_read_pair)(CPUHexagonState *env, uint32_t reg)
+
+{
+    if (reg == HEX_GREG_G0 || reg == HEX_GREG_G2) {
+        return (uint64_t)(env->greg[reg]) |
+               (((uint64_t)(env->greg[reg + 1])) << 32);
+    }
+    switch (reg) {
+    case HEX_GREG_GPCYCLELO:
+        return hexagon_get_sys_pcycle_count(env);
+    default:
+        return (uint64_t)hexagon_greg_read(env, reg) |
+               ((uint64_t)(hexagon_greg_read(env, reg + 1)) << 32);
+    }
+}
+
+/*
+ * setprio/resched - hardware-assisted scheduler helpers for managing
+ * the run queue and interrupt steering.
+ */
+void HELPER(setprio)(CPUHexagonState *env, uint32_t thread, uint32_t prio)
+{
+    CPUState *cs;
+
+    BQL_LOCK_GUARD();
+    CPU_FOREACH(cs) {
+        HexagonCPU *found_cpu = HEXAGON_CPU(cs);
+        CPUHexagonState *found_env = &found_cpu->env;
+        if (thread == found_env->threadId) {
+            SET_SYSTEM_FIELD(found_env, HEX_SREG_STID, STID_PRIO, prio);
+            qemu_log_mask(CPU_LOG_INT,
+                          "%s: tid %" PRIu32 " prio = 0x%" PRIx32 "\n",
+                          __func__, found_env->threadId, prio);
+            resched(env);
+            return;
+        }
+    }
+    g_assert_not_reached();
+}
+
+
+void HELPER(pending_interrupt)(CPUHexagonState *env)
+{
+    BQL_LOCK_GUARD();
+    hex_interrupt_update(env);
+}
+#endif
+
+
 /* These macros can be referenced in the generated helper functions */
 #define warn(...) /* Nothing */
 #define fatal(...) g_assert_not_reached();
diff --git a/target/hexagon/reg_fields_def.h.inc b/target/hexagon/reg_fields_def.h.inc
index f2a58d4..d2c706d 100644
--- a/target/hexagon/reg_fields_def.h.inc
+++ b/target/hexagon/reg_fields_def.h.inc
@@ -39,3 +39,110 @@
 DEF_REG_FIELD(USR_FPOVFE,        27, 1)
 DEF_REG_FIELD(USR_FPUNFE,        28, 1)
 DEF_REG_FIELD(USR_FPINPE,        29, 1)
+
+DEF_REG_FIELD(IPENDAD_IAD, 16, 16)
+DEF_REG_FIELD(IPENDAD_IPEND, 0, 16)
+
+DEF_REG_FIELD(SCHEDCFG_EN, 8, 1)
+DEF_REG_FIELD(SCHEDCFG_INTNO, 0, 4)
+DEF_REG_FIELD(BESTWAIT_PRIO, 0, 9)
+
+
+/* PTE (aka TLB entry) fields */
+DEF_REG_FIELD(PTE_PPD, 0, 24)
+DEF_REG_FIELD(PTE_C, 24, 4)
+DEF_REG_FIELD(PTE_U, 28, 1)
+DEF_REG_FIELD(PTE_R, 29, 1)
+DEF_REG_FIELD(PTE_W, 30, 1)
+DEF_REG_FIELD(PTE_X, 31, 1)
+DEF_REG_FIELD(PTE_VPN, 32, 20)
+DEF_REG_FIELD(PTE_ASID, 52, 7)
+DEF_REG_FIELD(PTE_ATR0, 59, 1)
+DEF_REG_FIELD(PTE_ATR1, 60, 1)
+DEF_REG_FIELD(PTE_PA35, 61, 1)
+DEF_REG_FIELD(PTE_G, 62, 1)
+DEF_REG_FIELD(PTE_V, 63, 1)
+
+/* SYSCFG fields */
+DEF_REG_FIELD(SYSCFG_MMUEN, 0, 1)
+DEF_REG_FIELD(SYSCFG_ICEN, 1, 1)
+DEF_REG_FIELD(SYSCFG_DCEN, 2, 1)
+DEF_REG_FIELD(SYSCFG_ISDBTRUSTED, 3, 1)
+DEF_REG_FIELD(SYSCFG_GIE, 4, 1)
+DEF_REG_FIELD(SYSCFG_ISDBREADY, 5, 1)
+DEF_REG_FIELD(SYSCFG_PCYCLEEN, 6, 1)
+DEF_REG_FIELD(SYSCFG_V2X, 7, 1)
+DEF_REG_FIELD(SYSCFG_IGNOREDABORT, 8, 1)
+DEF_REG_FIELD(SYSCFG_PM, 9, 1)
+DEF_REG_FIELD(SYSCFG_TLBLOCK, 11, 1)
+DEF_REG_FIELD(SYSCFG_K0LOCK, 12, 1)
+DEF_REG_FIELD(SYSCFG_BQ, 13, 1)
+DEF_REG_FIELD(SYSCFG_PRIO, 14, 1)
+DEF_REG_FIELD(SYSCFG_DMT, 15, 1)
+DEF_REG_FIELD(SYSCFG_L2CFG, 16, 3)
+DEF_REG_FIELD(SYSCFG_ITCM, 19, 1)
+DEF_REG_FIELD(SYSCFG_L2NWA, 21, 1)
+DEF_REG_FIELD(SYSCFG_L2NRA, 22, 1)
+DEF_REG_FIELD(SYSCFG_L2WB, 23, 1)
+DEF_REG_FIELD(SYSCFG_L2P, 24, 1)
+DEF_REG_FIELD(SYSCFG_SLVCTL0, 25, 2)
+DEF_REG_FIELD(SYSCFG_SLVCTL1, 27, 2)
+DEF_REG_FIELD(SYSCFG_L2PARTSIZE, 29, 2)
+DEF_REG_FIELD(SYSCFG_L2GCA, 31, 1)
+
+/* SSR fields */
+DEF_REG_FIELD(SSR_CAUSE, 0, 8)
+DEF_REG_FIELD(SSR_ASID, 8, 7)
+DEF_REG_FIELD(SSR_UM, 16, 1)
+DEF_REG_FIELD(SSR_EX, 17, 1)
+DEF_REG_FIELD(SSR_IE, 18, 1)
+DEF_REG_FIELD(SSR_GM, 19, 1)
+DEF_REG_FIELD(SSR_V0, 20, 1)
+DEF_REG_FIELD(SSR_V1, 21, 1)
+DEF_REG_FIELD(SSR_BVS, 22, 1)
+DEF_REG_FIELD(SSR_CE, 23, 1)
+DEF_REG_FIELD(SSR_PE, 24, 1)
+DEF_REG_FIELD(SSR_BP, 25, 1)
+DEF_REG_FIELD(SSR_XE2, 26, 1)
+DEF_REG_FIELD(SSR_XA, 27, 3)
+DEF_REG_FIELD(SSR_SS, 30, 1)
+DEF_REG_FIELD(SSR_XE, 31, 1)
+
+/* misc registers */
+DEF_REG_FIELD(IMASK_MASK, 0, 16)
+
+DEF_REG_FIELD(STID_PRIO, 16, 8)
+DEF_REG_FIELD(STID_STID, 0, 8)
+
+/* MODECTL fields */
+DEF_REG_FIELD(MODECTL_E, 0, 8)
+DEF_REG_FIELD(MODECTL_W, 16, 8)
+
+DEF_REG_FIELD(CCR_L1ICP, 0, 2)
+DEF_REG_FIELD(CCR_L1DCP, 3, 2)
+DEF_REG_FIELD(CCR_L2CP, 6, 2)
+
+DEF_REG_FIELD(CCR_HFI, 16, 1)
+DEF_REG_FIELD(CCR_HFD, 17, 1)
+DEF_REG_FIELD(CCR_HFIL2, 18, 1)
+DEF_REG_FIELD(CCR_HFDL2, 19, 1)
+DEF_REG_FIELD(CCR_SFD, 20, 1)
+
+DEF_REG_FIELD(CCR_GIE, 24, 1)
+DEF_REG_FIELD(CCR_GTE, 25, 1)
+DEF_REG_FIELD(CCR_GEE, 26, 1)
+DEF_REG_FIELD(CCR_GRE, 27, 1)
+DEF_REG_FIELD(CCR_VV1, 29, 1)
+DEF_REG_FIELD(CCR_VV2, 30, 1)
+DEF_REG_FIELD(CCR_VV3, 31, 1)
+
+/* ISDB ST fields */
+DEF_REG_FIELD(ISDBST_WAITRUN, 24, 8)
+DEF_REG_FIELD(ISDBST_ONOFF, 16, 8)
+DEF_REG_FIELD(ISDBST_DEBUGMODE, 8, 8)
+DEF_REG_FIELD(ISDBST_STUFFSTATUS, 5, 1)
+DEF_REG_FIELD(ISDBST_CMDSTATUS, 4, 1)
+DEF_REG_FIELD(ISDBST_PROCMODE, 3, 1)
+DEF_REG_FIELD(ISDBST_MBXINSTATUS, 2, 1)
+DEF_REG_FIELD(ISDBST_MBXOUTSTATUS, 1, 1)
+DEF_REG_FIELD(ISDBST_READY, 0, 1)
diff --git a/target/hexagon/sys_macros.h b/target/hexagon/sys_macros.h
new file mode 100644
index 0000000..a49c727
--- /dev/null
+++ b/target/hexagon/sys_macros.h
@@ -0,0 +1,252 @@
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HEXAGON_SYS_MACROS_H
+#define HEXAGON_SYS_MACROS_H
+
+/*
+ * Macro definitions for Hexagon system mode
+ */
+
+#ifndef CONFIG_USER_ONLY
+
+#include "system/physmem.h"
+
+#ifdef QEMU_GENERATE
+#define GET_SSR_FIELD(RES, FIELD) \
+    GET_FIELD(RES, FIELD, hex_t_sreg[HEX_SREG_SSR])
+#else
+
+#define GET_SSR_FIELD(FIELD, REGIN) \
+    (uint32_t)GET_FIELD(FIELD, REGIN)
+#define GET_SYSCFG_FIELD(FIELD, REGIN) \
+    (uint32_t)GET_FIELD(FIELD, REGIN)
+#define SET_SYSTEM_FIELD(ENV, REG, FIELD, VAL) \
+    do { \
+        HexagonCPU *_sf_cpu = env_archcpu(ENV); \
+        uint32_t regval; \
+        if ((REG) < HEX_SREG_GLB_START) { \
+            regval = (ENV)->t_sreg[(REG)]; \
+        } else { \
+            regval = _sf_cpu->globalregs ? \
+                hexagon_globalreg_read(_sf_cpu->globalregs, (REG), \
+                                       (ENV)->threadId) : 0; \
+        } \
+        fINSERT_BITS(regval, reg_field_info[FIELD].width, \
+                     reg_field_info[FIELD].offset, (VAL)); \
+        if ((REG) < HEX_SREG_GLB_START) { \
+            (ENV)->t_sreg[(REG)] = regval; \
+        } else if (_sf_cpu->globalregs) { \
+            hexagon_globalreg_write(_sf_cpu->globalregs, (REG), regval, \
+                                    (ENV)->threadId); \
+        } \
+    } while (0)
+#define SET_SSR_FIELD(ENV, FIELD, VAL) \
+    SET_SYSTEM_FIELD(ENV, HEX_SREG_SSR, FIELD, VAL)
+#define SET_SYSCFG_FIELD(ENV, FIELD, VAL) \
+    SET_SYSTEM_FIELD(ENV, HEX_SREG_SYSCFG, FIELD, VAL)
+
+#define CCR_FIELD_SET(ENV, FIELD) \
+    (!!GET_FIELD(FIELD, (ENV)->t_sreg[HEX_SREG_CCR]))
+
+/*
+ * Direct-to-guest is not implemented yet, continuing would cause unexpected
+ * behavior, so we abort.
+ */
+#define ASSERT_DIRECT_TO_GUEST_UNSET(ENV, EXCP) \
+    do { \
+        switch (EXCP) { \
+        case HEX_EVENT_TRAP0: \
+            g_assert(!CCR_FIELD_SET(ENV, CCR_GTE)); \
+            break; \
+        case HEX_EVENT_IMPRECISE: \
+        case HEX_EVENT_PRECISE: \
+        case HEX_EVENT_FPTRAP: \
+            g_assert(!CCR_FIELD_SET(ENV, CCR_GEE)); \
+            break; \
+        default: \
+            if ((EXCP) >= HEX_EVENT_INT0) { \
+                g_assert(!CCR_FIELD_SET(ENV, CCR_GIE)); \
+            } \
+            break; \
+        } \
+    } while (0)
+#endif
+
+#define fREAD_ELR() (env->t_sreg[HEX_SREG_ELR])
+
+#define fLOAD_PHYS(NUM, SIZE, SIGN, SRC1, SRC2, DST) { \
+  const uintptr_t rs = ((unsigned long)(unsigned)(SRC1)) & 0x7ff; \
+  const uintptr_t rt = ((unsigned long)(unsigned)(SRC2)) << 11; \
+  const uintptr_t addr = rs + rt;         \
+  physical_memory_read(addr, &DST, sizeof(uint32_t)); \
+}
+
+#define fPOW2_HELP_ROUNDUP(VAL) \
+    ((VAL) | \
+     ((VAL) >> 1) | \
+     ((VAL) >> 2) | \
+     ((VAL) >> 4) | \
+     ((VAL) >> 8) | \
+     ((VAL) >> 16))
+#define fPOW2_ROUNDUP(VAL) (fPOW2_HELP_ROUNDUP((VAL) - 1) + 1)
+
+#define fTRAP(TRAPTYPE, IMM) \
+    register_trap_exception(env, TRAPTYPE, IMM, PC)
+
+#ifdef QEMU_GENERATE
+#define fFRAMECHECK(ADDR, EA) gen_framecheck(ctx, ADDR, EA)
+#endif
+
+#define fVIRTINSN_SPSWAP(IMM, REG)
+#define fVIRTINSN_GETIE(IMM, REG) { REG = 0xdeafbeef; }
+#define fVIRTINSN_SETIE(IMM, REG)
+#define fVIRTINSN_RTE(IMM, REG)
+#define fGRE_ENABLED() \
+    GET_FIELD(CCR_GRE, env->t_sreg[HEX_SREG_CCR])
+#define fTRAP1_VIRTINSN(IMM) \
+    (fGRE_ENABLED() && \
+        (((IMM) == 1) || ((IMM) == 3) || ((IMM) == 4) || ((IMM) == 6)))
+
+/* Not modeled in qemu */
+
+#define MARK_LATE_PRED_WRITE(RNUM)
+#define fICINVIDX(REG)
+#define fICKILL()
+#define fDCKILL()
+#define fL2KILL()
+#define fL2UNLOCK()
+#define fL2CLEAN()
+#define fL2CLEANINV()
+#define fL2CLEANPA(REG)
+#define fL2CLEANINVPA(REG)
+#define fL2CLEANINVIDX(REG)
+#define fL2CLEANIDX(REG)
+#define fL2INVIDX(REG)
+#define fL2TAGR(INDEX, DST, DSTREG)
+#define fL2UNLOCKA(VA) ((void) VA)
+#define fL2TAGW(INDEX, PART2)
+#define fDCCLEANIDX(REG)
+#define fDCCLEANINVIDX(REG)
+
+/* Always succeed: */
+#define fL2LOCKA(EA, PDV, PDN) ((void) EA, PDV = 0xFF)
+#define fCLEAR_RTE_EX() \
+    do { \
+        uint32_t tmp = env->t_sreg[HEX_SREG_SSR]; \
+        fINSERT_BITS(tmp, reg_field_info[SSR_EX].width, \
+                     reg_field_info[SSR_EX].offset, 0); \
+        log_sreg_write(env, HEX_SREG_SSR, tmp, slot); \
+    } while (0)
+
+#define fDCINVIDX(REG)
+#define fDCINVA(REG) do { REG = REG; } while (0) /* Nothing to do in qemu */
+
+#define fSET_TLB_LOCK()       hex_tlb_lock(env);
+#define fCLEAR_TLB_LOCK()     hex_tlb_unlock(env);
+
+#define fSET_K0_LOCK()        hex_k0_lock(env);
+#define fCLEAR_K0_LOCK()      hex_k0_unlock(env);
+
+#define fTLB_IDXMASK(INDEX) \
+    ((INDEX) & (fPOW2_ROUNDUP( \
+        fCAST4u(hexagon_tlb_get_num_entries(env_archcpu(env)->tlb))) - 1))
+
+#define fTLB_NONPOW2WRAP(INDEX) \
+    (((INDEX) >= hexagon_tlb_get_num_entries(env_archcpu(env)->tlb)) ? \
+         ((INDEX) - hexagon_tlb_get_num_entries(env_archcpu(env)->tlb)) : \
+         (INDEX))
+
+
+#define fTLBW(INDEX, VALUE) \
+    hex_tlbw(env, (INDEX), (VALUE))
+#define fTLBW_EXTENDED(INDEX, VALUE) \
+    hex_tlbw(env, (INDEX), (VALUE))
+#define fTLB_ENTRY_OVERLAP(VALUE) \
+    (hex_tlb_check_overlap(env, VALUE, -1) != -2)
+#define fTLB_ENTRY_OVERLAP_IDX(VALUE) \
+    hex_tlb_check_overlap(env, VALUE, -1)
+#define fTLBR(INDEX) \
+    hexagon_tlb_read(env_archcpu(env)->tlb, \
+                     fTLB_NONPOW2WRAP(fTLB_IDXMASK(INDEX)))
+#define fTLBR_EXTENDED(INDEX) \
+    hexagon_tlb_read(env_archcpu(env)->tlb, \
+                     fTLB_NONPOW2WRAP(fTLB_IDXMASK(INDEX)))
+#define fTLBP(TLBHI) \
+    hex_tlb_lookup(env, ((TLBHI) >> 12), ((TLBHI) << 12))
+#define iic_flush_cache(p)
+
+#define fIN_DEBUG_MODE(TNUM) ({ \
+    HexagonCPU *_cpu = env_archcpu(env); \
+    uint32_t _isdbst = _cpu->globalregs ? \
+        hexagon_globalreg_read(_cpu->globalregs, \
+                               HEX_SREG_ISDBST, env->threadId) : 0; \
+    (GET_FIELD(ISDBST_DEBUGMODE, _isdbst) \
+        & (0x1 << (TNUM))) != 0; })
+
+#define fIN_DEBUG_MODE_NO_ISDB(TNUM) false
+#define fIN_DEBUG_MODE_WARN(TNUM) false
+
+#ifdef QEMU_GENERATE
+
+/*
+ * Read tags back as zero for now:
+ *
+ * tag value in RD[31:10] for 32k, RD[31:9] for 16k
+ */
+#define fICTAGR(RS, RD, RD2) \
+    do { \
+        RD = ctx->zero; \
+    } while (0)
+#define fICTAGW(RS, RD)
+#define fICDATAR(RS, RD) \
+    do { \
+        RD = ctx->zero; \
+    } while (0)
+#define fICDATAW(RS, RD)
+
+#define fDCTAGW(RS, RT)
+/* tag: RD[23:0], state: RD[30:29] */
+#define fDCTAGR(INDEX, DST, DST_REG_NUM) \
+    do { \
+        DST = ctx->zero; \
+    } while (0)
+#else
+
+/*
+ * Read tags back as zero for now:
+ *
+ * tag value in RD[31:10] for 32k, RD[31:9] for 16k
+ */
+#define fICTAGR(RS, RD, RD2) \
+    do { \
+        RD = 0x00; \
+    } while (0)
+#define fICTAGW(RS, RD)
+#define fICDATAR(RS, RD) \
+    do { \
+        RD = 0x00; \
+    } while (0)
+#define fICDATAW(RS, RD)
+
+#define fDCTAGW(RS, RT)
+/* tag: RD[23:0], state: RD[30:29] */
+#define fDCTAGR(INDEX, DST, DST_REG_NUM) \
+    do { \
+        DST = 0; \
+    } while (0)
+#endif
+
+#else
+#define ASSERT_DIRECT_TO_GUEST_UNSET(ENV, EXCP) do { } while (0)
+#endif
+
+#define NUM_TLB_REGS(x) (hexagon_tlb_get_num_entries(env_archcpu(env)->tlb))
+
+/* NMI routing not yet implemented; Y4_nmi is a no-op for now */
+#define fDO_NMI(THREAD_MASK) do { } while (0)
+
+#endif
diff --git a/target/hexagon/translate.c b/target/hexagon/translate.c
index 0b9337d..7723591 100644
--- a/target/hexagon/translate.c
+++ b/target/hexagon/translate.c
@@ -32,6 +32,7 @@
 #include "translate.h"
 #include "genptr.h"
 #include "printinsn.h"
+#include "exec/target_page.h"
 
 #define HELPER_H "helper.h"
 #include "exec/helper-info.c.inc"
@@ -61,10 +62,19 @@
 TCGv hex_llsc_addr;
 TCGv hex_llsc_val;
 TCGv_i64 hex_llsc_val_i64;
+#ifndef CONFIG_USER_ONLY
+TCGv_i64 hex_cycle_count;
+#endif
 TCGv hex_vstore_addr[VSTORES_MAX];
 TCGv hex_vstore_size[VSTORES_MAX];
 TCGv hex_vstore_pending[VSTORES_MAX];
 
+#ifndef CONFIG_USER_ONLY
+TCGv_i32 hex_greg[NUM_GREGS];
+TCGv_i32 hex_t_sreg[NUM_SREGS];
+TCGv_i32 hex_cause_code;
+#endif
+
 static const char * const hexagon_prednames[] = {
   "p0", "p1", "p2", "p3"
 };
@@ -117,11 +127,28 @@
     return offset;
 }
 
-static void gen_exception_raw(int excp)
+static void gen_exception(int excp, uint32_t PC)
 {
-    gen_helper_raise_exception(tcg_env, tcg_constant_i32(excp));
+    gen_helper_raise_exception(tcg_env, tcg_constant_i32(excp),
+                               tcg_constant_i32(PC));
 }
 
+#ifndef CONFIG_USER_ONLY
+static inline void gen_precise_exception(int excp, uint32_t PC)
+{
+    tcg_gen_movi_i32(hex_cause_code, excp);
+    gen_exception(HEX_EVENT_PRECISE, PC);
+}
+
+static void gen_pcycle_counters(DisasContext *ctx)
+{
+    if (ctx->pcycle_enabled) {
+        tcg_gen_addi_i64(hex_cycle_count, hex_cycle_count, ctx->num_cycles);
+    }
+}
+#endif
+
+
 static void gen_exec_counters(DisasContext *ctx)
 {
     tcg_gen_addi_tl(hex_gpr[HEX_REG_QEMU_PKT_CNT],
@@ -130,6 +157,9 @@
                     hex_gpr[HEX_REG_QEMU_INSN_CNT], ctx->num_insns);
     tcg_gen_addi_tl(hex_gpr[HEX_REG_QEMU_HVX_CNT],
                     hex_gpr[HEX_REG_QEMU_HVX_CNT], ctx->num_hvx_insns);
+#ifndef CONFIG_USER_ONLY
+    gen_pcycle_counters(ctx);
+#endif
 }
 
 static bool use_goto_tb(DisasContext *ctx, target_ulong dest)
@@ -187,11 +217,14 @@
     ctx->base.is_jmp = DISAS_NORETURN;
 }
 
-static void gen_exception_end_tb(DisasContext *ctx, int excp)
+void hex_gen_exception_end_tb(DisasContext *ctx, int excp)
 {
     gen_exec_counters(ctx);
-    tcg_gen_movi_tl(hex_gpr[HEX_REG_PC], ctx->next_PC);
-    gen_exception_raw(excp);
+#ifdef CONFIG_USER_ONLY
+    gen_exception(excp, ctx->pkt.pc);
+#else
+    gen_precise_exception(excp, ctx->pkt.pc);
+#endif
     ctx->base.is_jmp = DISAS_NORETURN;
 }
 
@@ -205,7 +238,7 @@
 
     gen_exec_counters(ctx);
     tcg_gen_movi_tl(hex_gpr[HEX_REG_PC], fail_pc);
-    gen_exception_raw(excp);
+    gen_exception(excp, fail_pc);
     ctx->base.is_jmp = DISAS_NORETURN;
     ctx->base.pc_next = fail_pc;
 }
@@ -249,6 +282,16 @@
     return false;
 }
 
+static bool check_for_opcode(Packet *pkt, uint16_t opcode)
+{
+    for (int i = 0; i < pkt->num_insns; i++) {
+        if (pkt->insn[i].opcode == opcode) {
+            return true;
+        }
+    }
+    return false;
+}
+
 static bool need_slot_cancelled(Packet *pkt)
 {
     /* We only need slot_cancelled for conditional store instructions */
@@ -262,6 +305,90 @@
     return false;
 }
 
+#ifndef CONFIG_USER_ONLY
+static bool sreg_write_ends_tb(int reg_num)
+{
+    return reg_num == HEX_SREG_SSR ||
+           reg_num == HEX_SREG_STID ||
+           reg_num == HEX_SREG_IMASK ||
+           reg_num == HEX_SREG_IPENDAD ||
+           reg_num == HEX_SREG_BESTWAIT ||
+           reg_num == HEX_SREG_SCHEDCFG;
+}
+
+static bool has_sreg_write_ends_tb(Packet const *pkt)
+{
+    for (int i = 0; i < pkt->num_insns; i++) {
+        Insn const *insn = &pkt->insn[i];
+        uint16_t opcode = insn->opcode;
+        if (opcode == Y2_tfrsrcr) {
+            /* Write to a single sreg */
+            int reg_num = insn->regno[0];
+            if (sreg_write_ends_tb(reg_num)) {
+                return true;
+            }
+        } else if (opcode == Y4_tfrspcp) {
+            /* Write to a sreg pair */
+            int reg_num = insn->regno[0];
+            if (sreg_write_ends_tb(reg_num)) {
+                return true;
+            }
+            if (sreg_write_ends_tb(reg_num + 1)) {
+                return true;
+            }
+        }
+    }
+    return false;
+}
+#endif
+
+static bool pkt_ends_tb(Packet *pkt)
+{
+    if (pkt->pkt_has_cof) {
+        return true;
+    }
+#ifndef CONFIG_USER_ONLY
+    /* System mode instructions that end TLB */
+    if (check_for_opcode(pkt, Y2_swi) ||
+        check_for_opcode(pkt, Y2_cswi) ||
+        check_for_opcode(pkt, Y2_ciad) ||
+        check_for_opcode(pkt, Y4_siad) ||
+        check_for_opcode(pkt, Y2_wait) ||
+        check_for_opcode(pkt, Y2_resume) ||
+        check_for_opcode(pkt, Y2_iassignw) ||
+        check_for_opcode(pkt, Y2_setimask) ||
+        check_for_opcode(pkt, Y4_nmi) ||
+        check_for_opcode(pkt, Y2_setprio) ||
+        check_for_opcode(pkt, Y2_start) ||
+        check_for_opcode(pkt, Y2_stop) ||
+        check_for_opcode(pkt, Y2_k0lock) ||
+        check_for_opcode(pkt, Y2_k0unlock) ||
+        check_for_opcode(pkt, Y2_tlblock) ||
+        check_for_opcode(pkt, Y2_tlbunlock) ||
+        check_for_opcode(pkt, Y2_break) ||
+        check_for_opcode(pkt, Y2_isync) ||
+        check_for_opcode(pkt, Y2_syncht) ||
+        check_for_opcode(pkt, Y2_tlbp) ||
+        check_for_opcode(pkt, Y2_tlbw) ||
+        check_for_opcode(pkt, Y5_ctlbw) ||
+        check_for_opcode(pkt, Y5_tlbasidi)) {
+        return true;
+    }
+
+    /*
+     * Check for sreg writes that would end the TB
+     */
+    if (check_for_attrib(pkt, A_IMPLICIT_WRITES_SSR)) {
+        return true;
+    }
+    if (has_sreg_write_ends_tb(pkt)) {
+        return true;
+    }
+#endif
+    return false;
+}
+
+
 static bool need_next_PC(DisasContext *ctx)
 {
     /* Check for conditional control flow or HW loop end */
@@ -309,6 +436,16 @@
     }
 }
 
+#ifndef CONFIG_USER_ONLY
+static void mark_implicit_sreg_write(DisasContext *ctx, int attrib, int snum)
+{
+    uint16_t opcode = ctx->insn->opcode;
+    if (GET_ATTRIB(opcode, attrib)) {
+        ctx_log_sreg_write(ctx, snum);
+    }
+}
+#endif
+
 static void mark_implicit_reg_writes(DisasContext *ctx)
 {
     mark_implicit_reg_write(ctx, A_IMPLICIT_WRITES_FP,  HEX_REG_FP);
@@ -321,6 +458,12 @@
 
     mark_implicit_usr_write(ctx, A_IMPLICIT_WRITES_USR);
     mark_implicit_usr_write(ctx, A_FPOP);
+
+#ifndef CONFIG_USER_ONLY
+    mark_implicit_sreg_write(ctx, A_IMPLICIT_WRITES_SGP0, HEX_SREG_SGP0);
+    mark_implicit_sreg_write(ctx, A_IMPLICIT_WRITES_SGP1, HEX_SREG_SGP1);
+    mark_implicit_sreg_write(ctx, A_IMPLICIT_WRITES_SSR, HEX_SREG_SSR);
+#endif
 }
 
 static void mark_implicit_pred_write(DisasContext *ctx, int attrib, int pnum)
@@ -418,7 +561,11 @@
 
 static void gen_start_packet(DisasContext *ctx)
 {
-    target_ulong next_PC = ctx->base.pc_next + ctx->pkt.encod_pkt_size_in_bytes;
+    Packet *pkt = &ctx->pkt;
+    target_ulong next_PC = (check_for_opcode(pkt, Y2_k0lock) ||
+                            check_for_opcode(pkt, Y2_tlblock)) ?
+                               ctx->base.pc_next :
+                               ctx->base.pc_next + pkt->encod_pkt_size_in_bytes;
     int i;
 
     /* Clear out the disassembly context */
@@ -426,6 +573,10 @@
     ctx->reg_log_idx = 0;
     bitmap_zero(ctx->regs_written, TOTAL_PER_THREAD_REGS);
     bitmap_zero(ctx->predicated_regs, TOTAL_PER_THREAD_REGS);
+#ifndef CONFIG_USER_ONLY
+    ctx->greg_log_idx = 0;
+    ctx->sreg_log_idx = 0;
+#endif
     ctx->preg_log_idx = 0;
     bitmap_zero(ctx->pregs_written, NUM_PREGS);
     ctx->future_vregs_idx = 0;
@@ -458,6 +609,25 @@
      * gen phase, so clear it again.
      */
     bitmap_zero(ctx->pregs_written, NUM_PREGS);
+#ifndef CONFIG_USER_ONLY
+    for (i = 0; i < HEX_SREG_GLB_START; i++) {
+        ctx->t_sreg_new_value[i] = NULL;
+    }
+    for (i = 0; i < ctx->sreg_log_idx; i++) {
+        int reg_num = ctx->sreg_log[i];
+        if (reg_num < HEX_SREG_GLB_START &&
+            (ctx->need_commit || reg_num == HEX_SREG_SSR)) {
+            ctx->t_sreg_new_value[reg_num] = tcg_temp_new();
+        }
+    }
+    for (i = 0; i < NUM_GREGS; i++) {
+        ctx->greg_new_value[i] = NULL;
+    }
+    for (i = 0; i < ctx->greg_log_idx; i++) {
+        int reg_num = ctx->greg_log[i];
+        ctx->greg_new_value[reg_num] = tcg_temp_new();
+    }
+#endif
 
     /* Initialize the runtime state for packet semantics */
     if (need_slot_cancelled(&ctx->pkt)) {
@@ -580,7 +750,7 @@
         ctx->insn->generate(ctx);
         mark_store_width(ctx);
     } else {
-        gen_exception_end_tb(ctx, HEX_CAUSE_INVALID_OPCODE);
+        hex_gen_exception_end_tb(ctx, HEX_CAUSE_INVALID_OPCODE);
     }
 }
 
@@ -615,6 +785,50 @@
     }
 }
 
+#ifndef CONFIG_USER_ONLY
+static void gen_greg_writes(DisasContext *ctx)
+{
+    int i;
+
+    for (i = 0; i < ctx->greg_log_idx; i++) {
+        int reg_num = ctx->greg_log[i];
+
+        tcg_gen_mov_tl(hex_greg[reg_num], ctx->greg_new_value[reg_num]);
+    }
+}
+
+
+static void gen_sreg_writes(DisasContext *ctx)
+{
+    int i;
+
+    TCGv_i32 old_reg = tcg_temp_new_i32();
+    for (i = 0; i < ctx->sreg_log_idx; i++) {
+        int reg_num = ctx->sreg_log[i];
+
+        if (reg_num == HEX_SREG_SSR) {
+            tcg_gen_mov_tl(old_reg, hex_t_sreg[reg_num]);
+            tcg_gen_mov_tl(hex_t_sreg[reg_num], ctx->t_sreg_new_value[reg_num]);
+            gen_helper_modify_ssr(tcg_env, ctx->t_sreg_new_value[reg_num],
+                                  old_reg);
+        } else if ((reg_num == HEX_SREG_STID) ||
+                   (reg_num == HEX_SREG_IMASK) ||
+                   (reg_num == HEX_SREG_IPENDAD)) {
+            if (ctx->need_commit && reg_num < HEX_SREG_GLB_START) {
+                tcg_gen_mov_tl(hex_t_sreg[reg_num],
+                               ctx->t_sreg_new_value[reg_num]);
+            }
+            gen_helper_pending_interrupt(tcg_env);
+        } else if ((reg_num == HEX_SREG_BESTWAIT) ||
+                   (reg_num == HEX_SREG_SCHEDCFG)) {
+            gen_helper_resched(tcg_env);
+        } else if (ctx->need_commit && reg_num < HEX_SREG_GLB_START) {
+            tcg_gen_mov_tl(hex_t_sreg[reg_num], ctx->t_sreg_new_value[reg_num]);
+        }
+    }
+}
+#endif
+
 static void gen_pred_writes(DisasContext *ctx)
 {
     /* Early exit if not needed or the log is empty */
@@ -804,6 +1018,8 @@
     }
 }
 
+#define PCYCLES_PER_PACKET 1
+
 static void update_exec_counters(DisasContext *ctx)
 {
     int num_real_insns = 0;
@@ -823,6 +1039,7 @@
     ctx->num_packets++;
     ctx->num_insns += num_real_insns;
     ctx->num_hvx_insns += num_hvx_insns;
+    ctx->num_cycles += PCYCLES_PER_PACKET;
 }
 
 static void gen_commit_packet(DisasContext *ctx)
@@ -908,6 +1125,10 @@
     process_store_log(ctx);
 
     gen_reg_writes(ctx);
+#ifndef CONFIG_USER_ONLY
+    gen_greg_writes(ctx);
+    gen_sreg_writes(ctx);
+#endif
     gen_pred_writes(ctx);
     if (ctx->pkt.pkt_has_hvx) {
         gen_commit_hvx(ctx);
@@ -920,7 +1141,7 @@
         ctx->pkt.vhist_insn->generate(ctx);
     }
 
-    if (ctx->pkt.pkt_has_cof) {
+    if (pkt_ends_tb(&ctx->pkt) || ctx->base.is_jmp == DISAS_NORETURN) {
         gen_end_tb(ctx);
     }
 }
@@ -964,7 +1185,7 @@
     HexagonCPU *hex_cpu = env_archcpu(cpu_env(cs));
     uint32_t hex_flags = dcbase->tb->flags;
 
-    ctx->mem_idx = MMU_USER_IDX;
+    ctx->mem_idx = FIELD_EX32(hex_flags, TB_FLAGS, MMU_INDEX);
     ctx->num_packets = 0;
     ctx->num_insns = 0;
     ctx->num_hvx_insns = 0;
@@ -972,6 +1193,10 @@
     ctx->is_tight_loop = FIELD_EX32(hex_flags, TB_FLAGS, IS_TIGHT_LOOP);
     ctx->short_circuit = hex_cpu->short_circuit;
     ctx->hex_def = HEXAGON_CPU_GET_CLASS(hex_cpu)->hex_def;
+#ifndef CONFIG_USER_ONLY
+    ctx->num_cycles = 0;
+    ctx->pcycle_enabled = FIELD_EX32(hex_flags, TB_FLAGS, PCYCLE_ENABLED);
+#endif
 }
 
 static void hexagon_tr_tb_start(DisasContextBase *db, CPUState *cpu)
@@ -1080,6 +1305,20 @@
 
     opcode_init();
 
+#ifndef CONFIG_USER_ONLY
+    for (i = 0; i < NUM_GREGS; i++) {
+            hex_greg[i] = tcg_global_mem_new_i32(tcg_env,
+                offsetof(CPUHexagonState, greg[i]),
+                hexagon_gregnames[i]);
+    }
+    for (i = 0; i < NUM_SREGS; i++) {
+        if (i < HEX_SREG_GLB_START) {
+            hex_t_sreg[i] = tcg_global_mem_new_i32(tcg_env,
+                offsetof(CPUHexagonState, t_sreg[i]),
+                hexagon_sregnames[i]);
+        }
+    }
+#endif
     for (i = 0; i < TOTAL_PER_THREAD_REGS; i++) {
         hex_gpr[i] = tcg_global_mem_new(tcg_env,
             offsetof(CPUHexagonState, gpr[i]),
@@ -1101,6 +1340,12 @@
         offsetof(CPUHexagonState, llsc_val), "llsc_val");
     hex_llsc_val_i64 = tcg_global_mem_new_i64(tcg_env,
         offsetof(CPUHexagonState, llsc_val_i64), "llsc_val_i64");
+#ifndef CONFIG_USER_ONLY
+    hex_cause_code = tcg_global_mem_new_i32(tcg_env,
+        offsetof(CPUHexagonState, cause_code), "cause_code");
+    hex_cycle_count = tcg_global_mem_new_i64(tcg_env,
+        offsetof(CPUHexagonState, t_cycle_count), "t_cycle_count");
+#endif
     for (i = 0; i < STORES_MAX; i++) {
         snprintf(store_addr_names[i], NAME_LEN, "store_addr_%d", i);
         hex_store_addr[i] = tcg_global_mem_new(tcg_env,
diff --git a/target/hexagon/translate.h b/target/hexagon/translate.h
index 1fc185e..0a7f37d 100644
--- a/target/hexagon/translate.h
+++ b/target/hexagon/translate.h
@@ -41,6 +41,14 @@
     DECLARE_BITMAP(regs_written, TOTAL_PER_THREAD_REGS);
     DECLARE_BITMAP(predicated_regs, TOTAL_PER_THREAD_REGS);
     bool implicit_usr_write;
+#ifndef CONFIG_USER_ONLY
+    int greg_log[GREG_WRITES_MAX];
+    int greg_log_idx;
+    int sreg_log[SREG_WRITES_MAX];
+    int sreg_log_idx;
+    TCGv_i32 t_sreg_new_value[HEX_SREG_GLB_START];
+    TCGv_i32 greg_new_value[NUM_GREGS];
+#endif
     int preg_log[PRED_WRITES_MAX];
     int preg_log_idx;
     DECLARE_BITMAP(pregs_written, NUM_PREGS);
@@ -77,10 +85,40 @@
     TCGv new_pred_value[NUM_PREGS];
     TCGv branch_taken;
     TCGv dczero_addr;
+    bool pcycle_enabled;
+    uint32_t num_cycles;
 } DisasContext;
 
 bool is_gather_store_insn(DisasContext *ctx);
 
+#ifndef CONFIG_USER_ONLY
+static inline void ctx_log_greg_write(DisasContext *ctx, int rnum)
+{
+    assert(rnum <= HEX_GREG_G3);
+    ctx->greg_log[ctx->greg_log_idx] = rnum;
+    ctx->greg_log_idx++;
+}
+
+static inline void ctx_log_greg_write_pair(DisasContext *ctx, int rnum)
+{
+    assert(!(rnum % 2));
+    ctx_log_greg_write(ctx, rnum);
+    ctx_log_greg_write(ctx, rnum + 1);
+}
+
+static inline void ctx_log_sreg_write(DisasContext *ctx, int rnum)
+{
+    ctx->sreg_log[ctx->sreg_log_idx] = rnum;
+    ctx->sreg_log_idx++;
+}
+
+static inline void ctx_log_sreg_write_pair(DisasContext *ctx, int rnum)
+{
+    ctx_log_sreg_write(ctx, rnum);
+    ctx_log_sreg_write(ctx, rnum + 1);
+}
+#endif
+
 static inline void ctx_log_pred_write(DisasContext *ctx, int pnum)
 {
     if (!test_bit(pnum, ctx->pregs_written)) {
@@ -282,6 +320,13 @@
 extern TCGv hex_vstore_addr[VSTORES_MAX];
 extern TCGv hex_vstore_size[VSTORES_MAX];
 extern TCGv hex_vstore_pending[VSTORES_MAX];
+#ifndef CONFIG_USER_ONLY
+extern TCGv_i32 hex_greg[NUM_GREGS];
+extern TCGv_i32 hex_t_sreg[NUM_SREGS];
+#endif
+
+
+void hex_gen_exception_end_tb(DisasContext *ctx, int excp);
 
 void process_store(DisasContext *ctx, int slot_num);
 
@@ -295,4 +340,6 @@
 FIELD(PROBE_PKT_SCALAR_HVX_STORES, S1_IS_PRED,     4, 1)
 FIELD(PROBE_PKT_SCALAR_HVX_STORES, MMU_IDX,        5, 2)
 
+void gen_framecheck(DisasContext *ctx, TCGv_i32 addr, TCGv_i32 ea);
+
 #endif
diff --git a/target/i386/cpu.c b/target/i386/cpu.c
index 8929a75..5805d33 100644
--- a/target/i386/cpu.c
+++ b/target/i386/cpu.c
@@ -26,6 +26,7 @@
 #include "tcg/helper-tcg.h"
 #include "exec/translation-block.h"
 #include "system/hvf.h"
+#include "system/mshv.h"
 #include "system/whpx.h"
 #include "whpx/whpx-i386.h"
 #include "hvf/hvf-i386.h"
@@ -8233,6 +8234,13 @@
         r = hvf_get_supported_cpuid(wi->cpuid.eax,
                                     wi->cpuid.ecx,
                                     wi->cpuid.reg);
+    } else if (mshv_enabled()) {
+        if (wi->type != CPUID_FEATURE_WORD) {
+            return 0;
+        }
+        r = mshv_get_supported_cpuid(wi->cpuid.eax,
+                                     wi->cpuid.ecx,
+                                     wi->cpuid.reg);
     } else if (whpx_enabled()) {
         switch (wi->type) {
         case CPUID_FEATURE_WORD:
diff --git a/target/i386/cpu.h b/target/i386/cpu.h
index 67e2ecf..e6a1976 100644
--- a/target/i386/cpu.h
+++ b/target/i386/cpu.h
@@ -1983,6 +1983,9 @@
         CPUCacheInfo *l3_cache;
 } CPUCaches;
 
+struct kvm_msrs;
+struct hv_vp_register_page;
+
 typedef struct CPUArchState {
     /* standard registers */
     target_ulong regs[CPU_NB_EREGS];
@@ -2289,6 +2292,10 @@
     QEMUTimer *xen_periodic_timer;
     QemuMutex xen_timers_lock;
 #endif
+#if defined(CONFIG_MSHV)
+    /* Shared register page */
+    struct hv_vp_register_page *regs_page;
+#endif
 #if defined(CONFIG_HVF) || defined(CONFIG_MSHV) || defined(CONFIG_WHPX)
     void *emu_mmio_buf;
 #endif
@@ -2315,8 +2322,6 @@
     DECLARE_BITMAP(avail_cpu_topo, CPU_TOPOLOGY_LEVEL__MAX);
 } CPUX86State;
 
-struct kvm_msrs;
-
 /**
  * X86CPU:
  * @env: #CPUX86State
diff --git a/target/i386/kvm/kvm.c b/target/i386/kvm/kvm.c
index 9e35288..64cc421 100644
--- a/target/i386/kvm/kvm.c
+++ b/target/i386/kvm/kvm.c
@@ -32,6 +32,7 @@
 #include "vmsr_energy.h"
 #include "system/system.h"
 #include "system/hw_accel.h"
+#include "system/accel-irq.h"
 #include "system/kvm_int.h"
 #include "system/runstate.h"
 #include "system/ramblock.h"
@@ -6678,7 +6679,7 @@
     kvm_gsi_routing_allowed = true;
 
     if (kvm_irqchip_is_split()) {
-        KVMRouteChange c = kvm_irqchip_begin_route_changes(s);
+        AccelRouteChange c = accel_irqchip_begin_route_changes();
         int i;
 
         /* If the ioapic is in QEMU and the lapics are in KVM, reserve
@@ -6689,7 +6690,7 @@
                 exit(1);
             }
         }
-        kvm_irqchip_commit_route_changes(&c);
+        accel_irqchip_commit_route_changes(&c);
     }
 }
 
diff --git a/target/i386/kvm/tdx.c b/target/i386/kvm/tdx.c
index dfad469..c9c3b05 100644
--- a/target/i386/kvm/tdx.c
+++ b/target/i386/kvm/tdx.c
@@ -560,19 +560,29 @@
 } TdxXFAMDep;
 
 /*
- * Note, only the CPUID bits whose virtualization type are "XFAM & Native" are
- * defiend here.
+ * Note, usually the CPUID bits whose virtualization type are "XFAM & Native"
+ * are defined here while "XFAM & Configured & Native" are not. Because the
+ * latter are reported as configurable bits by KVM when they are supported.
+ * And they are not supported when not in the configurable bits list from KVM
+ * even if the corresponding XFAM bit is supported.
  *
- * For those whose virtualization type are "XFAM & Configured & Native", they
- * are reported as configurable bits. And they are not supported if not in the
- * configureable bits list from KVM even if the corresponding XFAM bit is
- * supported.
+ * Special cases:
+ *
+ * - AMX alias bits, their type is "CPUID_Enabled & Native" which means their
+ * value is determined by the CPUID bit they are aliased to.
+ *
+ * - AVX10_VL_MASK, their type is "XFAM & CPUID_Enabled & Native" which means
+ * their value is determined by both the corresponding XFAM bit and CPUID bit.
+ *
+ * For simplicity, relax the dependency to related XFAM bit.
+ * tdx_check_features() will eventually catch the unsupported configurations.
  */
 TdxXFAMDep tdx_xfam_deps[] = {
     { XSTATE_YMM_BIT,       { FEAT_1_ECX, CPUID_EXT_FMA } },
     { XSTATE_YMM_BIT,       { FEAT_7_0_EBX, CPUID_7_0_EBX_AVX2 } },
     { XSTATE_OPMASK_BIT,    { FEAT_7_0_ECX, CPUID_7_0_ECX_AVX512_VBMI } },
     { XSTATE_OPMASK_BIT,    { FEAT_7_0_EDX, CPUID_7_0_EDX_AVX512_FP16 } },
+    { XSTATE_OPMASK_BIT,    { FEAT_24_0_EBX, CPUID_24_0_EBX_AVX10_VL_MASK } },
     { XSTATE_PT_BIT,        { FEAT_7_0_EBX, CPUID_7_0_EBX_INTEL_PT } },
     { XSTATE_PKRU_BIT,      { FEAT_7_0_ECX, CPUID_7_0_ECX_PKU } },
     { XSTATE_CET_U_BIT,     { FEAT_7_0_ECX, CPUID_7_0_ECX_CET_SHSTK } },
@@ -580,6 +590,10 @@
     { XSTATE_XTILE_CFG_BIT, { FEAT_7_0_EDX, CPUID_7_0_EDX_AMX_BF16 } },
     { XSTATE_XTILE_CFG_BIT, { FEAT_7_0_EDX, CPUID_7_0_EDX_AMX_TILE } },
     { XSTATE_XTILE_CFG_BIT, { FEAT_7_0_EDX, CPUID_7_0_EDX_AMX_INT8 } },
+    { XSTATE_XTILE_CFG_BIT, { FEAT_1E_1_EAX, CPUID_1E_1_EAX_AMX_INT8_ALIAS } },
+    { XSTATE_XTILE_CFG_BIT, { FEAT_1E_1_EAX, CPUID_1E_1_EAX_AMX_BF16_ALIAS } },
+    { XSTATE_XTILE_CFG_BIT, { FEAT_1E_1_EAX, CPUID_1E_1_EAX_AMX_COMPLEX_ALIAS } },
+    { XSTATE_XTILE_CFG_BIT, { FEAT_1E_1_EAX, CPUID_1E_1_EAX_AMX_FP16_ALIAS } },
 };
 
 static struct kvm_cpuid_entry2 *find_in_supported_entry(uint32_t function,
@@ -1403,7 +1417,7 @@
     uint64_t reg_mask = run->system_event.data[R_ECX];
     char *message = NULL;
     uint64_t *tmp;
-    uint64_t gpa = -1ull;
+    uint64_t gpa = 0;
     bool has_gpa = false;
 
     if (error_code & 0xffff) {
diff --git a/target/i386/meson.build b/target/i386/meson.build
index 80062d1..b4fbf8a 100644
--- a/target/i386/meson.build
+++ b/target/i386/meson.build
@@ -12,7 +12,7 @@
 i386_ss.add(when: 'CONFIG_HVF', if_true: files('xsave_helper.c', 'host-cpu.c'))
 i386_ss.add(when: 'CONFIG_WHPX', if_true: files('xsave_helper.c', 'host-cpu.c'))
 i386_ss.add(when: 'CONFIG_NVMM', if_true: files('host-cpu.c'))
-i386_ss.add(when: 'CONFIG_MSHV', if_true: files('host-cpu.c'))
+i386_ss.add(when: 'CONFIG_MSHV', if_true: files('xsave_helper.c', 'host-cpu.c'))
 
 i386_system_ss = ss.source_set()
 i386_system_ss.add(files(
diff --git a/target/i386/mshv/meson.build b/target/i386/mshv/meson.build
index 49f28d4..6091c21 100644
--- a/target/i386/mshv/meson.build
+++ b/target/i386/mshv/meson.build
@@ -2,6 +2,7 @@
 
 i386_mshv_ss.add(files(
   'mshv-cpu.c',
+  'msr.c',
 ))
 
 i386_system_ss.add_all(when: 'CONFIG_MSHV', if_true: i386_mshv_ss)
diff --git a/target/i386/mshv/mshv-cpu.c b/target/i386/mshv/mshv-cpu.c
index 4ed6e75..126ca40 100644
--- a/target/i386/mshv/mshv-cpu.c
+++ b/target/i386/mshv/mshv-cpu.c
@@ -24,10 +24,13 @@
 #include "hw/i386/apic_internal.h"
 
 #include "cpu.h"
+#include "host-cpu.h"
 #include "emulate/x86_decode.h"
 #include "emulate/x86_emu.h"
 #include "emulate/x86_flags.h"
 
+#include "accel/accel-cpu-target.h"
+
 #include "trace-accel_mshv.h"
 #include "trace.h"
 
@@ -107,6 +110,175 @@
     HV_X64_REGISTER_XMM_CONTROL_STATUS,
 };
 
+static int set_special_regs(const CPUState *cpu);
+
+static int get_xsave_state(CPUState *cpu)
+{
+    X86CPU *x86cpu = X86_CPU(cpu);
+    CPUX86State *env = &x86cpu->env;
+    int cpu_fd = mshv_vcpufd(cpu);
+    int ret;
+    void *xsavec_buf;
+    const size_t page = HV_HYP_PAGE_SIZE;
+    size_t xsavec_buf_len = page;
+
+    /* TODO: should properly determine xsavec size based on CPUID */
+    xsavec_buf = qemu_memalign(page, xsavec_buf_len);
+    memset(xsavec_buf, 0, xsavec_buf_len);
+
+    struct mshv_get_set_vp_state args = {
+        .type = MSHV_VP_STATE_XSAVE,
+        .buf_sz = xsavec_buf_len,
+        .buf_ptr = (uintptr_t)xsavec_buf,
+    };
+
+    ret = ioctl(cpu_fd, MSHV_GET_VP_STATE, &args);
+    if (ret < 0) {
+        error_report("failed to get xsave state: %s", strerror(errno));
+        return -errno;
+    }
+
+    ret = decompact_xsave_area(xsavec_buf, xsavec_buf_len, env);
+    g_free(xsavec_buf);
+    if (ret < 0) {
+        error_report("failed to decompact xsave area");
+        return ret;
+    }
+    x86_cpu_xrstor_all_areas(x86cpu, env->xsave_buf, env->xsave_buf_len);
+
+    return 0;
+}
+
+static int set_xsave_state(const CPUState *cpu)
+{
+    X86CPU *x86cpu = X86_CPU(cpu);
+    CPUX86State *env = &x86cpu->env;
+    int cpu_fd = mshv_vcpufd(cpu);
+    int ret;
+    void *xsavec_buf;
+    size_t page = HV_HYP_PAGE_SIZE, xsavec_buf_len;
+
+    /* allocate and populate compacted buffer */
+    xsavec_buf = qemu_memalign(page, page);
+    xsavec_buf_len = page;
+
+    /* save registers to standard format buffer */
+    x86_cpu_xsave_all_areas(x86cpu, env->xsave_buf, env->xsave_buf_len);
+
+    /* store compacted version of xsave area in xsavec_buf */
+    compact_xsave_area(env, xsavec_buf, xsavec_buf_len);
+
+    struct mshv_get_set_vp_state args = {
+        .type = MSHV_VP_STATE_XSAVE,
+        .buf_sz = xsavec_buf_len,
+        .buf_ptr = (uintptr_t)xsavec_buf,
+    };
+
+    ret = ioctl(cpu_fd, MSHV_SET_VP_STATE, &args);
+    g_free(xsavec_buf);
+    if (ret < 0) {
+        error_report("failed to set xsave state: %s", strerror(errno));
+        return -errno;
+    }
+
+    return 0;
+}
+
+static void populate_fpu(const hv_register_assoc *assocs, X86CPU *x86cpu)
+{
+    union hv_register_value value;
+    const union hv_x64_fp_control_status_register *ctrl_status;
+    const union hv_x64_xmm_control_status_register *xmm_ctrl;
+    CPUX86State *env = &x86cpu->env;
+    size_t i, fp_i;
+    bool valid;
+
+    /* first 16 registers are xmm0-xmm15 */
+    for (i = 0; i < 16; i++) {
+        value = assocs[i].value;
+        env->xmm_regs[i].ZMM_Q(0) = value.reg128.low_part;
+        env->xmm_regs[i].ZMM_Q(1) = value.reg128.high_part;
+    }
+
+    /* next 8 registers are fp_mmx0-fp_mmx7 */
+    for (i = 16; i < 24; i++) {
+        fp_i = i - 16;
+        value = assocs[i].value;
+        env->fpregs[fp_i].d.low = value.fp.mantissa;
+        env->fpregs[fp_i].d.high = (value.fp.sign << 15)
+                                 | (value.fp.biased_exponent & 0x7FFF);
+    }
+
+    /* last two registers are fp_control_status and xmm_control_status */
+    ctrl_status = &assocs[24].value.fp_control_status;
+    env->fpuc = ctrl_status->fp_control;
+
+    env->fpus = ctrl_status->fp_status & ~0x3800;
+    /* bits 11,12,13 are the top of stack pointer */
+    env->fpstt = (ctrl_status->fp_status >> 11) & 0x7;
+
+    for (i = 0; i < 8; i++) {
+        valid = ctrl_status->fp_tag & (1 << i);
+        env->fptags[i] = valid ? 0 : 1;
+    }
+
+    env->fpop = ctrl_status->last_fp_op;
+    env->fpip = ctrl_status->last_fp_rip;
+
+    xmm_ctrl = &assocs[25].value.xmm_control_status;
+    env->mxcsr = xmm_ctrl->xmm_status_control;
+    env->fpdp = xmm_ctrl->last_fp_rdp;
+}
+
+static int get_fpu(CPUState *cpu)
+{
+    struct hv_register_assoc assocs[ARRAY_SIZE(FPU_REGISTER_NAMES)];
+    int ret;
+    X86CPU *x86cpu = X86_CPU(cpu);
+    size_t n_regs = ARRAY_SIZE(FPU_REGISTER_NAMES);
+
+    for (size_t i = 0; i < n_regs; i++) {
+        assocs[i].name = FPU_REGISTER_NAMES[i];
+    }
+    ret = mshv_get_generic_regs(cpu, assocs, n_regs);
+    if (ret < 0) {
+        error_report("failed to get special registers");
+        return -errno;
+    }
+
+    populate_fpu(assocs, x86cpu);
+
+    return 0;
+}
+
+static int get_xc_reg(CPUState *cpu)
+{
+    int ret;
+    X86CPU *x86cpu = X86_CPU(cpu);
+    CPUX86State *env = &x86cpu->env;
+    struct hv_register_assoc assocs[1];
+
+    assocs[0].name = HV_X64_REGISTER_XFEM;
+
+    ret = mshv_get_generic_regs(cpu, assocs, 1);
+    if (ret < 0) {
+        error_report("failed to get xcr0");
+        return -1;
+    }
+    env->xcr0 = assocs[0].value.reg64;
+
+    return 0;
+}
+
+static enum hv_register_name NON_VP_PAGE_REGISTER_NAMES[6] = {
+    HV_X64_REGISTER_TR,
+    HV_X64_REGISTER_LDTR,
+    HV_X64_REGISTER_GDTR,
+    HV_X64_REGISTER_IDTR,
+    HV_X64_REGISTER_CR2,
+    HV_X64_REGISTER_APIC_BASE,
+};
+
 static int translate_gva(const CPUState *cpu, uint64_t gva, uint64_t *gpa,
                          uint64_t flags)
 {
@@ -190,8 +362,8 @@
     return 0;
 }
 
-static int get_generic_regs(CPUState *cpu, hv_register_assoc *assocs,
-                            size_t n_regs)
+int mshv_get_generic_regs(CPUState *cpu, hv_register_assoc *assocs,
+                          size_t n_regs)
 {
     int cpu_fd = mshv_vcpufd(cpu);
     int vp_index = cpu->cpu_index;
@@ -285,14 +457,56 @@
     return 0;
 }
 
-int mshv_store_regs(CPUState *cpu)
+static void mshv_set_standard_regs_vp_page(CPUState *cpu)
 {
+    X86CPU *x86cpu = X86_CPU(cpu);
+    CPUX86State *env = &x86cpu->env;
+
+    env->regs_page->rax = env->regs[R_EAX];
+    env->regs_page->rbx = env->regs[R_EBX];
+    env->regs_page->rcx = env->regs[R_ECX];
+    env->regs_page->rdx = env->regs[R_EDX];
+    env->regs_page->rsi = env->regs[R_ESI];
+    env->regs_page->rdi = env->regs[R_EDI];
+    env->regs_page->rsp = env->regs[R_ESP];
+    env->regs_page->rbp = env->regs[R_EBP];
+    env->regs_page->r8  = env->regs[R_R8];
+    env->regs_page->r9  = env->regs[R_R9];
+    env->regs_page->r10 = env->regs[R_R10];
+    env->regs_page->r11 = env->regs[R_R11];
+    env->regs_page->r12 = env->regs[R_R12];
+    env->regs_page->r13 = env->regs[R_R13];
+    env->regs_page->r14 = env->regs[R_R14];
+    env->regs_page->r15 = env->regs[R_R15];
+    env->regs_page->rip = env->eip;
+    lflags_to_rflags(env);
+    env->regs_page->rflags = env->eflags;
+
+    env->regs_page->dirty |= (1u << HV_X64_REGISTER_CLASS_GENERAL)
+                                | (1u << HV_X64_REGISTER_CLASS_IP)
+                                | (1u << HV_X64_REGISTER_CLASS_FLAGS);
+}
+
+static int store_regs(CPUState *cpu)
+{
+    X86CPU *x86cpu = X86_CPU(cpu);
+    CPUX86State *env = &x86cpu->env;
     int ret;
 
-    ret = set_standard_regs(cpu);
+    /* Use register vp page to optimize registers access */
+    if (env->regs_page && env->regs_page->isvalid != 0) {
+        mshv_set_standard_regs_vp_page(cpu);
+    } else {
+        ret = set_standard_regs(cpu);
+        if (ret < 0) {
+            return ret;
+        }
+    }
+
+    ret = set_special_regs(cpu);
     if (ret < 0) {
-        error_report("Failed to store standard registers");
-        return -1;
+        error_report("Failed to store speical registers");
+        return ret;
     }
 
     return 0;
@@ -323,7 +537,7 @@
     rflags_to_lflags(env);
 }
 
-int mshv_get_standard_regs(CPUState *cpu)
+static int get_standard_regs(CPUState *cpu)
 {
     struct hv_register_assoc assocs[ARRAY_SIZE(STANDARD_REGISTER_NAMES)];
     int ret;
@@ -334,7 +548,7 @@
     for (size_t i = 0; i < n_regs; i++) {
         assocs[i].name = STANDARD_REGISTER_NAMES[i];
     }
-    ret = get_generic_regs(cpu, assocs, n_regs);
+    ret = mshv_get_generic_regs(cpu, assocs, n_regs);
     if (ret < 0) {
         error_report("failed to get standard registers");
         return -1;
@@ -401,8 +615,107 @@
     cpu_set_apic_base(x86cpu->apic_state, assocs[16].value.reg64);
 }
 
+static void mshv_get_standard_regs_vp_page(CPUState *cpu)
+{
+    X86CPU *x86cpu = X86_CPU(cpu);
+    CPUX86State *env = &x86cpu->env;
 
-int mshv_get_special_regs(CPUState *cpu)
+    /* General Purpose Registers  */
+    env->regs[R_EAX] = env->regs_page->rax;
+    env->regs[R_EBX] = env->regs_page->rbx;
+    env->regs[R_ECX] = env->regs_page->rcx;
+    env->regs[R_EDX] = env->regs_page->rdx;
+    env->regs[R_ESI] = env->regs_page->rsi;
+    env->regs[R_EDI] = env->regs_page->rdi;
+    env->regs[R_ESP] = env->regs_page->rsp;
+    env->regs[R_EBP] = env->regs_page->rbp;
+    env->regs[R_R8]  = env->regs_page->r8;
+    env->regs[R_R9]  = env->regs_page->r9;
+    env->regs[R_R10] = env->regs_page->r10;
+    env->regs[R_R11] = env->regs_page->r11;
+    env->regs[R_R12] = env->regs_page->r12;
+    env->regs[R_R13] = env->regs_page->r13;
+    env->regs[R_R14] = env->regs_page->r14;
+    env->regs[R_R15] = env->regs_page->r15;
+
+    env->eip = env->regs_page->rip;
+    env->eflags = env->regs_page->rflags;
+    rflags_to_lflags(env);
+}
+
+static int mshv_get_special_regs_vp_page(CPUState *cpu)
+{
+    X86CPU *x86cpu = X86_CPU(cpu);
+    CPUX86State *env = &x86cpu->env;
+    struct hv_register_assoc assocs[ARRAY_SIZE(NON_VP_PAGE_REGISTER_NAMES)];
+    int ret;
+    size_t n_regs = ARRAY_SIZE(NON_VP_PAGE_REGISTER_NAMES);
+    hv_x64_segment_register seg;
+
+    /* Populate special registers that are in the VP register page */
+    env->cr[0] = env->regs_page->cr0;
+    env->cr[3] = env->regs_page->cr3;
+    env->cr[4] = env->regs_page->cr4;
+    env->efer = env->regs_page->efer;
+    cpu_set_apic_tpr(x86cpu->apic_state, env->regs_page->cr8);
+
+    /* Segment Registers - copy from packed struct to avoid unaligned access */
+    memcpy(&seg, &env->regs_page->es, sizeof(hv_x64_segment_register));
+    populate_segment_reg(&seg, &env->segs[R_ES]);
+    memcpy(&seg, &env->regs_page->cs, sizeof(hv_x64_segment_register));
+    populate_segment_reg(&seg, &env->segs[R_CS]);
+    memcpy(&seg, &env->regs_page->ss, sizeof(hv_x64_segment_register));
+    populate_segment_reg(&seg, &env->segs[R_SS]);
+    memcpy(&seg, &env->regs_page->ds, sizeof(hv_x64_segment_register));
+    populate_segment_reg(&seg, &env->segs[R_DS]);
+    memcpy(&seg, &env->regs_page->fs, sizeof(hv_x64_segment_register));
+    populate_segment_reg(&seg, &env->segs[R_FS]);
+    memcpy(&seg, &env->regs_page->gs, sizeof(hv_x64_segment_register));
+    populate_segment_reg(&seg, &env->segs[R_GS]);
+
+    /* The rest of the special registers that are not in the VP register page */
+    for (size_t i = 0; i < n_regs; i++) {
+        assocs[i].name = NON_VP_PAGE_REGISTER_NAMES[i];
+    }
+
+    ret = mshv_get_generic_regs(cpu, assocs, n_regs);
+    if (ret < 0) {
+        error_report("failed to get non-vp-page special registers");
+        return -1;
+    }
+
+    /* Non-VP page registers - TR, LDTR, GDTR, IDTR, CR2, APIC_BASE */
+    populate_segment_reg(&assocs[0].value.segment, &env->tr);
+    populate_segment_reg(&assocs[1].value.segment, &env->ldt);
+
+    populate_table_reg(&assocs[2].value.table, &env->gdt);
+    populate_table_reg(&assocs[3].value.table, &env->idt);
+    env->cr[2] = assocs[4].value.reg64;
+
+    cpu_set_apic_base(x86cpu->apic_state, assocs[5].value.reg64);
+
+    return ret;
+}
+
+static int mshv_get_registers_vp_page(CPUState *cpu)
+{
+    int ret;
+
+    /* General Purpose Registers  */
+    mshv_get_standard_regs_vp_page(cpu);
+
+    /* Special Registers - makes a hypercall */
+    ret = mshv_get_special_regs_vp_page(cpu);
+    if (ret < 0) {
+        error_report("failed to get special registers for vp page");
+        return -1;
+    }
+
+    return 0;
+}
+
+
+static int get_special_regs(CPUState *cpu)
 {
     struct hv_register_assoc assocs[ARRAY_SIZE(SPECIAL_REGISTER_NAMES)];
     int ret;
@@ -412,7 +725,7 @@
     for (size_t i = 0; i < n_regs; i++) {
         assocs[i].name = SPECIAL_REGISTER_NAMES[i];
     }
-    ret = get_generic_regs(cpu, assocs, n_regs);
+    ret = mshv_get_generic_regs(cpu, assocs, n_regs);
     if (ret < 0) {
         error_report("failed to get special registers");
         return -errno;
@@ -422,26 +735,245 @@
     return 0;
 }
 
-int mshv_load_regs(CPUState *cpu)
+static int load_regs(CPUState *cpu)
 {
+    X86CPU *x86_cpu = X86_CPU(cpu);
+    CPUX86State *env = &x86_cpu->env;
     int ret;
 
-    ret = mshv_get_standard_regs(cpu);
+    /* Use register vp page to optimize registers access */
+    if (env->regs_page && env->regs_page->isvalid != 0) {
+        ret = mshv_get_registers_vp_page(cpu);
+        return ret;
+    }
+
+    ret = get_standard_regs(cpu);
     if (ret < 0) {
-        error_report("Failed to load standard registers");
+        return ret;
+    }
+
+    ret = get_special_regs(cpu);
+    if (ret < 0) {
+        return ret;
+    }
+
+    return 0;
+}
+
+static int get_vcpu_events(CPUState *cpu)
+{
+    X86CPU *x86cpu = X86_CPU(cpu);
+    CPUX86State *env = &x86cpu->env;
+    struct hv_register_assoc assocs[] = {
+        { .name = HV_REGISTER_PENDING_INTERRUPTION },
+        { .name = HV_REGISTER_INTERRUPT_STATE },
+        { .name = HV_REGISTER_PENDING_EVENT0 },
+    };
+    union hv_x64_pending_interruption_register pending_int;
+    union hv_x64_interrupt_state_register int_state;
+    union hv_x64_pending_exception_event pending_exc;
+    int ret;
+
+    ret = mshv_get_generic_regs(cpu, assocs, ARRAY_SIZE(assocs));
+    if (ret < 0) {
+        error_report("failed to get vcpu event registers");
         return -1;
     }
 
-    ret = mshv_get_special_regs(cpu);
+    pending_int.as_uint64 = assocs[0].value.reg64;
+    int_state.as_uint64 = assocs[1].value.reg64;
+    pending_exc = assocs[2].value.pending_exception_event;
+
+    /* Clear previous state. injected ints/excs are blanked w/ -1 */
+    env->interrupt_injected    = -1;
+    env->soft_interrupt        = 0;
+    env->exception_injected    = 0;
+    env->exception_pending     = 0;
+    env->exception_nr          = -1;
+    env->has_error_code        = 0;
+    env->error_code            = 0;
+    env->exception_has_payload = 0;
+    env->exception_payload     = 0;
+    env->nmi_injected          = 0;
+
+    if (pending_int.interruption_pending) {
+        switch (pending_int.interruption_type) {
+        case MSHV_HV_INTERRUPTION_TYPE_EXT_INT:
+            env->interrupt_injected = pending_int.interruption_vector;
+            break;
+        case MSHV_HV_INTERRUPTION_TYPE_NMI:
+            env->nmi_injected = 1;
+            break;
+        case MSHV_HV_INTERRUPTION_TYPE_HW_EXC:
+            env->exception_injected = 1;
+            env->exception_nr       = pending_int.interruption_vector;
+            env->has_error_code     = pending_int.deliver_error_code;
+            env->error_code         = pending_int.error_code;
+            break;
+        case MSHV_HV_INTERRUPTION_TYPE_SW_INT:
+            env->interrupt_injected = pending_int.interruption_vector;
+            env->soft_interrupt     = 1;
+            break;
+        case MSHV_HV_INTERRUPTION_TYPE_SW_EXC:
+        case MSHV_HV_INTERRUPTION_TYPE_PRIV_SW_EXC:
+            env->exception_injected = 1;
+            env->exception_nr       = pending_int.interruption_vector;
+            env->has_error_code     = pending_int.deliver_error_code;
+            env->error_code         = pending_int.error_code;
+            break;
+        default:
+            error_report("unknown interruption type %u",
+                         pending_int.interruption_type);
+            return -EINVAL;
+        }
+    }
+
+    /* disabled for one instr after STI, MOV/POP SS, see hvf_store_events() */
+    if (int_state.interrupt_shadow) {
+        env->hflags |= HF_INHIBIT_IRQ_MASK;
+    } else {
+        env->hflags &= ~HF_INHIBIT_IRQ_MASK;
+    }
+
+    /* see kvm_get_vcpu_events(), hvf_store_events() */
+    if (int_state.nmi_masked) {
+        env->hflags2 |= HF2_NMI_MASK;
+    } else {
+        env->hflags2 &= ~HF2_NMI_MASK;
+    }
+
+    /* HV_REGISTER_PENDING_EVENT0: pending exception not yet injected */
+    if (pending_exc.event_pending) {
+        env->exception_pending     = 1;
+        env->exception_nr          = pending_exc.vector;
+        env->has_error_code        = pending_exc.deliver_error_code;
+        env->error_code            = pending_exc.error_code;
+        env->exception_has_payload = (pending_exc.exception_parameter != 0);
+        env->exception_payload     = pending_exc.exception_parameter;
+    }
+
+    /*
+     * Ignoring HV_REGISTER_PENDING_EVENT1, virtualization fault events, MSHV
+     * does not support nested virtualization.
+     */
+
+    return 0;
+}
+
+static int set_vcpu_events(const CPUState *cpu)
+{
+    X86CPU *x86cpu = X86_CPU(cpu);
+    CPUX86State *env = &x86cpu->env;
+    union hv_x64_pending_interruption_register pending_int = { 0 };
+    union hv_x64_interrupt_state_register int_state = { 0 };
+    union hv_x64_pending_exception_event pending_exc = { 0 };
+    struct hv_register_assoc assocs[3];
+    int ret;
+
+    /* build pending_int from CPUX86State */
+    if (env->exception_injected) {
+        pending_int.interruption_pending = 1;
+        pending_int.interruption_type    = MSHV_HV_INTERRUPTION_TYPE_HW_EXC;
+        pending_int.interruption_vector  = env->exception_nr;
+        pending_int.deliver_error_code   = env->has_error_code;
+        pending_int.error_code           = env->error_code;
+    } else if (env->nmi_injected) {
+        pending_int.interruption_pending = 1;
+        pending_int.interruption_type    = MSHV_HV_INTERRUPTION_TYPE_NMI;
+        pending_int.interruption_vector  = EXCP02_NMI;
+    } else if (env->interrupt_injected >= 0) {
+        pending_int.interruption_pending = 1;
+        pending_int.interruption_type    = env->soft_interrupt
+            ? MSHV_HV_INTERRUPTION_TYPE_SW_INT
+            : MSHV_HV_INTERRUPTION_TYPE_EXT_INT;
+        pending_int.interruption_vector  = env->interrupt_injected;
+    }
+
+    /* build int_state, normalize to bool */
+    int_state.interrupt_shadow = !!(env->hflags  & HF_INHIBIT_IRQ_MASK);
+    int_state.nmi_masked       = !!(env->hflags2 & HF2_NMI_MASK);
+
+    /* build pending_exc */
+    if (env->exception_pending) {
+        pending_exc.event_pending       = 1;
+        pending_exc.vector              = env->exception_nr;
+        pending_exc.deliver_error_code  = env->has_error_code;
+        pending_exc.error_code          = env->error_code;
+        pending_exc.exception_parameter = env->exception_payload;
+    }
+
+    assocs[0].name = HV_REGISTER_PENDING_INTERRUPTION;
+    assocs[0].value.reg64 = pending_int.as_uint64;
+    assocs[1].name = HV_REGISTER_INTERRUPT_STATE;
+    assocs[1].value.reg64 = int_state.as_uint64;
+    assocs[2].name = HV_REGISTER_PENDING_EVENT0;
+    assocs[2].value.pending_exception_event = pending_exc;
+
+    ret = mshv_set_generic_regs(cpu, assocs, ARRAY_SIZE(assocs));
     if (ret < 0) {
-        error_report("Failed to load special registers");
+        error_report("failed to set vcpu event registers");
         return -1;
     }
 
     return 0;
 }
 
-static void add_cpuid_entry(GList *cpuid_entries,
+static int update_hflags(CPUState *cpu)
+{
+    X86CPU *x86cpu = X86_CPU(cpu);
+    CPUX86State *env = &x86cpu->env;
+
+    x86_update_hflags(env);
+
+    return 0;
+}
+
+int mshv_arch_load_vcpu_state(CPUState *cpu)
+{
+    int ret;
+
+    ret = get_standard_regs(cpu);
+    if (ret < 0) {
+        return ret;
+    }
+
+    ret = get_special_regs(cpu);
+    if (ret < 0) {
+        return ret;
+    }
+
+    /* INVARIANT: hflags are derived from regs+sregs, need to get both first */
+    update_hflags(cpu);
+
+    ret = get_xc_reg(cpu);
+    if (ret < 0) {
+        return ret;
+    }
+
+    ret = get_fpu(cpu);
+    if (ret < 0) {
+        return ret;
+    }
+
+    ret = mshv_get_msrs(cpu);
+    if (ret < 0) {
+        return ret;
+    }
+
+    ret = get_xsave_state(cpu);
+    if (ret < 0) {
+        return ret;
+    }
+
+    ret = get_vcpu_events(cpu);
+    if (ret < 0) {
+        return ret;
+    }
+
+    return 0;
+}
+
+static void add_cpuid_entry(GList **cpuid_entries,
                             uint32_t function, uint32_t index,
                             uint32_t eax, uint32_t ebx,
                             uint32_t ecx, uint32_t edx)
@@ -456,23 +988,26 @@
     entry->ecx = ecx;
     entry->edx = edx;
 
-    cpuid_entries = g_list_append(cpuid_entries, entry);
+    *cpuid_entries = g_list_append(*cpuid_entries, entry);
 }
 
-static void collect_cpuid_entries(const CPUState *cpu, GList *cpuid_entries)
+static void collect_cpuid_entries(const CPUState *cpu, GList **cpuid_entries)
 {
     X86CPU *x86_cpu = X86_CPU(cpu);
     CPUX86State *env = &x86_cpu->env;
     uint32_t eax, ebx, ecx, edx;
     uint32_t leaf, subleaf;
-    size_t max_leaf = 0x1F;
-    size_t max_subleaf = 0x20;
-
-    uint32_t leaves_with_subleaves[] = {0x4, 0x7, 0xD, 0xF, 0x10};
+    uint32_t max_basic_leaf, max_extended_leaf;
+    uint32_t max_subleaf = 0x20;
+    uint32_t leaves_with_subleaves[] = {0x04, 0x07, 0x0d, 0x0f, 0x10};
     int n_subleaf_leaves = ARRAY_SIZE(leaves_with_subleaves);
 
-    /* Regular leaves without subleaves */
-    for (leaf = 0; leaf <= max_leaf; leaf++) {
+    /* Get maximum basic and and extended CPUID leaves */
+    cpu_x86_cpuid(env, 0, 0, &max_basic_leaf, &ebx, &ecx, &edx);
+    cpu_x86_cpuid(env, 0x80000000, 0, &max_extended_leaf, &ebx, &ecx, &edx);
+
+    /* Collect basic leaves (0x0 to max_basic_leaf) */
+    for (leaf = 0; leaf <= max_basic_leaf; leaf++) {
         bool has_subleaves = false;
         for (int i = 0; i < n_subleaf_leaves; i++) {
             if (leaf == leaves_with_subleaves[i]) {
@@ -483,32 +1018,46 @@
 
         if (!has_subleaves) {
             cpu_x86_cpuid(env, leaf, 0, &eax, &ebx, &ecx, &edx);
-            if (eax == 0 && ebx == 0 && ecx == 0 && edx == 0) {
-                /* all zeroes indicates no more leaves */
-                continue;
-            }
-
             add_cpuid_entry(cpuid_entries, leaf, 0, eax, ebx, ecx, edx);
             continue;
         }
 
+        /*
+         * Valid XSAVE components can exist at a higher index se we need to set
+         * all subleaves for leaf 0x0d, even if we encounter an empty one.
+         */
+        if (leaf == 0x0d) {
+            for (subleaf = 0; subleaf <= 63; subleaf++) {
+                cpu_x86_cpuid(env, leaf, subleaf, &eax, &ebx, &ecx, &edx);
+                add_cpuid_entry(cpuid_entries, leaf, subleaf,
+                                eax, ebx, ecx, edx);
+            }
+            continue;
+        }
+
         subleaf = 0;
         while (subleaf < max_subleaf) {
             cpu_x86_cpuid(env, leaf, subleaf, &eax, &ebx, &ecx, &edx);
 
             if (eax == 0 && ebx == 0 && ecx == 0 && edx == 0) {
-                /* all zeroes indicates no more leaves */
                 break;
             }
-            add_cpuid_entry(cpuid_entries, leaf, 0, eax, ebx, ecx, edx);
+            add_cpuid_entry(cpuid_entries, leaf, subleaf, eax, ebx, ecx, edx);
             subleaf++;
         }
     }
+
+    /* Collect extended leaves (0x80000000 to max_extended_leaf) */
+    for (leaf = 0x80000000; leaf <= max_extended_leaf; leaf++) {
+        cpu_x86_cpuid(env, leaf, 0, &eax, &ebx, &ecx, &edx);
+        add_cpuid_entry(cpuid_entries, leaf, 0, eax, ebx, ecx, edx);
+    }
 }
 
 static int register_intercept_result_cpuid_entry(const CPUState *cpu,
                                                  uint8_t subleaf_specific,
                                                  uint8_t always_override,
+                                                 uint32_t ebx_mask,
                                                  struct hv_cpuid_entry *entry)
 {
     int ret;
@@ -522,22 +1071,18 @@
         .input.always_override = always_override,
         .input.padding = 0,
         /*
-         * With regard to masks - these are to specify bits to be overwritten
-         * The current CpuidEntry structure wouldn't allow to carry the masks
-         * in addition to the actual register values. For this reason, the
-         * masks are set to the exact values of the corresponding register bits
-         * to be registered for an overwrite. To view resulting values the
-         * hypervisor would return, HvCallGetVpCpuidValues hypercall can be
-         * used.
+         * Masks specify which bits to override. Set to 0xFFFFFFFF to
+         * override all bits with the values from the QEMU CPU model.
+         * A mask of 0 lets the hypervisor supply its own value.
          */
         .result.eax = entry->eax,
-        .result.eax_mask = entry->eax,
+        .result.eax_mask = 0xFFFFFFFF,
         .result.ebx = entry->ebx,
-        .result.ebx_mask = entry->ebx,
+        .result.ebx_mask = ebx_mask,
         .result.ecx = entry->ecx,
-        .result.ecx_mask = entry->ecx,
+        .result.ecx_mask = 0xFFFFFFFF,
         .result.edx = entry->edx,
-        .result.edx_mask = entry->edx,
+        .result.edx_mask = 0xFFFFFFFF,
     };
     union hv_register_intercept_result_parameters parameters = {
         .cpuid = cpuid_params,
@@ -568,6 +1113,7 @@
     int ret = 0, entry_ret;
     struct hv_cpuid_entry *entry;
     uint8_t subleaf_specific, always_override;
+    uint32_t ebx_mask;
 
     for (size_t i = 0; i < cpuid->nent; i++) {
         entry = &cpuid->entries[i];
@@ -575,29 +1121,62 @@
         /* set defaults */
         subleaf_specific = 0;
         always_override = 1;
+        ebx_mask = 0xFFFFFFFF;
 
-        /* Intel */
-        /* 0xb - Extended Topology Enumeration Leaf */
-        /* 0x1f - V2 Extended Topology Enumeration Leaf */
-        /* AMD */
-        /* 0x8000_001e - Processor Topology Information */
-        /* 0x8000_0026 - Extended CPU Topology */
-        if (entry->function == 0xb
-            || entry->function == 0x1f
-            || entry->function == 0x8000001e
-            || entry->function == 0x80000026) {
+        /*
+         * Intel
+         * 0xb - Extended Topology Enumeration Leaf
+         * 0x1f - V2 Extended Topology Enumeration Leaf
+         * AMD
+         * 0x8000_001e - Processor Topology Information
+         * 0x8000_0026 - Extended CPU Topology
+         */
+        if (entry->function == 0xb ||
+            entry->function == 0x1f ||
+            entry->function == 0x8000001e ||
+            entry->function == 0x80000026) {
             subleaf_specific = 1;
             always_override = 1;
-        } else if (entry->function == 0x00000001
-            || entry->function == 0x80000000
-            || entry->function == 0x80000001
-            || entry->function == 0x80000008) {
+        /*
+         * Feature enumeration leaves (subleaf-specific)
+         * 0x04: Deterministic Cache Parameters
+         * 0x07: Structured Extended Feature Flags
+         * 0x0D: Processor Extended State Enumeration
+         * 0x0F: Platform QoS Monitoring
+         * 0x10: Platform QoS Enforcement
+         */
+        } else if (entry->function == 0x04 ||
+                   entry->function == 0x07 ||
+                   entry->function == 0x0d ||
+                   entry->function == 0x0f ||
+                   entry->function == 0x10) {
+            subleaf_specific = 1;
+            always_override = 1;
+        /* Basic feature leaves (no subleaves) */
+        } else if (entry->function == 0x00000001 ||
+                   entry->function == 0x80000000 ||
+                   entry->function == 0x80000001 ||
+                   entry->function == 0x80000008) {
             subleaf_specific = 0;
             always_override = 1;
         }
 
-        entry_ret = register_intercept_result_cpuid_entry(cpu, subleaf_specific,
+        /*
+         * CPUID[0xD,0].EBX and CPUID[0xD,1].EBX report the XSAVE area
+         * size based on features currently enabled in XCR0/XSS. These
+         * values are dynamic and must not be overridden with static
+         * results from the QEMU CPU model. Setting ebx_mask to 0 lets
+         * the hypervisor supply EBX based on the guest's actual state.
+         */
+        if (entry->function == 0x0d &&
+           (entry->index == 0 || entry->index == 1)) {
+            ebx_mask = 0;
+        }
+
+        entry_ret = register_intercept_result_cpuid_entry(cpu,
+                                                          subleaf_specific,
                                                           always_override,
+                                                          ebx_mask,
                                                           entry);
         if ((entry_ret < 0) && (ret == 0)) {
             ret = entry_ret;
@@ -607,7 +1186,7 @@
     return ret;
 }
 
-static int set_cpuid2(const CPUState *cpu)
+static int init_cpuid2(const CPUState *cpu)
 {
     int ret;
     size_t n_entries, cpuid_size;
@@ -615,7 +1194,7 @@
     struct hv_cpuid_entry *entry;
     GList *entries = NULL;
 
-    collect_cpuid_entries(cpu, entries);
+    collect_cpuid_entries(cpu, &entries);
     n_entries = g_list_length(entries);
 
     cpuid_size = sizeof(struct hv_cpuid)
@@ -710,48 +1289,65 @@
     return 0;
 }
 
-static int set_fpu(const CPUState *cpu, const struct MshvFPU *regs)
+static int set_fpu(const CPUState *cpu)
 {
     struct hv_register_assoc assocs[ARRAY_SIZE(FPU_REGISTER_NAMES)];
     union hv_register_value *value;
-    size_t fp_i;
     union hv_x64_fp_control_status_register *ctrl_status;
     union hv_x64_xmm_control_status_register *xmm_ctrl_status;
     int ret;
     size_t n_regs = ARRAY_SIZE(FPU_REGISTER_NAMES);
+    X86CPU *x86cpu = X86_CPU(cpu);
+    CPUX86State *env = &x86cpu->env;
+    size_t i, fp_i;
+    bool valid;
 
     /* first 16 registers are xmm0-xmm15 */
-    for (size_t i = 0; i < 16; i++) {
+    for (i = 0; i < 16; i++) {
         assocs[i].name = FPU_REGISTER_NAMES[i];
         value = &assocs[i].value;
-        memcpy(&value->reg128, &regs->xmm[i], 16);
+        value->reg128.low_part  = env->xmm_regs[i].ZMM_Q(0);
+        value->reg128.high_part = env->xmm_regs[i].ZMM_Q(1);
     }
 
     /* next 8 registers are fp_mmx0-fp_mmx7 */
-    for (size_t i = 16; i < 24; i++) {
-        assocs[i].name = FPU_REGISTER_NAMES[i];
+    for (i = 16; i < 24; i++) {
         fp_i = (i - 16);
+        assocs[i].name = FPU_REGISTER_NAMES[i];
         value = &assocs[i].value;
-        memcpy(&value->reg128, &regs->fpr[fp_i], 16);
+        value->fp.mantissa        = env->fpregs[fp_i].d.low;
+        value->fp.biased_exponent = env->fpregs[fp_i].d.high & 0x7FFF;
+        value->fp.sign            = (env->fpregs[fp_i].d.high >> 15) & 0x1;
+        value->fp.reserved        = 0;
     }
 
     /* last two registers are fp_control_status and xmm_control_status */
     assocs[24].name = FPU_REGISTER_NAMES[24];
     value = &assocs[24].value;
     ctrl_status = &value->fp_control_status;
-    ctrl_status->fp_control = regs->fcw;
-    ctrl_status->fp_status = regs->fsw;
-    ctrl_status->fp_tag = regs->ftwx;
+
+    ctrl_status->fp_control = env->fpuc;
+    /* bits 11,12,13 are the top of stack pointer */
+    ctrl_status->fp_status = (env->fpus & ~0x3800) | ((env->fpstt & 0x7) << 11);
+
+    ctrl_status->fp_tag = 0;
+    for (i = 0; i < 8; i++) {
+        valid = (env->fptags[i] == 0);
+        if (valid) {
+            ctrl_status->fp_tag |= (1u << i);
+        }
+    }
+
     ctrl_status->reserved = 0;
-    ctrl_status->last_fp_op = regs->last_opcode;
-    ctrl_status->last_fp_rip = regs->last_ip;
+    ctrl_status->last_fp_op = env->fpop;
+    ctrl_status->last_fp_rip = env->fpip;
 
     assocs[25].name = FPU_REGISTER_NAMES[25];
     value = &assocs[25].value;
     xmm_ctrl_status = &value->xmm_control_status;
-    xmm_ctrl_status->xmm_status_control = regs->mxcsr;
-    xmm_ctrl_status->xmm_status_control_mask = 0;
-    xmm_ctrl_status->last_fp_rdp = regs->last_dp;
+    xmm_ctrl_status->xmm_status_control = env->mxcsr;
+    xmm_ctrl_status->xmm_status_control_mask = 0x0000ffff;
+    xmm_ctrl_status->last_fp_rdp = env->fpdp;
 
     ret = mshv_set_generic_regs(cpu, assocs, n_regs);
     if (ret < 0) {
@@ -762,12 +1358,15 @@
     return 0;
 }
 
-static int set_xc_reg(const CPUState *cpu, uint64_t xcr0)
+static int set_xc_reg(const CPUState *cpu)
 {
     int ret;
+    X86CPU *x86cpu = X86_CPU(cpu);
+    CPUX86State *env = &x86cpu->env;
+
     struct hv_register_assoc assoc = {
         .name = HV_X64_REGISTER_XFEM,
-        .value.reg64 = xcr0,
+        .value.reg64 = env->xcr0,
     };
 
     ret = mshv_set_generic_regs(cpu, &assoc, 1);
@@ -778,30 +1377,6 @@
     return 0;
 }
 
-static int set_cpu_state(const CPUState *cpu, const MshvFPU *fpu_regs,
-                         uint64_t xcr0)
-{
-    int ret;
-
-    ret = set_standard_regs(cpu);
-    if (ret < 0) {
-        return ret;
-    }
-    ret = set_special_regs(cpu);
-    if (ret < 0) {
-        return ret;
-    }
-    ret = set_fpu(cpu, fpu_regs);
-    if (ret < 0) {
-        return ret;
-    }
-    ret = set_xc_reg(cpu, xcr0);
-    if (ret < 0) {
-        return ret;
-    }
-    return 0;
-}
-
 static int get_vp_state(int cpu_fd, struct mshv_get_set_vp_state *state)
 {
     int ret;
@@ -815,7 +1390,7 @@
     return 0;
 }
 
-static int get_lapic(int cpu_fd,
+static int get_lapic(const CPUState *cpu,
                      struct hv_local_interrupt_controller_state *state)
 {
     int ret;
@@ -823,6 +1398,7 @@
     /* buffer aligned to 4k, as *state requires that */
     void *buffer = qemu_memalign(size, size);
     struct mshv_get_set_vp_state mshv_state = { 0 };
+    int cpu_fd = mshv_vcpufd(cpu);
 
     mshv_state.buf_ptr = (uint64_t) buffer;
     mshv_state.buf_sz = size;
@@ -859,7 +1435,7 @@
     return 0;
 }
 
-static int set_lapic(int cpu_fd,
+static int set_lapic(const CPUState *cpu,
                      const struct hv_local_interrupt_controller_state *state)
 {
     int ret;
@@ -867,6 +1443,7 @@
     /* buffer aligned to 4k, as *state requires that */
     void *buffer = qemu_memalign(size, size);
     struct mshv_get_set_vp_state mshv_state = { 0 };
+    int cpu_fd = mshv_vcpufd(cpu);
 
     if (!state) {
         error_report("lapic state is NULL");
@@ -888,13 +1465,13 @@
     return 0;
 }
 
-static int set_lint(int cpu_fd)
+static int init_lint(const CPUState *cpu)
 {
     int ret;
     uint32_t *lvt_lint0, *lvt_lint1;
 
     struct hv_local_interrupt_controller_state lapic_state = { 0 };
-    ret = get_lapic(cpu_fd, &lapic_state);
+    ret = get_lapic(cpu, &lapic_state);
     if (ret < 0) {
         return ret;
     }
@@ -907,167 +1484,46 @@
 
     /* TODO: should we skip setting lapic if the values are the same? */
 
-    return set_lapic(cpu_fd, &lapic_state);
+    return set_lapic(cpu, &lapic_state);
 }
 
-static int setup_msrs(const CPUState *cpu)
+int mshv_arch_store_vcpu_state(const CPUState *cpu)
 {
     int ret;
-    uint64_t default_type = MSR_MTRR_ENABLE | MSR_MTRR_MEM_TYPE_WB;
 
-    /* boot msr entries */
-    MshvMsrEntry msrs[9] = {
-        { .index = IA32_MSR_SYSENTER_CS, .data = 0x0, },
-        { .index = IA32_MSR_SYSENTER_ESP, .data = 0x0, },
-        { .index = IA32_MSR_SYSENTER_EIP, .data = 0x0, },
-        { .index = IA32_MSR_STAR, .data = 0x0, },
-        { .index = IA32_MSR_CSTAR, .data = 0x0, },
-        { .index = IA32_MSR_LSTAR, .data = 0x0, },
-        { .index = IA32_MSR_KERNEL_GS_BASE, .data = 0x0, },
-        { .index = IA32_MSR_SFMASK, .data = 0x0, },
-        { .index = IA32_MSR_MTRR_DEF_TYPE, .data = default_type, },
-    };
-
-    ret = mshv_configure_msr(cpu, msrs, 9);
+    ret = set_standard_regs(cpu);
     if (ret < 0) {
-        error_report("failed to setup msrs");
-        return -1;
-    }
-
-    return 0;
-}
-
-/*
- * TODO: populate topology info:
- *
- * X86CPU *x86cpu = X86_CPU(cpu);
- * CPUX86State *env = &x86cpu->env;
- * X86CPUTopoInfo *topo_info = &env->topo_info;
- */
-int mshv_configure_vcpu(const CPUState *cpu, const struct MshvFPU *fpu,
-                        uint64_t xcr0)
-{
-    int ret;
-    int cpu_fd = mshv_vcpufd(cpu);
-
-    ret = set_cpuid2(cpu);
-    if (ret < 0) {
-        error_report("failed to set cpuid");
-        return -1;
-    }
-
-    ret = setup_msrs(cpu);
-    if (ret < 0) {
-        error_report("failed to setup msrs");
-        return -1;
-    }
-
-    ret = set_cpu_state(cpu, fpu, xcr0);
-    if (ret < 0) {
-        error_report("failed to set cpu state");
-        return -1;
-    }
-
-    ret = set_lint(cpu_fd);
-    if (ret < 0) {
-        error_report("failed to set lpic int");
-        return -1;
-    }
-
-    return 0;
-}
-
-static int put_regs(const CPUState *cpu)
-{
-    X86CPU *x86cpu = X86_CPU(cpu);
-    CPUX86State *env = &x86cpu->env;
-    MshvFPU fpu = {0};
-    int ret;
-
-    memset(&fpu, 0, sizeof(fpu));
-
-    ret = mshv_configure_vcpu(cpu, &fpu, env->xcr0);
-    if (ret < 0) {
-        error_report("failed to configure vcpu");
         return ret;
     }
 
-    return 0;
-}
-
-struct MsrPair {
-    uint32_t index;
-    uint64_t value;
-};
-
-static int put_msrs(const CPUState *cpu)
-{
-    int ret = 0;
-    X86CPU *x86cpu = X86_CPU(cpu);
-    CPUX86State *env = &x86cpu->env;
-    MshvMsrEntries *msrs = g_malloc0(sizeof(MshvMsrEntries));
-
-    struct MsrPair pairs[] = {
-        { MSR_IA32_SYSENTER_CS,    env->sysenter_cs },
-        { MSR_IA32_SYSENTER_ESP,   env->sysenter_esp },
-        { MSR_IA32_SYSENTER_EIP,   env->sysenter_eip },
-        { MSR_EFER,                env->efer },
-        { MSR_PAT,                 env->pat },
-        { MSR_STAR,                env->star },
-        { MSR_CSTAR,               env->cstar },
-        { MSR_LSTAR,               env->lstar },
-        { MSR_KERNELGSBASE,        env->kernelgsbase },
-        { MSR_FMASK,               env->fmask },
-        { MSR_MTRRdefType,         env->mtrr_deftype },
-        { MSR_VM_HSAVE_PA,         env->vm_hsave },
-        { MSR_SMI_COUNT,           env->msr_smi_count },
-        { MSR_IA32_PKRS,           env->pkrs },
-        { MSR_IA32_BNDCFGS,        env->msr_bndcfgs },
-        { MSR_IA32_XSS,            env->xss },
-        { MSR_IA32_UMWAIT_CONTROL, env->umwait },
-        { MSR_IA32_TSX_CTRL,       env->tsx_ctrl },
-        { MSR_AMD64_TSC_RATIO,     env->amd_tsc_scale_msr },
-        { MSR_TSC_AUX,             env->tsc_aux },
-        { MSR_TSC_ADJUST,          env->tsc_adjust },
-        { MSR_IA32_SMBASE,         env->smbase },
-        { MSR_IA32_SPEC_CTRL,      env->spec_ctrl },
-        { MSR_VIRT_SSBD,           env->virt_ssbd },
-    };
-
-    if (ARRAY_SIZE(pairs) > MSHV_MSR_ENTRIES_COUNT) {
-        error_report("MSR entries exceed maximum size");
-        g_free(msrs);
-        return -1;
-    }
-
-    for (size_t i = 0; i < ARRAY_SIZE(pairs); i++) {
-        MshvMsrEntry *entry = &msrs->entries[i];
-        entry->index = pairs[i].index;
-        entry->reserved = 0;
-        entry->data = pairs[i].value;
-        msrs->nmsrs++;
-    }
-
-    ret = mshv_configure_msr(cpu, &msrs->entries[0], msrs->nmsrs);
-    g_free(msrs);
-    return ret;
-}
-
-
-int mshv_arch_put_registers(const CPUState *cpu)
-{
-    int ret;
-
-    ret = put_regs(cpu);
+    ret = set_special_regs(cpu);
     if (ret < 0) {
-        error_report("Failed to put registers");
-        return -1;
+        return ret;
     }
 
-    ret = put_msrs(cpu);
+    ret = set_xc_reg(cpu);
     if (ret < 0) {
-        error_report("Failed to put msrs");
-        return -1;
+        return ret;
+    }
+
+    ret = set_fpu(cpu);
+    if (ret < 0) {
+        return ret;
+    }
+
+    ret = mshv_set_msrs(cpu);
+    if (ret < 0) {
+        return ret;
+    }
+
+    ret = set_xsave_state(cpu);
+    if (ret < 0) {
+        return ret;
+    }
+
+    ret = set_vcpu_events(cpu);
+    if (ret < 0) {
+        return ret;
     }
 
     return 0;
@@ -1079,6 +1535,12 @@
     features->access_guest_idle_reg = 1;
 }
 
+void mshv_arch_disable_partition_proc_features(
+     union hv_partition_processor_features *disabled_features)
+{
+    disabled_features->la57_support = 1;
+}
+
 static int set_memory_info(const struct hyperv_message *msg,
                            struct hv_x64_memory_intercept_message *info)
 {
@@ -1103,16 +1565,16 @@
     int ret;
     x86_insn_stream stream = { .bytes = insn_bytes, .len = insn_len };
 
-    ret = mshv_load_regs(cpu);
+    ret = load_regs(cpu);
     if (ret < 0) {
-        error_report("failed to load registers");
+        error_report("Failed to load registers");
         return -1;
     }
 
     decode_instruction_stream(env, &decode, &stream);
     exec_instruction(env, &decode);
 
-    ret = mshv_store_regs(cpu);
+    ret = store_regs(cpu);
     if (ret < 0) {
         error_report("failed to store registers");
         return -1;
@@ -1291,25 +1753,6 @@
     return 0;
 }
 
-static int fetch_guest_state(CPUState *cpu)
-{
-    int ret;
-
-    ret = mshv_get_standard_regs(cpu);
-    if (ret < 0) {
-        error_report("Failed to get standard registers");
-        return -1;
-    }
-
-    ret = mshv_get_special_regs(cpu);
-    if (ret < 0) {
-        error_report("Failed to get special registers");
-        return -1;
-    }
-
-    return 0;
-}
-
 static int read_memory(const CPUState *cpu, uint64_t initial_gva,
                        uint64_t initial_gpa, uint64_t gva, uint8_t *data,
                        size_t len)
@@ -1429,9 +1872,9 @@
     X86CPU *x86_cpu = X86_CPU(cpu);
     CPUX86State *env = &x86_cpu->env;
 
-    ret = fetch_guest_state(cpu);
+    ret = load_regs(cpu);
     if (ret < 0) {
-        error_report("Failed to fetch guest state");
+        error_report("Failed to load registers");
         return -1;
     }
 
@@ -1462,7 +1905,7 @@
 
     ret = set_x64_registers(cpu, reg_names, reg_values);
     if (ret < 0) {
-        error_report("Failed to set x64 registers");
+        error_report("Failed to set RIP and RAX registers");
         return -1;
     }
 
@@ -1605,8 +2048,10 @@
     X86CPU *x86_cpu = X86_CPU(cpu);
     CPUX86State *env = &x86_cpu->env;
     AccelCPUState *state = cpu->accel;
-    size_t page = HV_HYP_PAGE_SIZE;
+    size_t page = HV_HYP_PAGE_SIZE, xsave_len;
     void *mem = qemu_memalign(page, 2 * page);
+    int ret;
+    X86XSaveHeader *header;
 
     /* sanity check, to make sure we don't overflow the page */
     QEMU_BUILD_BUG_ON((MAX_REGISTER_COUNT
@@ -1614,11 +2059,48 @@
                       + sizeof(hv_input_get_vp_registers)
                       > HV_HYP_PAGE_SIZE));
 
+    /* mmap the registers page */
+    void *rp = mmap(NULL, page, PROT_READ | PROT_WRITE,
+                    MAP_SHARED, mshv_vcpufd(cpu),
+                    MSHV_VP_MMAP_OFFSET_REGISTERS * page);
+    if (rp == MAP_FAILED) {
+        warn_report("register page mmap failed, falling back to hypercalls: %s",
+                    strerror(errno));
+        env->regs_page = NULL;
+    } else {
+        env->regs_page = (struct hv_vp_register_page *) rp;
+    }
+
     state->hvcall_args.base = mem;
     state->hvcall_args.input_page = mem;
     state->hvcall_args.output_page = (uint8_t *)mem + page;
 
     env->emu_mmio_buf = g_new(char, 4096);
+
+    /* Initialize XSAVE buffer page-aligned */
+    /* TODO: pick proper size based on CPUID */
+    xsave_len = page;
+    env->xsave_buf = qemu_memalign(page, xsave_len);
+    env->xsave_buf_len = xsave_len;
+    memset(env->xsave_buf, 0, env->xsave_buf_len);
+
+    /* we need to set the compacted format bit in xsave header for mshv */
+    header = (X86XSaveHeader *)(env->xsave_buf + sizeof(X86LegacyXSaveArea));
+    header->xcomp_bv = header->xstate_bv | (1ULL << 63);
+
+    /*
+     * TODO: populate topology info:
+     * X86CPUTopoInfo *topo_info = &env->topo_info;
+     */
+
+    ret = init_cpuid2(cpu);
+    assert(ret == 0);
+
+    ret = mshv_init_msrs(cpu);
+    assert(ret == 0);
+
+    ret = init_lint(cpu);
+    assert(ret == 0);
 }
 
 void mshv_arch_destroy_vcpu(CPUState *cpu)
@@ -1627,9 +2109,55 @@
     CPUX86State *env = &x86_cpu->env;
     AccelCPUState *state = cpu->accel;
 
+    /* Unmap the register page */
+    if (env->regs_page) {
+        munmap(env->regs_page, HV_HYP_PAGE_SIZE);
+        env->regs_page = NULL;
+    }
     g_free(state->hvcall_args.base);
     state->hvcall_args = (MshvHvCallArgs){0};
     g_clear_pointer(&env->emu_mmio_buf, g_free);
+
+    qemu_vfree(env->xsave_buf);
+    env->xsave_buf = NULL;
+    env->xsave_buf_len = 0;
+}
+
+uint32_t mshv_get_supported_cpuid(uint32_t func, uint32_t idx, int reg)
+{
+    uint32_t eax, ebx, ecx, edx;
+    uint32_t ret = 0;
+
+    host_cpuid(func, idx, &eax, &ebx, &ecx, &edx);
+    switch (reg) {
+    case R_EAX:
+        ret = eax; break;
+    case R_EBX:
+        ret = ebx; break;
+    case R_ECX:
+        ret = ecx; break;
+    case R_EDX:
+        ret = edx; break;
+    }
+
+    /* Disable nested virtualization features not yet supported by MSHV */
+    if (func == 0x80000001 && reg == R_ECX) {
+        ret &= ~CPUID_EXT3_SVM;
+    }
+    if (func == 0x01       && reg == R_ECX) {
+        ret &= ~CPUID_EXT_VMX;
+    }
+
+    if (func == 0x07 && idx == 0 && reg == R_ECX) {
+        /*
+         * LA57 (5-level paging) causes incorrect GVA=>GPA translations
+         * in the instruction decoder/emulator. Disable until page table
+         * walk in x86_mmu.c works w/ 5-level paging.
+         */
+        ret &= ~CPUID_7_0_ECX_LA57;
+    }
+
+    return ret;
 }
 
 /*
@@ -1671,3 +2199,62 @@
 
     return ret;
 }
+
+static void mshv_cpu_xsave_init(void)
+{
+    static bool first = true;
+    uint32_t eax, ebx, ecx, edx;
+    int i;
+
+    if (!first) {
+        return;
+    }
+    first = false;
+
+    /* x87 and SSE states are in the legacy region of the XSAVE area. */
+    x86_ext_save_areas[XSTATE_FP_BIT].offset = 0;
+    x86_ext_save_areas[XSTATE_SSE_BIT].offset = 0;
+
+    for (i = XSTATE_SSE_BIT + 1; i < XSAVE_STATE_AREA_COUNT; i++) {
+        ExtSaveArea *esa = &x86_ext_save_areas[i];
+
+        if (!esa->size) {
+            continue;
+        }
+        host_cpuid(0xd, i, &eax, &ebx, &ecx, &edx);
+        if (eax != 0) {
+            assert(esa->size == eax);
+            esa->offset = ebx;
+            esa->ecx = ecx;
+        }
+    }
+}
+
+static void mshv_cpu_instance_init(CPUState *cs)
+{
+    X86CPU *cpu = X86_CPU(cs);
+
+    host_cpu_instance_init(cpu);
+    mshv_cpu_xsave_init();
+}
+
+static void mshv_cpu_accel_class_init(ObjectClass *oc, const void *data)
+{
+    AccelCPUClass *acc = ACCEL_CPU_CLASS(oc);
+
+    acc->cpu_instance_init = mshv_cpu_instance_init;
+}
+
+static const TypeInfo mshv_cpu_accel_type_info = {
+    .name = ACCEL_CPU_NAME("mshv"),
+    .parent = TYPE_ACCEL_CPU,
+    .class_init = mshv_cpu_accel_class_init,
+    .abstract = true,
+};
+
+static void mshv_cpu_accel_register_types(void)
+{
+    type_register_static(&mshv_cpu_accel_type_info);
+}
+
+type_init(mshv_cpu_accel_register_types);
diff --git a/target/i386/mshv/msr.c b/target/i386/mshv/msr.c
new file mode 100644
index 0000000..8c220a9
--- /dev/null
+++ b/target/i386/mshv/msr.c
@@ -0,0 +1,425 @@
+/*
+ * QEMU MSHV support
+ *
+ * Copyright Microsoft, Corp. 2025
+ *
+ * Authors: Magnus Kulke  <magnuskulke@microsoft.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "system/mshv.h"
+#include "system/mshv_int.h"
+#include "hw/hyperv/hvgdk_mini.h"
+#include "linux/mshv.h"
+#include "qemu/error-report.h"
+#include "cpu.h"
+
+#define MSHV_ENV_FIELD(env, offset) (*(uint64_t *)((char *)(env) + (offset)))
+
+typedef struct MshvMsrEnvMap {
+    uint32_t msr_index;
+    uint32_t hv_name;
+    ptrdiff_t env_offset;
+} MshvMsrEnvMap;
+
+/* We assert that 64bit access to sysenter_cs is safe because of padding */
+QEMU_BUILD_BUG_ON(offsetof(CPUX86State, sysenter_esp) -
+                  offsetof(CPUX86State, sysenter_cs)
+                  < sizeof(uint64_t));
+
+/* Those MSRs have a direct mapping to fields in CPUX86State  */
+static const MshvMsrEnvMap msr_env_map[] = {
+    /* Architectural */
+    { IA32_MSR_EFER, HV_X64_REGISTER_EFER, offsetof(CPUX86State, efer) },
+    { IA32_MSR_PAT,  HV_X64_REGISTER_PAT,  offsetof(CPUX86State, pat) },
+
+    /* Syscall */
+    { IA32_MSR_SYSENTER_CS,    HV_X64_REGISTER_SYSENTER_CS,
+                               offsetof(CPUX86State, sysenter_cs) },
+    { IA32_MSR_SYSENTER_ESP,   HV_X64_REGISTER_SYSENTER_ESP,
+                               offsetof(CPUX86State, sysenter_esp) },
+    { IA32_MSR_SYSENTER_EIP,   HV_X64_REGISTER_SYSENTER_EIP,
+                               offsetof(CPUX86State, sysenter_eip) },
+    { IA32_MSR_STAR,           HV_X64_REGISTER_STAR,
+                               offsetof(CPUX86State, star) },
+    { IA32_MSR_LSTAR,          HV_X64_REGISTER_LSTAR,
+                               offsetof(CPUX86State, lstar) },
+    { IA32_MSR_CSTAR,          HV_X64_REGISTER_CSTAR,
+                               offsetof(CPUX86State, cstar) },
+    { IA32_MSR_SFMASK,         HV_X64_REGISTER_SFMASK,
+                               offsetof(CPUX86State, fmask) },
+    { IA32_MSR_KERNEL_GS_BASE, HV_X64_REGISTER_KERNEL_GS_BASE,
+                               offsetof(CPUX86State, kernelgsbase) },
+
+    /* TSC-related */
+    { IA32_MSR_TSC,          HV_X64_REGISTER_TSC,
+                             offsetof(CPUX86State, tsc) },
+    { IA32_MSR_TSC_AUX,      HV_X64_REGISTER_TSC_AUX,
+                             offsetof(CPUX86State, tsc_aux) },
+    { IA32_MSR_TSC_ADJUST,   HV_X64_REGISTER_TSC_ADJUST,
+                             offsetof(CPUX86State, tsc_adjust) },
+
+    /* Hyper-V per-partition MSRs */
+    { HV_X64_MSR_HYPERCALL,     HV_X64_REGISTER_HYPERCALL,
+                                offsetof(CPUX86State, msr_hv_hypercall) },
+    { HV_X64_MSR_GUEST_OS_ID,   HV_REGISTER_GUEST_OS_ID,
+                                offsetof(CPUX86State, msr_hv_guest_os_id) },
+    { HV_X64_MSR_REFERENCE_TSC, HV_REGISTER_REFERENCE_TSC,
+                                offsetof(CPUX86State, msr_hv_tsc) },
+
+    /* Hyper-V MSRs (non-SINT) */
+    { HV_X64_MSR_SCONTROL,  HV_REGISTER_SCONTROL,
+                            offsetof(CPUX86State, msr_hv_synic_control) },
+    { HV_X64_MSR_SIEFP,     HV_REGISTER_SIEFP,
+                            offsetof(CPUX86State, msr_hv_synic_evt_page) },
+    { HV_X64_MSR_SIMP,      HV_REGISTER_SIMP,
+                            offsetof(CPUX86State, msr_hv_synic_msg_page) },
+
+    /* MTRR default type */
+    { IA32_MSR_MTRR_DEF_TYPE, HV_X64_REGISTER_MSR_MTRR_DEF_TYPE,
+                              offsetof(CPUX86State, mtrr_deftype) },
+
+    /* CET / Shadow Stack */
+    { MSR_IA32_U_CET,       HV_X64_REGISTER_U_CET,
+                            offsetof(CPUX86State, u_cet) },
+    { MSR_IA32_S_CET,       HV_X64_REGISTER_S_CET,
+                            offsetof(CPUX86State, s_cet) },
+    { MSR_IA32_PL0_SSP,     HV_X64_REGISTER_PL0_SSP,
+                            offsetof(CPUX86State, pl0_ssp) },
+    { MSR_IA32_PL1_SSP,     HV_X64_REGISTER_PL1_SSP,
+                            offsetof(CPUX86State, pl1_ssp) },
+    { MSR_IA32_PL2_SSP,     HV_X64_REGISTER_PL2_SSP,
+                            offsetof(CPUX86State, pl2_ssp) },
+    { MSR_IA32_PL3_SSP,     HV_X64_REGISTER_PL3_SSP,
+                            offsetof(CPUX86State, pl3_ssp) },
+    { MSR_IA32_INT_SSP_TAB, HV_X64_REGISTER_INTERRUPT_SSP_TABLE_ADDR,
+                            offsetof(CPUX86State, int_ssp_table) },
+
+    /* XSAVE Supervisor State */
+    { MSR_IA32_XSS,         HV_X64_REGISTER_U_XSS,
+                            offsetof(CPUX86State, xss) },
+
+    /* Other */
+
+    /* TODO: find out processor features that correlate to unsupported MSRs. */
+    /* { IA32_MSR_MISC_ENABLE, HV_X64_REGISTER_MSR_IA32_MISC_ENABLE, */
+    /*                         offsetof(CPUX86State, msr_ia32_misc_enable) }, */
+    /* { IA32_MSR_BNDCFGS,     HV_X64_REGISTER_BNDCFGS, */
+    /*                         offsetof(CPUX86State, msr_bndcfgs) }, */
+    { IA32_MSR_SPEC_CTRL,   HV_X64_REGISTER_SPEC_CTRL,
+                            offsetof(CPUX86State, spec_ctrl) },
+};
+
+/*
+ * The assocs have to be set according to this schema:
+ *      8  entries for 0-7 mtrr_base
+ *      8  entries for mtrr_mask 0-7
+ *      11 entries for 1 x 64k, 2 x 16k, 8 x 4k fixed MTRR
+ *      27 total entries
+ */
+
+#define MSHV_MTRR_MSR_COUNT 27
+#define MSHV_MSR_TOTAL_COUNT (ARRAY_SIZE(msr_env_map) + MSHV_MTRR_MSR_COUNT)
+
+static void store_in_env_mtrr_phys(CPUState *cpu,
+                                   const struct hv_register_assoc *assocs,
+                                   size_t n_assocs)
+{
+    X86CPU *x86_cpu = X86_CPU(cpu);
+    CPUX86State *env = &x86_cpu->env;
+    size_t i, fixed_offset;
+    hv_register_name hv_name;
+    uint64_t base, mask;
+
+    assert(n_assocs == MSHV_MTRR_MSR_COUNT);
+
+    for (i = 0; i < MSR_MTRRcap_VCNT; i++) {
+        hv_name = HV_X64_REGISTER_MSR_MTRR_PHYS_BASE0 + i;
+        assert(assocs[i].name == hv_name);
+        hv_name = HV_X64_REGISTER_MSR_MTRR_PHYS_MASK0 + i;
+        assert(assocs[i + MSR_MTRRcap_VCNT].name == hv_name);
+
+        base = assocs[i].value.reg64;
+        mask = assocs[i + MSR_MTRRcap_VCNT].value.reg64;
+        env->mtrr_var[i].base = base;
+        env->mtrr_var[i].mask = mask;
+    }
+
+    /* fixed 1x 64, 2x 16, 8x 4 kB */
+    fixed_offset = MSR_MTRRcap_VCNT * 2;
+    for (i = 0; i < 11; i++) {
+        hv_name = HV_X64_REGISTER_MSR_MTRR_FIX64K00000 + i;
+        assert(assocs[fixed_offset + i].name == hv_name);
+        env->mtrr_fixed[i] = assocs[fixed_offset + i].value.reg64;
+    }
+}
+
+/*
+ * The assocs have to be set according to this schema:
+ *      8  entries for 0-7 mtrr_base
+ *      8  entries for mtrr_mask 0-7
+ *      11 entries for 1 x 64k, 2 x 16k, 8 x 4k fixed MTRR
+ *      27 total entries
+ */
+static void load_from_env_mtrr_phys(const CPUState *cpu,
+                                    struct hv_register_assoc *assocs,
+                                    size_t n_assocs)
+{
+    X86CPU *x86_cpu = X86_CPU(cpu);
+    CPUX86State *env = &x86_cpu->env;
+    size_t i, fixed_offset;
+    uint64_t base, mask, fixed_value;
+    hv_register_name base_name, mask_name, fixed_name;
+    hv_register_assoc *assoc;
+
+    assert(n_assocs == MSHV_MTRR_MSR_COUNT);
+
+    for (i = 0; i < MSR_MTRRcap_VCNT; i++) {
+        base = env->mtrr_var[i].base;
+        mask = env->mtrr_var[i].mask;
+
+        base_name = HV_X64_REGISTER_MSR_MTRR_PHYS_BASE0 + i;
+        mask_name = HV_X64_REGISTER_MSR_MTRR_PHYS_MASK0 + i;
+
+        assoc = &assocs[i];
+        assoc->name = base_name;
+        assoc->value.reg64 = base;
+
+        assoc = &assocs[i + MSR_MTRRcap_VCNT];
+        assoc->name = mask_name;
+        assoc->value.reg64 = mask;
+    }
+
+    /* fixed 1x 64, 2x 16, 8x 4 kB */
+    fixed_offset = MSR_MTRRcap_VCNT * 2;
+    for (i = 0; i < 11; i++) {
+        fixed_name = HV_X64_REGISTER_MSR_MTRR_FIX64K00000 + i;
+        fixed_value = env->mtrr_fixed[i];
+
+        assoc = &assocs[fixed_offset + i];
+        assoc->name = fixed_name;
+        assoc->value.reg64 = fixed_value;
+    }
+}
+
+int mshv_init_msrs(const CPUState *cpu)
+{
+    int ret;
+    uint64_t d_t = MSR_MTRR_ENABLE | MSR_MTRR_MEM_TYPE_WB;
+
+    const struct hv_register_assoc assocs[] = {
+        { .name = HV_X64_REGISTER_SYSENTER_CS,       .value.reg64 = 0x0 },
+        { .name = HV_X64_REGISTER_SYSENTER_ESP,      .value.reg64 = 0x0 },
+        { .name = HV_X64_REGISTER_SYSENTER_EIP,      .value.reg64 = 0x0 },
+        { .name = HV_X64_REGISTER_STAR,              .value.reg64 = 0x0 },
+        { .name = HV_X64_REGISTER_CSTAR,             .value.reg64 = 0x0 },
+        { .name = HV_X64_REGISTER_LSTAR,             .value.reg64 = 0x0 },
+        { .name = HV_X64_REGISTER_KERNEL_GS_BASE,    .value.reg64 = 0x0 },
+        { .name = HV_X64_REGISTER_SFMASK,            .value.reg64 = 0x0 },
+        { .name = HV_X64_REGISTER_MSR_MTRR_DEF_TYPE, .value.reg64 = d_t },
+    };
+
+    ret = mshv_set_generic_regs(cpu, assocs, ARRAY_SIZE(assocs));
+    if (ret < 0) {
+        error_report("failed to put msrs");
+        return -1;
+    }
+
+    return 0;
+}
+
+
+/*
+ * INVARIANT: this fn expects assocs in the same order as they appear in
+ * msr_env_map.
+ */
+static void store_in_env(CPUState *cpu, const struct hv_register_assoc *assocs,
+                         size_t n_assocs)
+{
+    X86CPU *x86_cpu = X86_CPU(cpu);
+    CPUX86State *env = &x86_cpu->env;
+    size_t i, j;
+    const MshvMsrEnvMap *mapping;
+    union hv_register_value hv_value;
+    ptrdiff_t offset;
+    uint32_t hv_name;
+    size_t mtrr_index;
+
+    assert(n_assocs <= MSHV_MSR_TOTAL_COUNT);
+
+    for (i = 0, j = 0; i < ARRAY_SIZE(msr_env_map); i++) {
+        hv_name = assocs[j].name;
+        mapping = &msr_env_map[i];
+        if (hv_name != mapping->hv_name) {
+            continue;
+        }
+
+        hv_value = assocs[j].value;
+        offset = mapping->env_offset;
+        MSHV_ENV_FIELD(env, offset) = hv_value.reg64;
+        j++;
+    }
+
+    mtrr_index = j;
+    store_in_env_mtrr_phys(cpu, &assocs[mtrr_index], MSHV_MTRR_MSR_COUNT);
+}
+
+static void set_hv_name_in_assocs(struct hv_register_assoc *assocs,
+                                  size_t n_assocs)
+{
+    size_t i;
+    size_t mtrr_offset, mtrr_fixed_offset;
+    hv_register_name hv_name;
+
+    assert(n_assocs == MSHV_MSR_TOTAL_COUNT);
+
+    for (i = 0; i < ARRAY_SIZE(msr_env_map); i++) {
+        assocs[i].name = msr_env_map[i].hv_name;
+    }
+
+    mtrr_offset = ARRAY_SIZE(msr_env_map);
+    for (i = 0; i < MSR_MTRRcap_VCNT; i++) {
+        hv_name = HV_X64_REGISTER_MSR_MTRR_PHYS_BASE0 + i;
+        assocs[mtrr_offset + i].name = hv_name;
+        hv_name = HV_X64_REGISTER_MSR_MTRR_PHYS_MASK0 + i;
+        assocs[mtrr_offset + MSR_MTRRcap_VCNT + i].name = hv_name;
+    }
+
+    /* fixed 1x 64, 2x 16, 8x 4 kB */
+    mtrr_fixed_offset = mtrr_offset + MSR_MTRRcap_VCNT * 2;
+    for (i = 0; i < 11; i++) {
+        hv_name = HV_X64_REGISTER_MSR_MTRR_FIX64K00000 + i;
+        assocs[mtrr_fixed_offset + i].name = hv_name;
+    }
+}
+
+static bool msr_supported(uint32_t name)
+{
+    /*
+     * This check is not done comprehensively, it's meant to avoid hvcall
+     * failures for certain MSRs on architectures that don't support them.
+     */
+
+    switch (name) {
+    case HV_X64_REGISTER_SPEC_CTRL:
+        return mshv_state->processor_features.ibrs_support;
+    case HV_X64_REGISTER_TSC_ADJUST:
+        return mshv_state->processor_features.tsc_adjust_support;
+    case HV_X64_REGISTER_U_CET:
+    case HV_X64_REGISTER_S_CET:
+    case HV_X64_REGISTER_PL0_SSP:
+    case HV_X64_REGISTER_PL1_SSP:
+    case HV_X64_REGISTER_PL2_SSP:
+    case HV_X64_REGISTER_PL3_SSP:
+    case HV_X64_REGISTER_INTERRUPT_SSP_TABLE_ADDR:
+    case HV_X64_REGISTER_U_XSS:
+        return mshv_state->processor_features.cet_ss_support ||
+               mshv_state->processor_features.cet_ibt_support;
+    }
+
+    return true;
+}
+
+int mshv_get_msrs(CPUState *cpu)
+{
+    int ret = 0;
+    size_t n_assocs = MSHV_MSR_TOTAL_COUNT;
+    struct hv_register_assoc assocs[MSHV_MSR_TOTAL_COUNT];
+    size_t i, j;
+    uint32_t name;
+
+    set_hv_name_in_assocs(assocs, n_assocs);
+
+    /* Filter out MSRs that cannot be read */
+    for (i = 0, j = 0; i < n_assocs; i++) {
+        name = assocs[i].name;
+
+        if (!msr_supported(name)) {
+            continue;
+        }
+
+        if (j != i) {
+            assocs[j] = assocs[i];
+        }
+        j++;
+    }
+    n_assocs = j;
+
+    ret = mshv_get_generic_regs(cpu, assocs, n_assocs);
+    if (ret < 0) {
+        error_report("Failed to get MSRs");
+        return -errno;
+    }
+
+    store_in_env(cpu, assocs, n_assocs);
+
+    return 0;
+}
+
+static void load_from_env(const CPUState *cpu, struct hv_register_assoc *assocs,
+                          size_t n_assocs)
+{
+    size_t i;
+    const MshvMsrEnvMap *mapping;
+    X86CPU *x86_cpu = X86_CPU(cpu);
+    CPUX86State *env = &x86_cpu->env;
+    ptrdiff_t offset;
+    union hv_register_value *hv_value;
+    size_t mtrr_offset;
+
+    assert(n_assocs == MSHV_MSR_TOTAL_COUNT);
+
+    for (i = 0; i < ARRAY_SIZE(msr_env_map); i++) {
+        mapping = &msr_env_map[i];
+        offset = mapping->env_offset;
+        assocs[i].name = mapping->hv_name;
+        hv_value = &assocs[i].value;
+        hv_value->reg64 = MSHV_ENV_FIELD(env, offset);
+    }
+
+    mtrr_offset = ARRAY_SIZE(msr_env_map);
+    load_from_env_mtrr_phys(cpu, &assocs[mtrr_offset], MSHV_MTRR_MSR_COUNT);
+}
+
+int mshv_set_msrs(const CPUState *cpu)
+{
+    size_t n_assocs = MSHV_MSR_TOTAL_COUNT;
+    struct hv_register_assoc assocs[MSHV_MSR_TOTAL_COUNT];
+    int ret;
+    size_t i, j;
+
+    load_from_env(cpu, assocs, n_assocs);
+
+    /* Filter out MSRs that cannot be written */
+    for (i = 0, j = 0; i < n_assocs; i++) {
+        uint32_t name = assocs[i].name;
+
+        /* Partition-wide MSRs: only write on vCPU 0 */
+        if (cpu->cpu_index != 0 &&
+            (name == HV_X64_REGISTER_HYPERCALL ||
+             name == HV_REGISTER_GUEST_OS_ID ||
+             name == HV_REGISTER_REFERENCE_TSC)) {
+            continue;
+        }
+
+        if (!msr_supported(name)) {
+            continue;
+        }
+
+        if (j != i) {
+            assocs[j] = assocs[i];
+        }
+        j++;
+    }
+    n_assocs = j;
+
+    ret = mshv_set_generic_regs(cpu, assocs, n_assocs);
+    if (ret < 0) {
+        error_report("Failed to set MSRs");
+        return -errno;
+    }
+
+    return 0;
+}
diff --git a/target/i386/sev.c b/target/i386/sev.c
index 99cf308..debe634 100644
--- a/target/i386/sev.c
+++ b/target/i386/sev.c
@@ -160,10 +160,6 @@
  *
  * The SevGuestState object is used for creating and managing a SEV
  * guest.
- *
- * # $QEMU \
- *         -object sev-guest,id=sev0 \
- *         -machine ...,memory-encryption=sev0
  */
 struct SevGuestState {
     SevCommonState parent_obj;
diff --git a/tests/docker/dockerfiles/emsdk-wasm64-cross.docker b/tests/docker/dockerfiles/emsdk-wasm64-cross.docker
index cbe20c7..c04a741 100644
--- a/tests/docker/dockerfiles/emsdk-wasm64-cross.docker
+++ b/tests/docker/dockerfiles/emsdk-wasm64-cross.docker
@@ -19,7 +19,9 @@
 ENV LDFLAGS="-sWASM_BIGINT -sASYNCIFY=1 -L$TARGET/lib"
 RUN apt-get update && apt-get install -y \
     autoconf \
+    bison \
     build-essential \
+    flex \
     libglib2.0-dev \
     libtool \
     pkgconf \
diff --git a/tests/functional/pylintrc b/tests/functional/pylintrc
index 049c3e7..949bea6 100644
--- a/tests/functional/pylintrc
+++ b/tests/functional/pylintrc
@@ -58,6 +58,7 @@
 # no Warning level messages displayed, use "--disable=all --enable=classes
 # --disable=W".
 disable=bad-inline-option,
+        c-extension-no-member,
         consider-using-f-string,
         file-ignored,
         fixme,
diff --git a/tests/qemu-iotests/testenv.py b/tests/qemu-iotests/testenv.py
index c357e6e..86bcdf7 100644
--- a/tests/qemu-iotests/testenv.py
+++ b/tests/qemu-iotests/testenv.py
@@ -259,6 +259,7 @@ def __init__(self, source_dir: str, build_dir: str,
             ('arm', 'virt'),
             ('aarch64', 'virt'),
             ('avr', 'mega2560'),
+            ('hexagon', 'virt'),
             ('m68k', 'virt'),
             ('or1k', 'virt'),
             ('riscv32', 'virt'),
diff --git a/tests/qtest/boot-serial-test.c b/tests/qtest/boot-serial-test.c
index bcd0a9c..37fee7a 100644
--- a/tests/qtest/boot-serial-test.c
+++ b/tests/qtest/boot-serial-test.c
@@ -142,6 +142,13 @@
     0x04, 0x38, 0x01, 0x40                  /* 0x40013804 = USART1 TXD */
 };
 
+static const uint8_t bios_hexagon[] = {
+    0x00, 0x40, 0x00, 0x01,                 /* immext(#0x10000000) */
+    0x00, 0xc0, 0x00, 0x78,                 /* r0 = ##0x10000000 */
+    0x54, 0xc0, 0x00, 0x3c,                 /* memb(r0+#0) = #0x54 Write 'T' */
+    0xf8, 0xff, 0xff, 0x59                  /* jump 0x0 ; Loop back to start */
+};
+
 typedef struct testdef {
     const char *arch;       /* Target architecture */
     const char *machine;    /* Name of the machine */
@@ -194,6 +201,7 @@
     { "arm", "microbit", "", "T", sizeof(kernel_nrf51), kernel_nrf51 },
     { "arm", "stm32vldiscovery", "", "T",
       sizeof(kernel_stm32vldiscovery), kernel_stm32vldiscovery },
+    { "hexagon", "virt", "", "TT", sizeof(bios_hexagon), NULL, bios_hexagon },
 
     { NULL }
 };
diff --git a/tests/qtest/libqos/virtio-9p-client.c b/tests/qtest/libqos/virtio-9p-client.c
index af01d4c..305b0da 100644
--- a/tests/qtest/libqos/virtio-9p-client.c
+++ b/tests/qtest/libqos/virtio-9p-client.c
@@ -241,6 +241,9 @@
         id == P9_RUNLINKAT ? "RUNLINKAT" :
         id == P9_RFLUSH ? "RFLUSH" :
         id == P9_RREADDIR ? "RREADDIR" :
+        id == P9_RREAD ? "RREAD" :
+        id == P9_RCLUNK ? "RCLUNK" :
+        id == P9_RXATTRCREATE ? "RXATTRCREATE" :
         "<unknown>";
 }
 
@@ -1103,3 +1106,124 @@
     v9fs_req_recv(req, P9_RUNLINKAT);
     v9fs_req_free(req);
 }
+
+/* size[4] Tread tag[2] fid[4] offset[8] count[4] */
+TReadRes v9fs_tread(TReadOpt opt)
+{
+    P9Req *req;
+    uint32_t err;
+
+    g_assert(opt.client);
+
+    uint32_t body_size = 4 + 8 + 4;
+
+    req = v9fs_req_init(opt.client, body_size, P9_TREAD, opt.tag);
+    v9fs_uint32_write(req, opt.fid);
+    v9fs_uint64_write(req, opt.offset);
+    v9fs_uint32_write(req, opt.count);
+    v9fs_req_send(req);
+
+    if (!opt.requestOnly) {
+        v9fs_req_wait_for_reply(req, NULL);
+        if (opt.expectErr) {
+            v9fs_rlerror(req, &err);
+            g_assert_cmpint(err, ==, opt.expectErr);
+        } else {
+            v9fs_rread(req, opt.rread.count, opt.rread.data);
+        }
+        req = NULL; /* request was freed */
+    }
+
+    return (TReadRes) {
+        .req = req,
+        .count = opt.rread.count ? *opt.rread.count : 0
+    };
+}
+
+/* size[4] Rread tag[2] count[4] data[count] */
+void v9fs_rread(P9Req *req, uint32_t *count, void *data)
+{
+    v9fs_req_recv(req, P9_RREAD);
+    v9fs_uint32_read(req, count);
+    if (data && *count > 0) {
+        v9fs_memread(req, data, *count);
+    }
+    v9fs_req_free(req);
+}
+
+/* size[4] Tclunk tag[2] fid[4] */
+TClunkRes v9fs_tclunk(TClunkOpt opt)
+{
+    P9Req *req;
+    uint32_t err;
+
+    g_assert(opt.client);
+
+    req = v9fs_req_init(opt.client, 4, P9_TCLUNK, opt.tag);
+    v9fs_uint32_write(req, opt.fid);
+    v9fs_req_send(req);
+
+    if (!opt.requestOnly) {
+        v9fs_req_wait_for_reply(req, NULL);
+        if (opt.expectErr) {
+            v9fs_rlerror(req, &err);
+            g_assert_cmpint(err, ==, opt.expectErr);
+        } else {
+            v9fs_rclunk(req);
+        }
+        req = NULL; /* request was freed */
+    }
+
+    return (TClunkRes) { .req = req };
+}
+
+/* size[4] Rclunk tag[2] */
+void v9fs_rclunk(P9Req *req)
+{
+    v9fs_req_recv(req, P9_RCLUNK);
+    v9fs_req_free(req);
+}
+
+/* size[4] Txattrcreate tag[2] fid[4] name[s] attr_size[8] flags[4] */
+TXattrCreateRes v9fs_txattrcreate(TXattrCreateOpt opt)
+{
+    P9Req *req;
+    uint32_t err;
+
+    g_assert(opt.client);
+    g_assert(opt.name);
+
+    uint32_t body_size = 4 + 8 + 4;
+    uint16_t string_size = v9fs_string_size(opt.name);
+
+    g_assert_cmpint(body_size, <=, UINT32_MAX - string_size);
+    body_size += string_size;
+
+    req = v9fs_req_init(opt.client, body_size, P9_TXATTRCREATE, opt.tag);
+    v9fs_uint32_write(req, opt.fid);
+    v9fs_string_write(req, opt.name);
+    v9fs_uint64_write(req, opt.size);
+    v9fs_uint32_write(req, opt.flags);
+    v9fs_req_send(req);
+
+    err = 0;
+    if (!opt.requestOnly) {
+        v9fs_req_wait_for_reply(req, NULL);
+        if (opt.expectErr) {
+            v9fs_rlerror(req, &err);
+            g_assert_cmpint(err, ==, opt.expectErr);
+        } else {
+            v9fs_rxattrcreate(req);
+        }
+        req = NULL; /* request was freed */
+    }
+
+    return (TXattrCreateRes) { .req = req, .err = err };
+}
+
+/* size[4] Rxattrcreate tag[2] */
+void v9fs_rxattrcreate(P9Req *req)
+{
+    v9fs_req_recv(req, P9_RXATTRCREATE);
+    v9fs_req_free(req);
+}
diff --git a/tests/qtest/libqos/virtio-9p-client.h b/tests/qtest/libqos/virtio-9p-client.h
index e3221a3..4b04324 100644
--- a/tests/qtest/libqos/virtio-9p-client.h
+++ b/tests/qtest/libqos/virtio-9p-client.h
@@ -21,7 +21,8 @@
 #include "qgraph.h"
 #include "tests/qtest/libqtest-single.h"
 
-#define P9_MAX_SIZE 4096 /* Max size of a T-message or R-message */
+/* Max size of a T-message or R-message */
+#define P9_MAX_SIZE (32 * 1024)
 
 typedef struct {
     QTestState *qts;
@@ -473,6 +474,85 @@
     P9Req *req;
 } TunlinkatRes;
 
+/* options for 'Tread' 9p request */
+typedef struct TReadOpt {
+    /* 9P client being used (mandatory) */
+    QVirtio9P *client;
+    /* user supplied tag number being returned with response (optional) */
+    uint16_t tag;
+    /* file ID of file to read from (required) */
+    uint32_t fid;
+    /* start position of read from beginning of file (optional) */
+    uint64_t offset;
+    /* how many bytes to read (required) */
+    uint32_t count;
+    /* data being received from 9p server as 'Rread' response (optional) */
+    struct {
+        uint32_t *count;
+        void *data;
+    } rread;
+    /* only send Tread request but not wait for a reply? (optional) */
+    bool requestOnly;
+    /* do we expect an Rlerror response, if yes which error code? (optional) */
+    uint32_t expectErr;
+} TReadOpt;
+
+/* result of 'Tread' 9p request */
+typedef struct TReadRes {
+    /* if requestOnly was set: request object for further processing */
+    P9Req *req;
+    /* amount of bytes read */
+    uint32_t count;
+} TReadRes;
+
+/* options for 'Tclunk' 9p request */
+typedef struct TClunkOpt {
+    /* 9P client being used (mandatory) */
+    QVirtio9P *client;
+    /* user supplied tag number being returned with response (optional) */
+    uint16_t tag;
+    /* file ID to clunk (required) */
+    uint32_t fid;
+    /* only send Tclunk request but not wait for a reply? (optional) */
+    bool requestOnly;
+    /* do we expect an Rlerror response, if yes which error code? (optional) */
+    uint32_t expectErr;
+} TClunkOpt;
+
+/* result of 'Tclunk' 9p request */
+typedef struct TClunkRes {
+    /* if requestOnly was set: request object for further processing */
+    P9Req *req;
+} TClunkRes;
+
+/* options for 'Txattrcreate' 9p request */
+typedef struct TXattrCreateOpt {
+    /* 9P client being used (mandatory) */
+    QVirtio9P *client;
+    /* user supplied tag number being returned with response (optional) */
+    uint16_t tag;
+    /* file ID to convert to xattr fid (required) */
+    uint32_t fid;
+    /* name of the xattr (required) */
+    const char *name;
+    /* size of the xattr value (required) */
+    uint64_t size;
+    /* flags: P9_XATTR_CREATE or P9_XATTR_REPLACE (optional) */
+    uint32_t flags;
+    /* only send Txattrcreate request but not wait for a reply? (optional) */
+    bool requestOnly;
+    /* do we expect an Rlerror response, if yes which error code? (optional) */
+    uint32_t expectErr;
+} TXattrCreateOpt;
+
+/* result of 'Txattrcreate' 9p request */
+typedef struct TXattrCreateRes {
+    /* if requestOnly was set: request object for further processing */
+    P9Req *req;
+    /* error code if Rlerror received */
+    uint32_t err;
+} TXattrCreateRes;
+
 void v9fs_set_allocator(QGuestAllocator *t_alloc);
 void v9fs_memwrite(P9Req *req, const void *addr, size_t len);
 void v9fs_memskip(P9Req *req, size_t len);
@@ -524,5 +604,11 @@
 void v9fs_rlink(P9Req *req);
 TunlinkatRes v9fs_tunlinkat(TunlinkatOpt);
 void v9fs_runlinkat(P9Req *req);
+TReadRes v9fs_tread(TReadOpt opt);
+void v9fs_rread(P9Req *req, uint32_t *count, void *data);
+TClunkRes v9fs_tclunk(TClunkOpt opt);
+void v9fs_rclunk(P9Req *req);
+TXattrCreateRes v9fs_txattrcreate(TXattrCreateOpt opt);
+void v9fs_rxattrcreate(P9Req *req);
 
 #endif
diff --git a/tests/qtest/libqos/virtio-9p.c b/tests/qtest/libqos/virtio-9p.c
index 186fcc1..823756d 100644
--- a/tests/qtest/libqos/virtio-9p.c
+++ b/tests/qtest/libqos/virtio-9p.c
@@ -228,6 +228,12 @@
     g_string_assign(haystack, s);
 }
 
+void virtio_9p_add_synth_driver_args(GString *cmd_line, const char *args)
+{
+    /* append passed args to '-fsdev ...' group */
+    regex_replace(cmd_line, "(-fsdev \\w[^ ]*)", "\\1,%s", args);
+}
+
 void virtio_9p_assign_local_driver(GString *cmd_line, const char *args)
 {
     g_assert_nonnull(local_test_path);
diff --git a/tests/qtest/libqos/virtio-9p.h b/tests/qtest/libqos/virtio-9p.h
index 4807271..e7efeef 100644
--- a/tests/qtest/libqos/virtio-9p.h
+++ b/tests/qtest/libqos/virtio-9p.h
@@ -45,6 +45,12 @@
 };
 
 /**
+ * Add required test specific args to the QEMU command line for the 9pfs
+ * 'synth' fs driver.
+ */
+void virtio_9p_add_synth_driver_args(GString *cmd_line, const char *args);
+
+/**
  * Creates the directory for the 9pfs 'local' filesystem driver to access.
  */
 void virtio_9p_create_local_test_dir(void);
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index e154abb..822e0bd 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -296,6 +296,8 @@
    ['iommu-riscv-test'] : []) + \
   (config_all_devices.has_key('CONFIG_K230') ? ['k230-wdt-test'] : [])
 
+qtests_hexagon = ['boot-serial-test']
+
 qos_test_ss = ss.source_set()
 qos_test_ss.add(
   'ac97-test.c',
diff --git a/tests/qtest/virtio-9p-test.c b/tests/qtest/virtio-9p-test.c
index 1c69d41..cfd3c02 100644
--- a/tests/qtest/virtio-9p-test.c
+++ b/tests/qtest/virtio-9p-test.c
@@ -31,6 +31,18 @@
 #define tsymlink(...) v9fs_tsymlink((TsymlinkOpt) __VA_ARGS__)
 #define tlink(...) v9fs_tlink((TlinkOpt) __VA_ARGS__)
 #define tunlinkat(...) v9fs_tunlinkat((TunlinkatOpt) __VA_ARGS__)
+#define tread(...) v9fs_tread((TReadOpt) __VA_ARGS__)
+#define tclunk(...) v9fs_tclunk((TClunkOpt) __VA_ARGS__)
+#define txattrcreate(...) v9fs_txattrcreate((TXattrCreateOpt) __VA_ARGS__)
+
+/*
+ * xattr size to be used for xattr tests
+ *
+ * 64k is the max. xattr size supported by the Linux kernel, However btrfs
+ * for instance supports only 16219 bytes. So let's be conservative and
+ * just use 8k for the xattr tests.
+ */
+#define TEST_XATTR_SIZE (8 * 1024)
 
 static void pci_config(void *obj, void *data, QGuestAllocator *t_alloc)
 {
@@ -104,6 +116,42 @@
     return false;
 }
 
+/*
+ * Returns the current internal xattr FID count (works with synth driver only).
+ */
+static size_t get_xattr_count(QVirtio9P *v9p)
+{
+    uint16_t nwqid;
+    v9fs_qid *wqid;
+    const char *xattr_count_path[] = { "stat", "xattr_count" };
+    size_t xattr_count;
+    uint32_t bytes_read;
+
+    /* walk to /stat/xattr_count file */
+    uint32_t fid = twalk({
+        .client = v9p, .fid = 0,
+        .nwname = 2, .wnames = (char **)xattr_count_path,
+        .rwalk = { .nwqid = &nwqid, .wqid = &wqid }
+    }).newfid;
+
+    /* open for read */
+    tlopen({
+        .client = v9p, .fid = fid, .flags = O_RDONLY,
+        .rlopen = { .qid = NULL, .iounit = NULL }
+    });
+
+    /* read the internal xattr FID count */
+    tread({
+        .client = v9p, .fid = fid, .offset = 0, .count = sizeof(xattr_count),
+        .rread = { .count = &bytes_read, .data = &xattr_count }
+    });
+
+    /* cleanup */
+    tclunk({ .client = v9p, .fid = fid });
+
+    return xattr_count;
+}
+
 /* basic readdir test where reply fits into a single response message */
 static void fs_readdir(void *obj, void *data, QGuestAllocator *t_alloc)
 {
@@ -245,6 +293,121 @@
     g_free(wnames[0]);
 }
 
+/*
+ * Test 9p server's xattr FID count limit enforcement.
+ *
+ * Shared test code for both 'synth' and 'local' driver to verify correct
+ * behaviour of 9p server enforcing preconfigured xattr FID count limit
+ * correctly.
+ *
+ * @v9p: 9pfs client
+ *
+ * @max_xattr: max. allowed xattr FIDs, or -1 for infinite
+ *
+ * @check_counter: whether to verify 9p server internal xattr FID counter
+ *                 (only works with 'synth' fs driver)
+ */
+static void do_xattr_limit(QVirtio9P *v9p, int max_xattr, bool check_counter)
+{
+    size_t count;
+    int i;
+    int limit = (max_xattr != -1) ? max_xattr : V9FS_MAX_XATTR_DEFAULT + 100;
+    g_autofree uint32_t *fids = g_new0(uint32_t, limit);
+    uint32_t err_fid = 0;
+    const char *file_path[] = { QTEST_V9FS_SYNTH_WRITE_FILE };
+    g_autofree uint8_t *xattr_data = g_malloc(TEST_XATTR_SIZE);
+
+    if (!g_test_slow()) {
+        g_test_skip("This is a slow test, run with -m slow");
+        return;
+    }
+
+    /* prepare xattr data with 'X' characters */
+    memset(xattr_data, 'X', TEST_XATTR_SIZE);
+
+    tattach({ .client = v9p });
+
+    /* create max. amount of permitted xattrs */
+    for (i = 0; i < limit; i++) {
+        /* walk to create a new fid */
+        fids[i] = twalk({
+            .client = v9p, .fid = 0,
+            .nwname = 1, .wnames = (char **) file_path
+        }).newfid;
+
+        /* create new xattr fid */
+        txattrcreate({
+            .client = v9p, .fid = fids[i], .name = "user.test",
+            .size = TEST_XATTR_SIZE, .flags = 0
+        });
+
+        /* transfer the xattr data */
+        twrite({
+            .client = v9p, .fid = fids[i], .offset = 0,
+            .count = TEST_XATTR_SIZE, .data = xattr_data
+        });
+
+        /* verify server internal xattr counter */
+        if (check_counter) {
+            count = get_xattr_count(v9p);
+            g_assert_cmpuint(count, ==, (i + 1));
+        }
+
+        /* avoid virtio descriptor exhaustion */
+        qvirtqueue_reset_pool(v9p->vq);
+    }
+
+    /* if xattrs are limited, the next xattr should fail */
+    if (max_xattr != -1) {
+        /* walk to create another fid */
+        err_fid = twalk({
+            .client = v9p, .fid = 0,
+            .nwname = 1, .wnames = (char **) file_path
+        }).newfid;
+
+        /* try to create one more xattr fid - should fail */
+        txattrcreate({
+            .client = v9p, .fid = err_fid, .name = "user.test_exceed",
+            .size = TEST_XATTR_SIZE, .flags = 0,
+            .expectErr = ENOSPC
+        });
+
+        /* verify internal xattr counter hasn't changed */
+        if (check_counter) {
+            count = get_xattr_count(v9p);
+            g_assert_cmpuint(count, ==, limit);
+        }
+    }
+
+    /* clunk all fids (should decrement xattr counter) */
+    for (i = 0; i < limit; i++) {
+        tclunk({ .client = v9p, .fid = fids[i] });
+        qvirtqueue_reset_pool(v9p->vq);
+    }
+    if (err_fid) {
+        tclunk({ .client = v9p, .fid = err_fid });
+    }
+
+    /* verify internal xattr counter is zero */
+    if (check_counter) {
+        count = get_xattr_count(v9p);
+        g_assert_cmpuint(count, ==, 0);
+    }
+}
+
+static void do_local_xattr_limit(QVirtio9P *v9p, int max_xattr)
+{
+    g_autofree char *test_file = virtio_9p_test_path("WRITE");
+
+    /*
+     * this file must be created for the test to work with the 'local' fs driver
+     */
+    g_file_set_contents(test_file, "", 0, NULL);
+
+    /* the actual test code shared with the 'synth' fs driver tests */
+    do_xattr_limit(v9p, max_xattr, false);
+}
+
 static void fs_walk_no_slash(void *obj, void *data, QGuestAllocator *t_alloc)
 {
     QVirtio9P *v9p = obj;
@@ -505,6 +668,27 @@
     do_readdir_split(obj, 512);
 }
 
+static void fs_synth_xattr_limit_default(void *obj, void *data,
+                                         QGuestAllocator *t_alloc)
+{
+    v9fs_set_allocator(t_alloc);
+    do_xattr_limit(obj, V9FS_MAX_XATTR_DEFAULT, true);
+}
+
+static void fs_synth_xattr_limit_custom(void *obj, void *data,
+                                        QGuestAllocator *t_alloc)
+{
+    v9fs_set_allocator(t_alloc);
+    do_xattr_limit(obj, 100, true);
+}
+
+static void fs_synth_xattr_limit_unlimited(void *obj, void *data,
+                                           QGuestAllocator *t_alloc)
+{
+    v9fs_set_allocator(t_alloc);
+    do_xattr_limit(obj, -1, true);
+}
+
 
 /* tests using the 9pfs 'local' fs driver */
 
@@ -819,26 +1003,81 @@
     g_string_free(path, TRUE);
 }
 
+static void fs_local_xattr_limit_default(void *obj, void *data,
+                                         QGuestAllocator *t_alloc)
+{
+    v9fs_set_allocator(t_alloc);
+    do_local_xattr_limit(obj, V9FS_MAX_XATTR_DEFAULT);
+}
+
+static void fs_local_xattr_limit_custom(void *obj, void *data,
+                                        QGuestAllocator *t_alloc)
+{
+    v9fs_set_allocator(t_alloc);
+    do_local_xattr_limit(obj, 100);
+}
+
+static void fs_local_xattr_limit_unlimited(void *obj, void *data,
+                                           QGuestAllocator *t_alloc)
+{
+    v9fs_set_allocator(t_alloc);
+    do_local_xattr_limit(obj, -1);
+}
+
+static void *synth_max_xattr_custom_opt(GString *cmd_line, void *arg)
+{
+    virtio_9p_add_synth_driver_args(cmd_line, "max_xattr=100");
+    return arg;
+}
+
+static void *synth_max_xattr_unlimited_opt(GString *cmd_line, void *arg)
+{
+    virtio_9p_add_synth_driver_args(cmd_line, "max_xattr=0");
+    return arg;
+}
+
 static void cleanup_9p_local_driver(void *data)
 {
     /* remove previously created test dir when test is completed */
     virtio_9p_remove_local_test_dir();
 }
 
-static void *assign_9p_local_driver(GString *cmd_line, void *arg)
+static void assign_9p_local_driver_with_args(GString *cmd_line,
+                                             const char *extra_opts)
 {
     /* make sure test dir for the 'local' tests exists */
     virtio_9p_create_local_test_dir();
 
-    virtio_9p_assign_local_driver(cmd_line, "security_model=mapped-xattr");
+    g_autofree char *opts =
+        (extra_opts) ?
+            g_strdup_printf("security_model=mapped-xattr,%s", extra_opts) :
+            g_strdup("security_model=mapped-xattr");
+
+    virtio_9p_assign_local_driver(cmd_line, opts);
 
     g_test_queue_destroy(cleanup_9p_local_driver, NULL);
+}
+
+static void *assign_9p_local_driver(GString *cmd_line, void *arg)
+{
+    assign_9p_local_driver_with_args(cmd_line, NULL);
+    return arg;
+}
+
+static void *local_max_xattr_custom_opt(GString *cmd_line, void *arg)
+{
+    assign_9p_local_driver_with_args(cmd_line, "max_xattr=100");
+    return arg;
+}
+
+static void *local_max_xattr_unlimited_opt(GString *cmd_line, void *arg)
+{
+    assign_9p_local_driver_with_args(cmd_line, "max_xattr=0");
     return arg;
 }
 
 static void register_virtio_9p_test(void)
 {
-
     QOSGraphTestOptions opts = {
     };
 
@@ -869,7 +1108,14 @@
                  fs_readdir_split_256,  &opts);
     qos_add_test("synth/readdir/split_128", "virtio-9p",
                  fs_readdir_split_128,  &opts);
-
+    qos_add_test("synth/xattr_limit/default", "virtio-9p",
+                 fs_synth_xattr_limit_default, &opts);
+    opts.before = synth_max_xattr_custom_opt;
+    qos_add_test("synth/xattr_limit/custom", "virtio-9p",
+                 fs_synth_xattr_limit_custom, &opts);
+    opts.before = synth_max_xattr_unlimited_opt;
+    qos_add_test("synth/xattr_limit/unlimited", "virtio-9p",
+                 fs_synth_xattr_limit_unlimited, &opts);
 
     /* 9pfs test cases using the 'local' filesystem driver */
     opts.before = assign_9p_local_driver;
@@ -888,6 +1134,14 @@
                  &opts);
     qos_add_test("local/deep_absolute_path", "virtio-9p",
                  fs_deep_absolute_path, &opts);
+    qos_add_test("local/xattr_limit/default", "virtio-9p",
+                 fs_local_xattr_limit_default, &opts);
+    opts.before = local_max_xattr_custom_opt;
+    qos_add_test("local/xattr_limit/custom", "virtio-9p",
+                 fs_local_xattr_limit_custom, &opts);
+    opts.before = local_max_xattr_unlimited_opt;
+    qos_add_test("local/xattr_limit/unlimited", "virtio-9p",
+                 fs_local_xattr_limit_unlimited, &opts);
 }
 
 libqos_init(register_virtio_9p_test);