[PATCH] target/loongarch: add LVZ support for TCG

SignKirigami posted 1 patch 1 week, 5 days ago
Patches applied successfully (tree, apply log)
git fetch https://github.com/patchew-project/qemu tags/patchew/7f9a2f3f-896b-4415-b535-429b858fb337@mtasv.net
Maintainers: Song Gao <gaosong@loongson.cn>
There is a newer version of this series
target/loongarch/cpu-csr.h                    |  42 ++
target/loongarch/cpu-mmu.h                    |  37 +-
target/loongarch/cpu.c                        | 132 ++++
target/loongarch/cpu.h                        | 125 +++-
target/loongarch/cpu_helper.c                 | 119 ++--
target/loongarch/csr.c                        | 122 ++++
target/loongarch/csr.h                        |   3 +
target/loongarch/disas.c                      |  10 +
target/loongarch/insns.decode                 |  17 +
target/loongarch/internals.h                  |   8 +-
target/loongarch/kvm/kvm.c                    |   1 +
target/loongarch/machine.c                    | 249 ++++---
target/loongarch/tcg/constant_timer.c         |  62 +-
target/loongarch/tcg/csr_helper.c             | 127 +++-
target/loongarch/tcg/helper.h                 |  30 +-
.../tcg/insn_trans/trans_privileged.c.inc     | 353 +++++++++-
target/loongarch/tcg/op_helper.c              |  83 ++-
target/loongarch/tcg/tcg_cpu.c                | 182 +++--
target/loongarch/tcg/tcg_loongarch.h          |   6 +-
target/loongarch/tcg/tlb_helper.c             | 629 +++++++++++++-----
target/loongarch/tcg/translate.c              |   6 +-
target/loongarch/translate.h                  |   2 +
22 files changed, 1909 insertions(+), 436 deletions(-)
[PATCH] target/loongarch: add LVZ support for TCG
Posted by SignKirigami 1 week, 5 days ago
This patch implements Loongson VirtualiZation (LVZ) extension
support for LoongArch's TCG target. With this patch, it is
now possible to start a nested KVM-accelerated virtual machine
on a TCG-emulated virtual machine.

Cc: Bibo Mao <maobibo@loongson.cn>
Cc: xianglai li <lixianglai@loongson.cn>
Signed-off-by: SignKirigami <prcups@krgm.moe>
Signed-off-by: Hengyu Yu <yuhengyu25@mails.ucas.ac.cn>
---
 target/loongarch/cpu-csr.h                    |  42 ++
 target/loongarch/cpu-mmu.h                    |  37 +-
 target/loongarch/cpu.c                        | 132 ++++
 target/loongarch/cpu.h                        | 125 +++-
 target/loongarch/cpu_helper.c                 | 119 ++--
 target/loongarch/csr.c                        | 122 ++++
 target/loongarch/csr.h                        |   3 +
 target/loongarch/disas.c                      |  10 +
 target/loongarch/insns.decode                 |  17 +
 target/loongarch/internals.h                  |   8 +-
 target/loongarch/kvm/kvm.c                    |   1 +
 target/loongarch/machine.c                    | 249 ++++---
 target/loongarch/tcg/constant_timer.c         |  62 +-
 target/loongarch/tcg/csr_helper.c             | 127 +++-
 target/loongarch/tcg/helper.h                 |  30 +-
 .../tcg/insn_trans/trans_privileged.c.inc     | 353 +++++++++-
 target/loongarch/tcg/op_helper.c              |  83 ++-
 target/loongarch/tcg/tcg_cpu.c                | 182 +++--
 target/loongarch/tcg/tcg_loongarch.h          |   6 +-
 target/loongarch/tcg/tlb_helper.c             | 629 +++++++++++++-----
 target/loongarch/tcg/translate.c              |   6 +-
 target/loongarch/translate.h                  |   2 +
 22 files changed, 1909 insertions(+), 436 deletions(-)

diff --git a/target/loongarch/cpu-csr.h b/target/loongarch/cpu-csr.h
index d860417af2..4b0bb4d2e5 100644
--- a/target/loongarch/cpu-csr.h
+++ b/target/loongarch/cpu-csr.h
@@ -180,11 +180,13 @@ FIELD(CSR_TLBREHI_64, VPPN, 13, 35)
 #define LOONGARCH_CSR_TLBRPRMD       0x8f /* TLB refill mode info */
 FIELD(CSR_TLBRPRMD, PPLV, 0, 2)
 FIELD(CSR_TLBRPRMD, PIE, 2, 1)
+FIELD(CSR_TLBRPRMD, PGM, 3, 1)
 FIELD(CSR_TLBRPRMD, PWE, 4, 1)
 
 /* Machine Error CSRs */
 #define LOONGARCH_CSR_MERRCTL        0x90 /* ERRCTL */
 FIELD(CSR_MERRCTL, ISMERR, 0, 1)
+FIELD(CSR_MERRCTL, PGM, 5, 1)
 #define LOONGARCH_CSR_MERRINFO1      0x91
 #define LOONGARCH_CSR_MERRINFO2      0x92
 #define LOONGARCH_CSR_MERRENTRY      0x93 /* MError exception base */
@@ -224,4 +226,44 @@ FIELD(CSR_DBG, ECODE, 16, 6)
 #define LOONGARCH_CSR_DERA           0x501 /* Debug era */
 #define LOONGARCH_CSR_DSAVE          0x502 /* Debug save */
 
+/* LVZ (LoongArch Virtualization) CSRs */
+#define LOONGARCH_CSR_GSTAT          0x50 /* Guest status */
+FIELD(CSR_GSTAT, VM, 0, 1)
+FIELD(CSR_GSTAT, PVM, 1, 1)
+FIELD(CSR_GSTAT, GIDBIT, 4, 6)
+FIELD(CSR_GSTAT, GID, 16, 8)
+
+#define LOONGARCH_CSR_GCFG           0x51 /* Guest config */
+FIELD(CSR_GCFG, MATP, 0, 4)
+FIELD(CSR_GCFG, MATC, 4, 2)
+FIELD(CSR_GCFG, TOPIP, 6, 1)
+FIELD(CSR_GCFG, TOPI, 7, 1)
+FIELD(CSR_GCFG, TOTIP, 8, 1)
+FIELD(CSR_GCFG, TOTI, 9, 1)
+FIELD(CSR_GCFG, TOEP, 10, 1)
+FIELD(CSR_GCFG, TOE, 11, 1)
+FIELD(CSR_GCFG, TOPP, 12, 1)
+FIELD(CSR_GCFG, TOP, 13, 1)
+FIELD(CSR_GCFG, TOHUP, 14, 1)
+FIELD(CSR_GCFG, TOHU, 15, 1)
+FIELD(CSR_GCFG, TOCIP, 16, 4)
+FIELD(CSR_GCFG, TOCI, 20, 2)
+FIELD(CSR_GCFG, GPMP, 23, 1)
+FIELD(CSR_GCFG, GPMNUM, 24, 3)
+
+#define LOONGARCH_CSR_GINTC          0x52 /* Guest interrupt config */
+FIELD(CSR_GINTC, HWIS, 0, 8)
+FIELD(CSR_GINTC, HWIP, 8, 8)
+FIELD(CSR_GINTC, HWIC, 16, 8)
+
+#define LOONGARCH_CSR_GCNTC          0x53 /* Guest counter compensation */
+
+#define LOONGARCH_CSR_GTLBC          0x15 /* Guest TLB control */
+FIELD(CSR_GTLBC, GMTLBSZ, 0, 6)
+FIELD(CSR_GTLBC, USETGID, 12, 1)
+FIELD(CSR_GTLBC, TOTI, 13, 1)
+FIELD(CSR_GTLBC, TGID, 16, 8)
+
+#define LOONGARCH_CSR_TRGP           0x16 /* Trapped guest physical address */
+
 #endif /* LOONGARCH_CPU_CSR_H */
diff --git a/target/loongarch/cpu-mmu.h b/target/loongarch/cpu-mmu.h
index 2d7ebb2d72..31093641ff 100644
--- a/target/loongarch/cpu-mmu.h
+++ b/target/loongarch/cpu-mmu.h
@@ -17,6 +17,14 @@ typedef enum TLBRet {
     TLBRET_RI,
     TLBRET_XI,
     TLBRET_PE,
+    TLBRET_HOST_MATCH,
+    TLBRET_HOST_BADADDR,
+    TLBRET_HOST_NOMATCH,
+    TLBRET_HOST_INVALID,
+    TLBRET_HOST_DIRTY,
+    TLBRET_HOST_RI,
+    TLBRET_HOST_XI,
+    TLBRET_HOST_PE,
 } TLBRet;
 
 typedef struct MMUContext {
@@ -30,16 +38,17 @@ typedef struct MMUContext {
     uint64_t      pte_buddy[2];
 } MMUContext;
 
-static inline bool cpu_has_ptw(CPULoongArchState *env)
+static inline bool cpu_has_ptw(CPULoongArchState *env, bool guest)
 {
-    return !!FIELD_EX64(env->CSR_PWCH, CSR_PWCH, HPTW_EN);
+    return !!FIELD_EX64(GET_CSR_IF(guest, PWCH), CSR_PWCH, HPTW_EN);
 }
 
-static inline bool pte_present(CPULoongArchState *env, uint64_t entry)
+static inline bool pte_present(CPULoongArchState *env, uint64_t entry,
+                               bool guest)
 {
     uint8_t present;
 
-    if (cpu_has_ptw(env)) {
+    if (cpu_has_ptw(env, guest)) {
         present = FIELD_EX64(entry, TLBENTRY, P);
     } else {
         present = FIELD_EX64(entry, TLBENTRY, V);
@@ -48,11 +57,11 @@ static inline bool pte_present(CPULoongArchState *env, uint64_t entry)
     return !!present;
 }
 
-static inline bool pte_write(CPULoongArchState *env, uint64_t entry)
+static inline bool pte_write(CPULoongArchState *env, uint64_t entry, bool guest)
 {
     uint8_t writable;
 
-    if (cpu_has_ptw(env)) {
+    if (cpu_has_ptw(env, guest)) {
         writable = FIELD_EX64(entry, TLBENTRY, W);
     } else {
         writable = FIELD_EX64(entry, TLBENTRY, D);
@@ -89,14 +98,22 @@ static inline bool pte_dirty(uint64_t entry)
 
 bool check_ps(CPULoongArchState *ent, uint8_t ps);
 TLBRet loongarch_check_pte(CPULoongArchState *env, MMUContext *context,
-                           MMUAccessType access_type, int mmu_idx);
+                           MMUAccessType access_type, int mmu_idx, bool guest);
 TLBRet get_physical_address(CPULoongArchState *env, MMUContext *context,
                             MMUAccessType access_type, int mmu_idx,
-                            int is_debug);
+                            int is_debug, uintptr_t retaddr);
 TLBRet loongarch_ptw(CPULoongArchState *env, MMUContext *context,
-                     int access_type, int mmu_idx, int debug);
+                     int access_type, int mmu_idx, int debug, bool guest,
+                     uintptr_t retaddr);
+hwaddr loongarch_get_host_address(CPULoongArchState *env, hwaddr gpa,
+                                  uintptr_t retaddr);
+TLBRet loongarch_map_host_address(CPULoongArchState *env, MMUContext *context,
+                                  MMUAccessType access_type, uintptr_t retaddr);
+TLBRet loongarch_map_address(CPULoongArchState *env, MMUContext *context,
+                             MMUAccessType access_type, int mmu_idx,
+                             int is_debug, bool guest, uintptr_t retaddr);
 void get_dir_base_width(CPULoongArchState *env, uint64_t *dir_base,
-                        uint64_t *dir_width, unsigned int level);
+                        uint64_t *dir_width, unsigned int level, bool guest);
 hwaddr loongarch_cpu_get_phys_addr_debug(CPUState *cpu, vaddr addr);
 uint64_t loongarch_palen_mask(CPULoongArchState *env);
 
diff --git a/target/loongarch/cpu.c b/target/loongarch/cpu.c
index 8f277f7696..2477d84625 100644
--- a/target/loongarch/cpu.c
+++ b/target/loongarch/cpu.c
@@ -67,6 +67,11 @@ void loongarch_cpu_set_irq(void *opaque, int irq, int level)
         return;
     }
 
+    if (FIELD_EX64(env->CSR_GINTC, CSR_GINTC, HWIP) & BIT(irq)) {
+        loongarch_cpu_set_irq_guest(opaque, irq, level);
+        return;
+    }
+
     if (kvm_enabled()) {
         kvm_loongarch_set_interrupt(cpu, irq, level);
     } else if (tcg_enabled()) {
@@ -79,6 +84,26 @@ void loongarch_cpu_set_irq(void *opaque, int irq, int level)
     }
 }
 
+void loongarch_cpu_set_irq_guest(void *opaque, int irq, int level)
+{
+    LoongArchCPU *cpu = opaque;
+    CPULoongArchState *env = &cpu->env;
+    CPUState *cs = CPU(cpu);
+
+    if (irq < 0 || irq >= N_IRQS) {
+        return;
+    }
+
+    env->GCSR_ESTAT = deposit64(env->GCSR_ESTAT, irq, 1, level != 0);
+    if (env->guest) {
+        if (FIELD_EX64(env->GCSR_ESTAT, CSR_ESTAT, IS)) {
+            cpu_interrupt(cs, CPU_INTERRUPT_GUEST);
+        } else {
+            cpu_reset_interrupt(cs, CPU_INTERRUPT_GUEST);
+        }
+    }
+}
+
 /* Check if there is pending and not masked out interrupt */
 bool cpu_loongarch_hw_interrupts_pending(CPULoongArchState *env)
 {
@@ -90,6 +115,30 @@ bool cpu_loongarch_hw_interrupts_pending(CPULoongArchState *env)
 
     return (pending & status) != 0;
 }
+
+static inline bool
+cpu_loongarch_hw_interrupts_enabled_guest(CPULoongArchState *env)
+{
+    return FIELD_EX64(env->GCSR_CRMD, CSR_CRMD, IE);
+}
+
+static inline bool
+cpu_loongarch_hw_interrupts_pending_guest(CPULoongArchState *env)
+{
+    uint32_t pending;
+    uint32_t status;
+
+    pending = FIELD_EX64(env->GCSR_ESTAT, CSR_ESTAT, IS);
+    status = FIELD_EX64(env->GCSR_ECFG, CSR_ECFG, LIE);
+
+    return (pending & status) != 0;
+}
+
+bool loongarch_guest_has_interrupt(CPULoongArchState *env)
+{
+    return env->guest && cpu_loongarch_hw_interrupts_enabled_guest(env) &&
+           cpu_loongarch_hw_interrupts_pending_guest(env);
+}
 #endif
 
 #ifndef CONFIG_USER_ONLY
@@ -102,10 +151,54 @@ bool loongarch_cpu_has_work(CPUState *cs)
         has_work = true;
     }
 
+    if (cpu_test_interrupt(cs, CPU_INTERRUPT_GUEST) &&
+        loongarch_guest_has_interrupt(cpu_env(cs))) {
+        has_work = true;
+    }
+
     return has_work;
 }
 #endif /* !CONFIG_USER_ONLY */
 
+uint8_t get_tgid(CPULoongArchState *env)
+{
+    if (env->guest) {
+        return get_gid(env);
+    }
+
+    if (FIELD_EX64(env->CSR_GTLBC, CSR_GTLBC, USETGID)) {
+        return FIELD_EX64(env->CSR_GTLBC, CSR_GTLBC, TGID);
+    } else if (will_return_to_guest(env)) {
+        return get_gid(env);
+    }
+    return 0;
+}
+
+bool will_return_to_guest(CPULoongArchState *env)
+{
+    if (!has_lvz_capability(env) || env->guest) {
+        return false;
+    }
+    return FIELD_EX64(env->CSR_GSTAT, CSR_GSTAT, PVM);
+}
+
+bool has_lvz_capability(CPULoongArchState *env)
+{
+    return FIELD_EX32(env->cpucfg[2], CPUCFG2, LVZ);
+}
+
+uint8_t get_gid(CPULoongArchState *env)
+{
+    return FIELD_EX64(env->CSR_GSTAT, CSR_GSTAT, GID);
+}
+
+void trigger_vm_exit(CPULoongArchState *env)
+{
+    cpu_loongarch_set_guest_timer(env_archcpu(env), false);
+    env->CSR_GSTAT = FIELD_DP64(env->CSR_GSTAT, CSR_GSTAT, PVM, 1);
+    env->vm_exit = true;
+}
+
 static void loongarch_la464_init_csr(DeviceState *dev)
 {
 #ifndef CONFIG_USER_ONLY
@@ -248,12 +341,33 @@ static void loongarch_set_ptw(Object *obj, bool value, Error **errp)
     cpu->env.cpucfg[2] = FIELD_DP32(cpu->env.cpucfg[2], CPUCFG2, HPTW, value);
 }
 
+static bool loongarch_get_lvz(Object *obj, Error **errp)
+{
+    return LOONGARCH_CPU(obj)->lvz != ON_OFF_AUTO_OFF;
+}
+
+static void loongarch_set_lvz(Object *obj, bool value, Error **errp)
+{
+    LoongArchCPU *cpu = LOONGARCH_CPU(obj);
+
+    cpu->lvz = value ? ON_OFF_AUTO_ON : ON_OFF_AUTO_OFF;
+
+    if (kvm_enabled()) {
+        return;
+    }
+
+    cpu->env.cpucfg[2] = FIELD_DP32(cpu->env.cpucfg[2], CPUCFG2, LVZ, value);
+    cpu->env.cpucfg[2] =
+        FIELD_DP32(cpu->env.cpucfg[2], CPUCFG2, LVZ_VER, value ? 1 : 0);
+}
+
 static void loongarch_cpu_post_init(Object *obj)
 {
     LoongArchCPU *cpu = LOONGARCH_CPU(obj);
 
     cpu->lbt = ON_OFF_AUTO_OFF;
     cpu->pmu = ON_OFF_AUTO_OFF;
+    cpu->lvz = ON_OFF_AUTO_AUTO;
     cpu->lsx = ON_OFF_AUTO_AUTO;
     cpu->lasx = ON_OFF_AUTO_AUTO;
     object_property_add_bool(obj, "lsx", loongarch_get_lsx,
@@ -264,6 +378,8 @@ static void loongarch_cpu_post_init(Object *obj)
                              loongarch_set_msgint);
     object_property_add_bool(obj, "ptw", loongarch_get_ptw,
                              loongarch_set_ptw);
+    object_property_add_bool(obj, "lvz", loongarch_get_lvz,
+                             loongarch_set_lvz);
     /* lbt is enabled only in kvm mode, not supported in tcg mode */
 
     if (kvm_enabled()) {
@@ -317,6 +433,8 @@ static void loongarch_la464_initfn(Object *obj)
     data = FIELD_DP32(data, CPUCFG2, FP_VER, 1);
     data = FIELD_DP32(data, CPUCFG2, LSX, 1),
     data = FIELD_DP32(data, CPUCFG2, LASX, 1),
+    data = FIELD_DP32(data, CPUCFG2, LVZ, 1);
+    data = FIELD_DP32(data, CPUCFG2, LVZ_VER, 1);
     data = FIELD_DP32(data, CPUCFG2, LLFTP, 1);
     data = FIELD_DP32(data, CPUCFG2, LLFTP_VER, 1);
     data = FIELD_DP32(data, CPUCFG2, LSPW, 1);
@@ -395,6 +513,7 @@ static void loongarch_la464_initfn(Object *obj)
     env->CSR_PRCFG3 = FIELD_DP64(env->CSR_PRCFG3, CSR_PRCFG3, STLB_SETS, 8);
 
     cpu->msgint = ON_OFF_AUTO_OFF;
+    cpu->lvz = ON_OFF_AUTO_AUTO;
     cpu->ptw = ON_OFF_AUTO_OFF;
     loongarch_cpu_post_init(obj);
 }
@@ -432,6 +551,7 @@ static void loongarch_la132_initfn(Object *obj)
     data = FIELD_DP32(data, CPUCFG1, CRC, 1);
     env->cpucfg[1] = data;
     cpu->msgint = ON_OFF_AUTO_OFF;
+    cpu->lvz = ON_OFF_AUTO_OFF;
     cpu->ptw = ON_OFF_AUTO_OFF;
 }
 
@@ -637,6 +757,7 @@ static void loongarch_cpu_reset_hold(Object *obj, ResetType type)
     env->CSR_RVACFG = FIELD_DP64(env->CSR_RVACFG, CSR_RVACFG, RBITS, 0);
     env->CSR_CPUID = cs->cpu_index;
     env->CSR_TCFG = FIELD_DP64(env->CSR_TCFG, CSR_TCFG, EN, 0);
+    env->GCSR_TCFG = FIELD_DP64(env->GCSR_TCFG, CSR_TCFG, EN, 0);
     env->CSR_LLBCTL = FIELD_DP64(env->CSR_LLBCTL, CSR_LLBCTL, KLO, 0);
     env->CSR_TLBRERA = FIELD_DP64(env->CSR_TLBRERA, CSR_TLBRERA, ISTLBR, 0);
     env->CSR_MERRCTL = FIELD_DP64(env->CSR_MERRCTL, CSR_MERRCTL, ISMERR, 0);
@@ -671,6 +792,15 @@ static void loongarch_cpu_reset_hold(Object *obj, ResetType type)
     env->pc = 0x1c000000;
 #ifdef CONFIG_TCG
     memset(env->tlb, 0, sizeof(env->tlb));
+    env->guest = false;
+    env->vm_exit = false;
+    env->CSR_GSTAT = FIELD_DP64(0, CSR_GSTAT, GIDBIT, 8);
+    env->CSR_GCFG = 0;
+    env->CSR_GINTC = 0;
+    env->CSR_GCNTC = 0;
+    env->CSR_GTLBC = 0;
+    env->CSR_TRGP = 0;
+    env->GCSR_ASID = FIELD_DP64(0, CSR_ASID, ASIDBITS, 0xa);
 #endif
     if (kvm_enabled()) {
         kvm_arch_reset_vcpu(cs);
@@ -731,6 +861,8 @@ static void loongarch_cpu_init(Object *obj)
 #ifdef CONFIG_TCG
     timer_init_ns(&cpu->timer, QEMU_CLOCK_VIRTUAL,
                   &loongarch_constant_timer_cb, cpu);
+    timer_init_ns(&cpu->guest_timer, QEMU_CLOCK_VIRTUAL,
+                  &loongarch_constant_timer_cb_guest, cpu);
 #endif
 #endif
 }
diff --git a/target/loongarch/cpu.h b/target/loongarch/cpu.h
index 096d778928..8d3cae59fa 100644
--- a/target/loongarch/cpu.h
+++ b/target/loongarch/cpu.h
@@ -20,6 +20,20 @@
 #include "cpu-csr.h"
 #include "cpu-qom.h"
 
+#define GET_CSR_IF(guest_mode, csr_name) \
+    ((guest_mode) ? (env->GCSR_##csr_name) : (env->CSR_##csr_name))
+
+#define SET_CSR_IF(guest_mode, csr_name, value) \
+    do {                                        \
+        if (guest_mode) {                       \
+            env->GCSR_##csr_name = (value);     \
+        } else {                                \
+            env->CSR_##csr_name = (value);      \
+        }                                       \
+    } while (0)
+
+#define CPU_INTERRUPT_GUEST CPU_INTERRUPT_TGT_EXT_0
+
 #define FCSR0_M1    0x1f         /* FCSR1 mask, Enables */
 #define FCSR0_M2    0x1f1f0000   /* FCSR2 mask, Cause and Flags */
 #define FCSR0_M3    0x300        /* FCSR3 mask, Round Mode */
@@ -93,6 +107,10 @@ FIELD(FCSR0, CAUSE, 24, 5)
 #define  EXCCODE_WPEM                EXCODE(19, 1)
 #define  EXCCODE_BTD                 EXCODE(20, 0)
 #define  EXCCODE_BTE                 EXCODE(21, 0)
+#define  EXCCODE_GSPR                EXCODE(22, 0)
+#define  EXCCODE_HVC                 EXCODE(23, 0)
+#define  EXCCODE_GCSC                EXCODE(24, 0)
+#define  EXCCODE_GCHC                EXCODE(25, 0)
 #define  EXCCODE_DBP                 EXCODE(26, 0) /* Reserved subcode used for debug */
 
 /* cpucfg[0] bits */
@@ -255,6 +273,7 @@ FIELD(TLB_MISC, E, 0, 1)
 FIELD(TLB_MISC, ASID, 1, 10)
 FIELD(TLB_MISC, VPPN, 13, 35)
 FIELD(TLB_MISC, PS, 48, 6)
+FIELD(TLB_MISC, GID, 54, 8)
 
 /*Msg interrupt registers */
 #define N_MSGIS                4
@@ -389,6 +408,79 @@ typedef struct CPUArchState {
     uint64_t CSR_DBG;
     uint64_t CSR_DERA;
     uint64_t CSR_DSAVE;
+
+    /* LVZ (LoongArch Virtualization) CSRs */
+    uint64_t CSR_GSTAT;
+    uint64_t CSR_GCFG;
+    uint64_t CSR_GINTC;
+    uint64_t CSR_GCNTC;
+    uint64_t CSR_GTLBC;
+    uint64_t CSR_TRGP;
+
+    /* Guest CSR registers (GCSR) */
+    uint64_t GCSR_CRMD;
+    uint64_t GCSR_PRMD;
+    uint64_t GCSR_EUEN;
+    uint64_t GCSR_MISC;
+    uint64_t GCSR_ECFG;
+    uint64_t GCSR_ESTAT;
+    uint64_t GCSR_ERA;
+    uint64_t GCSR_BADV;
+    uint64_t GCSR_BADI;
+    uint64_t GCSR_EENTRY;
+    uint64_t GCSR_TLBIDX;
+    uint64_t GCSR_TLBEHI;
+    uint64_t GCSR_TLBELO0;
+    uint64_t GCSR_TLBELO1;
+    uint64_t GCSR_ASID;
+    uint64_t GCSR_PGDL;
+    uint64_t GCSR_PGDH;
+    uint64_t GCSR_PGD;
+    uint64_t GCSR_PWCL;
+    uint64_t GCSR_PWCH;
+    uint64_t GCSR_STLBPS;
+    uint64_t GCSR_RVACFG;
+    uint64_t GCSR_CPUID;
+    uint64_t GCSR_PRCFG1;
+    uint64_t GCSR_PRCFG2;
+    uint64_t GCSR_PRCFG3;
+    uint64_t GCSR_SAVE[16];
+    uint64_t GCSR_TID;
+    uint64_t GCSR_TCFG;
+    uint64_t GCSR_TVAL;
+    uint64_t GCSR_CNTC;
+    uint64_t GCSR_TICLR;
+    uint64_t GCSR_LLBCTL;
+    uint64_t GCSR_IMPCTL1;
+    uint64_t GCSR_IMPCTL2;
+    uint64_t GCSR_TLBRENTRY;
+    uint64_t GCSR_TLBRBADV;
+    uint64_t GCSR_TLBRERA;
+    uint64_t GCSR_TLBRSAVE;
+    uint64_t GCSR_TLBRELO0;
+    uint64_t GCSR_TLBRELO1;
+    uint64_t GCSR_TLBREHI;
+    uint64_t GCSR_TLBRPRMD;
+    uint64_t GCSR_MERRCTL;
+    uint64_t GCSR_MERRINFO1;
+    uint64_t GCSR_MERRINFO2;
+    uint64_t GCSR_MERRENTRY;
+    uint64_t GCSR_MERRERA;
+    uint64_t GCSR_MERRSAVE;
+    uint64_t GCSR_CTAG;
+    uint64_t GCSR_DMW[4];
+    uint64_t GCSR_DBG;
+    uint64_t GCSR_DERA;
+    uint64_t GCSR_DSAVE;
+    uint64_t GCSR_GSTAT;
+    uint64_t GCSR_GCFG;
+    uint64_t GCSR_GINTC;
+    uint64_t GCSR_GCNTC;
+    uint64_t GCSR_GTLBC;
+    uint64_t GCSR_TRGP;
+
+    bool guest;
+    bool vm_exit;
     /* Msg interrupt registers */
     uint64_t CSR_MSGIS[N_MSGIS];
     uint64_t CSR_MSGIR;
@@ -410,6 +502,7 @@ typedef struct CPUArchState {
 #ifndef CONFIG_USER_ONLY
 #ifdef CONFIG_TCG
     LoongArchTLB  tlb[LOONGARCH_TLB_MAX];
+    LoongArchTLB gtlb[LOONGARCH_TLB_MAX];
 #endif
 
     AddressSpace *address_space_iocsr;
@@ -434,9 +527,11 @@ struct ArchCPU {
 
     CPULoongArchState env;
     QEMUTimer timer;
+    QEMUTimer guest_timer;
     uint32_t  phy_id;
     OnOffAuto lbt;
     OnOffAuto pmu;
+    OnOffAuto lvz;
     OnOffAuto ptw;
     OnOffAuto lsx;
     OnOffAuto lasx;
@@ -480,6 +575,24 @@ struct LoongArchCPUClass {
 #define MMU_KERNEL_IDX   MMU_PLV_KERNEL
 #define MMU_USER_IDX     MMU_PLV_USER
 #define MMU_DA_IDX       4
+#define MMU_GUEST_IDX    5
+#define MMU_GUEST_DA_IDX 9
+
+static inline bool is_guest_mmu_idx(int mmu_idx)
+{
+    return mmu_idx >= MMU_GUEST_IDX;
+}
+
+static inline int mmu_idx_to_plv(int mmu_idx)
+{
+    if (mmu_idx == MMU_DA_IDX || mmu_idx == MMU_GUEST_DA_IDX) {
+        return 0;
+    }
+    if (is_guest_mmu_idx(mmu_idx)) {
+        return mmu_idx - MMU_GUEST_IDX;
+    }
+    return mmu_idx;
+}
 
 static inline bool is_la64(CPULoongArchState *env)
 {
@@ -490,8 +603,9 @@ static inline bool is_va32(CPULoongArchState *env)
 {
     /* VA32 if !LA64 or VA32L[1-3] */
     bool va32 = !is_la64(env);
-    uint64_t plv = FIELD_EX64(env->CSR_CRMD, CSR_CRMD, PLV);
-    if (plv >= 1 && (FIELD_EX64(env->CSR_MISC, CSR_MISC, VA32) & (1 << plv))) {
+    uint64_t plv = FIELD_EX64(GET_CSR_IF(env->guest, CRMD), CSR_CRMD, PLV);
+    if (plv >= 1 &&
+        (FIELD_EX64(GET_CSR_IF(env->guest, MISC), CSR_MISC, VA32) & BIT(plv))) {
         va32 = true;
     }
     return va32;
@@ -515,6 +629,13 @@ static inline void set_pc(CPULoongArchState *env, uint64_t value)
 #define HW_FLAGS_CRMD_PG    R_CSR_CRMD_PG_MASK   /* 0x10 */
 #define HW_FLAGS_VA32       0x20
 #define HW_FLAGS_EUEN_ASXE  0x40
+#define HW_FLAGS_GUEST_MODE 0x80
+
+bool has_lvz_capability(CPULoongArchState *env);
+bool will_return_to_guest(CPULoongArchState *env);
+uint8_t get_gid(CPULoongArchState *env);
+uint8_t get_tgid(CPULoongArchState *env);
+void trigger_vm_exit(CPULoongArchState *env);
 
 #define CPU_RESOLVING_TYPE TYPE_LOONGARCH_CPU
 
diff --git a/target/loongarch/cpu_helper.c b/target/loongarch/cpu_helper.c
index eb9684a4a1..bad6062ac9 100644
--- a/target/loongarch/cpu_helper.c
+++ b/target/loongarch/cpu_helper.c
@@ -17,46 +17,56 @@
 #include "cpu-mmu.h"
 #include "tcg/tcg_loongarch.h"
 
-void get_dir_base_width(CPULoongArchState *env, uint64_t *dir_base,
-                        uint64_t *dir_width, unsigned int level)
+static void get_dir_base_width_csr(CPULoongArchState *env, uint64_t *dir_base,
+                                   uint64_t *dir_width, unsigned int level,
+                                   bool guest)
 {
+    uint64_t pwcl = GET_CSR_IF(guest, PWCL);
+    uint64_t pwch = GET_CSR_IF(guest, PWCH);
+
     switch (level) {
     case 1:
-        *dir_base = FIELD_EX64(env->CSR_PWCL, CSR_PWCL, DIR1_BASE);
-        *dir_width = FIELD_EX64(env->CSR_PWCL, CSR_PWCL, DIR1_WIDTH);
+        *dir_base = FIELD_EX64(pwcl, CSR_PWCL, DIR1_BASE);
+        *dir_width = FIELD_EX64(pwcl, CSR_PWCL, DIR1_WIDTH);
         break;
     case 2:
-        *dir_base = FIELD_EX64(env->CSR_PWCL, CSR_PWCL, DIR2_BASE);
-        *dir_width = FIELD_EX64(env->CSR_PWCL, CSR_PWCL, DIR2_WIDTH);
+        *dir_base = FIELD_EX64(pwcl, CSR_PWCL, DIR2_BASE);
+        *dir_width = FIELD_EX64(pwcl, CSR_PWCL, DIR2_WIDTH);
         break;
     case 3:
-        *dir_base = FIELD_EX64(env->CSR_PWCH, CSR_PWCH, DIR3_BASE);
-        *dir_width = FIELD_EX64(env->CSR_PWCH, CSR_PWCH, DIR3_WIDTH);
+        *dir_base = FIELD_EX64(pwch, CSR_PWCH, DIR3_BASE);
+        *dir_width = FIELD_EX64(pwch, CSR_PWCH, DIR3_WIDTH);
         break;
     case 4:
-        *dir_base = FIELD_EX64(env->CSR_PWCH, CSR_PWCH, DIR4_BASE);
-        *dir_width = FIELD_EX64(env->CSR_PWCH, CSR_PWCH, DIR4_WIDTH);
+        *dir_base = FIELD_EX64(pwch, CSR_PWCH, DIR4_BASE);
+        *dir_width = FIELD_EX64(pwch, CSR_PWCH, DIR4_WIDTH);
         break;
     default:
         /* level may be zero for ldpte */
-        *dir_base = FIELD_EX64(env->CSR_PWCL, CSR_PWCL, PTBASE);
-        *dir_width = FIELD_EX64(env->CSR_PWCL, CSR_PWCL, PTWIDTH);
+        *dir_base = FIELD_EX64(pwcl, CSR_PWCL, PTBASE);
+        *dir_width = FIELD_EX64(pwcl, CSR_PWCL, PTWIDTH);
         break;
     }
 }
 
+void get_dir_base_width(CPULoongArchState *env, uint64_t *dir_base,
+                        uint64_t *dir_width, unsigned int level, bool guest)
+{
+    get_dir_base_width_csr(env, dir_base, dir_width, level, guest);
+}
+
 TLBRet loongarch_check_pte(CPULoongArchState *env, MMUContext *context,
-                           MMUAccessType access_type, int mmu_idx)
+                           MMUAccessType access_type, int mmu_idx, bool guest)
 {
-    uint64_t plv = mmu_idx;
+    uint64_t plv = mmu_idx_to_plv(mmu_idx);
     uint64_t tlb_entry, tlb_ppn;
     uint8_t tlb_ps, tlb_plv, tlb_nx, tlb_nr, tlb_rplv;
     bool tlb_v, tlb_d;
 
     tlb_entry = context->pte;
     tlb_ps = context->ps;
-    tlb_v = pte_present(env, tlb_entry);
-    tlb_d = pte_write(env, tlb_entry);
+    tlb_v = pte_present(env, tlb_entry, guest);
+    tlb_d = pte_write(env, tlb_entry, guest);
     tlb_plv = FIELD_EX64(tlb_entry, TLBENTRY, PLV);
     if (is_la64(env)) {
         tlb_ppn = FIELD_EX64(tlb_entry, TLBENTRY_64, PPN);
@@ -98,7 +108,7 @@ TLBRet loongarch_check_pte(CPULoongArchState *env, MMUContext *context,
     context->physical = (tlb_ppn << R_TLBENTRY_64_PPN_SHIFT) |
                         (context->addr & MAKE_64BIT_MASK(0, tlb_ps));
     context->prot = PAGE_READ;
-    context->mmu_index = tlb_plv;
+    context->mmu_index = mmu_idx;
     if (tlb_d) {
         context->prot |= PAGE_WRITE;
     }
@@ -144,7 +154,8 @@ static MemTxResult loongarch_cmpxchg_phys(CPUState *cs, hwaddr phys,
 }
 
 TLBRet loongarch_ptw(CPULoongArchState *env, MMUContext *context,
-                     int access_type, int mmu_idx, int debug)
+                     int access_type, int mmu_idx, int debug, bool guest,
+                     uintptr_t retaddr)
 {
     const MemTxAttrs attrs = MEMTXATTRS_UNSPECIFIED;
     CPUState *cs = env_cpu(env);
@@ -160,14 +171,14 @@ TLBRet loongarch_ptw(CPULoongArchState *env, MMUContext *context,
 
     address = context->addr;
     if ((address >> 63) & 0x1) {
-        base = env->CSR_PGDH;
+        base = GET_CSR_IF(guest, PGDH);
     } else {
-        base = env->CSR_PGDL;
+        base = GET_CSR_IF(guest, PGDL);
     }
     base &= palen_mask;
 
     for (level = 4; level >= 0; level--) {
-        get_dir_base_width(env, &dir_base, &dir_width, level);
+        get_dir_base_width(env, &dir_base, &dir_width, level, guest);
 
         if (dir_width == 0) {
             continue;
@@ -176,7 +187,10 @@ TLBRet loongarch_ptw(CPULoongArchState *env, MMUContext *context,
         /* get next level page directory */
         index = (address >> dir_base) & ((1 << dir_width) - 1);
         phys = base | index << 3;
-        base = address_space_ldq_le(cs->as, phys, attrs, NULL);
+        base = address_space_ldq_le(
+            cs->as,
+            (guest ? loongarch_get_host_address(env, phys, retaddr) : phys),
+            attrs, NULL);
         if (level) {
             if (FIELD_EX64(base, TLBENTRY, HUGE)) {
                 /* base is a huge pte */
@@ -205,19 +219,22 @@ restart:
         context->pte_buddy[index] = base;
         context->pte_buddy[1 - index] = base + BIT_ULL(dir_base);
         base += (BIT_ULL(dir_base) & address);
-    } else if (cpu_has_ptw(env)) {
+    } else if (cpu_has_ptw(env, guest)) {
         uint64_t val;
 
         index &= 1;
         context->pte_buddy[index] = base;
-        val = address_space_ldq_le(cs->as, phys + 8 * (1 - 2 * index),
-                                   attrs, NULL);
+        val = address_space_ldq_le(
+            cs->as,
+            (guest ? loongarch_get_host_address(env, phys, retaddr) : phys) +
+                8 * (1 - 2 * index),
+            attrs, NULL);
         context->pte_buddy[1 - index] = val;
     }
 
     context->ps = dir_base;
     context->pte = base;
-    ret = loongarch_check_pte(env, context, access_type, mmu_idx);
+    ret = loongarch_check_pte(env, context, access_type, mmu_idx, guest);
     if (debug) {
         return ret;
     }
@@ -228,7 +245,7 @@ restart:
      * Need atomic compchxg operation with pte update, other vCPUs may
      * update pte at the same time.
      */
-    if (ret == TLBRET_MATCH && cpu_has_ptw(env)) {
+    if (ret == TLBRET_MATCH && cpu_has_ptw(env, guest)) {
         if (access_type == MMU_DATA_STORE && pte_dirty(base)) {
             return ret;
         }
@@ -241,10 +258,15 @@ restart:
         if (access_type == MMU_DATA_STORE) {
             base = pte_mkdirty(base);
         }
-        ret1 = loongarch_cmpxchg_phys(cs, phys, pte, base);
+        ret1 = loongarch_cmpxchg_phys(
+            cs, (guest ? loongarch_get_host_address(env, phys, retaddr) : phys),
+            pte, base);
         /* PTE updated by other CPU, reload PTE entry */
         if (ret1 == MEMTX_DECODE_ERROR) {
-            base = address_space_ldq_le(cs->as, phys, attrs, NULL);
+            base = address_space_ldq_le(
+                cs->as,
+                (guest ? loongarch_get_host_address(env, phys, retaddr) : phys),
+                attrs, NULL);
             goto restart;
         }
 
@@ -270,15 +292,15 @@ restart:
     return ret;
 }
 
-static TLBRet loongarch_map_address(CPULoongArchState *env,
-                                    MMUContext *context,
-                                    MMUAccessType access_type, int mmu_idx,
-                                    int is_debug)
+TLBRet loongarch_map_address(CPULoongArchState *env, MMUContext *context,
+                             MMUAccessType access_type, int mmu_idx,
+                             int is_debug, bool guest, uintptr_t retaddr)
 {
     TLBRet ret;
 
     if (tcg_enabled()) {
-        ret = loongarch_get_addr_from_tlb(env, context, access_type, mmu_idx);
+        ret = loongarch_get_addr_from_tlb(env, context, access_type, mmu_idx,
+                                          guest);
         if (ret != TLBRET_NOMATCH) {
             return ret;
         }
@@ -290,7 +312,8 @@ static TLBRet loongarch_map_address(CPULoongArchState *env,
          * legal mapping, even if the mapping is not yet in TLB. return 0 if
          * there is a valid map, else none zero.
          */
-        return loongarch_ptw(env, context, access_type, mmu_idx, is_debug);
+        return loongarch_ptw(env, context, access_type, mmu_idx, is_debug,
+                             guest, retaddr);
     }
 
     return TLBRET_NOMATCH;
@@ -309,14 +332,14 @@ static hwaddr dmw_va2pa(CPULoongArchState *env, vaddr va, uint64_t dmw)
 
 TLBRet get_physical_address(CPULoongArchState *env, MMUContext *context,
                             MMUAccessType access_type, int mmu_idx,
-                            int is_debug)
+                            int is_debug, uintptr_t retaddr)
 {
-    int user_mode = mmu_idx == MMU_USER_IDX;
-    int kernel_mode = mmu_idx == MMU_KERNEL_IDX;
+    int user_mode = mmu_idx_to_plv(mmu_idx) == MMU_USER_IDX;
+    int kernel_mode = mmu_idx_to_plv(mmu_idx) == MMU_KERNEL_IDX;
     uint32_t plv, base_c, base_v;
     int64_t addr_high;
-    uint8_t da = FIELD_EX64(env->CSR_CRMD, CSR_CRMD, DA);
-    uint8_t pg = FIELD_EX64(env->CSR_CRMD, CSR_CRMD, PG);
+    uint8_t da = FIELD_EX64(GET_CSR_IF(env->guest, CRMD), CSR_CRMD, DA);
+    uint8_t pg = FIELD_EX64(GET_CSR_IF(env->guest, CRMD), CSR_CRMD, PG);
     vaddr address;
 
     /* Check PG and DA */
@@ -337,12 +360,15 @@ TLBRet get_physical_address(CPULoongArchState *env, MMUContext *context,
     /* Check direct map window */
     for (int i = 0; i < 4; i++) {
         if (is_la64(env)) {
-            base_c = FIELD_EX64(env->CSR_DMW[i], CSR_DMW_64, VSEG);
+            base_c =
+                FIELD_EX64(GET_CSR_IF(env->guest, DMW[i]), CSR_DMW_64, VSEG);
         } else {
-            base_c = FIELD_EX64(env->CSR_DMW[i], CSR_DMW_32, VSEG);
+            base_c =
+                FIELD_EX64(GET_CSR_IF(env->guest, DMW[i]), CSR_DMW_32, VSEG);
         }
-        if ((plv & env->CSR_DMW[i]) && (base_c == base_v)) {
-            context->physical = dmw_va2pa(env, address, env->CSR_DMW[i]);
+        if ((plv & GET_CSR_IF(env->guest, DMW[i])) && (base_c == base_v)) {
+            context->physical =
+                dmw_va2pa(env, address, GET_CSR_IF(env->guest, DMW[i]));
             context->prot = PAGE_READ | PAGE_WRITE | PAGE_EXEC;
             context->mmu_index = MMU_DA_IDX;
             return TLBRET_MATCH;
@@ -356,7 +382,8 @@ TLBRet get_physical_address(CPULoongArchState *env, MMUContext *context,
     }
 
     /* Mapped address */
-    return loongarch_map_address(env, context, access_type, mmu_idx, is_debug);
+    return loongarch_map_address(env, context, access_type, mmu_idx, is_debug,
+                                 env->guest, retaddr);
 }
 
 hwaddr loongarch_cpu_get_phys_addr_debug(CPUState *cs, vaddr addr)
@@ -366,7 +393,7 @@ hwaddr loongarch_cpu_get_phys_addr_debug(CPUState *cs, vaddr addr)
 
     context.addr = addr;
     if (get_physical_address(env, &context, MMU_DATA_LOAD,
-                             cpu_mmu_index(cs, false), 1) != TLBRET_MATCH) {
+                             cpu_mmu_index(cs, false), 1, 0) != TLBRET_MATCH) {
         return -1;
     }
     return context.physical;
diff --git a/target/loongarch/csr.c b/target/loongarch/csr.c
index fff2312f87..1c4505536d 100644
--- a/target/loongarch/csr.c
+++ b/target/loongarch/csr.c
@@ -20,8 +20,27 @@
         .flags = 0, .readfn = NULL, .writefn = NULL           \
     }
 
+#define GCSR_OFF_FUNCS(NAME, FL, RD, WR)                              \
+    [LOONGARCH_CSR_##                                                 \
+        NAME] = { .name = (stringify(GCSR_##NAME)),                   \
+                  .offset = offsetof(CPULoongArchState, GCSR_##NAME), \
+                  .flags = FL,                                        \
+                  .readfn = RD,                                       \
+                  .writefn = WR }
+
+#define GCSR_OFF_ARRAY(NAME, N)                                         \
+    [LOONGARCH_CSR_##NAME(N)] = { .name = (stringify(GCSR_##NAME##N)),  \
+                                  .offset = offsetof(CPULoongArchState, \
+                                                     GCSR_##NAME[N]),   \
+                                  .flags = 0,                           \
+                                  .readfn = NULL,                       \
+                                  .writefn = NULL }
+
 #define CSR_OFF_FLAGS(NAME, FL)   CSR_OFF_FUNCS(NAME, FL, NULL, NULL)
 #define CSR_OFF(NAME)             CSR_OFF_FLAGS(NAME, 0)
+#define GCSR_OFF_FLAGS(NAME, FL) GCSR_OFF_FUNCS(NAME, FL, NULL, NULL)
+#define GCSR_OFF(NAME) GCSR_OFF_FLAGS(NAME, 0)
+#define GCSR_GSPR(NAME) GCSR_OFF_FUNCS(NAME, CSRFL_GSPR, NULL, NULL)
 
 static CSRInfo csr_info[] = {
     CSR_OFF_FLAGS(CRMD, CSRFL_EXITTB),
@@ -35,6 +54,8 @@ static CSRInfo csr_info[] = {
     CSR_OFF_FLAGS(BADI, CSRFL_READONLY),
     CSR_OFF(EENTRY),
     CSR_OFF(TLBIDX),
+    CSR_OFF(GTLBC),
+    CSR_OFF(TRGP),
     CSR_OFF(TLBEHI),
     CSR_OFF(TLBELO0),
     CSR_OFF(TLBELO1),
@@ -71,6 +92,10 @@ static CSRInfo csr_info[] = {
     CSR_OFF_FLAGS(TVAL, CSRFL_READONLY | CSRFL_IO),
     CSR_OFF(CNTC),
     CSR_OFF_FLAGS(TICLR, CSRFL_IO),
+    CSR_OFF(GSTAT),
+    CSR_OFF(GCFG),
+    CSR_OFF_FLAGS(GINTC, CSRFL_IO),
+    CSR_OFF(GCNTC),
     CSR_OFF(LLBCTL),
     CSR_OFF(IMPCTL1),
     CSR_OFF(IMPCTL2),
@@ -135,6 +160,87 @@ static CSRInfo csr_info[] = {
     CSR_OFF(MSGIR),
 };
 
+static CSRInfo gcsr_info[] = {
+    GCSR_OFF_FLAGS(CRMD, CSRFL_EXITTB),
+    GCSR_OFF(PRMD),
+    GCSR_OFF_FLAGS(EUEN, CSRFL_EXITTB),
+    GCSR_OFF_FLAGS(MISC, CSRFL_GUEST_READONLY),
+    GCSR_OFF(ECFG),
+    GCSR_OFF_FLAGS(ESTAT, CSRFL_EXITTB),
+    GCSR_OFF(ERA),
+    GCSR_OFF(BADV),
+    GCSR_OFF_FLAGS(BADI, CSRFL_GUEST_READONLY),
+    GCSR_OFF(EENTRY),
+    GCSR_OFF(TLBIDX),
+    GCSR_GSPR(GTLBC),
+    GCSR_GSPR(TRGP),
+    GCSR_OFF(TLBEHI),
+    GCSR_OFF(TLBELO0),
+    GCSR_OFF(TLBELO1),
+    GCSR_OFF_FLAGS(ASID, CSRFL_EXITTB),
+    GCSR_OFF(PGDL),
+    GCSR_OFF(PGDH),
+    GCSR_OFF_FLAGS(PGD, CSRFL_GUEST_READONLY),
+    GCSR_OFF(PWCL),
+    GCSR_OFF(PWCH),
+    GCSR_OFF(STLBPS),
+    GCSR_OFF(RVACFG),
+    GCSR_OFF_FLAGS(CPUID, CSRFL_GUEST_READONLY),
+    GCSR_OFF_FLAGS(PRCFG1, CSRFL_GUEST_READONLY),
+    GCSR_OFF_FLAGS(PRCFG2, CSRFL_GUEST_READONLY),
+    GCSR_OFF_FLAGS(PRCFG3, CSRFL_GUEST_READONLY),
+    GCSR_OFF_ARRAY(SAVE, 0),
+    GCSR_OFF_ARRAY(SAVE, 1),
+    GCSR_OFF_ARRAY(SAVE, 2),
+    GCSR_OFF_ARRAY(SAVE, 3),
+    GCSR_OFF_ARRAY(SAVE, 4),
+    GCSR_OFF_ARRAY(SAVE, 5),
+    GCSR_OFF_ARRAY(SAVE, 6),
+    GCSR_OFF_ARRAY(SAVE, 7),
+    GCSR_OFF_ARRAY(SAVE, 8),
+    GCSR_OFF_ARRAY(SAVE, 9),
+    GCSR_OFF_ARRAY(SAVE, 10),
+    GCSR_OFF_ARRAY(SAVE, 11),
+    GCSR_OFF_ARRAY(SAVE, 12),
+    GCSR_OFF_ARRAY(SAVE, 13),
+    GCSR_OFF_ARRAY(SAVE, 14),
+    GCSR_OFF_ARRAY(SAVE, 15),
+    GCSR_OFF(TID),
+    GCSR_OFF_FLAGS(TCFG, CSRFL_IO),
+    GCSR_OFF_FLAGS(TVAL, CSRFL_GUEST_READONLY | CSRFL_IO),
+    GCSR_OFF(CNTC),
+    GCSR_OFF_FLAGS(TICLR, CSRFL_IO),
+    GCSR_GSPR(GSTAT),
+    GCSR_GSPR(GCFG),
+    GCSR_GSPR(GINTC),
+    GCSR_GSPR(GCNTC),
+    GCSR_OFF(LLBCTL),
+    GCSR_GSPR(IMPCTL1),
+    GCSR_GSPR(IMPCTL2),
+    GCSR_OFF(TLBRENTRY),
+    GCSR_OFF(TLBRBADV),
+    GCSR_OFF(TLBRERA),
+    GCSR_OFF(TLBRSAVE),
+    GCSR_OFF(TLBRELO0),
+    GCSR_OFF(TLBRELO1),
+    GCSR_OFF(TLBREHI),
+    GCSR_OFF(TLBRPRMD),
+    GCSR_GSPR(MERRCTL),
+    GCSR_GSPR(MERRINFO1),
+    GCSR_GSPR(MERRINFO2),
+    GCSR_GSPR(MERRENTRY),
+    GCSR_GSPR(MERRERA),
+    GCSR_GSPR(MERRSAVE),
+    GCSR_GSPR(CTAG),
+    GCSR_OFF_ARRAY(DMW, 0),
+    GCSR_OFF_ARRAY(DMW, 1),
+    GCSR_OFF_ARRAY(DMW, 2),
+    GCSR_OFF_ARRAY(DMW, 3),
+    GCSR_GSPR(DBG),
+    GCSR_GSPR(DERA),
+    GCSR_GSPR(DSAVE),
+};
+
 CSRInfo *get_csr(unsigned int csr_num)
 {
     CSRInfo *csr;
@@ -151,6 +257,22 @@ CSRInfo *get_csr(unsigned int csr_num)
     return csr;
 }
 
+CSRInfo *get_gcsr(unsigned int csr_num)
+{
+    CSRInfo *csr;
+
+    if (csr_num >= ARRAY_SIZE(gcsr_info)) {
+        return NULL;
+    }
+
+    csr = &gcsr_info[csr_num];
+    if (csr->offset == 0) {
+        return NULL;
+    }
+
+    return csr;
+}
+
 bool set_csr_flag(unsigned int csr_num, int flag)
 {
     CSRInfo *csr;
diff --git a/target/loongarch/csr.h b/target/loongarch/csr.h
index 81a656baae..a0846353de 100644
--- a/target/loongarch/csr.h
+++ b/target/loongarch/csr.h
@@ -14,6 +14,8 @@ enum {
     CSRFL_EXITTB   = (1 << 1),
     CSRFL_IO       = (1 << 2),
     CSRFL_UNUSED   = (1 << 3),
+    CSRFL_GUEST_READONLY = (1 << 4),
+    CSRFL_GSPR     = (1 << 5),
 };
 
 typedef struct {
@@ -25,5 +27,6 @@ typedef struct {
 } CSRInfo;
 
 CSRInfo *get_csr(unsigned int csr_num);
+CSRInfo *get_gcsr(unsigned int csr_num);
 bool set_csr_flag(unsigned int csr_num, int flag);
 #endif /* TARGET_LOONGARCH_CSR_H */
diff --git a/target/loongarch/disas.c b/target/loongarch/disas.c
index 3249ab7ac6..b282da6ea6 100644
--- a/target/loongarch/disas.c
+++ b/target/loongarch/disas.c
@@ -698,6 +698,16 @@ INSN(tlbfill,      empty)
 INSN(tlbclr,       empty)
 INSN(tlbflush,     empty)
 INSN(invtlb,       i_rr)
+INSN(gcsrrd,       r_csr)
+INSN(gcsrwr,       r_csr)
+INSN(gcsrxchg,     rr_csr)
+INSN(gtlbclr,      empty)
+INSN(gtlbflush,    empty)
+INSN(gtlbsrch,     empty)
+INSN(gtlbrd,       empty)
+INSN(gtlbwr,       empty)
+INSN(gtlbfill,     empty)
+INSN(hvcl,         i)
 INSN(cacop,        cop_r_i)
 INSN(lddir,        rr_i)
 INSN(ldpte,        j_i)
diff --git a/target/loongarch/insns.decode b/target/loongarch/insns.decode
index 3089d42044..b19c40b423 100644
--- a/target/loongarch/insns.decode
+++ b/target/loongarch/insns.decode
@@ -493,6 +493,23 @@ bgeu            0110 11 ................ ..... .....     @rr_offs16
   csrxchg           0000 0100 .............. ..... .....     @rr_csr
 }
 
+#
+# LVZ (LoongArch Virtualization) instructions
+#
+{
+  gcsrrd            0000 0101 .............. 00000 .....     @r_csr
+  gcsrwr            0000 0101 .............. 00001 .....     @r_csr
+  gcsrxchg          0000 0101 .............. ..... .....     @rr_csr
+}
+
+gtlbclr          0000 01100100 10000 01000 00000 00001    @empty
+gtlbflush        0000 01100100 10000 01001 00000 00001    @empty
+gtlbsrch         0000 01100100 10000 01010 00000 00001    @empty
+gtlbrd           0000 01100100 10000 01011 00000 00001    @empty
+gtlbwr           0000 01100100 10000 01100 00000 00001    @empty
+gtlbfill         0000 01100100 10000 01101 00000 00001    @empty
+hvcl             0000 0000 0010 1011 1 ...............    @i15
+
 iocsrrd_b        0000 01100100 10000 00000 ..... .....    @rr
 iocsrrd_h        0000 01100100 10000 00001 ..... .....    @rr
 iocsrrd_w        0000 01100100 10000 00010 ..... .....    @rr
diff --git a/target/loongarch/internals.h b/target/loongarch/internals.h
index e01dbed40f..8a06ab9868 100644
--- a/target/loongarch/internals.h
+++ b/target/loongarch/internals.h
@@ -32,14 +32,18 @@ void restore_fp_status(CPULoongArchState *env);
 extern const VMStateDescription vmstate_loongarch_cpu;
 
 void loongarch_cpu_set_irq(void *opaque, int irq, int level);
+void loongarch_cpu_set_irq_guest(void *opaque, int irq, int level);
 
 void loongarch_constant_timer_cb(void *opaque);
+void loongarch_constant_timer_cb_guest(void *opaque);
 uint64_t cpu_loongarch_get_constant_timer_counter(LoongArchCPU *cpu);
-uint64_t cpu_loongarch_get_constant_timer_ticks(LoongArchCPU *cpu);
+uint64_t cpu_loongarch_get_constant_timer_ticks(LoongArchCPU *cpu, bool guest);
+void cpu_loongarch_set_guest_timer(LoongArchCPU *cpu, bool on);
 void cpu_loongarch_store_constant_timer_config(LoongArchCPU *cpu,
-                                               uint64_t value);
+                                               uint64_t value, bool guest);
 bool loongarch_cpu_has_work(CPUState *cs);
 bool cpu_loongarch_hw_interrupts_pending(CPULoongArchState *env);
+bool loongarch_guest_has_interrupt(CPULoongArchState *env);
 #endif /* !CONFIG_USER_ONLY */
 
 uint64_t read_fcc(CPULoongArchState *env);
diff --git a/target/loongarch/kvm/kvm.c b/target/loongarch/kvm/kvm.c
index 9d844c4905..114f115e90 100644
--- a/target/loongarch/kvm/kvm.c
+++ b/target/loongarch/kvm/kvm.c
@@ -28,6 +28,7 @@
 #include "cpu-csr.h"
 #include "kvm_loongarch.h"
 #include "trace.h"
+#include "exec/target_long.h"
 
 static bool cap_has_mp_state;
 static unsigned int brk_insn;
diff --git a/target/loongarch/machine.c b/target/loongarch/machine.c
index 4db53fec26..df7a7c9075 100644
--- a/target/loongarch/machine.c
+++ b/target/loongarch/machine.c
@@ -197,11 +197,101 @@ static const VMStateDescription vmstate_tlb = {
     .version_id = 0,
     .minimum_version_id = 0,
     .needed = tlb_needed,
-    .fields = (const VMStateField[]) {
-        VMSTATE_STRUCT_ARRAY(env.tlb, LoongArchCPU, LOONGARCH_TLB_MAX,
-                             0, vmstate_tlb_entry, LoongArchTLB),
-        VMSTATE_END_OF_LIST()
-    }
+    .fields =
+        (const VMStateField[]){
+            VMSTATE_STRUCT_ARRAY(env.tlb, LoongArchCPU, LOONGARCH_TLB_MAX, 0,
+                                 vmstate_tlb_entry, LoongArchTLB),
+            VMSTATE_END_OF_LIST() }
+};
+
+static bool lvz_needed(void *opaque)
+{
+    LoongArchCPU *cpu = opaque;
+
+    return FIELD_EX32(cpu->env.cpucfg[2], CPUCFG2, LVZ);
+}
+
+static const VMStateDescription vmstate_lvz = {
+    .name = "cpu/lvz",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .needed = lvz_needed,
+    .fields =
+        (const VMStateField[]){
+            VMSTATE_UINT64(env.CSR_GSTAT, LoongArchCPU),
+            VMSTATE_UINT64(env.CSR_GCFG, LoongArchCPU),
+            VMSTATE_UINT64(env.CSR_GINTC, LoongArchCPU),
+            VMSTATE_UINT64(env.CSR_GCNTC, LoongArchCPU),
+            VMSTATE_UINT64(env.CSR_GTLBC, LoongArchCPU),
+            VMSTATE_UINT64(env.CSR_TRGP, LoongArchCPU),
+
+            VMSTATE_UINT64(env.GCSR_CRMD, LoongArchCPU),
+            VMSTATE_UINT64(env.GCSR_PRMD, LoongArchCPU),
+            VMSTATE_UINT64(env.GCSR_EUEN, LoongArchCPU),
+            VMSTATE_UINT64(env.GCSR_MISC, LoongArchCPU),
+            VMSTATE_UINT64(env.GCSR_ECFG, LoongArchCPU),
+            VMSTATE_UINT64(env.GCSR_ESTAT, LoongArchCPU),
+            VMSTATE_UINT64(env.GCSR_ERA, LoongArchCPU),
+            VMSTATE_UINT64(env.GCSR_BADV, LoongArchCPU),
+            VMSTATE_UINT64(env.GCSR_BADI, LoongArchCPU),
+            VMSTATE_UINT64(env.GCSR_EENTRY, LoongArchCPU),
+
+            VMSTATE_UINT64(env.GCSR_TLBIDX, LoongArchCPU),
+            VMSTATE_UINT64(env.GCSR_TLBEHI, LoongArchCPU),
+            VMSTATE_UINT64(env.GCSR_TLBELO0, LoongArchCPU),
+            VMSTATE_UINT64(env.GCSR_TLBELO1, LoongArchCPU),
+            VMSTATE_UINT64(env.GCSR_ASID, LoongArchCPU),
+            VMSTATE_UINT64(env.GCSR_PGDL, LoongArchCPU),
+            VMSTATE_UINT64(env.GCSR_PGDH, LoongArchCPU),
+            VMSTATE_UINT64(env.GCSR_PGD, LoongArchCPU),
+            VMSTATE_UINT64(env.GCSR_PWCL, LoongArchCPU),
+            VMSTATE_UINT64(env.GCSR_PWCH, LoongArchCPU),
+            VMSTATE_UINT64(env.GCSR_STLBPS, LoongArchCPU),
+            VMSTATE_UINT64(env.GCSR_RVACFG, LoongArchCPU),
+
+            VMSTATE_UINT64(env.GCSR_CPUID, LoongArchCPU),
+            VMSTATE_UINT64(env.GCSR_PRCFG1, LoongArchCPU),
+            VMSTATE_UINT64(env.GCSR_PRCFG2, LoongArchCPU),
+            VMSTATE_UINT64(env.GCSR_PRCFG3, LoongArchCPU),
+            VMSTATE_UINT64_ARRAY(env.GCSR_SAVE, LoongArchCPU, 16),
+
+            VMSTATE_UINT64(env.GCSR_TID, LoongArchCPU),
+            VMSTATE_UINT64(env.GCSR_TCFG, LoongArchCPU),
+            VMSTATE_UINT64(env.GCSR_TVAL, LoongArchCPU),
+            VMSTATE_UINT64(env.GCSR_CNTC, LoongArchCPU),
+            VMSTATE_UINT64(env.GCSR_TICLR, LoongArchCPU),
+            VMSTATE_UINT64(env.GCSR_LLBCTL, LoongArchCPU),
+
+            VMSTATE_UINT64(env.GCSR_IMPCTL1, LoongArchCPU),
+            VMSTATE_UINT64(env.GCSR_IMPCTL2, LoongArchCPU),
+
+            VMSTATE_UINT64(env.GCSR_TLBRENTRY, LoongArchCPU),
+            VMSTATE_UINT64(env.GCSR_TLBRBADV, LoongArchCPU),
+            VMSTATE_UINT64(env.GCSR_TLBRERA, LoongArchCPU),
+            VMSTATE_UINT64(env.GCSR_TLBRSAVE, LoongArchCPU),
+            VMSTATE_UINT64(env.GCSR_TLBRELO0, LoongArchCPU),
+            VMSTATE_UINT64(env.GCSR_TLBRELO1, LoongArchCPU),
+            VMSTATE_UINT64(env.GCSR_TLBREHI, LoongArchCPU),
+            VMSTATE_UINT64(env.GCSR_TLBRPRMD, LoongArchCPU),
+
+            VMSTATE_UINT64(env.GCSR_MERRCTL, LoongArchCPU),
+            VMSTATE_UINT64(env.GCSR_MERRINFO1, LoongArchCPU),
+            VMSTATE_UINT64(env.GCSR_MERRINFO2, LoongArchCPU),
+            VMSTATE_UINT64(env.GCSR_MERRENTRY, LoongArchCPU),
+            VMSTATE_UINT64(env.GCSR_MERRERA, LoongArchCPU),
+            VMSTATE_UINT64(env.GCSR_MERRSAVE, LoongArchCPU),
+            VMSTATE_UINT64(env.GCSR_CTAG, LoongArchCPU),
+
+            VMSTATE_UINT64_ARRAY(env.GCSR_DMW, LoongArchCPU, 4),
+
+            VMSTATE_UINT64(env.GCSR_DBG, LoongArchCPU),
+            VMSTATE_UINT64(env.GCSR_DERA, LoongArchCPU),
+            VMSTATE_UINT64(env.GCSR_DSAVE, LoongArchCPU),
+
+            VMSTATE_BOOL(env.guest, LoongArchCPU),
+            VMSTATE_STRUCT_ARRAY(env.gtlb, LoongArchCPU, LOONGARCH_TLB_MAX, 0,
+                                 vmstate_tlb_entry, LoongArchTLB),
+            VMSTATE_END_OF_LIST() },
 };
 #endif
 
@@ -210,83 +300,78 @@ const VMStateDescription vmstate_loongarch_cpu = {
     .name = "cpu",
     .version_id = 4,
     .minimum_version_id = 4,
-    .fields = (const VMStateField[]) {
-        VMSTATE_UINT64_ARRAY(env.gpr, LoongArchCPU, 32),
-        VMSTATE_UINT64(env.pc, LoongArchCPU),
-
-        /* Remaining CSRs */
-        VMSTATE_UINT64(env.CSR_CRMD, LoongArchCPU),
-        VMSTATE_UINT64(env.CSR_PRMD, LoongArchCPU),
-        VMSTATE_UINT64(env.CSR_EUEN, LoongArchCPU),
-        VMSTATE_UINT64(env.CSR_MISC, LoongArchCPU),
-        VMSTATE_UINT64(env.CSR_ECFG, LoongArchCPU),
-        VMSTATE_UINT64(env.CSR_ESTAT, LoongArchCPU),
-        VMSTATE_UINT64(env.CSR_ERA, LoongArchCPU),
-        VMSTATE_UINT64(env.CSR_BADV, LoongArchCPU),
-        VMSTATE_UINT64(env.CSR_BADI, LoongArchCPU),
-        VMSTATE_UINT64(env.CSR_EENTRY, LoongArchCPU),
-        VMSTATE_UINT64(env.CSR_TLBIDX, LoongArchCPU),
-        VMSTATE_UINT64(env.CSR_TLBEHI, LoongArchCPU),
-        VMSTATE_UINT64(env.CSR_TLBELO0, LoongArchCPU),
-        VMSTATE_UINT64(env.CSR_TLBELO1, LoongArchCPU),
-        VMSTATE_UINT64(env.CSR_ASID, LoongArchCPU),
-        VMSTATE_UINT64(env.CSR_PGDL, LoongArchCPU),
-        VMSTATE_UINT64(env.CSR_PGDH, LoongArchCPU),
-        VMSTATE_UINT64(env.CSR_PGD, LoongArchCPU),
-        VMSTATE_UINT64(env.CSR_PWCL, LoongArchCPU),
-        VMSTATE_UINT64(env.CSR_PWCH, LoongArchCPU),
-        VMSTATE_UINT64(env.CSR_STLBPS, LoongArchCPU),
-        VMSTATE_UINT64(env.CSR_RVACFG, LoongArchCPU),
-        VMSTATE_UINT64(env.CSR_PRCFG1, LoongArchCPU),
-        VMSTATE_UINT64(env.CSR_PRCFG2, LoongArchCPU),
-        VMSTATE_UINT64(env.CSR_PRCFG3, LoongArchCPU),
-        VMSTATE_UINT64_ARRAY(env.CSR_SAVE, LoongArchCPU, 16),
-        VMSTATE_UINT64(env.CSR_TID, LoongArchCPU),
-        VMSTATE_UINT64(env.CSR_TCFG, LoongArchCPU),
-        VMSTATE_UINT64(env.CSR_TVAL, LoongArchCPU),
-        VMSTATE_UINT64(env.CSR_CNTC, LoongArchCPU),
-        VMSTATE_UINT64(env.CSR_TICLR, LoongArchCPU),
-        VMSTATE_UINT64(env.CSR_LLBCTL, LoongArchCPU),
-        VMSTATE_UINT64(env.CSR_IMPCTL1, LoongArchCPU),
-        VMSTATE_UINT64(env.CSR_IMPCTL2, LoongArchCPU),
-        VMSTATE_UINT64(env.CSR_TLBRENTRY, LoongArchCPU),
-        VMSTATE_UINT64(env.CSR_TLBRBADV, LoongArchCPU),
-        VMSTATE_UINT64(env.CSR_TLBRERA, LoongArchCPU),
-        VMSTATE_UINT64(env.CSR_TLBRSAVE, LoongArchCPU),
-        VMSTATE_UINT64(env.CSR_TLBRELO0, LoongArchCPU),
-        VMSTATE_UINT64(env.CSR_TLBRELO1, LoongArchCPU),
-        VMSTATE_UINT64(env.CSR_TLBREHI, LoongArchCPU),
-        VMSTATE_UINT64(env.CSR_TLBRPRMD, LoongArchCPU),
-        VMSTATE_UINT64(env.CSR_MERRCTL, LoongArchCPU),
-        VMSTATE_UINT64(env.CSR_MERRINFO1, LoongArchCPU),
-        VMSTATE_UINT64(env.CSR_MERRINFO2, LoongArchCPU),
-        VMSTATE_UINT64(env.CSR_MERRENTRY, LoongArchCPU),
-        VMSTATE_UINT64(env.CSR_MERRERA, LoongArchCPU),
-        VMSTATE_UINT64(env.CSR_MERRSAVE, LoongArchCPU),
-        VMSTATE_UINT64(env.CSR_CTAG, LoongArchCPU),
-        VMSTATE_UINT64_ARRAY(env.CSR_DMW, LoongArchCPU, 4),
-
-        /* Debug CSRs */
-        VMSTATE_UINT64(env.CSR_DBG, LoongArchCPU),
-        VMSTATE_UINT64(env.CSR_DERA, LoongArchCPU),
-        VMSTATE_UINT64(env.CSR_DSAVE, LoongArchCPU),
-
-        VMSTATE_UINT64(kvm_state_counter, LoongArchCPU),
-        /* PV steal time */
-        VMSTATE_UINT64(env.stealtime.guest_addr, LoongArchCPU),
-
-        VMSTATE_END_OF_LIST()
-    },
-    .subsections = (const VMStateDescription * const []) {
-        &vmstate_fpu,
-        &vmstate_lsx,
-        &vmstate_lasx,
+    .fields =
+        (const VMStateField[]){
+            VMSTATE_UINT64_ARRAY(env.gpr, LoongArchCPU, 32),
+            VMSTATE_UINT64(env.pc, LoongArchCPU),
+
+            /* Remaining CSRs */
+            VMSTATE_UINT64(env.CSR_CRMD, LoongArchCPU),
+            VMSTATE_UINT64(env.CSR_PRMD, LoongArchCPU),
+            VMSTATE_UINT64(env.CSR_EUEN, LoongArchCPU),
+            VMSTATE_UINT64(env.CSR_MISC, LoongArchCPU),
+            VMSTATE_UINT64(env.CSR_ECFG, LoongArchCPU),
+            VMSTATE_UINT64(env.CSR_ESTAT, LoongArchCPU),
+            VMSTATE_UINT64(env.CSR_ERA, LoongArchCPU),
+            VMSTATE_UINT64(env.CSR_BADV, LoongArchCPU),
+            VMSTATE_UINT64(env.CSR_BADI, LoongArchCPU),
+            VMSTATE_UINT64(env.CSR_EENTRY, LoongArchCPU),
+            VMSTATE_UINT64(env.CSR_TLBIDX, LoongArchCPU),
+            VMSTATE_UINT64(env.CSR_TLBEHI, LoongArchCPU),
+            VMSTATE_UINT64(env.CSR_TLBELO0, LoongArchCPU),
+            VMSTATE_UINT64(env.CSR_TLBELO1, LoongArchCPU),
+            VMSTATE_UINT64(env.CSR_ASID, LoongArchCPU),
+            VMSTATE_UINT64(env.CSR_PGDL, LoongArchCPU),
+            VMSTATE_UINT64(env.CSR_PGDH, LoongArchCPU),
+            VMSTATE_UINT64(env.CSR_PGD, LoongArchCPU),
+            VMSTATE_UINT64(env.CSR_PWCL, LoongArchCPU),
+            VMSTATE_UINT64(env.CSR_PWCH, LoongArchCPU),
+            VMSTATE_UINT64(env.CSR_STLBPS, LoongArchCPU),
+            VMSTATE_UINT64(env.CSR_RVACFG, LoongArchCPU),
+            VMSTATE_UINT64(env.CSR_PRCFG1, LoongArchCPU),
+            VMSTATE_UINT64(env.CSR_PRCFG2, LoongArchCPU),
+            VMSTATE_UINT64(env.CSR_PRCFG3, LoongArchCPU),
+            VMSTATE_UINT64_ARRAY(env.CSR_SAVE, LoongArchCPU, 16),
+            VMSTATE_UINT64(env.CSR_TID, LoongArchCPU),
+            VMSTATE_UINT64(env.CSR_TCFG, LoongArchCPU),
+            VMSTATE_UINT64(env.CSR_TVAL, LoongArchCPU),
+            VMSTATE_UINT64(env.CSR_CNTC, LoongArchCPU),
+            VMSTATE_UINT64(env.CSR_TICLR, LoongArchCPU),
+            VMSTATE_UINT64(env.CSR_LLBCTL, LoongArchCPU),
+            VMSTATE_UINT64(env.CSR_IMPCTL1, LoongArchCPU),
+            VMSTATE_UINT64(env.CSR_IMPCTL2, LoongArchCPU),
+            VMSTATE_UINT64(env.CSR_TLBRENTRY, LoongArchCPU),
+            VMSTATE_UINT64(env.CSR_TLBRBADV, LoongArchCPU),
+            VMSTATE_UINT64(env.CSR_TLBRERA, LoongArchCPU),
+            VMSTATE_UINT64(env.CSR_TLBRSAVE, LoongArchCPU),
+            VMSTATE_UINT64(env.CSR_TLBRELO0, LoongArchCPU),
+            VMSTATE_UINT64(env.CSR_TLBRELO1, LoongArchCPU),
+            VMSTATE_UINT64(env.CSR_TLBREHI, LoongArchCPU),
+            VMSTATE_UINT64(env.CSR_TLBRPRMD, LoongArchCPU),
+            VMSTATE_UINT64(env.CSR_MERRCTL, LoongArchCPU),
+            VMSTATE_UINT64(env.CSR_MERRINFO1, LoongArchCPU),
+            VMSTATE_UINT64(env.CSR_MERRINFO2, LoongArchCPU),
+            VMSTATE_UINT64(env.CSR_MERRENTRY, LoongArchCPU),
+            VMSTATE_UINT64(env.CSR_MERRERA, LoongArchCPU),
+            VMSTATE_UINT64(env.CSR_MERRSAVE, LoongArchCPU),
+            VMSTATE_UINT64(env.CSR_CTAG, LoongArchCPU),
+            VMSTATE_UINT64_ARRAY(env.CSR_DMW, LoongArchCPU, 4),
+
+            /* Debug CSRs */
+            VMSTATE_UINT64(env.CSR_DBG, LoongArchCPU),
+            VMSTATE_UINT64(env.CSR_DERA, LoongArchCPU),
+            VMSTATE_UINT64(env.CSR_DSAVE, LoongArchCPU),
+
+            VMSTATE_UINT64(kvm_state_counter, LoongArchCPU),
+            /* PV steal time */
+            VMSTATE_UINT64(env.stealtime.guest_addr, LoongArchCPU),
+
+            VMSTATE_END_OF_LIST() },
+    .subsections =
+        (const VMStateDescription *const[]) {
+            &vmstate_fpu, &vmstate_lsx, &vmstate_lasx,
 #if defined(CONFIG_TCG) && !defined(CONFIG_USER_ONLY)
-        &vmstate_tlb,
+            &vmstate_tlb, &vmstate_lvz,
 #endif
-        &vmstate_lbt,
-        &vmstate_msgint,
-        &vmstate_pmu,
-        NULL
-    }
+            &vmstate_lbt, &vmstate_msgint, &vmstate_pmu, NULL }
 };
diff --git a/target/loongarch/tcg/constant_timer.c b/target/loongarch/tcg/constant_timer.c
index 1851f53fd6..97892e3ff9 100644
--- a/target/loongarch/tcg/constant_timer.c
+++ b/target/loongarch/tcg/constant_timer.c
@@ -20,29 +20,62 @@ uint64_t cpu_loongarch_get_constant_timer_counter(LoongArchCPU *cpu)
     return qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) / TIMER_PERIOD;
 }
 
-uint64_t cpu_loongarch_get_constant_timer_ticks(LoongArchCPU *cpu)
+uint64_t cpu_loongarch_get_constant_timer_ticks(LoongArchCPU *cpu, bool guest)
 {
     uint64_t now, expire;
+    CPULoongArchState *env = &cpu->env;
+
+    if (guest && !env->guest) {
+        return env->GCSR_TVAL;
+    }
 
     now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
-    expire = timer_expire_time_ns(&cpu->timer);
+    expire = timer_expire_time_ns(guest ? &cpu->guest_timer : &cpu->timer);
 
     return (expire - now) / TIMER_PERIOD;
 }
 
 void cpu_loongarch_store_constant_timer_config(LoongArchCPU *cpu,
-                                               uint64_t value)
+                                               uint64_t value, bool guest)
 {
     CPULoongArchState *env = &cpu->env;
     uint64_t now, next;
+    QEMUTimer *timer = guest ? &cpu->guest_timer : &cpu->timer;
+
+    SET_CSR_IF(guest, TCFG, value);
+
+    if (guest && !env->guest) {
+        return;
+    }
 
-    env->CSR_TCFG = value;
     if (value & CONSTANT_TIMER_ENABLE) {
         now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
         next = now + (value & CONSTANT_TIMER_TICK_MASK) * TIMER_PERIOD;
-        timer_mod(&cpu->timer, next);
+        timer_mod(timer, next);
     } else {
-        timer_del(&cpu->timer);
+        timer_del(timer);
+    }
+}
+
+void cpu_loongarch_set_guest_timer(LoongArchCPU *cpu, bool on)
+{
+    CPULoongArchState *env = &cpu->env;
+    uint64_t now, next, ticks;
+
+    if (!(env->GCSR_TCFG & CONSTANT_TIMER_ENABLE)) {
+        return;
+    }
+
+    if (on) {
+        now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+        ticks = env->GCSR_TVAL ? env->GCSR_TVAL :
+                                 (env->GCSR_TCFG & CONSTANT_TIMER_TICK_MASK);
+        next = now + ticks * TIMER_PERIOD;
+        env->GCSR_TVAL = 0;
+        timer_mod(&cpu->guest_timer, next);
+    } else {
+        env->GCSR_TVAL = cpu_loongarch_get_constant_timer_ticks(cpu, true);
+        timer_del(&cpu->guest_timer);
     }
 }
 
@@ -62,3 +95,20 @@ void loongarch_constant_timer_cb(void *opaque)
 
     loongarch_cpu_set_irq(opaque, IRQ_TIMER, 1);
 }
+
+void loongarch_constant_timer_cb_guest(void *opaque)
+{
+    LoongArchCPU *cpu = opaque;
+    CPULoongArchState *env = &cpu->env;
+    uint64_t now, next;
+
+    if (FIELD_EX64(env->GCSR_TCFG, CSR_TCFG, PERIODIC)) {
+        now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+        next = now + (env->GCSR_TCFG & CONSTANT_TIMER_TICK_MASK) * TIMER_PERIOD;
+        timer_mod(&cpu->guest_timer, next);
+    } else {
+        env->GCSR_TCFG = FIELD_DP64(env->GCSR_TCFG, CSR_TCFG, EN, 0);
+    }
+
+    loongarch_cpu_set_irq_guest(opaque, IRQ_TIMER, 1);
+}
diff --git a/target/loongarch/tcg/csr_helper.c b/target/loongarch/tcg/csr_helper.c
index cd35ca93c7..a680c6cbfe 100644
--- a/target/loongarch/tcg/csr_helper.c
+++ b/target/loongarch/tcg/csr_helper.c
@@ -58,6 +58,25 @@ target_ulong helper_csrrd_pgd(CPULoongArchState *env)
     return v;
 }
 
+target_ulong helper_gcsrrd_pgd(CPULoongArchState *env)
+{
+    int64_t v;
+
+    if (env->GCSR_TLBRERA & 0x1) {
+        v = env->GCSR_TLBRBADV;
+    } else {
+        v = env->GCSR_BADV;
+    }
+
+    if ((v >> 63) & 0x1) {
+        v = env->GCSR_PGDH;
+    } else {
+        v = env->GCSR_PGDL;
+    }
+
+    return v;
+}
+
 target_ulong helper_csrrd_cpuid(CPULoongArchState *env)
 {
     LoongArchCPU *lac = env_archcpu(env);
@@ -71,7 +90,14 @@ target_ulong helper_csrrd_tval(CPULoongArchState *env)
 {
     LoongArchCPU *cpu = env_archcpu(env);
 
-    return cpu_loongarch_get_constant_timer_ticks(cpu);
+    return cpu_loongarch_get_constant_timer_ticks(cpu, false);
+}
+
+target_ulong helper_gcsrrd_tval(CPULoongArchState *env)
+{
+    LoongArchCPU *cpu = env_archcpu(env);
+
+    return cpu_loongarch_get_constant_timer_ticks(cpu, true);
 }
 
 target_ulong helper_csrrd_msgir(CPULoongArchState *env)
@@ -105,6 +131,27 @@ target_ulong helper_csrwr_estat(CPULoongArchState *env, target_ulong val)
     return old_v;
 }
 
+target_ulong helper_gcsrwr_estat(CPULoongArchState *env, target_ulong val)
+{
+    int64_t old_v = env->GCSR_ESTAT;
+
+    env->GCSR_ESTAT = deposit64(env->GCSR_ESTAT, 0, 2, val);
+    if (!env->guest) {
+        env->GCSR_ESTAT =
+            deposit64(env->GCSR_ESTAT, 2, 11, extract64(val, 2, 11));
+        if (extract64(val, 2, 8) &
+            FIELD_EX64(env->CSR_GINTC, CSR_GINTC, HWIC)) {
+            env->CSR_ESTAT = deposit64(env->CSR_ESTAT, 2, 8,
+                                       extract64(env->CSR_ESTAT, 2, 8) &
+                                           ~extract64(val, 2, 8));
+        }
+        env->GCSR_ESTAT =
+            deposit64(env->GCSR_ESTAT, 16, 15, extract64(val, 16, 15));
+    }
+
+    return old_v;
+}
+
 target_ulong helper_csrwr_asid(CPULoongArchState *env, target_ulong val)
 {
     int64_t old_v = env->CSR_ASID;
@@ -117,12 +164,33 @@ target_ulong helper_csrwr_asid(CPULoongArchState *env, target_ulong val)
     return old_v;
 }
 
+target_ulong helper_gcsrwr_asid(CPULoongArchState *env, target_ulong val)
+{
+    int64_t old_v = env->GCSR_ASID;
+
+    env->GCSR_ASID = deposit64(env->GCSR_ASID, 0, 10, val);
+    if (old_v != env->GCSR_ASID) {
+        tlb_flush(env_cpu(env));
+    }
+    return old_v;
+}
+
 target_ulong helper_csrwr_tcfg(CPULoongArchState *env, target_ulong val)
 {
     LoongArchCPU *cpu = env_archcpu(env);
     int64_t old_v = env->CSR_TCFG;
 
-    cpu_loongarch_store_constant_timer_config(cpu, val);
+    cpu_loongarch_store_constant_timer_config(cpu, val, false);
+
+    return old_v;
+}
+
+target_ulong helper_gcsrwr_tcfg(CPULoongArchState *env, target_ulong val)
+{
+    LoongArchCPU *cpu = env_archcpu(env);
+    int64_t old_v = env->GCSR_TCFG;
+
+    cpu_loongarch_store_constant_timer_config(cpu, val, true);
 
     return old_v;
 }
@@ -140,6 +208,61 @@ target_ulong helper_csrwr_ticlr(CPULoongArchState *env, target_ulong val)
     return old_v;
 }
 
+target_ulong helper_gcsrwr_ticlr(CPULoongArchState *env, target_ulong val)
+{
+    LoongArchCPU *cpu = env_archcpu(env);
+    int64_t old_v = 0;
+
+    if (val & 0x1) {
+        bql_lock();
+        loongarch_cpu_set_irq_guest(cpu, IRQ_TIMER, 0);
+        bql_unlock();
+    }
+    return old_v;
+}
+
+target_ulong helper_csrwr_gstat(CPULoongArchState *env, target_ulong val)
+{
+    int64_t old_v = env->CSR_GSTAT;
+    uint8_t old_gid = FIELD_EX64(env->CSR_GSTAT, CSR_GSTAT, GID);
+
+    env->CSR_GSTAT = FIELD_DP64(env->CSR_GSTAT, CSR_GSTAT, PVM,
+                                FIELD_EX64(val, CSR_GSTAT, PVM));
+    env->CSR_GSTAT = FIELD_DP64(env->CSR_GSTAT, CSR_GSTAT, GID,
+                                FIELD_EX64(val, CSR_GSTAT, GID));
+
+    if (old_gid != FIELD_EX64(env->CSR_GSTAT, CSR_GSTAT, GID)) {
+        tlb_flush(env_cpu(env));
+    }
+
+    return old_v;
+}
+
+target_ulong helper_csrwr_gtlbc(CPULoongArchState *env, target_ulong val)
+{
+    int64_t old_v = env->CSR_GTLBC;
+    uint8_t old_use_tgid = FIELD_EX64(old_v, CSR_GTLBC, USETGID);
+    uint8_t old_tgid = FIELD_EX64(old_v, CSR_GTLBC, TGID);
+
+    env->CSR_GTLBC = val;
+    if (old_use_tgid != FIELD_EX64(env->CSR_GTLBC, CSR_GTLBC, USETGID) ||
+        old_tgid != FIELD_EX64(env->CSR_GTLBC, CSR_GTLBC, TGID)) {
+        tlb_flush(env_cpu(env));
+    }
+
+    return old_v;
+}
+
+target_ulong helper_csrwr_gintc(CPULoongArchState *env, target_ulong val)
+{
+    int64_t old_v = env->CSR_GINTC;
+
+    env->CSR_GINTC = val & 0xffff00;
+    env->GCSR_ESTAT = deposit64(env->GCSR_ESTAT, 2, 8, extract64(val, 0, 8));
+
+    return old_v;
+}
+
 target_ulong helper_csrwr_pwcl(CPULoongArchState *env, target_ulong val)
 {
     uint8_t shift, ptbase;
diff --git a/target/loongarch/tcg/helper.h b/target/loongarch/tcg/helper.h
index 8a6c62f116..648328a7ef 100644
--- a/target/loongarch/tcg/helper.h
+++ b/target/loongarch/tcg/helper.h
@@ -14,7 +14,7 @@ DEF_HELPER_FLAGS_3(asrtgt_d, TCG_CALL_NO_WG, void, env, tl, tl)
 
 DEF_HELPER_FLAGS_3(crc32, TCG_CALL_NO_RWG_SE, tl, tl, tl, tl)
 DEF_HELPER_FLAGS_3(crc32c, TCG_CALL_NO_RWG_SE, tl, tl, tl, tl)
-DEF_HELPER_FLAGS_2(cpucfg, TCG_CALL_NO_RWG_SE, tl, env, tl)
+DEF_HELPER_FLAGS_2(cpucfg, TCG_CALL_NO_WG_SE, tl, env, tl)
 
 /* Floating-point helper */
 DEF_HELPER_FLAGS_3(fadd_s, TCG_CALL_NO_WG, i64, env, i64, i64)
@@ -98,14 +98,23 @@ DEF_HELPER_1(rdtime_d, i64, env)
 #ifndef CONFIG_USER_ONLY
 /* CSRs helper */
 DEF_HELPER_1(csrrd_pgd, i64, env)
+DEF_HELPER_1(gcsrrd_pgd, i64, env)
 DEF_HELPER_1(csrrd_cpuid, i64, env)
 DEF_HELPER_1(csrrd_tval, i64, env)
+DEF_HELPER_1(gcsrrd_tval, i64, env)
 DEF_HELPER_1(csrrd_msgir, i64, env)
 DEF_HELPER_2(csrwr_stlbps, i64, env, tl)
 DEF_HELPER_2(csrwr_estat, i64, env, tl)
+DEF_HELPER_2(gcsrwr_estat, i64, env, tl)
 DEF_HELPER_2(csrwr_asid, i64, env, tl)
+DEF_HELPER_2(gcsrwr_asid, i64, env, tl)
 DEF_HELPER_2(csrwr_tcfg, i64, env, tl)
+DEF_HELPER_2(gcsrwr_tcfg, i64, env, tl)
 DEF_HELPER_2(csrwr_ticlr, i64, env, tl)
+DEF_HELPER_2(gcsrwr_ticlr, i64, env, tl)
+DEF_HELPER_2(csrwr_gstat, i64, env, tl)
+DEF_HELPER_2(csrwr_gtlbc, i64, env, tl)
+DEF_HELPER_2(csrwr_gintc, i64, env, tl)
 DEF_HELPER_2(csrwr_pwcl, i64, env, tl)
 DEF_HELPER_2(csrwr_pwch, i64, env, tl)
 DEF_HELPER_2(iocsrrd_b, i64, env, tl)
@@ -124,16 +133,25 @@ DEF_HELPER_1(tlbsrch, void, env)
 DEF_HELPER_1(tlbrd, void, env)
 DEF_HELPER_1(tlbclr, void, env)
 DEF_HELPER_1(tlbflush, void, env)
-DEF_HELPER_1(invtlb_all, void, env)
-DEF_HELPER_2(invtlb_all_g, void, env, i32)
-DEF_HELPER_2(invtlb_all_asid, void, env, tl)
-DEF_HELPER_3(invtlb_page_asid, void, env, tl, tl)
-DEF_HELPER_3(invtlb_page_asid_or_g, void, env, tl, tl)
+DEF_HELPER_4(invtlb_all, void, env, tl, i32, i32)
+DEF_HELPER_4(invtlb_all_g, void, env, tl, i32, i32)
+DEF_HELPER_3(invtlb_all_asid, void, env, tl, i32)
+DEF_HELPER_4(invtlb_page_asid, void, env, tl, tl, i32)
+DEF_HELPER_4(invtlb_page_asid_or_g, void, env, tl, tl, i32)
+
+DEF_HELPER_1(gtlbwr, void, env)
+DEF_HELPER_1(gtlbfill, void, env)
+DEF_HELPER_1(gtlbsrch, void, env)
+DEF_HELPER_1(gtlbrd, void, env)
+DEF_HELPER_1(gtlbclr, void, env)
+DEF_HELPER_1(gtlbflush, void, env)
 
 DEF_HELPER_4(lddir, tl, env, tl, i32, i32)
 DEF_HELPER_4(ldpte, void, env, tl, tl, i32)
 DEF_HELPER_1(ertn, void, env)
 DEF_HELPER_1(idle, void, env)
+DEF_HELPER_2(hvcl, void, env, i32)
+DEF_HELPER_1(gspr, void, env)
 #endif
 
 /* LoongArch LSX  */
diff --git a/target/loongarch/tcg/insn_trans/trans_privileged.c.inc b/target/loongarch/tcg/insn_trans/trans_privileged.c.inc
index 2094d182ac..19b02e6a78 100644
--- a/target/loongarch/tcg/insn_trans/trans_privileged.c.inc
+++ b/target/loongarch/tcg/insn_trans/trans_privileged.c.inc
@@ -39,6 +39,16 @@ GEN_FALSE_TRANS(lddir)
 GEN_FALSE_TRANS(ertn)
 GEN_FALSE_TRANS(dbcl)
 GEN_FALSE_TRANS(idle)
+GEN_FALSE_TRANS(gcsrrd)
+GEN_FALSE_TRANS(gcsrwr)
+GEN_FALSE_TRANS(gcsrxchg)
+GEN_FALSE_TRANS(gtlbclr)
+GEN_FALSE_TRANS(gtlbflush)
+GEN_FALSE_TRANS(gtlbsrch)
+GEN_FALSE_TRANS(gtlbrd)
+GEN_FALSE_TRANS(gtlbwr)
+GEN_FALSE_TRANS(gtlbfill)
+GEN_FALSE_TRANS(hvcl)
 
 #else
 
@@ -69,8 +79,25 @@ static bool set_csr_trans_func(unsigned int csr_num, GenCSRRead readfn,
     return true;
 }
 
+static bool set_gcsr_trans_func(unsigned int csr_num, GenCSRRead readfn,
+                                GenCSRWrite writefn)
+{
+    CSRInfo *csr;
+
+    csr = get_gcsr(csr_num);
+    if (!csr) {
+        return false;
+    }
+
+    csr->readfn = (GenCSRFunc)readfn;
+    csr->writefn = (GenCSRFunc)writefn;
+    return true;
+}
+
 #define SET_CSR_FUNC(NAME, read, write)                 \
         set_csr_trans_func(LOONGARCH_CSR_##NAME, read, write)
+#define SET_GCSR_FUNC(NAME, read, write)                \
+        set_gcsr_trans_func(LOONGARCH_CSR_##NAME, read, write)
 
 void loongarch_csr_translate_init(void)
 {
@@ -85,14 +112,28 @@ void loongarch_csr_translate_init(void)
     SET_CSR_FUNC(TVAL,  gen_helper_csrrd_tval, NULL);
     SET_CSR_FUNC(TICLR, NULL, gen_helper_csrwr_ticlr);
     SET_CSR_FUNC(MSGIR, gen_helper_csrrd_msgir, NULL);
+    SET_CSR_FUNC(GSTAT, NULL, gen_helper_csrwr_gstat);
+    SET_CSR_FUNC(GTLBC, NULL, gen_helper_csrwr_gtlbc);
+    SET_CSR_FUNC(GINTC, NULL, gen_helper_csrwr_gintc);
+
+    SET_GCSR_FUNC(ESTAT, NULL, gen_helper_gcsrwr_estat);
+    SET_GCSR_FUNC(ASID, NULL, gen_helper_gcsrwr_asid);
+    SET_GCSR_FUNC(PGD, gen_helper_gcsrrd_pgd, NULL);
+    SET_GCSR_FUNC(TCFG, NULL, gen_helper_gcsrwr_tcfg);
+    SET_GCSR_FUNC(TVAL, gen_helper_gcsrrd_tval, NULL);
+    SET_GCSR_FUNC(TICLR, NULL, gen_helper_gcsrwr_ticlr);
 }
 #undef SET_CSR_FUNC
+#undef SET_GCSR_FUNC
 
 static bool check_csr_flags(DisasContext *ctx, const CSRInfo *csr, bool write)
 {
     if ((csr->flags & CSRFL_READONLY) && write) {
         return false;
     }
+    if ((csr->flags & CSRFL_GUEST_READONLY) && ctx->guest_mode && write) {
+        return false;
+    }
     if ((csr->flags & CSRFL_IO) && translator_io_start(&ctx->base)) {
         ctx->base.is_jmp = DISAS_EXIT_UPDATE;
     } else if ((csr->flags & CSRFL_EXITTB) && write) {
@@ -110,12 +151,17 @@ static bool trans_csrrd(DisasContext *ctx, arg_csrrd *a)
     if (check_plv(ctx)) {
         return false;
     }
-    csr = get_csr(a->csr);
+    csr = ctx->guest_mode ? get_gcsr(a->csr) : get_csr(a->csr);
     if (csr == NULL) {
         /* CSR is undefined: read as 0. */
         dest = tcg_constant_tl(0);
     } else {
         check_csr_flags(ctx, csr, false);
+        if (ctx->guest_mode && (csr->flags & CSRFL_GSPR)) {
+            gen_helper_gspr(tcg_env);
+            gen_set_gpr(a->rd, tcg_constant_tl(0), EXT_NONE);
+            return true;
+        }
         dest = gpr_dst(ctx, a->rd, EXT_NONE);
         readfn = (GenCSRRead)csr->readfn;
         if (readfn) {
@@ -137,12 +183,17 @@ static bool trans_csrwr(DisasContext *ctx, arg_csrwr *a)
     if (check_plv(ctx)) {
         return false;
     }
-    csr = get_csr(a->csr);
+    csr = ctx->guest_mode ? get_gcsr(a->csr) : get_csr(a->csr);
     if (csr == NULL) {
         /* CSR is undefined: write ignored, read old_value as 0. */
         gen_set_gpr(a->rd, tcg_constant_tl(0), EXT_NONE);
         return true;
     }
+    if (ctx->guest_mode && (csr->flags & CSRFL_GSPR)) {
+        gen_helper_gspr(tcg_env);
+        gen_set_gpr(a->rd, tcg_constant_tl(0), EXT_NONE);
+        return true;
+    }
     if (!check_csr_flags(ctx, csr, true)) {
         /* CSR is readonly: trap. */
         return false;
@@ -170,28 +221,35 @@ static bool trans_csrxchg(DisasContext *ctx, arg_csrxchg *a)
     if (check_plv(ctx)) {
         return false;
     }
-    csr = get_csr(a->csr);
+    csr = ctx->guest_mode ? get_gcsr(a->csr) : get_csr(a->csr);
     if (csr == NULL) {
         /* CSR is undefined: write ignored, read old_value as 0. */
         gen_set_gpr(a->rd, tcg_constant_tl(0), EXT_NONE);
         return true;
     }
+    if (ctx->guest_mode && (csr->flags & CSRFL_GSPR)) {
+        gen_helper_gspr(tcg_env);
+        gen_set_gpr(a->rd, tcg_constant_tl(0), EXT_NONE);
+        return true;
+    }
 
     if (!check_csr_flags(ctx, csr, true)) {
         /* CSR is readonly: trap. */
         return false;
     }
 
-    /* So far only readonly csrs have readfn. */
-    assert(csr->readfn == NULL);
-
     src1 = gpr_src(ctx, a->rd, EXT_NONE);
     mask = gpr_src(ctx, a->rj, EXT_NONE);
     oldv = tcg_temp_new();
     newv = tcg_temp_new();
     temp = tcg_temp_new();
 
-    tcg_gen_ld_tl(oldv, tcg_env, csr->offset);
+    if (csr->readfn) {
+        GenCSRRead readfn = (GenCSRRead)csr->readfn;
+        readfn(oldv, tcg_env);
+    } else {
+        tcg_gen_ld_tl(oldv, tcg_env, csr->offset);
+    }
     tcg_gen_and_tl(newv, src1, mask);
     tcg_gen_andc_tl(temp, oldv, mask);
     tcg_gen_or_tl(newv, newv, temp);
@@ -212,6 +270,11 @@ static bool gen_iocsrrd(DisasContext *ctx, arg_rr *a,
     TCGv dest = gpr_dst(ctx, a->rd, EXT_NONE);
     TCGv src1 = gpr_src(ctx, a->rj, EXT_NONE);
 
+    if (ctx->guest_mode) {
+        gen_helper_gspr(tcg_env);
+        return true;
+    }
+
     if (check_plv(ctx)) {
         return false;
     }
@@ -225,6 +288,11 @@ static bool gen_iocsrwr(DisasContext *ctx, arg_rr *a,
     TCGv val = gpr_src(ctx, a->rd, EXT_NONE);
     TCGv addr = gpr_src(ctx, a->rj, EXT_NONE);
 
+    if (ctx->guest_mode) {
+        gen_helper_gspr(tcg_env);
+        return true;
+    }
+
     if (check_plv(ctx)) {
         return false;
     }
@@ -243,7 +311,7 @@ TRANS64(iocsrwr_d, IOCSR, gen_iocsrwr, gen_helper_iocsrwr_d)
 
 static void check_mmu_idx(DisasContext *ctx)
 {
-    if (ctx->mem_idx != MMU_DA_IDX) {
+    if (ctx->mem_idx != MMU_DA_IDX && ctx->mem_idx != MMU_GUEST_DA_IDX) {
         tcg_gen_movi_tl(cpu_pc, ctx->base.pc_next + 4);
         ctx->base.is_jmp = DISAS_EXIT;
     }
@@ -316,25 +384,61 @@ static bool trans_invtlb(DisasContext *ctx, arg_invtlb *a)
         return false;
     }
 
+    if (!avail_LVZ(ctx) && a->imm > 0x6) {
+        return false;
+    }
+
+    //TODO: futher refinement of op 0x9 and 0x10-0x16
     switch (a->imm) {
-    case 0:
-    case 1:
-        gen_helper_invtlb_all(tcg_env);
+    case 0x0:
+    case 0x1:
+        gen_helper_invtlb_all(tcg_env, rj, tcg_constant_i32(a->imm),
+                              tcg_constant_i32(0));
+        break;
+    case 0x2:
+        gen_helper_invtlb_all_g(tcg_env, rj, tcg_constant_i32(1),
+                                tcg_constant_i32(0));
+        break;
+    case 0x3:
+        gen_helper_invtlb_all_g(tcg_env, rj, tcg_constant_i32(0),
+                                tcg_constant_i32(0));
+        break;
+    case 0x4:
+        gen_helper_invtlb_all_asid(tcg_env, rj, tcg_constant_i32(0));
+        break;
+    case 0x5:
+        gen_helper_invtlb_page_asid(tcg_env, rj, rk, tcg_constant_i32(0));
         break;
-    case 2:
-        gen_helper_invtlb_all_g(tcg_env, tcg_constant_i32(1));
+    case 0x6:
+        gen_helper_invtlb_page_asid_or_g(tcg_env, rj, rk, tcg_constant_i32(0));
         break;
-    case 3:
-        gen_helper_invtlb_all_g(tcg_env, tcg_constant_i32(0));
+    case 0x9:
+    case 0x10:
+    case 0x11:
+    case 0x12:
+    case 0x13:
+    case 0x14:
+    case 0x15:
+    case 0x16:
+        gen_helper_invtlb_all(tcg_env, rj, tcg_constant_i32(0),
+                              tcg_constant_i32(0));
         break;
-    case 4:
-        gen_helper_invtlb_all_asid(tcg_env, rj);
+    case 0xa:
+        gen_helper_invtlb_all_g(tcg_env, rj, tcg_constant_i32(1),
+                                tcg_constant_i32(1));
         break;
-    case 5:
-        gen_helper_invtlb_page_asid(tcg_env, rj, rk);
+    case 0xb:
+        gen_helper_invtlb_all_g(tcg_env, rj, tcg_constant_i32(0),
+                                tcg_constant_i32(1));
         break;
-    case 6:
-        gen_helper_invtlb_page_asid_or_g(tcg_env, rj, rk);
+    case 0xc:
+        gen_helper_invtlb_all_asid(tcg_env, rj, tcg_constant_i32(1));
+        break;
+    case 0xd:
+        gen_helper_invtlb_page_asid(tcg_env, rj, rk, tcg_constant_i32(1));
+        break;
+    case 0xe:
+        gen_helper_invtlb_page_asid_or_g(tcg_env, rj, rk, tcg_constant_i32(1));
         break;
     default:
         return false;
@@ -400,6 +504,10 @@ static bool trans_dbcl(DisasContext *ctx, arg_dbcl *a)
     if (check_plv(ctx)) {
         return false;
     }
+    if (ctx->guest_mode) {
+        gen_helper_gspr(tcg_env);
+        return true;
+    }
     generate_exception(ctx, EXCCODE_DBP);
     return true;
 }
@@ -410,9 +518,212 @@ static bool trans_idle(DisasContext *ctx, arg_idle *a)
         return false;
     }
 
+    if (ctx->guest_mode) {
+        gen_helper_gspr(tcg_env);
+        return true;
+    }
+
     tcg_gen_movi_tl(cpu_pc, ctx->base.pc_next + 4);
     gen_helper_idle(tcg_env);
     ctx->base.is_jmp = DISAS_NORETURN;
     return true;
 }
+
+static bool trans_gcsrrd(DisasContext *ctx, arg_gcsrrd *a)
+{
+    TCGv dest;
+    const CSRInfo *csr;
+    GenCSRRead readfn;
+
+    if (check_plv(ctx)) {
+        return false;
+    }
+    if (!avail_LVZ(ctx) || ctx->guest_mode) {
+        return false;
+    }
+
+    csr = get_gcsr(a->csr);
+    if (csr == NULL) {
+        dest = tcg_constant_tl(0);
+    } else {
+        dest = gpr_dst(ctx, a->rd, EXT_NONE);
+        if (csr->flags & CSRFL_GSPR) {
+            tcg_gen_movi_tl(dest, 0);
+        } else {
+            readfn = (GenCSRRead)csr->readfn;
+            if (readfn) {
+                readfn(dest, tcg_env);
+            } else {
+                tcg_gen_ld_tl(dest, tcg_env, csr->offset);
+            }
+        }
+    }
+    gen_set_gpr(a->rd, dest, EXT_NONE);
+    return true;
+}
+
+static bool trans_gcsrwr(DisasContext *ctx, arg_gcsrwr *a)
+{
+    TCGv dest, src1;
+    const CSRInfo *csr;
+    GenCSRWrite writefn;
+
+    if (check_plv(ctx)) {
+        return false;
+    }
+    if (!avail_LVZ(ctx) || ctx->guest_mode) {
+        return false;
+    }
+
+    csr = get_gcsr(a->csr);
+    if (csr == NULL) {
+        gen_set_gpr(a->rd, tcg_constant_tl(0), EXT_NONE);
+        return true;
+    }
+    if (!check_csr_flags(ctx, csr, true)) {
+        return false;
+    }
+    if (csr->flags & CSRFL_GSPR) {
+        gen_set_gpr(a->rd, tcg_constant_tl(0), EXT_NONE);
+        return true;
+    }
+
+    src1 = gpr_src(ctx, a->rd, EXT_NONE);
+    writefn = (GenCSRWrite)csr->writefn;
+    if (writefn) {
+        dest = gpr_dst(ctx, a->rd, EXT_NONE);
+        writefn(dest, tcg_env, src1);
+    } else {
+        dest = tcg_temp_new();
+        tcg_gen_ld_tl(dest, tcg_env, csr->offset);
+        tcg_gen_st_tl(src1, tcg_env, csr->offset);
+    }
+    gen_set_gpr(a->rd, dest, EXT_NONE);
+    return true;
+}
+
+static bool trans_gcsrxchg(DisasContext *ctx, arg_gcsrxchg *a)
+{
+    TCGv src1, mask, oldv, newv, temp;
+    const CSRInfo *csr;
+    GenCSRRead readfn;
+    GenCSRWrite writefn;
+
+    if (check_plv(ctx)) {
+        return false;
+    }
+    if (!avail_LVZ(ctx) || ctx->guest_mode) {
+        return false;
+    }
+
+    csr = get_gcsr(a->csr);
+    if (csr == NULL) {
+        gen_set_gpr(a->rd, tcg_constant_tl(0), EXT_NONE);
+        return true;
+    }
+    if (!check_csr_flags(ctx, csr, true)) {
+        return false;
+    }
+    if (csr->flags & CSRFL_GSPR) {
+        gen_set_gpr(a->rd, tcg_constant_tl(0), EXT_NONE);
+        return true;
+    }
+
+    src1 = gpr_src(ctx, a->rd, EXT_NONE);
+    mask = gpr_src(ctx, a->rj, EXT_NONE);
+    oldv = tcg_temp_new();
+    newv = tcg_temp_new();
+    temp = tcg_temp_new();
+
+    readfn = (GenCSRRead)csr->readfn;
+    if (readfn) {
+        readfn(oldv, tcg_env);
+    } else {
+        tcg_gen_ld_tl(oldv, tcg_env, csr->offset);
+    }
+    tcg_gen_and_tl(newv, src1, mask);
+    tcg_gen_andc_tl(temp, oldv, mask);
+    tcg_gen_or_tl(newv, newv, temp);
+
+    writefn = (GenCSRWrite)csr->writefn;
+    if (writefn) {
+        writefn(oldv, tcg_env, newv);
+    } else {
+        tcg_gen_st_tl(newv, tcg_env, csr->offset);
+    }
+    gen_set_gpr(a->rd, oldv, EXT_NONE);
+    return true;
+}
+
+static bool trans_gtlbsrch(DisasContext *ctx, arg_gtlbsrch *a)
+{
+    if (check_plv(ctx) || !avail_LVZ(ctx) || ctx->guest_mode) {
+        return false;
+    }
+    gen_helper_gtlbsrch(tcg_env);
+    return true;
+}
+
+static bool trans_gtlbrd(DisasContext *ctx, arg_gtlbrd *a)
+{
+    if (check_plv(ctx) || !avail_LVZ(ctx) || ctx->guest_mode) {
+        return false;
+    }
+    gen_helper_gtlbrd(tcg_env);
+    return true;
+}
+
+static bool trans_gtlbwr(DisasContext *ctx, arg_gtlbwr *a)
+{
+    if (check_plv(ctx) || !avail_LVZ(ctx) || ctx->guest_mode) {
+        return false;
+    }
+    gen_helper_gtlbwr(tcg_env);
+    check_mmu_idx(ctx);
+    return true;
+}
+
+static bool trans_gtlbfill(DisasContext *ctx, arg_gtlbfill *a)
+{
+    if (check_plv(ctx) || !avail_LVZ(ctx) || ctx->guest_mode) {
+        return false;
+    }
+    gen_helper_gtlbfill(tcg_env);
+    check_mmu_idx(ctx);
+    return true;
+}
+
+static bool trans_gtlbclr(DisasContext *ctx, arg_gtlbclr *a)
+{
+    if (check_plv(ctx) || !avail_LVZ(ctx) || ctx->guest_mode) {
+        return false;
+    }
+    gen_helper_gtlbclr(tcg_env);
+    check_mmu_idx(ctx);
+    return true;
+}
+
+static bool trans_gtlbflush(DisasContext *ctx, arg_gtlbflush *a)
+{
+    if (check_plv(ctx) || !avail_LVZ(ctx) || ctx->guest_mode) {
+        return false;
+    }
+    gen_helper_gtlbflush(tcg_env);
+    check_mmu_idx(ctx);
+    return true;
+}
+
+static bool trans_hvcl(DisasContext *ctx, arg_hvcl *a)
+{
+    if (!avail_LVZ(ctx)) {
+        return false;
+    }
+    if (!ctx->guest_mode) {
+        return false;
+    }
+    tcg_gen_movi_tl(cpu_pc, ctx->base.pc_next);
+    gen_helper_hvcl(tcg_env, tcg_constant_i32(a->imm));
+    ctx->base.is_jmp = DISAS_NORETURN;
+    return true;
+}
 #endif
diff --git a/target/loongarch/tcg/op_helper.c b/target/loongarch/tcg/op_helper.c
index 16ac0d43bc..28043697d8 100644
--- a/target/loongarch/tcg/op_helper.c
+++ b/target/loongarch/tcg/op_helper.c
@@ -15,6 +15,7 @@
 #include "qemu/crc32c.h"
 #include <zlib.h> /* for crc32 */
 #include "cpu-csr.h"
+#include "qemu/main-loop.h"
 
 /* Exceptions helpers */
 void helper_raise_exception(CPULoongArchState *env, uint32_t exception)
@@ -81,6 +82,10 @@ target_ulong helper_crc32c(target_ulong val, target_ulong m, uint64_t sz)
 
 target_ulong helper_cpucfg(CPULoongArchState *env, target_ulong rj)
 {
+    if (env->guest) {
+        trigger_vm_exit(env);
+        do_raise_exception(env, EXCCODE_GSPR, GETPC());
+    }
     return rj >= ARRAY_SIZE(env->cpucfg) ? 0 : env->cpucfg[rj];
 }
 
@@ -92,8 +97,9 @@ uint64_t helper_rdtime_d(CPULoongArchState *env)
     uint64_t plv;
     LoongArchCPU *cpu = env_archcpu(env);
 
-    plv = FIELD_EX64(env->CSR_CRMD, CSR_CRMD, PLV);
-    if (extract64(env->CSR_MISC, R_CSR_MISC_DRDTL_SHIFT + plv, 1)) {
+    plv = FIELD_EX64(GET_CSR_IF(env->guest, CRMD), CSR_CRMD, PLV);
+    if (extract64(GET_CSR_IF(env->guest, MISC), R_CSR_MISC_DRDTL_SHIFT + plv,
+                  1)) {
         do_raise_exception(env, EXCCODE_IPE, GETPC());
     }
 
@@ -105,28 +111,50 @@ uint64_t helper_rdtime_d(CPULoongArchState *env)
 void helper_ertn(CPULoongArchState *env)
 {
     uint64_t csr_pplv, csr_pie;
-    if (FIELD_EX64(env->CSR_TLBRERA, CSR_TLBRERA, ISTLBR)) {
-        csr_pplv = FIELD_EX64(env->CSR_TLBRPRMD, CSR_TLBRPRMD, PPLV);
-        csr_pie = FIELD_EX64(env->CSR_TLBRPRMD, CSR_TLBRPRMD, PIE);
-
-        env->CSR_TLBRERA = FIELD_DP64(env->CSR_TLBRERA, CSR_TLBRERA, ISTLBR, 0);
-        env->CSR_CRMD = FIELD_DP64(env->CSR_CRMD, CSR_CRMD, DA, 0);
-        env->CSR_CRMD = FIELD_DP64(env->CSR_CRMD, CSR_CRMD, PG, 1);
-        set_pc(env, env->CSR_TLBRERA);
-        qemu_log_mask(CPU_LOG_INT, "%s: TLBRERA " TARGET_FMT_lx "\n",
-                      __func__, env->CSR_TLBRERA);
+
+    if (FIELD_EX64(GET_CSR_IF(env->guest, TLBRERA), CSR_TLBRERA, ISTLBR)) {
+        csr_pplv =
+            FIELD_EX64(GET_CSR_IF(env->guest, TLBRPRMD), CSR_TLBRPRMD, PPLV);
+        csr_pie =
+            FIELD_EX64(GET_CSR_IF(env->guest, TLBRPRMD), CSR_TLBRPRMD, PIE);
+
+        SET_CSR_IF(env->guest, TLBRERA,
+                   FIELD_DP64(GET_CSR_IF(env->guest, TLBRERA), CSR_TLBRERA,
+                              ISTLBR, 0));
+        SET_CSR_IF(env->guest, CRMD,
+                   FIELD_DP64(GET_CSR_IF(env->guest, CRMD), CSR_CRMD, DA, 0));
+        SET_CSR_IF(env->guest, CRMD,
+                   FIELD_DP64(GET_CSR_IF(env->guest, CRMD), CSR_CRMD, PG, 1));
+        set_pc(env, GET_CSR_IF(env->guest, TLBRERA));
+        qemu_log_mask(CPU_LOG_INT, "%s: TLBRERA " TARGET_FMT_lx "\n", __func__,
+                      GET_CSR_IF(env->guest, TLBRERA));
     } else {
-        csr_pplv = FIELD_EX64(env->CSR_PRMD, CSR_PRMD, PPLV);
-        csr_pie = FIELD_EX64(env->CSR_PRMD, CSR_PRMD, PIE);
+        csr_pplv = FIELD_EX64(GET_CSR_IF(env->guest, PRMD), CSR_PRMD, PPLV);
+        csr_pie = FIELD_EX64(GET_CSR_IF(env->guest, PRMD), CSR_PRMD, PIE);
 
-        set_pc(env, env->CSR_ERA);
-        qemu_log_mask(CPU_LOG_INT, "%s: ERA " TARGET_FMT_lx "\n",
-                      __func__, env->CSR_ERA);
+        set_pc(env, GET_CSR_IF(env->guest, ERA));
+        qemu_log_mask(CPU_LOG_INT, "%s: ERA " TARGET_FMT_lx "\n", __func__,
+                      GET_CSR_IF(env->guest, ERA));
     }
-    env->CSR_CRMD = FIELD_DP64(env->CSR_CRMD, CSR_CRMD, PLV, csr_pplv);
-    env->CSR_CRMD = FIELD_DP64(env->CSR_CRMD, CSR_CRMD, IE, csr_pie);
+    SET_CSR_IF(
+        env->guest, CRMD,
+        FIELD_DP64(GET_CSR_IF(env->guest, CRMD), CSR_CRMD, PLV, csr_pplv));
+    SET_CSR_IF(env->guest, CRMD,
+               FIELD_DP64(GET_CSR_IF(env->guest, CRMD), CSR_CRMD, IE, csr_pie));
 
     env->lladdr = 1;
+    if (will_return_to_guest(env)) {
+        env->guest = true;
+        env->CSR_GSTAT = FIELD_DP64(env->CSR_GSTAT, CSR_GSTAT, VM, 1);
+        cpu_loongarch_set_guest_timer(env_archcpu(env), true);
+        bql_lock();
+        if (loongarch_guest_has_interrupt(env)) {
+            cpu_interrupt(env_cpu(env), CPU_INTERRUPT_GUEST);
+        } else {
+            cpu_reset_interrupt(env_cpu(env), CPU_INTERRUPT_GUEST);
+        }
+        bql_unlock();
+    }
 }
 
 void helper_idle(CPULoongArchState *env)
@@ -136,4 +164,21 @@ void helper_idle(CPULoongArchState *env)
     cs->halted = 1;
     do_raise_exception(env, EXCP_HLT, 0);
 }
+
+void helper_hvcl(CPULoongArchState *env, uint32_t code)
+{
+    if (!env->guest) {
+        do_raise_exception(env, EXCCODE_INE, GETPC());
+        return;
+    }
+
+    trigger_vm_exit(env);
+    do_raise_exception(env, EXCCODE_HVC, GETPC());
+}
+
+void helper_gspr(CPULoongArchState *env)
+{
+    trigger_vm_exit(env);
+    do_raise_exception(env, EXCCODE_GSPR, GETPC());
+}
 #endif
diff --git a/target/loongarch/tcg/tcg_cpu.c b/target/loongarch/tcg/tcg_cpu.c
index 31d3db6e8e..7bc4911524 100644
--- a/target/loongarch/tcg/tcg_cpu.c
+++ b/target/loongarch/tcg/tcg_cpu.c
@@ -43,6 +43,10 @@ static const struct TypeExcp excp_names[] = {
     {EXCCODE_BCE, "Bound Check Exception"},
     {EXCCODE_SXD, "128 bit vector instructions Disable exception"},
     {EXCCODE_ASXD, "256 bit vector instructions Disable exception"},
+    {EXCCODE_GSPR, "Guest Sensitive and Privileged Resources"},
+    {EXCCODE_HVC, "Hypervisor call"},
+    {EXCCODE_GCSC, "Guest CSR visited by Software"},
+    {EXCCODE_GCHC, "Guest CSR visited by Hardware"},
     {EXCP_HLT, "EXCP_HLT"},
 };
 
@@ -79,9 +83,12 @@ static void loongarch_cpu_do_interrupt(CPUState *cs)
     CPULoongArchState *env = cpu_env(cs);
     bool update_badinstr = 1;
     int cause = -1;
-    bool tlbfill = FIELD_EX64(env->CSR_TLBRERA, CSR_TLBRERA, ISTLBR);
-    uint32_t vec_size = FIELD_EX64(env->CSR_ECFG, CSR_ECFG, VS);
+    bool real_guest = !env->vm_exit && env->guest;
+    bool tlbfill =
+        FIELD_EX64(GET_CSR_IF(real_guest, TLBRERA), CSR_TLBRERA, ISTLBR);
+    uint32_t vec_size = FIELD_EX64(GET_CSR_IF(real_guest, ECFG), CSR_ECFG, VS);
     uint64_t last_pc = env->pc;
+    uint32_t badinstr;
 
     if (cs->exception_index != EXCCODE_INT) {
         qemu_log_mask(CPU_LOG_INT,
@@ -115,7 +122,11 @@ static void loongarch_cpu_do_interrupt(CPUState *cs)
         update_badinstr = 0;
         break;
     case EXCCODE_BCE:
-        env->CSR_BADV = env->pc;
+    case EXCCODE_GSPR:
+    case EXCCODE_GCHC:
+    case EXCCODE_GCSC:
+    case EXCCODE_HVC:
+        SET_CSR_IF(real_guest, BADV, env->pc);
         QEMU_FALLTHROUGH;
     case EXCCODE_SYS:
     case EXCCODE_BRK:
@@ -142,35 +153,51 @@ static void loongarch_cpu_do_interrupt(CPUState *cs)
     if (update_badinstr) {
         MemOpIdx oi = make_memop_idx(MO_LEUL, cpu_mmu_index(cs, true));
 
-        env->CSR_BADI = cpu_ldl_code_mmu(env, env->pc, oi, 0);
+        badinstr = cpu_ldl_code_mmu(env, env->pc, oi, 0);
+        SET_CSR_IF(real_guest, BADI, badinstr);
     }
 
     /* Save PLV and IE */
     if (tlbfill) {
-        env->CSR_TLBRPRMD = FIELD_DP64(env->CSR_TLBRPRMD, CSR_TLBRPRMD, PPLV,
-                                       FIELD_EX64(env->CSR_CRMD,
-                                       CSR_CRMD, PLV));
-        env->CSR_TLBRPRMD = FIELD_DP64(env->CSR_TLBRPRMD, CSR_TLBRPRMD, PIE,
-                                       FIELD_EX64(env->CSR_CRMD, CSR_CRMD, IE));
+        SET_CSR_IF(real_guest, TLBRPRMD,
+                   FIELD_DP64(GET_CSR_IF(real_guest, TLBRPRMD), CSR_TLBRPRMD,
+                              PPLV,
+                              FIELD_EX64(GET_CSR_IF(real_guest, CRMD), CSR_CRMD,
+                                         PLV)));
+        SET_CSR_IF(
+            real_guest, TLBRPRMD,
+            FIELD_DP64(GET_CSR_IF(real_guest, TLBRPRMD), CSR_TLBRPRMD, PIE,
+                       FIELD_EX64(GET_CSR_IF(real_guest, CRMD), CSR_CRMD, IE)));
         /* set the DA mode */
-        env->CSR_CRMD = FIELD_DP64(env->CSR_CRMD, CSR_CRMD, DA, 1);
-        env->CSR_CRMD = FIELD_DP64(env->CSR_CRMD, CSR_CRMD, PG, 0);
-        env->CSR_TLBRERA = FIELD_DP64(env->CSR_TLBRERA, CSR_TLBRERA,
-                                      PC, (env->pc >> 2));
+        SET_CSR_IF(real_guest, CRMD,
+                   FIELD_DP64(GET_CSR_IF(real_guest, CRMD), CSR_CRMD, DA, 1));
+        SET_CSR_IF(real_guest, CRMD,
+                   FIELD_DP64(GET_CSR_IF(real_guest, CRMD), CSR_CRMD, PG, 0));
+        SET_CSR_IF(real_guest, TLBRERA,
+                   FIELD_DP64(GET_CSR_IF(real_guest, TLBRERA), CSR_TLBRERA, PC,
+                              (env->pc >> 2)));
     } else {
-        env->CSR_ESTAT = FIELD_DP64(env->CSR_ESTAT, CSR_ESTAT, ECODE,
-                                    EXCODE_MCODE(cause));
-        env->CSR_ESTAT = FIELD_DP64(env->CSR_ESTAT, CSR_ESTAT, ESUBCODE,
-                                    EXCODE_SUBCODE(cause));
-        env->CSR_PRMD = FIELD_DP64(env->CSR_PRMD, CSR_PRMD, PPLV,
-                                   FIELD_EX64(env->CSR_CRMD, CSR_CRMD, PLV));
-        env->CSR_PRMD = FIELD_DP64(env->CSR_PRMD, CSR_PRMD, PIE,
-                                   FIELD_EX64(env->CSR_CRMD, CSR_CRMD, IE));
-        env->CSR_ERA = env->pc;
+        SET_CSR_IF(real_guest, ESTAT,
+                   FIELD_DP64(GET_CSR_IF(real_guest, ESTAT), CSR_ESTAT, ECODE,
+                              EXCODE_MCODE(cause)));
+        SET_CSR_IF(real_guest, ESTAT,
+                   FIELD_DP64(GET_CSR_IF(real_guest, ESTAT), CSR_ESTAT,
+                              ESUBCODE, EXCODE_SUBCODE(cause)));
+        SET_CSR_IF(real_guest, PRMD,
+                   FIELD_DP64(GET_CSR_IF(real_guest, PRMD), CSR_PRMD, PPLV,
+                              FIELD_EX64(GET_CSR_IF(real_guest, CRMD), CSR_CRMD,
+                                         PLV)));
+        SET_CSR_IF(
+            real_guest, PRMD,
+            FIELD_DP64(GET_CSR_IF(real_guest, PRMD), CSR_PRMD, PIE,
+                       FIELD_EX64(GET_CSR_IF(real_guest, CRMD), CSR_CRMD, IE)));
+        SET_CSR_IF(real_guest, ERA, env->pc);
     }
 
-    env->CSR_CRMD = FIELD_DP64(env->CSR_CRMD, CSR_CRMD, PLV, 0);
-    env->CSR_CRMD = FIELD_DP64(env->CSR_CRMD, CSR_CRMD, IE, 0);
+    SET_CSR_IF(real_guest, CRMD,
+               FIELD_DP64(GET_CSR_IF(real_guest, CRMD), CSR_CRMD, PLV, 0));
+    SET_CSR_IF(real_guest, CRMD,
+               FIELD_DP64(GET_CSR_IF(real_guest, CRMD), CSR_CRMD, IE, 0));
 
     if (vec_size) {
         vec_size = (1 << vec_size) * 4;
@@ -179,43 +206,54 @@ static void loongarch_cpu_do_interrupt(CPUState *cs)
     if  (cs->exception_index == EXCCODE_INT) {
         /* Interrupt */
         uint32_t vector = 0;
-        uint32_t pending = FIELD_EX64(env->CSR_ESTAT, CSR_ESTAT, IS);
-        pending &= FIELD_EX64(env->CSR_ECFG, CSR_ECFG, LIE);
+        uint32_t pending =
+            FIELD_EX64(GET_CSR_IF(real_guest, ESTAT), CSR_ESTAT, IS);
+        pending &= FIELD_EX64(GET_CSR_IF(real_guest, ECFG), CSR_ECFG, LIE);
 
         /* Find the highest-priority interrupt. */
         vector = 31 - clz32(pending);
-        set_pc(env, env->CSR_EENTRY + \
-               (EXCCODE_EXTERNAL_INT + vector) * vec_size);
-        qemu_log_mask(CPU_LOG_INT,
-                      "%s: PC " TARGET_FMT_lx " ERA " TARGET_FMT_lx
-                      " cause %d\n" "    A " TARGET_FMT_lx " D "
-                      TARGET_FMT_lx " vector = %d ExC " TARGET_FMT_lx "ExS"
-                      TARGET_FMT_lx "\n",
-                      __func__, env->pc, env->CSR_ERA,
-                      cause, env->CSR_BADV, env->CSR_DERA, vector,
-                      env->CSR_ECFG, env->CSR_ESTAT);
+        set_pc(env, GET_CSR_IF(real_guest, EENTRY) +
+                        (EXCCODE_EXTERNAL_INT + vector) * vec_size);
+        qemu_log_mask(
+            CPU_LOG_INT,
+            "%s: PC " TARGET_FMT_lx " ERA " TARGET_FMT_lx " cause %d\n"
+            "    A " TARGET_FMT_lx " D " TARGET_FMT_lx
+            " vector = %d ExC " TARGET_FMT_lx "ExS" TARGET_FMT_lx "\n",
+            __func__, env->pc, GET_CSR_IF(real_guest, ERA), cause,
+            GET_CSR_IF(real_guest, BADV), GET_CSR_IF(real_guest, DERA), vector,
+            GET_CSR_IF(real_guest, ECFG), GET_CSR_IF(real_guest, ESTAT));
         qemu_plugin_vcpu_interrupt_cb(cs, last_pc);
     } else {
         if (tlbfill) {
-            set_pc(env, env->CSR_TLBRENTRY);
+            set_pc(env, GET_CSR_IF(real_guest, TLBRENTRY));
         } else {
-            set_pc(env, env->CSR_EENTRY + EXCODE_MCODE(cause) * vec_size);
+            set_pc(env, GET_CSR_IF(real_guest, EENTRY) +
+                            EXCODE_MCODE(cause) * vec_size);
         }
-        qemu_log_mask(CPU_LOG_INT,
-                      "%s: PC " TARGET_FMT_lx " ERA " TARGET_FMT_lx
-                      " cause %d%s\n, ESTAT " TARGET_FMT_lx
-                      " EXCFG " TARGET_FMT_lx " BADVA " TARGET_FMT_lx
-                      "BADI " TARGET_FMT_lx " SYS_NUM " TARGET_FMT_lu
-                      " cpu %d asid " TARGET_FMT_lx "\n", __func__, env->pc,
-                      tlbfill ? env->CSR_TLBRERA : env->CSR_ERA,
-                      cause, tlbfill ? "(refill)" : "", env->CSR_ESTAT,
-                      env->CSR_ECFG,
-                      tlbfill ? env->CSR_TLBRBADV : env->CSR_BADV,
-                      env->CSR_BADI, env->gpr[11], cs->cpu_index,
-                      env->CSR_ASID);
+        qemu_log_mask(
+            CPU_LOG_INT,
+            "%s: PC " TARGET_FMT_lx " ERA " TARGET_FMT_lx
+            " cause %d%s\n, ESTAT " TARGET_FMT_lx " EXCFG " TARGET_FMT_lx
+            " BADVA " TARGET_FMT_lx "BADI " TARGET_FMT_lx
+            " SYS_NUM " TARGET_FMT_lu " cpu %d asid " TARGET_FMT_lx "\n",
+            __func__, env->pc,
+            tlbfill ? GET_CSR_IF(real_guest, TLBRERA) :
+                      GET_CSR_IF(real_guest, ERA),
+            cause, tlbfill ? "(refill)" : "", GET_CSR_IF(real_guest, ESTAT),
+            GET_CSR_IF(real_guest, ECFG),
+            tlbfill ? GET_CSR_IF(real_guest, TLBRBADV) :
+                      GET_CSR_IF(real_guest, BADV),
+            GET_CSR_IF(real_guest, BADI), env->gpr[11], cs->cpu_index,
+            GET_CSR_IF(real_guest, ASID));
         qemu_plugin_vcpu_exception_cb(cs, last_pc);
     }
     cs->exception_index = -1;
+    if (env->vm_exit) {
+        env->CSR_GSTAT = FIELD_DP64(env->CSR_GSTAT, CSR_GSTAT, VM, 0);
+        env->guest = false;
+        cpu_reset_interrupt(cs, CPU_INTERRUPT_GUEST);
+    }
+    env->vm_exit = false;
 }
 
 static void loongarch_cpu_do_transaction_failed(CPUState *cs, hwaddr physaddr,
@@ -247,16 +285,25 @@ static inline bool cpu_loongarch_hw_interrupts_enabled(CPULoongArchState *env)
 
 static bool loongarch_cpu_exec_interrupt(CPUState *cs, int interrupt_request)
 {
-    if (interrupt_request & CPU_INTERRUPT_HARD) {
-        CPULoongArchState *env = cpu_env(cs);
+    CPULoongArchState *env = cpu_env(cs);
+    bool has_interrupt = false;
 
+    if (interrupt_request & CPU_INTERRUPT_HARD) {
         if (cpu_loongarch_hw_interrupts_enabled(env) &&
             cpu_loongarch_hw_interrupts_pending(env)) {
-            /* Raise it */
-            cs->exception_index = EXCCODE_INT;
-            loongarch_cpu_do_interrupt(cs);
-            return true;
+            if (env->guest) {
+                trigger_vm_exit(env);
+            }
+            has_interrupt = true;
         }
+    } else if (interrupt_request & CPU_INTERRUPT_GUEST) {
+        has_interrupt = loongarch_guest_has_interrupt(env);
+    }
+
+    if (has_interrupt) {
+        cs->exception_index = EXCCODE_INT;
+        loongarch_cpu_do_interrupt(cs);
+        return true;
     }
     return false;
 }
@@ -273,10 +320,18 @@ static TCGTBCPUState loongarch_get_tb_cpu_state(CPUState *cs)
     CPULoongArchState *env = cpu_env(cs);
     uint32_t flags;
 
-    flags = env->CSR_CRMD & (R_CSR_CRMD_PLV_MASK | R_CSR_CRMD_PG_MASK);
-    flags |= FIELD_EX64(env->CSR_EUEN, CSR_EUEN, FPE) * HW_FLAGS_EUEN_FPE;
-    flags |= FIELD_EX64(env->CSR_EUEN, CSR_EUEN, SXE) * HW_FLAGS_EUEN_SXE;
-    flags |= FIELD_EX64(env->CSR_EUEN, CSR_EUEN, ASXE) * HW_FLAGS_EUEN_ASXE;
+    if (env->guest) {
+        flags = env->GCSR_CRMD & (R_CSR_CRMD_PLV_MASK | R_CSR_CRMD_PG_MASK);
+        flags |= HW_FLAGS_GUEST_MODE;
+    } else {
+        flags = env->CSR_CRMD & (R_CSR_CRMD_PLV_MASK | R_CSR_CRMD_PG_MASK);
+    }
+    flags |= FIELD_EX64(GET_CSR_IF(env->guest, EUEN), CSR_EUEN, FPE) *
+             HW_FLAGS_EUEN_FPE;
+    flags |= FIELD_EX64(GET_CSR_IF(env->guest, EUEN), CSR_EUEN, SXE) *
+             HW_FLAGS_EUEN_SXE;
+    flags |= FIELD_EX64(GET_CSR_IF(env->guest, EUEN), CSR_EUEN, ASXE) *
+             HW_FLAGS_EUEN_ASXE;
     flags |= is_va32(env) * HW_FLAGS_VA32;
 
     return (TCGTBCPUState){ .pc = env->pc, .flags = flags };
@@ -300,6 +355,13 @@ static int loongarch_cpu_mmu_index(CPUState *cs, bool ifetch)
 {
     CPULoongArchState *env = cpu_env(cs);
 
+    if (env->guest) {
+        if (FIELD_EX64(env->GCSR_CRMD, CSR_CRMD, PG)) {
+            return MMU_GUEST_IDX + FIELD_EX64(env->GCSR_CRMD, CSR_CRMD, PLV);
+        }
+        return MMU_GUEST_DA_IDX;
+    }
+
     if (FIELD_EX64(env->CSR_CRMD, CSR_CRMD, PG)) {
         return FIELD_EX64(env->CSR_CRMD, CSR_CRMD, PLV);
     }
diff --git a/target/loongarch/tcg/tcg_loongarch.h b/target/loongarch/tcg/tcg_loongarch.h
index 7fb627f2d6..ceba1e4062 100644
--- a/target/loongarch/tcg/tcg_loongarch.h
+++ b/target/loongarch/tcg/tcg_loongarch.h
@@ -16,8 +16,8 @@ bool loongarch_cpu_tlb_fill(CPUState *cs, vaddr address, int size,
                             MMUAccessType access_type, int mmu_idx,
                             bool probe, uintptr_t retaddr);
 
-TLBRet loongarch_get_addr_from_tlb(CPULoongArchState *env,
-                                   MMUContext *context,
-                                   MMUAccessType access_type, int mmu_idx);
+TLBRet loongarch_get_addr_from_tlb(CPULoongArchState *env, MMUContext *context,
+                                   MMUAccessType access_type, int mmu_idx,
+                                   bool guest);
 
 #endif  /* TARGET_LOONGARCH_TCG_LOONGARCH_H */
diff --git a/target/loongarch/tcg/tlb_helper.c b/target/loongarch/tcg/tlb_helper.c
index 892e0eb473..fec0aeeb57 100644
--- a/target/loongarch/tcg/tlb_helper.c
+++ b/target/loongarch/tcg/tlb_helper.c
@@ -34,6 +34,13 @@ static bool tlb_match_asid(bool global, int asid, int tlb_asid)
     return !global && tlb_asid == asid;
 }
 
+static inline bool tlb_entry_matches_gid(LoongArchTLB *tlb, uint8_t gid)
+{
+    uint8_t entry_gid = FIELD_EX64(tlb->tlb_misc, TLB_MISC, GID);
+
+    return entry_gid == gid;
+}
+
 bool check_ps(CPULoongArchState *env, uint8_t tlb_ps)
 {
     if (tlb_ps >= 64) {
@@ -46,14 +53,22 @@ static void raise_mmu_exception(CPULoongArchState *env, vaddr address,
                                 MMUAccessType access_type, TLBRet tlb_error)
 {
     CPUState *cs = env_cpu(env);
+    bool real_guest;
+
+    if (env->guest && tlb_error > TLBRET_HOST_MATCH) {
+        trigger_vm_exit(env);
+    }
+    real_guest = !env->vm_exit && env->guest;
 
     switch (tlb_error) {
     default:
     case TLBRET_BADADDR:
+    case TLBRET_HOST_BADADDR:
         cs->exception_index = access_type == MMU_INST_FETCH
                               ? EXCCODE_ADEF : EXCCODE_ADEM;
         break;
     case TLBRET_NOMATCH:
+    case TLBRET_HOST_NOMATCH:
         /* No TLB match for a mapped address */
         if (access_type == MMU_DATA_LOAD) {
             cs->exception_index = EXCCODE_PIL;
@@ -62,9 +77,12 @@ static void raise_mmu_exception(CPULoongArchState *env, vaddr address,
         } else if (access_type == MMU_INST_FETCH) {
             cs->exception_index = EXCCODE_PIF;
         }
-        env->CSR_TLBRERA = FIELD_DP64(env->CSR_TLBRERA, CSR_TLBRERA, ISTLBR, 1);
+        SET_CSR_IF(real_guest, TLBRERA,
+                   FIELD_DP64(GET_CSR_IF(real_guest, TLBRERA), CSR_TLBRERA,
+                              ISTLBR, 1));
         break;
     case TLBRET_INVALID:
+    case TLBRET_HOST_INVALID:
         /* TLB match with no valid bit */
         if (access_type == MMU_DATA_LOAD) {
             cs->exception_index = EXCCODE_PIL;
@@ -75,46 +93,58 @@ static void raise_mmu_exception(CPULoongArchState *env, vaddr address,
         }
         break;
     case TLBRET_DIRTY:
+    case TLBRET_HOST_DIRTY:
         /* TLB match but 'D' bit is cleared */
         cs->exception_index = EXCCODE_PME;
         break;
     case TLBRET_XI:
+    case TLBRET_HOST_XI:
         /* Execute-Inhibit Exception */
         cs->exception_index = EXCCODE_PNX;
         break;
     case TLBRET_RI:
+    case TLBRET_HOST_RI:
         /* Read-Inhibit Exception */
         cs->exception_index = EXCCODE_PNR;
         break;
     case TLBRET_PE:
+    case TLBRET_HOST_PE:
         /* Privileged Exception */
         cs->exception_index = EXCCODE_PPI;
         break;
     }
 
-    if (tlb_error == TLBRET_NOMATCH) {
-        env->CSR_TLBRBADV = address;
+    if (tlb_error == TLBRET_NOMATCH || tlb_error == TLBRET_HOST_NOMATCH) {
+        SET_CSR_IF(real_guest, TLBRBADV, address);
         if (is_la64(env)) {
-            env->CSR_TLBREHI = FIELD_DP64(env->CSR_TLBREHI, CSR_TLBREHI_64,
-                                        VPPN, extract64(address, 13, 35));
+            SET_CSR_IF(real_guest, TLBREHI,
+                       FIELD_DP64(GET_CSR_IF(real_guest, TLBREHI),
+                                  CSR_TLBREHI_64, VPPN,
+                                  extract64(address, 13, 35)));
         } else {
-            env->CSR_TLBREHI = FIELD_DP64(env->CSR_TLBREHI, CSR_TLBREHI_32,
-                                        VPPN, extract64(address, 13, 19));
+            SET_CSR_IF(real_guest, TLBREHI,
+                       FIELD_DP64(GET_CSR_IF(real_guest, TLBREHI),
+                                  CSR_TLBREHI_32, VPPN,
+                                  extract64(address, 13, 19)));
         }
     } else {
         if (!FIELD_EX64(env->CSR_DBG, CSR_DBG, DST)) {
-            env->CSR_BADV = address;
+            SET_CSR_IF(real_guest, BADV, address);
         }
-        env->CSR_TLBEHI = address & (TARGET_PAGE_MASK << 1);
-   }
+        SET_CSR_IF(real_guest, TLBEHI, address & (TARGET_PAGE_MASK << 1));
+    }
 }
 
-static void invalidate_tlb_entry(CPULoongArchState *env, int index)
+static void invalidate_tlb_entry(CPULoongArchState *env, int index, bool guest)
 {
     target_ulong addr, mask, pagesize;
     uint8_t tlb_ps;
-    LoongArchTLB *tlb = &env->tlb[index];
-    int idxmap = BIT(MMU_KERNEL_IDX) | BIT(MMU_USER_IDX);
+    LoongArchTLB *tlb = guest ? &env->gtlb[index] : &env->tlb[index];
+    int idxmap =
+        guest ? (BIT(MMU_GUEST_IDX) | BIT(MMU_GUEST_IDX + 1) |
+                 BIT(MMU_GUEST_IDX + 2) | BIT(MMU_GUEST_IDX + 3) |
+                 BIT(MMU_GUEST_DA_IDX)) :
+                (BIT(MMU_KERNEL_IDX) | BIT(MMU_USER_IDX) | BIT(MMU_DA_IDX));
     uint64_t tlb_vppn = FIELD_EX64(tlb->tlb_misc, TLB_MISC, VPPN);
     bool tlb_v;
 
@@ -124,27 +154,27 @@ static void invalidate_tlb_entry(CPULoongArchState *env, int index)
     addr = (tlb_vppn << R_TLB_MISC_VPPN_SHIFT) & ~mask;
     addr = sextract64(addr, 0, TARGET_VIRT_ADDR_SPACE_BITS);
 
-    tlb_v = pte_present(env, tlb->tlb_entry0);
+    tlb_v = pte_present(env, tlb->tlb_entry0, guest);
     if (tlb_v) {
         tlb_flush_range_by_mmuidx(env_cpu(env), addr, pagesize,
                                   idxmap, TARGET_LONG_BITS);
     }
 
-    tlb_v = pte_present(env, tlb->tlb_entry1);
+    tlb_v = pte_present(env, tlb->tlb_entry1, guest);
     if (tlb_v) {
         tlb_flush_range_by_mmuidx(env_cpu(env), addr + pagesize, pagesize,
                                   idxmap, TARGET_LONG_BITS);
     }
 }
 
-static void invalidate_tlb(CPULoongArchState *env, int index)
+static void invalidate_tlb(CPULoongArchState *env, int index, bool guest)
 {
     LoongArchTLB *tlb;
     uint16_t csr_asid, tlb_asid, tlb_g;
     uint8_t tlb_e;
 
-    csr_asid = FIELD_EX64(env->CSR_ASID, CSR_ASID, ASID);
-    tlb = &env->tlb[index];
+    csr_asid = FIELD_EX64(GET_CSR_IF(guest, ASID), CSR_ASID, ASID);
+    tlb = guest ? &env->gtlb[index] : &env->tlb[index];
     tlb_e = FIELD_EX64(tlb->tlb_misc, TLB_MISC, E);
     if (!tlb_e) {
         return;
@@ -157,33 +187,38 @@ static void invalidate_tlb(CPULoongArchState *env, int index)
     if (tlb_g == 0 && tlb_asid != csr_asid) {
         return;
     }
-    invalidate_tlb_entry(env, index);
+    invalidate_tlb_entry(env, index, guest);
 }
 
 /* Prepare tlb entry information in software PTW mode */
-static void sptw_prepare_context(CPULoongArchState *env, MMUContext *context)
+static void sptw_prepare_context(CPULoongArchState *env, MMUContext *context,
+                                 bool guest)
 {
     uint64_t lo0, lo1, csr_vppn;
     uint8_t csr_ps;
 
-    if (FIELD_EX64(env->CSR_TLBRERA, CSR_TLBRERA, ISTLBR)) {
-        csr_ps = FIELD_EX64(env->CSR_TLBREHI, CSR_TLBREHI, PS);
+    if (FIELD_EX64(GET_CSR_IF(guest, TLBRERA), CSR_TLBRERA, ISTLBR)) {
+        csr_ps = FIELD_EX64(GET_CSR_IF(guest, TLBREHI), CSR_TLBREHI, PS);
         if (is_la64(env)) {
-            csr_vppn = FIELD_EX64(env->CSR_TLBREHI, CSR_TLBREHI_64, VPPN);
+            csr_vppn =
+                FIELD_EX64(GET_CSR_IF(guest, TLBREHI), CSR_TLBREHI_64, VPPN);
         } else {
-            csr_vppn = FIELD_EX64(env->CSR_TLBREHI, CSR_TLBREHI_32, VPPN);
+            csr_vppn =
+                FIELD_EX64(GET_CSR_IF(guest, TLBREHI), CSR_TLBREHI_32, VPPN);
         }
-        lo0 = env->CSR_TLBRELO0;
-        lo1 = env->CSR_TLBRELO1;
+        lo0 = GET_CSR_IF(guest, TLBRELO0);
+        lo1 = GET_CSR_IF(guest, TLBRELO1);
     } else {
-        csr_ps = FIELD_EX64(env->CSR_TLBIDX, CSR_TLBIDX, PS);
+        csr_ps = FIELD_EX64(GET_CSR_IF(guest, TLBIDX), CSR_TLBIDX, PS);
         if (is_la64(env)) {
-            csr_vppn = FIELD_EX64(env->CSR_TLBEHI, CSR_TLBEHI_64, VPPN);
+            csr_vppn =
+                FIELD_EX64(GET_CSR_IF(guest, TLBEHI), CSR_TLBEHI_64, VPPN);
         } else {
-            csr_vppn = FIELD_EX64(env->CSR_TLBEHI, CSR_TLBEHI_32, VPPN);
+            csr_vppn =
+                FIELD_EX64(GET_CSR_IF(guest, TLBEHI), CSR_TLBEHI_32, VPPN);
         }
-        lo0 = env->CSR_TLBELO0;
-        lo1 = env->CSR_TLBELO1;
+        lo0 = GET_CSR_IF(guest, TLBELO0);
+        lo1 = GET_CSR_IF(guest, TLBELO1);
     }
 
     context->ps = csr_ps;
@@ -193,7 +228,7 @@ static void sptw_prepare_context(CPULoongArchState *env, MMUContext *context)
 }
 
 static void fill_tlb_entry(CPULoongArchState *env, LoongArchTLB *tlb,
-                           MMUContext *context)
+                           MMUContext *context, bool guest)
 {
     uint64_t lo0, lo1, csr_vppn;
     uint16_t csr_asid;
@@ -208,8 +243,9 @@ static void fill_tlb_entry(CPULoongArchState *env, LoongArchTLB *tlb,
     tlb->tlb_misc = FIELD_DP64(tlb->tlb_misc, TLB_MISC, PS, csr_ps);
     tlb->tlb_misc = FIELD_DP64(tlb->tlb_misc, TLB_MISC, VPPN, csr_vppn);
     tlb->tlb_misc = FIELD_DP64(tlb->tlb_misc, TLB_MISC, E, 1);
-    csr_asid = FIELD_EX64(env->CSR_ASID, CSR_ASID, ASID);
+    csr_asid = FIELD_EX64(GET_CSR_IF(guest, ASID), CSR_ASID, ASID);
     tlb->tlb_misc = FIELD_DP64(tlb->tlb_misc, TLB_MISC, ASID, csr_asid);
+    tlb->tlb_misc = FIELD_DP64(tlb->tlb_misc, TLB_MISC, GID, get_tgid(env));
 
     tlb->tlb_entry0 = lo0;
     tlb->tlb_entry1 = lo1;
@@ -233,7 +269,8 @@ static uint32_t get_random_tlb(uint32_t low, uint32_t high)
  */
 static LoongArchTLB *loongarch_tlb_search_cb(CPULoongArchState *env,
                                              vaddr vaddr, int csr_asid,
-                                             tlb_match func)
+                                             tlb_match func, bool guest,
+                                             uint8_t gid)
 {
     LoongArchTLB *tlb;
     uint16_t tlb_asid, stlb_idx;
@@ -242,14 +279,15 @@ static LoongArchTLB *loongarch_tlb_search_cb(CPULoongArchState *env,
     int i, compare_shift;
     uint64_t vpn, tlb_vppn;
 
-    stlb_ps = FIELD_EX64(env->CSR_STLBPS, CSR_STLBPS, PS);
+    stlb_ps = FIELD_EX64(GET_CSR_IF(guest, STLBPS), CSR_STLBPS, PS);
     vpn = (vaddr & TARGET_VIRT_MASK) >> (stlb_ps + 1);
     stlb_idx = vpn & 0xff; /* VA[25:15] <==> TLBIDX.index for 16KiB Page */
     compare_shift = stlb_ps + 1 - R_TLB_MISC_VPPN_SHIFT;
 
     /* Search STLB */
     for (i = 0; i < 8; ++i) {
-        tlb = &env->tlb[i * 256 + stlb_idx];
+        tlb = guest ? &env->gtlb[i * 256 + stlb_idx] :
+                      &env->tlb[i * 256 + stlb_idx];
         tlb_e = FIELD_EX64(tlb->tlb_misc, TLB_MISC, E);
         if (tlb_e) {
             tlb_vppn = FIELD_EX64(tlb->tlb_misc, TLB_MISC, VPPN);
@@ -257,6 +295,7 @@ static LoongArchTLB *loongarch_tlb_search_cb(CPULoongArchState *env,
             tlb_g = !!FIELD_EX64(tlb->tlb_entry0, TLBENTRY, G);
 
             if (func(tlb_g, csr_asid, tlb_asid) &&
+                tlb_entry_matches_gid(tlb, gid) &&
                 (vpn == (tlb_vppn >> compare_shift))) {
                 return tlb;
             }
@@ -265,7 +304,7 @@ static LoongArchTLB *loongarch_tlb_search_cb(CPULoongArchState *env,
 
     /* Search MTLB */
     for (i = LOONGARCH_STLB; i < LOONGARCH_TLB_MAX; ++i) {
-        tlb = &env->tlb[i];
+        tlb = guest ? &env->gtlb[i] : &env->tlb[i];
         tlb_e = FIELD_EX64(tlb->tlb_misc, TLB_MISC, E);
         if (tlb_e) {
             tlb_vppn = FIELD_EX64(tlb->tlb_misc, TLB_MISC, VPPN);
@@ -275,6 +314,7 @@ static LoongArchTLB *loongarch_tlb_search_cb(CPULoongArchState *env,
             compare_shift = tlb_ps + 1 - R_TLB_MISC_VPPN_SHIFT;
             vpn = (vaddr & TARGET_VIRT_MASK) >> (tlb_ps + 1);
             if (func(tlb_g, csr_asid, tlb_asid) &&
+                tlb_entry_matches_gid(tlb, gid) &&
                 (vpn == (tlb_vppn >> compare_shift))) {
                 return tlb;
             }
@@ -284,17 +324,17 @@ static LoongArchTLB *loongarch_tlb_search_cb(CPULoongArchState *env,
 }
 
 static bool loongarch_tlb_search(CPULoongArchState *env, vaddr vaddr,
-                                 int *index)
+                                 int *index, bool guest, uint8_t gid)
 {
     int csr_asid;
     tlb_match func;
     LoongArchTLB *tlb;
 
     func = tlb_match_any;
-    csr_asid = FIELD_EX64(env->CSR_ASID, CSR_ASID, ASID);
-    tlb = loongarch_tlb_search_cb(env, vaddr, csr_asid, func);
+    csr_asid = FIELD_EX64(GET_CSR_IF(guest, ASID), CSR_ASID, ASID);
+    tlb = loongarch_tlb_search_cb(env, vaddr, csr_asid, func, guest, gid);
     if (tlb) {
-        *index = tlb - env->tlb;
+        *index = guest ? (tlb - env->gtlb) : (tlb - env->tlb);
         return true;
     }
 
@@ -304,66 +344,112 @@ static bool loongarch_tlb_search(CPULoongArchState *env, vaddr vaddr,
 void helper_tlbsrch(CPULoongArchState *env)
 {
     int index, match;
+    vaddr search_ehi;
 
-    if (FIELD_EX64(env->CSR_TLBRERA, CSR_TLBRERA, ISTLBR)) {
-        match = loongarch_tlb_search(env, env->CSR_TLBREHI, &index);
+    if (FIELD_EX64(GET_CSR_IF(env->guest, TLBRERA), CSR_TLBRERA, ISTLBR)) {
+        search_ehi = GET_CSR_IF(env->guest, TLBREHI);
     } else {
-        match = loongarch_tlb_search(env, env->CSR_TLBEHI, &index);
+        search_ehi = GET_CSR_IF(env->guest, TLBEHI);
     }
 
+    match = loongarch_tlb_search(env, search_ehi, &index, env->guest,
+                                 get_tgid(env));
+
     if (match) {
-        env->CSR_TLBIDX = FIELD_DP64(env->CSR_TLBIDX, CSR_TLBIDX, INDEX, index);
-        env->CSR_TLBIDX = FIELD_DP64(env->CSR_TLBIDX, CSR_TLBIDX, NE, 0);
+        SET_CSR_IF(env->guest, TLBIDX,
+                   FIELD_DP64(GET_CSR_IF(env->guest, TLBIDX), CSR_TLBIDX, INDEX,
+                              index));
+        SET_CSR_IF(
+            env->guest, TLBIDX,
+            FIELD_DP64(GET_CSR_IF(env->guest, TLBIDX), CSR_TLBIDX, NE, 0));
         return;
     }
 
-    env->CSR_TLBIDX = FIELD_DP64(env->CSR_TLBIDX, CSR_TLBIDX, NE, 1);
+    SET_CSR_IF(env->guest, TLBIDX,
+               FIELD_DP64(GET_CSR_IF(env->guest, TLBIDX), CSR_TLBIDX, NE, 1));
 }
 
-void helper_tlbrd(CPULoongArchState *env)
+static void read_tlb(CPULoongArchState *env, bool guest)
 {
     LoongArchTLB *tlb;
     int index;
     uint8_t tlb_ps, tlb_e;
 
-    index = FIELD_EX64(env->CSR_TLBIDX, CSR_TLBIDX, INDEX);
-    tlb = &env->tlb[index];
+    index = FIELD_EX64(GET_CSR_IF(guest, TLBIDX), CSR_TLBIDX, INDEX);
+    tlb = guest ? &env->gtlb[index] : &env->tlb[index];
     tlb_ps = FIELD_EX64(tlb->tlb_misc, TLB_MISC, PS);
     tlb_e = FIELD_EX64(tlb->tlb_misc, TLB_MISC, E);
 
     if (!tlb_e) {
         /* Invalid TLB entry */
-        env->CSR_TLBIDX = FIELD_DP64(env->CSR_TLBIDX, CSR_TLBIDX, NE, 1);
-        env->CSR_ASID  = FIELD_DP64(env->CSR_ASID, CSR_ASID, ASID, 0);
-        env->CSR_TLBEHI = 0;
-        env->CSR_TLBELO0 = 0;
-        env->CSR_TLBELO1 = 0;
-        env->CSR_TLBIDX = FIELD_DP64(env->CSR_TLBIDX, CSR_TLBIDX, PS, 0);
+        SET_CSR_IF(guest, TLBIDX,
+                   FIELD_DP64(GET_CSR_IF(guest, TLBIDX), CSR_TLBIDX, NE, 1));
+        SET_CSR_IF(guest, ASID,
+                   FIELD_DP64(GET_CSR_IF(guest, ASID), CSR_ASID, ASID, 0));
+        SET_CSR_IF(guest, TLBEHI, 0);
+        SET_CSR_IF(guest, TLBELO0, 0);
+        SET_CSR_IF(guest, TLBELO1, 0);
+        SET_CSR_IF(guest, TLBIDX,
+                   FIELD_DP64(GET_CSR_IF(guest, TLBIDX), CSR_TLBIDX, PS, 0));
     } else {
         /* Valid TLB entry */
-        env->CSR_TLBIDX = FIELD_DP64(env->CSR_TLBIDX, CSR_TLBIDX, NE, 0);
-        env->CSR_TLBIDX = FIELD_DP64(env->CSR_TLBIDX, CSR_TLBIDX,
-                                     PS, (tlb_ps & 0x3f));
-        env->CSR_TLBEHI = FIELD_EX64(tlb->tlb_misc, TLB_MISC, VPPN) <<
-                                     R_TLB_MISC_VPPN_SHIFT;
-        env->CSR_TLBELO0 = tlb->tlb_entry0;
-        env->CSR_TLBELO1 = tlb->tlb_entry1;
+        SET_CSR_IF(guest, TLBIDX,
+                   FIELD_DP64(GET_CSR_IF(guest, TLBIDX), CSR_TLBIDX, NE, 0));
+        SET_CSR_IF(guest, TLBIDX,
+                   FIELD_DP64(GET_CSR_IF(guest, TLBIDX), CSR_TLBIDX, PS,
+                              tlb_ps & 0x3f));
+        SET_CSR_IF(guest, TLBEHI,
+                   FIELD_EX64(tlb->tlb_misc, TLB_MISC, VPPN)
+                       << R_TLB_MISC_VPPN_SHIFT);
+        SET_CSR_IF(guest, TLBELO0, tlb->tlb_entry0);
+        SET_CSR_IF(guest, TLBELO1, tlb->tlb_entry1);
+    }
+}
+
+void helper_tlbrd(CPULoongArchState *env)
+{
+    read_tlb(env, env->guest);
+}
+
+void helper_gtlbsrch(CPULoongArchState *env)
+{
+    int index, match;
+    vaddr search_ehi;
+
+    if (FIELD_EX64(env->GCSR_TLBRERA, CSR_TLBRERA, ISTLBR)) {
+        search_ehi = env->GCSR_TLBREHI;
+    } else {
+        search_ehi = env->GCSR_TLBEHI;
+    }
+
+    match = loongarch_tlb_search(env, search_ehi, &index, true, get_tgid(env));
+    if (match) {
+        env->GCSR_TLBIDX =
+            FIELD_DP64(env->GCSR_TLBIDX, CSR_TLBIDX, INDEX, index);
+        env->GCSR_TLBIDX = FIELD_DP64(env->GCSR_TLBIDX, CSR_TLBIDX, NE, 0);
+        return;
     }
+    env->GCSR_TLBIDX = FIELD_DP64(env->GCSR_TLBIDX, CSR_TLBIDX, NE, 1);
+}
+
+void helper_gtlbrd(CPULoongArchState *env)
+{
+    read_tlb(env, true);
 }
 
 static void update_tlb_index(CPULoongArchState *env, MMUContext *context,
-                             int index)
+                             int index, bool guest)
 {
     LoongArchTLB *old, new = {};
     bool skip_inv = false, tlb_v0, tlb_v1;
 
-    old = env->tlb + index;
-    fill_tlb_entry(env, &new, context);
+    old = guest ? env->gtlb + index : env->tlb + index;
+    fill_tlb_entry(env, &new, context, guest);
     /* Check whether ASID/VPPN is the same */
     if (old->tlb_misc == new.tlb_misc) {
         /* Check whether both even/odd pages is the same or invalid */
-        tlb_v0 = pte_present(env, old->tlb_entry0);
-        tlb_v1 = pte_present(env, old->tlb_entry1);
+        tlb_v0 = pte_present(env, old->tlb_entry0, guest);
+        tlb_v1 = pte_present(env, old->tlb_entry1, guest);
         if ((!tlb_v0 || new.tlb_entry0 == old->tlb_entry0) &&
             (!tlb_v1 || new.tlb_entry1 == old->tlb_entry1)) {
             skip_inv = true;
@@ -372,7 +458,7 @@ static void update_tlb_index(CPULoongArchState *env, MMUContext *context,
 
     /* flush tlb before updating the entry */
     if (!skip_inv) {
-        invalidate_tlb(env, index);
+        invalidate_tlb(env, index, guest);
     }
 
     *old = new;
@@ -380,20 +466,34 @@ static void update_tlb_index(CPULoongArchState *env, MMUContext *context,
 
 void helper_tlbwr(CPULoongArchState *env)
 {
-    int index = FIELD_EX64(env->CSR_TLBIDX, CSR_TLBIDX, INDEX);
+    int index = FIELD_EX64(GET_CSR_IF(env->guest, TLBIDX), CSR_TLBIDX, INDEX);
+    MMUContext context;
+
+    if (FIELD_EX64(GET_CSR_IF(env->guest, TLBIDX), CSR_TLBIDX, NE)) {
+        invalidate_tlb(env, index, env->guest);
+        return;
+    }
+
+    sptw_prepare_context(env, &context, env->guest);
+    update_tlb_index(env, &context, index, env->guest);
+}
+
+void helper_gtlbwr(CPULoongArchState *env)
+{
+    int index = FIELD_EX64(env->GCSR_TLBIDX, CSR_TLBIDX, INDEX);
     MMUContext context;
 
-    if (FIELD_EX64(env->CSR_TLBIDX, CSR_TLBIDX, NE)) {
-        invalidate_tlb(env, index);
+    if (FIELD_EX64(env->GCSR_TLBIDX, CSR_TLBIDX, NE)) {
+        invalidate_tlb(env, index, true);
         return;
     }
 
-    sptw_prepare_context(env, &context);
-    update_tlb_index(env, &context, index);
+    sptw_prepare_context(env, &context, true);
+    update_tlb_index(env, &context, index, true);
 }
 
 static int get_tlb_random_index(CPULoongArchState *env, vaddr addr,
-                                int pagesize)
+                                int pagesize, bool guest)
 {
     uint64_t address;
     int index, set, i, stlb_idx;
@@ -402,15 +502,16 @@ static int get_tlb_random_index(CPULoongArchState *env, vaddr addr,
     uint8_t tlb_e, tlb_g;
 
     /* Validity of stlb_ps is checked in helper_csrwr_stlbps() */
-    stlb_ps = FIELD_EX64(env->CSR_STLBPS, CSR_STLBPS, PS);
-    asid = FIELD_EX64(env->CSR_ASID, CSR_ASID, ASID);
+    stlb_ps = FIELD_EX64(GET_CSR_IF(guest, STLBPS), CSR_STLBPS, PS);
+    asid = FIELD_EX64(GET_CSR_IF(guest, ASID), CSR_ASID, ASID);
     if (pagesize == stlb_ps) {
         /* Only write into STLB bits [47:13] */
         address = addr & ~MAKE_64BIT_MASK(0, R_CSR_TLBEHI_64_VPPN_SHIFT);
         set = -1;
         stlb_idx = (address >> (stlb_ps + 1)) & 0xff; /* [0,255] */
         for (i = 0; i < 8; ++i) {
-            tlb = &env->tlb[i * 256 + stlb_idx];
+            tlb = guest ? &env->gtlb[i * 256 + stlb_idx] :
+                          &env->tlb[i * 256 + stlb_idx];
             tlb_e = FIELD_EX64(tlb->tlb_misc, TLB_MISC, E);
             if (!tlb_e) {
                 set = i;
@@ -419,7 +520,8 @@ static int get_tlb_random_index(CPULoongArchState *env, vaddr addr,
 
             tlb_asid = FIELD_EX64(tlb->tlb_misc, TLB_MISC, ASID);
             tlb_g = FIELD_EX64(tlb->tlb_entry0, TLBENTRY, G);
-            if (tlb_g == 0 && asid != tlb_asid) {
+            if (tlb_g == 0 && asid != tlb_asid &&
+                tlb_entry_matches_gid(tlb, get_tgid(env))) {
                 set = i;
             }
         }
@@ -433,7 +535,7 @@ static int get_tlb_random_index(CPULoongArchState *env, vaddr addr,
         /* Only write into MTLB */
         index = -1;
         for (i = LOONGARCH_STLB; i < LOONGARCH_TLB_MAX; i++) {
-            tlb = &env->tlb[i];
+            tlb = guest ? &env->gtlb[i] : &env->tlb[i];
             tlb_e = FIELD_EX64(tlb->tlb_misc, TLB_MISC, E);
 
             if (!tlb_e) {
@@ -443,7 +545,8 @@ static int get_tlb_random_index(CPULoongArchState *env, vaddr addr,
 
             tlb_asid = FIELD_EX64(tlb->tlb_misc, TLB_MISC, ASID);
             tlb_g = FIELD_EX64(tlb->tlb_entry0, TLBENTRY, G);
-            if (tlb_g == 0 && asid != tlb_asid) {
+            if (tlb_g == 0 && asid != tlb_asid &&
+                tlb_entry_matches_gid(tlb, get_tgid(env))) {
                 index = i;
             }
         }
@@ -462,48 +565,70 @@ void helper_tlbfill(CPULoongArchState *env)
     int index, pagesize;
     MMUContext context;
 
-    if (FIELD_EX64(env->CSR_TLBRERA, CSR_TLBRERA, ISTLBR)) {
-        entryhi = env->CSR_TLBREHI;
+    if (FIELD_EX64(GET_CSR_IF(env->guest, TLBRERA), CSR_TLBRERA, ISTLBR)) {
+        entryhi = GET_CSR_IF(env->guest, TLBREHI);
         /* Validity of pagesize is checked in helper_ldpte() */
-        pagesize = FIELD_EX64(env->CSR_TLBREHI, CSR_TLBREHI, PS);
+        pagesize = FIELD_EX64(GET_CSR_IF(env->guest, TLBREHI), CSR_TLBREHI, PS);
     } else {
-        entryhi = env->CSR_TLBEHI;
+        entryhi = GET_CSR_IF(env->guest, TLBEHI);
         /* Validity of pagesize is checked in helper_tlbrd() */
-        pagesize = FIELD_EX64(env->CSR_TLBIDX, CSR_TLBIDX, PS);
+        pagesize = FIELD_EX64(GET_CSR_IF(env->guest, TLBIDX), CSR_TLBIDX, PS);
     }
 
-    sptw_prepare_context(env, &context);
-    index = get_tlb_random_index(env, entryhi, pagesize);
-    invalidate_tlb(env, index);
-    fill_tlb_entry(env, env->tlb + index, &context);
+    sptw_prepare_context(env, &context, env->guest);
+    index = get_tlb_random_index(env, entryhi, pagesize, env->guest);
+    invalidate_tlb(env, index, env->guest);
+    fill_tlb_entry(env, env->guest ? env->gtlb + index : env->tlb + index,
+                   &context, env->guest);
 }
 
-void helper_tlbclr(CPULoongArchState *env)
+void helper_gtlbfill(CPULoongArchState *env)
+{
+    vaddr entryhi;
+    int index, pagesize;
+    MMUContext context;
+
+    if (FIELD_EX64(env->GCSR_TLBRERA, CSR_TLBRERA, ISTLBR)) {
+        entryhi = env->GCSR_TLBREHI;
+        pagesize = FIELD_EX64(env->GCSR_TLBREHI, CSR_TLBREHI, PS);
+    } else {
+        entryhi = env->GCSR_TLBEHI;
+        pagesize = FIELD_EX64(env->GCSR_TLBIDX, CSR_TLBIDX, PS);
+    }
+
+    sptw_prepare_context(env, &context, true);
+    index = get_tlb_random_index(env, entryhi, pagesize, true);
+    invalidate_tlb(env, index, true);
+    fill_tlb_entry(env, env->gtlb + index, &context, true);
+}
+
+static void clear_tlb_by_index(CPULoongArchState *env, bool guest)
 {
     LoongArchTLB *tlb;
     int i, index;
     uint16_t csr_asid, tlb_asid, tlb_g;
 
-    csr_asid = FIELD_EX64(env->CSR_ASID, CSR_ASID, ASID);
-    index = FIELD_EX64(env->CSR_TLBIDX, CSR_TLBIDX, INDEX);
+    csr_asid = FIELD_EX64(GET_CSR_IF(guest, ASID), CSR_ASID, ASID);
+    index = FIELD_EX64(GET_CSR_IF(guest, TLBIDX), CSR_TLBIDX, INDEX);
 
     if (index < LOONGARCH_STLB) {
-        /* STLB. One line per operation */
         for (i = 0; i < 8; i++) {
-            tlb = &env->tlb[i * 256 + (index % 256)];
+            tlb = guest ? &env->gtlb[i * 256 + (index % 256)] :
+                          &env->tlb[i * 256 + (index % 256)];
             tlb_asid = FIELD_EX64(tlb->tlb_misc, TLB_MISC, ASID);
             tlb_g = FIELD_EX64(tlb->tlb_entry0, TLBENTRY, G);
-            if (!tlb_g && tlb_asid == csr_asid) {
+            if (!tlb_g && tlb_asid == csr_asid &&
+                tlb_entry_matches_gid(tlb, get_tgid(env))) {
                 tlb->tlb_misc = FIELD_DP64(tlb->tlb_misc, TLB_MISC, E, 0);
             }
         }
     } else if (index < LOONGARCH_TLB_MAX) {
-        /* All MTLB entries */
         for (i = LOONGARCH_STLB; i < LOONGARCH_TLB_MAX; i++) {
-            tlb = &env->tlb[i];
+            tlb = guest ? &env->gtlb[i] : &env->tlb[i];
             tlb_asid = FIELD_EX64(tlb->tlb_misc, TLB_MISC, ASID);
             tlb_g = FIELD_EX64(tlb->tlb_entry0, TLBENTRY, G);
-            if (!tlb_g && tlb_asid == csr_asid) {
+            if (!tlb_g && tlb_asid == csr_asid &&
+                tlb_entry_matches_gid(tlb, get_tgid(env))) {
                 tlb->tlb_misc = FIELD_DP64(tlb->tlb_misc, TLB_MISC, E, 0);
             }
         }
@@ -512,62 +637,116 @@ void helper_tlbclr(CPULoongArchState *env)
     tlb_flush(env_cpu(env));
 }
 
-void helper_tlbflush(CPULoongArchState *env)
+void helper_tlbclr(CPULoongArchState *env)
+{
+    clear_tlb_by_index(env, env->guest);
+}
+
+void helper_gtlbclr(CPULoongArchState *env)
+{
+    clear_tlb_by_index(env, true);
+}
+
+static void flush_tlb_by_index(CPULoongArchState *env, bool guest)
 {
     int i, index;
 
-    index = FIELD_EX64(env->CSR_TLBIDX, CSR_TLBIDX, INDEX);
+    index = FIELD_EX64(GET_CSR_IF(guest, TLBIDX), CSR_TLBIDX, INDEX);
 
     if (index < LOONGARCH_STLB) {
-        /* STLB. One line per operation */
         for (i = 0; i < 8; i++) {
             int s_idx = i * 256 + (index % 256);
-            env->tlb[s_idx].tlb_misc = FIELD_DP64(env->tlb[s_idx].tlb_misc,
-                                                  TLB_MISC, E, 0);
+            LoongArchTLB *tlb = guest ? &env->gtlb[s_idx] : &env->tlb[s_idx];
+
+            tlb->tlb_misc = FIELD_DP64(tlb->tlb_misc, TLB_MISC, E, 0);
         }
     } else if (index < LOONGARCH_TLB_MAX) {
-        /* All MTLB entries */
         for (i = LOONGARCH_STLB; i < LOONGARCH_TLB_MAX; i++) {
-            env->tlb[i].tlb_misc = FIELD_DP64(env->tlb[i].tlb_misc,
-                                              TLB_MISC, E, 0);
+            LoongArchTLB *tlb = guest ? &env->gtlb[i] : &env->tlb[i];
+
+            tlb->tlb_misc = FIELD_DP64(tlb->tlb_misc, TLB_MISC, E, 0);
         }
     }
 
     tlb_flush(env_cpu(env));
 }
 
-void helper_invtlb_all(CPULoongArchState *env)
+void helper_tlbflush(CPULoongArchState *env)
+{
+    flush_tlb_by_index(env, env->guest);
+}
+
+void helper_gtlbflush(CPULoongArchState *env)
+{
+    flush_tlb_by_index(env, true);
+}
+
+void helper_invtlb_all(CPULoongArchState *env, target_ulong info, uint32_t op,
+                       uint32_t to_guest)
 {
+    uint16_t gid = to_guest ? (info & 0xff) : get_tgid(env);
+
+    if (to_guest && env->guest) {
+        do_raise_exception(env, EXCCODE_IPE, GETPC());
+    }
+
+    to_guest |= env->guest;
+
     for (int i = 0; i < LOONGARCH_TLB_MAX; i++) {
-        env->tlb[i].tlb_misc = FIELD_DP64(env->tlb[i].tlb_misc,
-                                          TLB_MISC, E, 0);
+        LoongArchTLB *tlb = &env->tlb[i];
+        LoongArchTLB *gtlb = &env->gtlb[i];
+
+        if (!to_guest && (op == 0 || tlb_entry_matches_gid(tlb, 0))) {
+            tlb->tlb_misc = FIELD_DP64(tlb->tlb_misc, TLB_MISC, E, 0);
+        }
+        if ((!to_guest && op == 0) ||
+            (to_guest && tlb_entry_matches_gid(gtlb, gid))) {
+            gtlb->tlb_misc = FIELD_DP64(gtlb->tlb_misc, TLB_MISC, E, 0);
+        }
     }
     tlb_flush(env_cpu(env));
 }
 
-void helper_invtlb_all_g(CPULoongArchState *env, uint32_t g)
+void helper_invtlb_all_g(CPULoongArchState *env, target_ulong info, uint32_t g,
+                         uint32_t to_guest)
 {
+    uint16_t gid = to_guest ? (info & 0xff) : get_tgid(env);
+
+    if (to_guest && env->guest) {
+        do_raise_exception(env, EXCCODE_IPE, GETPC());
+    }
+
+    to_guest |= env->guest;
+
     for (int i = 0; i < LOONGARCH_TLB_MAX; i++) {
-        LoongArchTLB *tlb = &env->tlb[i];
+        LoongArchTLB *tlb = to_guest ? &env->gtlb[i] : &env->tlb[i];
         uint8_t tlb_g = FIELD_EX64(tlb->tlb_entry0, TLBENTRY, G);
 
-        if (tlb_g == g) {
+        if (tlb_g == g && tlb_entry_matches_gid(tlb, gid)) {
             tlb->tlb_misc = FIELD_DP64(tlb->tlb_misc, TLB_MISC, E, 0);
         }
     }
     tlb_flush(env_cpu(env));
 }
 
-void helper_invtlb_all_asid(CPULoongArchState *env, target_ulong info)
+void helper_invtlb_all_asid(CPULoongArchState *env, target_ulong info,
+                            uint32_t to_guest)
 {
     uint16_t asid = info & R_CSR_ASID_ASID_MASK;
+    uint16_t gid = to_guest ? ((info >> 16) & 0xff) : get_tgid(env);
+
+    if (to_guest && env->guest) {
+        do_raise_exception(env, EXCCODE_IPE, GETPC());
+    }
+
+    to_guest |= env->guest;
 
     for (int i = 0; i < LOONGARCH_TLB_MAX; i++) {
-        LoongArchTLB *tlb = &env->tlb[i];
+        LoongArchTLB *tlb = to_guest ? &env->gtlb[i] : &env->tlb[i];
         uint8_t tlb_g = FIELD_EX64(tlb->tlb_entry0, TLBENTRY, G);
         uint16_t tlb_asid = FIELD_EX64(tlb->tlb_misc, TLB_MISC, ASID);
 
-        if (!tlb_g && (tlb_asid == asid)) {
+        if (!tlb_g && tlb_asid == asid && tlb_entry_matches_gid(tlb, gid)) {
             tlb->tlb_misc = FIELD_DP64(tlb->tlb_misc, TLB_MISC, E, 0);
         }
     }
@@ -575,66 +754,82 @@ void helper_invtlb_all_asid(CPULoongArchState *env, target_ulong info)
 }
 
 void helper_invtlb_page_asid(CPULoongArchState *env, target_ulong info,
-                             target_ulong addr)
+                             target_ulong addr, uint32_t to_guest)
 {
-    int asid = info & 0x3ff;
+    uint16_t asid = info & R_CSR_ASID_ASID_MASK;
+    uint16_t gid = to_guest ? ((info >> 16) & 0xff) : get_tgid(env);
     LoongArchTLB *tlb;
-    tlb_match func;
+    int index;
+
+    if (to_guest && env->guest) {
+        do_raise_exception(env, EXCCODE_IPE, GETPC());
+    }
+    to_guest |= env->guest;
 
-    func = tlb_match_asid;
-    tlb = loongarch_tlb_search_cb(env, addr, asid, func);
+    tlb =
+        loongarch_tlb_search_cb(env, addr, asid, tlb_match_asid, to_guest, gid);
     if (tlb) {
-        invalidate_tlb(env, tlb - env->tlb);
+        index = to_guest ? (tlb - env->gtlb) : (tlb - env->tlb);
+        invalidate_tlb(env, index, to_guest);
     }
 }
 
-void helper_invtlb_page_asid_or_g(CPULoongArchState *env,
-                                  target_ulong info, target_ulong addr)
+void helper_invtlb_page_asid_or_g(CPULoongArchState *env, target_ulong info,
+                                  target_ulong addr, uint32_t to_guest)
 {
-    int asid = info & 0x3ff;
+    uint16_t asid = info & R_CSR_ASID_ASID_MASK;
+    uint16_t gid = to_guest ? ((info >> 16) & 0xff) : get_tgid(env);
     LoongArchTLB *tlb;
-    tlb_match func;
+    int index;
 
-    func = tlb_match_any;
-    tlb = loongarch_tlb_search_cb(env, addr, asid, func);
+    if (to_guest && env->guest) {
+        do_raise_exception(env, EXCCODE_IPE, GETPC());
+    }
+
+    to_guest |= env->guest;
+
+    tlb =
+        loongarch_tlb_search_cb(env, addr, asid, tlb_match_any, to_guest, gid);
     if (tlb) {
-        invalidate_tlb(env, tlb - env->tlb);
+        index = to_guest ? (tlb - env->gtlb) : (tlb - env->tlb);
+        invalidate_tlb(env, index, to_guest);
     }
 }
 
-static void ptw_update_tlb(CPULoongArchState *env, MMUContext *context)
+static void ptw_update_tlb(CPULoongArchState *env, MMUContext *context,
+                           bool guest)
 {
     int index;
 
     index = context->tlb_index;
     if (index < 0) {
-        index = get_tlb_random_index(env, context->addr, context->ps);
+        index = get_tlb_random_index(env, context->addr, context->ps, guest);
     }
 
-    update_tlb_index(env, context, index);
+    update_tlb_index(env, context, index, guest);
 }
 
-bool loongarch_cpu_tlb_fill(CPUState *cs, vaddr address, int size,
-                            MMUAccessType access_type, int mmu_idx,
-                            bool probe, uintptr_t retaddr)
+TLBRet loongarch_map_host_address(CPULoongArchState *env, MMUContext *context,
+                                  MMUAccessType access_type, uintptr_t retaddr)
 {
-    CPULoongArchState *env = cpu_env(cs);
-    hwaddr physical;
-    int prot;
-    MMUContext context;
     TLBRet ret;
+    ret = loongarch_map_address(env, context, access_type, MMU_KERNEL_IDX,
+                                false, false, retaddr);
+    return TLBRET_HOST_MATCH + ret;
+}
 
-    /* Data access */
-    context.addr = address;
-    context.tlb_index = -1;
-    ret = get_physical_address(env, &context, access_type, mmu_idx, 0);
-    if (ret == TLBRET_MATCH && context.mmu_index != MMU_DA_IDX
-        && cpu_has_ptw(env)) {
+static void loongarch_try_ptw(CPULoongArchState *env, MMUContext *context,
+                              MMUAccessType access_type, int mmu_index,
+                              TLBRet *status, bool guest, uintptr_t retaddr)
+{
+    if ((*status == TLBRET_MATCH || *status == TLBRET_HOST_MATCH) &&
+        context->mmu_index != MMU_DA_IDX &&
+        context->mmu_index != MMU_GUEST_DA_IDX && cpu_has_ptw(env, guest)) {
         bool need_update = true;
 
-        if (access_type == MMU_DATA_STORE && pte_dirty(context.pte)) {
+        if (access_type == MMU_DATA_STORE && pte_dirty(context->pte)) {
             need_update = false;
-        } else if (access_type != MMU_DATA_STORE && pte_access(context.pte)) {
+        } else if (access_type != MMU_DATA_STORE && pte_access(context->pte)) {
             need_update = false;
 
             /*
@@ -649,31 +844,77 @@ bool loongarch_cpu_tlb_fill(CPUState *cs, vaddr address, int size,
 
         if (need_update) {
             /* Need update bit A/D in PTE entry, take PTW again */
-            ret = TLBRET_NOMATCH;
+            *status =
+                (env->guest && !guest) ? TLBRET_HOST_NOMATCH : TLBRET_NOMATCH;
         }
     }
 
-    if (ret != TLBRET_MATCH && cpu_has_ptw(env)) {
+    if (*status != TLBRET_MATCH && *status != TLBRET_HOST_MATCH &&
+        cpu_has_ptw(env, guest)) {
         /* Take HW PTW if TLB missed or bit P is zero */
-        if (ret == TLBRET_NOMATCH || ret == TLBRET_INVALID) {
-            ret = loongarch_ptw(env, &context, access_type, mmu_idx, 0);
-            if (ret == TLBRET_MATCH) {
-                ptw_update_tlb(env, &context);
+        if (*status == TLBRET_NOMATCH || *status == TLBRET_INVALID ||
+            *status == TLBRET_HOST_NOMATCH || *status == TLBRET_HOST_INVALID) {
+            *status =
+                ((env->guest && !guest) ? TLBRET_HOST_MATCH : TLBRET_MATCH) +
+                loongarch_ptw(env, context, access_type, mmu_index, 0, guest,
+                              retaddr);
+            if (*status == TLBRET_MATCH || *status == TLBRET_HOST_MATCH) {
+                ptw_update_tlb(env, context, guest);
             }
-        } else if (context.tlb_index >= 0) {
-            invalidate_tlb(env, context.tlb_index);
+        } else if (context->tlb_index >= 0) {
+            invalidate_tlb(env, context->tlb_index, guest);
         }
     }
+}
+
+bool loongarch_cpu_tlb_fill(CPUState *cs, vaddr address, int size,
+                            MMUAccessType access_type, int mmu_idx, bool probe,
+                            uintptr_t retaddr)
+{
+    CPULoongArchState *env = cpu_env(cs);
+    MMUContext host_context;
+    hwaddr physical;
+    int prot, host_prot;
+    MMUContext context;
+    TLBRet ret;
+
+    /* Data access */
+    context.addr = address;
+    context.tlb_index = -1;
+    ret = get_physical_address(env, &context, access_type, mmu_idx, 0, retaddr);
+    loongarch_try_ptw(env, &context, access_type, mmu_idx, &ret, env->guest,
+                      retaddr);
 
     if (ret == TLBRET_MATCH) {
         physical = context.physical;
         prot = context.prot;
+        if (env->guest) {
+            host_context.addr = physical;
+            host_context.tlb_index = -1;
+            ret = loongarch_map_host_address(env, &host_context, access_type,
+                                             retaddr);
+            loongarch_try_ptw(env, &host_context, access_type, MMU_KERNEL_IDX,
+                              &ret, false, retaddr);
+            if (ret != TLBRET_HOST_MATCH) {
+                if (probe) {
+                    return false;
+                }
+                raise_mmu_exception(env, physical, access_type, ret);
+                cpu_loop_exit_restore(cs, retaddr);
+                return false;
+            }
+            physical = host_context.physical;
+            host_prot = host_context.prot;
+            prot &= host_prot;
+        }
         tlb_set_page(cs, address & TARGET_PAGE_MASK,
                      physical & TARGET_PAGE_MASK, prot,
                      mmu_idx, TARGET_PAGE_SIZE);
         qemu_log_mask(CPU_LOG_MMU,
                       "%s address=%" VADDR_PRIx " physical " HWADDR_FMT_plx
-                      " prot %d\n", __func__, address, physical, prot);
+                      " prot %d guest %d\n",
+                      __func__, address, physical, prot,
+                      is_guest_mmu_idx(mmu_idx));
         return true;
     } else {
         qemu_log_mask(CPU_LOG_MMU,
@@ -702,6 +943,31 @@ static inline uint64_t loongarch_sanitize_hw_pte(CPULoongArchState *env,
     return (pte & ~ppn_mask) | ((pte & ppn_mask) & palen_mask);
 }
 
+hwaddr loongarch_get_host_address(CPULoongArchState *env, hwaddr gpa,
+                                  uintptr_t retaddr)
+{
+    MMUContext host_context;
+    TLBRet ret;
+
+    if (!env->guest) {
+        return gpa;
+    }
+
+    host_context.addr = gpa;
+    host_context.tlb_index = -1;
+    ret =
+        loongarch_map_host_address(env, &host_context, MMU_DATA_LOAD, retaddr);
+    loongarch_try_ptw(env, &host_context, MMU_DATA_LOAD, MMU_KERNEL_IDX, &ret,
+                      false, retaddr);
+
+    if (ret != TLBRET_HOST_MATCH) {
+        raise_mmu_exception(env, gpa, MMU_DATA_LOAD, ret);
+        cpu_loop_exit_restore(env_cpu(env), retaddr);
+    }
+
+    return host_context.physical;
+}
+
 target_ulong helper_lddir(CPULoongArchState *env, target_ulong base,
                           uint32_t level, uint32_t mem_idx)
 {
@@ -732,12 +998,15 @@ target_ulong helper_lddir(CPULoongArchState *env, target_ulong base,
         }
     }
 
-    badvaddr = env->CSR_TLBRBADV;
+    badvaddr = GET_CSR_IF(env->guest, TLBRBADV);
     base = base & palen_mask;
-    get_dir_base_width(env, &dir_base, &dir_width, level);
+    get_dir_base_width(env, &dir_base, &dir_width, level, env->guest);
     index = (badvaddr >> dir_base) & ((1 << dir_width) - 1);
     phys = base | index << 3;
-    val = address_space_ldq_le(cs->as, phys, MEMTXATTRS_UNSPECIFIED, NULL);
+    val = address_space_ldq_le(
+        cs->as,
+        (env->guest ? loongarch_get_host_address(env, phys, GETPC()) : phys),
+        MEMTXATTRS_UNSPECIFIED, NULL);
 
     return val & palen_mask;
 }
@@ -749,8 +1018,10 @@ void helper_ldpte(CPULoongArchState *env, target_ulong base, target_ulong odd,
     hwaddr phys, tmp0, ptindex, ptoffset0, ptoffset1;
     uint64_t pte_raw;
     uint64_t badv;
-    uint64_t ptbase = FIELD_EX64(env->CSR_PWCL, CSR_PWCL, PTBASE);
-    uint64_t ptwidth = FIELD_EX64(env->CSR_PWCL, CSR_PWCL, PTWIDTH);
+    uint64_t ptbase =
+        FIELD_EX64(GET_CSR_IF(env->guest, PWCL), CSR_PWCL, PTBASE);
+    uint64_t ptwidth =
+        FIELD_EX64(GET_CSR_IF(env->guest, PWCL), CSR_PWCL, PTWIDTH);
     uint64_t palen_mask = loongarch_palen_mask(env);
     uint64_t dir_base, dir_width;
     uint8_t  ps;
@@ -771,7 +1042,7 @@ void helper_ldpte(CPULoongArchState *env, target_ulong base, target_ulong odd,
          * Move HGLOBAL bit to GLOBAL bit.
          */
         get_dir_base_width(env, &dir_base, &dir_width,
-                           FIELD_EX64(base, TLBENTRY, LEVEL));
+                           FIELD_EX64(base, TLBENTRY, LEVEL), env->guest);
 
         base = FIELD_DP64(base, TLBENTRY, LEVEL, 0);
         base = FIELD_DP64(base, TLBENTRY, HUGE, 0);
@@ -796,7 +1067,7 @@ void helper_ldpte(CPULoongArchState *env, target_ulong base, target_ulong odd,
             return;
         }
     } else {
-        badv = env->CSR_TLBRBADV;
+        badv = GET_CSR_IF(env->guest, TLBRBADV);
 
         base = base & palen_mask;
 
@@ -805,26 +1076,31 @@ void helper_ldpte(CPULoongArchState *env, target_ulong base, target_ulong odd,
         ptoffset0 = ptindex << 3;
         ptoffset1 = (ptindex + 1) << 3;
         phys = base | (odd ? ptoffset1 : ptoffset0);
-        pte_raw = address_space_ldq_le(cs->as, phys,
-                                       MEMTXATTRS_UNSPECIFIED, NULL);
+        pte_raw = address_space_ldq_le(
+            cs->as,
+            (env->guest ? loongarch_get_host_address(env, phys, GETPC()) :
+                          phys),
+            MEMTXATTRS_UNSPECIFIED, NULL);
         tmp0 = loongarch_sanitize_hw_pte(env, pte_raw);
         ps = ptbase;
     }
 
     if (odd) {
-        env->CSR_TLBRELO1 = tmp0;
+        SET_CSR_IF(env->guest, TLBRELO1, tmp0);
     } else {
-        env->CSR_TLBRELO0 = tmp0;
+        SET_CSR_IF(env->guest, TLBRELO0, tmp0);
     }
-    env->CSR_TLBREHI = FIELD_DP64(env->CSR_TLBREHI, CSR_TLBREHI, PS, ps);
+    SET_CSR_IF(
+        env->guest, TLBREHI,
+        FIELD_DP64(GET_CSR_IF(env->guest, TLBREHI), CSR_TLBREHI, PS, ps));
 }
 
 static TLBRet loongarch_map_tlb_entry(CPULoongArchState *env,
                                       MMUContext *context,
                                       MMUAccessType access_type, int index,
-                                      int mmu_idx)
+                                      int mmu_idx, bool guest)
 {
-    LoongArchTLB *tlb = &env->tlb[index];
+    LoongArchTLB *tlb = guest ? &env->gtlb[index] : &env->tlb[index];
     uint8_t tlb_ps, n;
 
     tlb_ps = FIELD_EX64(tlb->tlb_misc, TLB_MISC, PS);
@@ -832,19 +1108,20 @@ static TLBRet loongarch_map_tlb_entry(CPULoongArchState *env,
     context->pte = n ? tlb->tlb_entry1 : tlb->tlb_entry0;
     context->ps = tlb_ps;
     context->tlb_index = index;
-    return loongarch_check_pte(env, context, access_type, mmu_idx);
+    return loongarch_check_pte(env, context, access_type, mmu_idx, guest);
 }
 
-TLBRet loongarch_get_addr_from_tlb(CPULoongArchState *env,
-                                   MMUContext *context,
-                                   MMUAccessType access_type, int mmu_idx)
+TLBRet loongarch_get_addr_from_tlb(CPULoongArchState *env, MMUContext *context,
+                                   MMUAccessType access_type, int mmu_idx,
+                                   bool guest)
 {
     int index, match;
 
-    match = loongarch_tlb_search(env, context->addr, &index);
+    match =
+        loongarch_tlb_search(env, context->addr, &index, guest, get_tgid(env));
     if (match) {
         return loongarch_map_tlb_entry(env, context, access_type, index,
-                                       mmu_idx);
+                                       mmu_idx, guest);
     }
 
     return TLBRET_NOMATCH;
diff --git a/target/loongarch/tcg/translate.c b/target/loongarch/tcg/translate.c
index 124dce6269..15c83ef72d 100644
--- a/target/loongarch/tcg/translate.c
+++ b/target/loongarch/tcg/translate.c
@@ -122,12 +122,16 @@ static void loongarch_tr_init_disas_context(DisasContextBase *dcbase,
     CPULoongArchState *env = cpu_env(cs);
     DisasContext *ctx = container_of(dcbase, DisasContext, base);
 
+    ctx->guest_mode = (ctx->base.tb->flags & HW_FLAGS_GUEST_MODE) != 0;
     ctx->page_start = ctx->base.pc_first & TARGET_PAGE_MASK;
     ctx->plv = ctx->base.tb->flags & HW_FLAGS_PLV_MASK;
     if (ctx->base.tb->flags & HW_FLAGS_CRMD_PG) {
         ctx->mem_idx = ctx->plv;
+        if (ctx->guest_mode) {
+            ctx->mem_idx += MMU_GUEST_IDX;
+        }
     } else {
-        ctx->mem_idx = MMU_DA_IDX;
+        ctx->mem_idx = ctx->guest_mode ? MMU_GUEST_DA_IDX : MMU_DA_IDX;
     }
 
     /* Bound the number of insns to execute to those left on the page.  */
diff --git a/target/loongarch/translate.h b/target/loongarch/translate.h
index 8aa8325dc6..db0650e713 100644
--- a/target/loongarch/translate.h
+++ b/target/loongarch/translate.h
@@ -26,6 +26,7 @@
 #define avail_FP_DP(C)  (FIELD_EX32((C)->cpucfg2, CPUCFG2, FP_DP))
 #define avail_LSPW(C)   (FIELD_EX32((C)->cpucfg2, CPUCFG2, LSPW))
 #define avail_LAM(C)    (FIELD_EX32((C)->cpucfg2, CPUCFG2, LAM))
+#define avail_LVZ(C)    (FIELD_EX32((C)->cpucfg2, CPUCFG2, LVZ))
 #define avail_LAM_BH(C) (FIELD_EX32((C)->cpucfg2, CPUCFG2, LAM_BH))
 #define avail_LAMCAS(C) (FIELD_EX32((C)->cpucfg2, CPUCFG2, LAMCAS))
 #define avail_LSX(C)    (FIELD_EX32((C)->cpucfg2, CPUCFG2, LSX))
@@ -66,6 +67,7 @@ typedef struct DisasContext {
     TCGv zero;
     bool la64; /* LoongArch64 mode */
     bool va32; /* 32-bit virtual address */
+    bool guest_mode;
     uint32_t cpucfg1;
     uint32_t cpucfg2;
     uint32_t cpucfg3;
-- 
2.52.0
Re: [PATCH] target/loongarch: add LVZ support for TCG
Posted by Bibo Mao 1 week, 5 days ago
Hi SignKirigami,

Thanks for providing LVZ patch, this patch adds 1900 lines and it is 
huge. I sugguest that it should be split smaller patch so that it is 
convenient to review.

Just keep patient since it is a big feature :)


Regards
Bibo Mao


On 2026/5/18 下午3:42, SignKirigami wrote:
> This patch implements Loongson VirtualiZation (LVZ) extension
> support for LoongArch's TCG target. With this patch, it is
> now possible to start a nested KVM-accelerated virtual machine
> on a TCG-emulated virtual machine.
> 
> Cc: Bibo Mao <maobibo@loongson.cn>
> Cc: xianglai li <lixianglai@loongson.cn>
> Signed-off-by: SignKirigami <prcups@krgm.moe>
> Signed-off-by: Hengyu Yu <yuhengyu25@mails.ucas.ac.cn>
> ---
>   target/loongarch/cpu-csr.h                    |  42 ++
>   target/loongarch/cpu-mmu.h                    |  37 +-
>   target/loongarch/cpu.c                        | 132 ++++
>   target/loongarch/cpu.h                        | 125 +++-
>   target/loongarch/cpu_helper.c                 | 119 ++--
>   target/loongarch/csr.c                        | 122 ++++
>   target/loongarch/csr.h                        |   3 +
>   target/loongarch/disas.c                      |  10 +
>   target/loongarch/insns.decode                 |  17 +
>   target/loongarch/internals.h                  |   8 +-
>   target/loongarch/kvm/kvm.c                    |   1 +
>   target/loongarch/machine.c                    | 249 ++++---
>   target/loongarch/tcg/constant_timer.c         |  62 +-
>   target/loongarch/tcg/csr_helper.c             | 127 +++-
>   target/loongarch/tcg/helper.h                 |  30 +-
>   .../tcg/insn_trans/trans_privileged.c.inc     | 353 +++++++++-
>   target/loongarch/tcg/op_helper.c              |  83 ++-
>   target/loongarch/tcg/tcg_cpu.c                | 182 +++--
>   target/loongarch/tcg/tcg_loongarch.h          |   6 +-
>   target/loongarch/tcg/tlb_helper.c             | 629 +++++++++++++-----
>   target/loongarch/tcg/translate.c              |   6 +-
>   target/loongarch/translate.h                  |   2 +
>   22 files changed, 1909 insertions(+), 436 deletions(-)
> 
> diff --git a/target/loongarch/cpu-csr.h b/target/loongarch/cpu-csr.h
> index d860417af2..4b0bb4d2e5 100644
> --- a/target/loongarch/cpu-csr.h
> +++ b/target/loongarch/cpu-csr.h
> @@ -180,11 +180,13 @@ FIELD(CSR_TLBREHI_64, VPPN, 13, 35)
>   #define LOONGARCH_CSR_TLBRPRMD       0x8f /* TLB refill mode info */
>   FIELD(CSR_TLBRPRMD, PPLV, 0, 2)
>   FIELD(CSR_TLBRPRMD, PIE, 2, 1)
> +FIELD(CSR_TLBRPRMD, PGM, 3, 1)
>   FIELD(CSR_TLBRPRMD, PWE, 4, 1)
>   
>   /* Machine Error CSRs */
>   #define LOONGARCH_CSR_MERRCTL        0x90 /* ERRCTL */
>   FIELD(CSR_MERRCTL, ISMERR, 0, 1)
> +FIELD(CSR_MERRCTL, PGM, 5, 1)
>   #define LOONGARCH_CSR_MERRINFO1      0x91
>   #define LOONGARCH_CSR_MERRINFO2      0x92
>   #define LOONGARCH_CSR_MERRENTRY      0x93 /* MError exception base */
> @@ -224,4 +226,44 @@ FIELD(CSR_DBG, ECODE, 16, 6)
>   #define LOONGARCH_CSR_DERA           0x501 /* Debug era */
>   #define LOONGARCH_CSR_DSAVE          0x502 /* Debug save */
>   
> +/* LVZ (LoongArch Virtualization) CSRs */
> +#define LOONGARCH_CSR_GSTAT          0x50 /* Guest status */
> +FIELD(CSR_GSTAT, VM, 0, 1)
> +FIELD(CSR_GSTAT, PVM, 1, 1)
> +FIELD(CSR_GSTAT, GIDBIT, 4, 6)
> +FIELD(CSR_GSTAT, GID, 16, 8)
> +
> +#define LOONGARCH_CSR_GCFG           0x51 /* Guest config */
> +FIELD(CSR_GCFG, MATP, 0, 4)
> +FIELD(CSR_GCFG, MATC, 4, 2)
> +FIELD(CSR_GCFG, TOPIP, 6, 1)
> +FIELD(CSR_GCFG, TOPI, 7, 1)
> +FIELD(CSR_GCFG, TOTIP, 8, 1)
> +FIELD(CSR_GCFG, TOTI, 9, 1)
> +FIELD(CSR_GCFG, TOEP, 10, 1)
> +FIELD(CSR_GCFG, TOE, 11, 1)
> +FIELD(CSR_GCFG, TOPP, 12, 1)
> +FIELD(CSR_GCFG, TOP, 13, 1)
> +FIELD(CSR_GCFG, TOHUP, 14, 1)
> +FIELD(CSR_GCFG, TOHU, 15, 1)
> +FIELD(CSR_GCFG, TOCIP, 16, 4)
> +FIELD(CSR_GCFG, TOCI, 20, 2)
> +FIELD(CSR_GCFG, GPMP, 23, 1)
> +FIELD(CSR_GCFG, GPMNUM, 24, 3)
> +
> +#define LOONGARCH_CSR_GINTC          0x52 /* Guest interrupt config */
> +FIELD(CSR_GINTC, HWIS, 0, 8)
> +FIELD(CSR_GINTC, HWIP, 8, 8)
> +FIELD(CSR_GINTC, HWIC, 16, 8)
> +
> +#define LOONGARCH_CSR_GCNTC          0x53 /* Guest counter compensation */
> +
> +#define LOONGARCH_CSR_GTLBC          0x15 /* Guest TLB control */
> +FIELD(CSR_GTLBC, GMTLBSZ, 0, 6)
> +FIELD(CSR_GTLBC, USETGID, 12, 1)
> +FIELD(CSR_GTLBC, TOTI, 13, 1)
> +FIELD(CSR_GTLBC, TGID, 16, 8)
> +
> +#define LOONGARCH_CSR_TRGP           0x16 /* Trapped guest physical address */
> +
>   #endif /* LOONGARCH_CPU_CSR_H */
> diff --git a/target/loongarch/cpu-mmu.h b/target/loongarch/cpu-mmu.h
> index 2d7ebb2d72..31093641ff 100644
> --- a/target/loongarch/cpu-mmu.h
> +++ b/target/loongarch/cpu-mmu.h
> @@ -17,6 +17,14 @@ typedef enum TLBRet {
>       TLBRET_RI,
>       TLBRET_XI,
>       TLBRET_PE,
> +    TLBRET_HOST_MATCH,
> +    TLBRET_HOST_BADADDR,
> +    TLBRET_HOST_NOMATCH,
> +    TLBRET_HOST_INVALID,
> +    TLBRET_HOST_DIRTY,
> +    TLBRET_HOST_RI,
> +    TLBRET_HOST_XI,
> +    TLBRET_HOST_PE,
>   } TLBRet;
>   
>   typedef struct MMUContext {
> @@ -30,16 +38,17 @@ typedef struct MMUContext {
>       uint64_t      pte_buddy[2];
>   } MMUContext;
>   
> -static inline bool cpu_has_ptw(CPULoongArchState *env)
> +static inline bool cpu_has_ptw(CPULoongArchState *env, bool guest)
>   {
> -    return !!FIELD_EX64(env->CSR_PWCH, CSR_PWCH, HPTW_EN);
> +    return !!FIELD_EX64(GET_CSR_IF(guest, PWCH), CSR_PWCH, HPTW_EN);
>   }
>   
> -static inline bool pte_present(CPULoongArchState *env, uint64_t entry)
> +static inline bool pte_present(CPULoongArchState *env, uint64_t entry,
> +                               bool guest)
>   {
>       uint8_t present;
>   
> -    if (cpu_has_ptw(env)) {
> +    if (cpu_has_ptw(env, guest)) {
>           present = FIELD_EX64(entry, TLBENTRY, P);
>       } else {
>           present = FIELD_EX64(entry, TLBENTRY, V);
> @@ -48,11 +57,11 @@ static inline bool pte_present(CPULoongArchState *env, uint64_t entry)
>       return !!present;
>   }
>   
> -static inline bool pte_write(CPULoongArchState *env, uint64_t entry)
> +static inline bool pte_write(CPULoongArchState *env, uint64_t entry, bool guest)
>   {
>       uint8_t writable;
>   
> -    if (cpu_has_ptw(env)) {
> +    if (cpu_has_ptw(env, guest)) {
>           writable = FIELD_EX64(entry, TLBENTRY, W);
>       } else {
>           writable = FIELD_EX64(entry, TLBENTRY, D);
> @@ -89,14 +98,22 @@ static inline bool pte_dirty(uint64_t entry)
>   
>   bool check_ps(CPULoongArchState *ent, uint8_t ps);
>   TLBRet loongarch_check_pte(CPULoongArchState *env, MMUContext *context,
> -                           MMUAccessType access_type, int mmu_idx);
> +                           MMUAccessType access_type, int mmu_idx, bool guest);
>   TLBRet get_physical_address(CPULoongArchState *env, MMUContext *context,
>                               MMUAccessType access_type, int mmu_idx,
> -                            int is_debug);
> +                            int is_debug, uintptr_t retaddr);
>   TLBRet loongarch_ptw(CPULoongArchState *env, MMUContext *context,
> -                     int access_type, int mmu_idx, int debug);
> +                     int access_type, int mmu_idx, int debug, bool guest,
> +                     uintptr_t retaddr);
> +hwaddr loongarch_get_host_address(CPULoongArchState *env, hwaddr gpa,
> +                                  uintptr_t retaddr);
> +TLBRet loongarch_map_host_address(CPULoongArchState *env, MMUContext *context,
> +                                  MMUAccessType access_type, uintptr_t retaddr);
> +TLBRet loongarch_map_address(CPULoongArchState *env, MMUContext *context,
> +                             MMUAccessType access_type, int mmu_idx,
> +                             int is_debug, bool guest, uintptr_t retaddr);
>   void get_dir_base_width(CPULoongArchState *env, uint64_t *dir_base,
> -                        uint64_t *dir_width, unsigned int level);
> +                        uint64_t *dir_width, unsigned int level, bool guest);
>   hwaddr loongarch_cpu_get_phys_addr_debug(CPUState *cpu, vaddr addr);
>   uint64_t loongarch_palen_mask(CPULoongArchState *env);
>   
> diff --git a/target/loongarch/cpu.c b/target/loongarch/cpu.c
> index 8f277f7696..2477d84625 100644
> --- a/target/loongarch/cpu.c
> +++ b/target/loongarch/cpu.c
> @@ -67,6 +67,11 @@ void loongarch_cpu_set_irq(void *opaque, int irq, int level)
>           return;
>       }
>   
> +    if (FIELD_EX64(env->CSR_GINTC, CSR_GINTC, HWIP) & BIT(irq)) {
> +        loongarch_cpu_set_irq_guest(opaque, irq, level);
> +        return;
> +    }
> +
>       if (kvm_enabled()) {
>           kvm_loongarch_set_interrupt(cpu, irq, level);
>       } else if (tcg_enabled()) {
> @@ -79,6 +84,26 @@ void loongarch_cpu_set_irq(void *opaque, int irq, int level)
>       }
>   }
>   
> +void loongarch_cpu_set_irq_guest(void *opaque, int irq, int level)
> +{
> +    LoongArchCPU *cpu = opaque;
> +    CPULoongArchState *env = &cpu->env;
> +    CPUState *cs = CPU(cpu);
> +
> +    if (irq < 0 || irq >= N_IRQS) {
> +        return;
> +    }
> +
> +    env->GCSR_ESTAT = deposit64(env->GCSR_ESTAT, irq, 1, level != 0);
> +    if (env->guest) {
> +        if (FIELD_EX64(env->GCSR_ESTAT, CSR_ESTAT, IS)) {
> +            cpu_interrupt(cs, CPU_INTERRUPT_GUEST);
> +        } else {
> +            cpu_reset_interrupt(cs, CPU_INTERRUPT_GUEST);
> +        }
> +    }
> +}
> +
>   /* Check if there is pending and not masked out interrupt */
>   bool cpu_loongarch_hw_interrupts_pending(CPULoongArchState *env)
>   {
> @@ -90,6 +115,30 @@ bool cpu_loongarch_hw_interrupts_pending(CPULoongArchState *env)
>   
>       return (pending & status) != 0;
>   }
> +
> +static inline bool
> +cpu_loongarch_hw_interrupts_enabled_guest(CPULoongArchState *env)
> +{
> +    return FIELD_EX64(env->GCSR_CRMD, CSR_CRMD, IE);
> +}
> +
> +static inline bool
> +cpu_loongarch_hw_interrupts_pending_guest(CPULoongArchState *env)
> +{
> +    uint32_t pending;
> +    uint32_t status;
> +
> +    pending = FIELD_EX64(env->GCSR_ESTAT, CSR_ESTAT, IS);
> +    status = FIELD_EX64(env->GCSR_ECFG, CSR_ECFG, LIE);
> +
> +    return (pending & status) != 0;
> +}
> +
> +bool loongarch_guest_has_interrupt(CPULoongArchState *env)
> +{
> +    return env->guest && cpu_loongarch_hw_interrupts_enabled_guest(env) &&
> +           cpu_loongarch_hw_interrupts_pending_guest(env);
> +}
>   #endif
>   
>   #ifndef CONFIG_USER_ONLY
> @@ -102,10 +151,54 @@ bool loongarch_cpu_has_work(CPUState *cs)
>           has_work = true;
>       }
>   
> +    if (cpu_test_interrupt(cs, CPU_INTERRUPT_GUEST) &&
> +        loongarch_guest_has_interrupt(cpu_env(cs))) {
> +        has_work = true;
> +    }
> +
>       return has_work;
>   }
>   #endif /* !CONFIG_USER_ONLY */
>   
> +uint8_t get_tgid(CPULoongArchState *env)
> +{
> +    if (env->guest) {
> +        return get_gid(env);
> +    }
> +
> +    if (FIELD_EX64(env->CSR_GTLBC, CSR_GTLBC, USETGID)) {
> +        return FIELD_EX64(env->CSR_GTLBC, CSR_GTLBC, TGID);
> +    } else if (will_return_to_guest(env)) {
> +        return get_gid(env);
> +    }
> +    return 0;
> +}
> +
> +bool will_return_to_guest(CPULoongArchState *env)
> +{
> +    if (!has_lvz_capability(env) || env->guest) {
> +        return false;
> +    }
> +    return FIELD_EX64(env->CSR_GSTAT, CSR_GSTAT, PVM);
> +}
> +
> +bool has_lvz_capability(CPULoongArchState *env)
> +{
> +    return FIELD_EX32(env->cpucfg[2], CPUCFG2, LVZ);
> +}
> +
> +uint8_t get_gid(CPULoongArchState *env)
> +{
> +    return FIELD_EX64(env->CSR_GSTAT, CSR_GSTAT, GID);
> +}
> +
> +void trigger_vm_exit(CPULoongArchState *env)
> +{
> +    cpu_loongarch_set_guest_timer(env_archcpu(env), false);
> +    env->CSR_GSTAT = FIELD_DP64(env->CSR_GSTAT, CSR_GSTAT, PVM, 1);
> +    env->vm_exit = true;
> +}
> +
>   static void loongarch_la464_init_csr(DeviceState *dev)
>   {
>   #ifndef CONFIG_USER_ONLY
> @@ -248,12 +341,33 @@ static void loongarch_set_ptw(Object *obj, bool value, Error **errp)
>       cpu->env.cpucfg[2] = FIELD_DP32(cpu->env.cpucfg[2], CPUCFG2, HPTW, value);
>   }
>   
> +static bool loongarch_get_lvz(Object *obj, Error **errp)
> +{
> +    return LOONGARCH_CPU(obj)->lvz != ON_OFF_AUTO_OFF;
> +}
> +
> +static void loongarch_set_lvz(Object *obj, bool value, Error **errp)
> +{
> +    LoongArchCPU *cpu = LOONGARCH_CPU(obj);
> +
> +    cpu->lvz = value ? ON_OFF_AUTO_ON : ON_OFF_AUTO_OFF;
> +
> +    if (kvm_enabled()) {
> +        return;
> +    }
> +
> +    cpu->env.cpucfg[2] = FIELD_DP32(cpu->env.cpucfg[2], CPUCFG2, LVZ, value);
> +    cpu->env.cpucfg[2] =
> +        FIELD_DP32(cpu->env.cpucfg[2], CPUCFG2, LVZ_VER, value ? 1 : 0);
> +}
> +
>   static void loongarch_cpu_post_init(Object *obj)
>   {
>       LoongArchCPU *cpu = LOONGARCH_CPU(obj);
>   
>       cpu->lbt = ON_OFF_AUTO_OFF;
>       cpu->pmu = ON_OFF_AUTO_OFF;
> +    cpu->lvz = ON_OFF_AUTO_AUTO;
>       cpu->lsx = ON_OFF_AUTO_AUTO;
>       cpu->lasx = ON_OFF_AUTO_AUTO;
>       object_property_add_bool(obj, "lsx", loongarch_get_lsx,
> @@ -264,6 +378,8 @@ static void loongarch_cpu_post_init(Object *obj)
>                                loongarch_set_msgint);
>       object_property_add_bool(obj, "ptw", loongarch_get_ptw,
>                                loongarch_set_ptw);
> +    object_property_add_bool(obj, "lvz", loongarch_get_lvz,
> +                             loongarch_set_lvz);
>       /* lbt is enabled only in kvm mode, not supported in tcg mode */
>   
>       if (kvm_enabled()) {
> @@ -317,6 +433,8 @@ static void loongarch_la464_initfn(Object *obj)
>       data = FIELD_DP32(data, CPUCFG2, FP_VER, 1);
>       data = FIELD_DP32(data, CPUCFG2, LSX, 1),
>       data = FIELD_DP32(data, CPUCFG2, LASX, 1),
> +    data = FIELD_DP32(data, CPUCFG2, LVZ, 1);
> +    data = FIELD_DP32(data, CPUCFG2, LVZ_VER, 1);
>       data = FIELD_DP32(data, CPUCFG2, LLFTP, 1);
>       data = FIELD_DP32(data, CPUCFG2, LLFTP_VER, 1);
>       data = FIELD_DP32(data, CPUCFG2, LSPW, 1);
> @@ -395,6 +513,7 @@ static void loongarch_la464_initfn(Object *obj)
>       env->CSR_PRCFG3 = FIELD_DP64(env->CSR_PRCFG3, CSR_PRCFG3, STLB_SETS, 8);
>   
>       cpu->msgint = ON_OFF_AUTO_OFF;
> +    cpu->lvz = ON_OFF_AUTO_AUTO;
>       cpu->ptw = ON_OFF_AUTO_OFF;
>       loongarch_cpu_post_init(obj);
>   }
> @@ -432,6 +551,7 @@ static void loongarch_la132_initfn(Object *obj)
>       data = FIELD_DP32(data, CPUCFG1, CRC, 1);
>       env->cpucfg[1] = data;
>       cpu->msgint = ON_OFF_AUTO_OFF;
> +    cpu->lvz = ON_OFF_AUTO_OFF;
>       cpu->ptw = ON_OFF_AUTO_OFF;
>   }
>   
> @@ -637,6 +757,7 @@ static void loongarch_cpu_reset_hold(Object *obj, ResetType type)
>       env->CSR_RVACFG = FIELD_DP64(env->CSR_RVACFG, CSR_RVACFG, RBITS, 0);
>       env->CSR_CPUID = cs->cpu_index;
>       env->CSR_TCFG = FIELD_DP64(env->CSR_TCFG, CSR_TCFG, EN, 0);
> +    env->GCSR_TCFG = FIELD_DP64(env->GCSR_TCFG, CSR_TCFG, EN, 0);
>       env->CSR_LLBCTL = FIELD_DP64(env->CSR_LLBCTL, CSR_LLBCTL, KLO, 0);
>       env->CSR_TLBRERA = FIELD_DP64(env->CSR_TLBRERA, CSR_TLBRERA, ISTLBR, 0);
>       env->CSR_MERRCTL = FIELD_DP64(env->CSR_MERRCTL, CSR_MERRCTL, ISMERR, 0);
> @@ -671,6 +792,15 @@ static void loongarch_cpu_reset_hold(Object *obj, ResetType type)
>       env->pc = 0x1c000000;
>   #ifdef CONFIG_TCG
>       memset(env->tlb, 0, sizeof(env->tlb));
> +    env->guest = false;
> +    env->vm_exit = false;
> +    env->CSR_GSTAT = FIELD_DP64(0, CSR_GSTAT, GIDBIT, 8);
> +    env->CSR_GCFG = 0;
> +    env->CSR_GINTC = 0;
> +    env->CSR_GCNTC = 0;
> +    env->CSR_GTLBC = 0;
> +    env->CSR_TRGP = 0;
> +    env->GCSR_ASID = FIELD_DP64(0, CSR_ASID, ASIDBITS, 0xa);
>   #endif
>       if (kvm_enabled()) {
>           kvm_arch_reset_vcpu(cs);
> @@ -731,6 +861,8 @@ static void loongarch_cpu_init(Object *obj)
>   #ifdef CONFIG_TCG
>       timer_init_ns(&cpu->timer, QEMU_CLOCK_VIRTUAL,
>                     &loongarch_constant_timer_cb, cpu);
> +    timer_init_ns(&cpu->guest_timer, QEMU_CLOCK_VIRTUAL,
> +                  &loongarch_constant_timer_cb_guest, cpu);
>   #endif
>   #endif
>   }
> diff --git a/target/loongarch/cpu.h b/target/loongarch/cpu.h
> index 096d778928..8d3cae59fa 100644
> --- a/target/loongarch/cpu.h
> +++ b/target/loongarch/cpu.h
> @@ -20,6 +20,20 @@
>   #include "cpu-csr.h"
>   #include "cpu-qom.h"
>   
> +#define GET_CSR_IF(guest_mode, csr_name) \
> +    ((guest_mode) ? (env->GCSR_##csr_name) : (env->CSR_##csr_name))
> +
> +#define SET_CSR_IF(guest_mode, csr_name, value) \
> +    do {                                        \
> +        if (guest_mode) {                       \
> +            env->GCSR_##csr_name = (value);     \
> +        } else {                                \
> +            env->CSR_##csr_name = (value);      \
> +        }                                       \
> +    } while (0)
> +
> +#define CPU_INTERRUPT_GUEST CPU_INTERRUPT_TGT_EXT_0
> +
>   #define FCSR0_M1    0x1f         /* FCSR1 mask, Enables */
>   #define FCSR0_M2    0x1f1f0000   /* FCSR2 mask, Cause and Flags */
>   #define FCSR0_M3    0x300        /* FCSR3 mask, Round Mode */
> @@ -93,6 +107,10 @@ FIELD(FCSR0, CAUSE, 24, 5)
>   #define  EXCCODE_WPEM                EXCODE(19, 1)
>   #define  EXCCODE_BTD                 EXCODE(20, 0)
>   #define  EXCCODE_BTE                 EXCODE(21, 0)
> +#define  EXCCODE_GSPR                EXCODE(22, 0)
> +#define  EXCCODE_HVC                 EXCODE(23, 0)
> +#define  EXCCODE_GCSC                EXCODE(24, 0)
> +#define  EXCCODE_GCHC                EXCODE(25, 0)
>   #define  EXCCODE_DBP                 EXCODE(26, 0) /* Reserved subcode used for debug */
>   
>   /* cpucfg[0] bits */
> @@ -255,6 +273,7 @@ FIELD(TLB_MISC, E, 0, 1)
>   FIELD(TLB_MISC, ASID, 1, 10)
>   FIELD(TLB_MISC, VPPN, 13, 35)
>   FIELD(TLB_MISC, PS, 48, 6)
> +FIELD(TLB_MISC, GID, 54, 8)
>   
>   /*Msg interrupt registers */
>   #define N_MSGIS                4
> @@ -389,6 +408,79 @@ typedef struct CPUArchState {
>       uint64_t CSR_DBG;
>       uint64_t CSR_DERA;
>       uint64_t CSR_DSAVE;
> +
> +    /* LVZ (LoongArch Virtualization) CSRs */
> +    uint64_t CSR_GSTAT;
> +    uint64_t CSR_GCFG;
> +    uint64_t CSR_GINTC;
> +    uint64_t CSR_GCNTC;
> +    uint64_t CSR_GTLBC;
> +    uint64_t CSR_TRGP;
> +
> +    /* Guest CSR registers (GCSR) */
> +    uint64_t GCSR_CRMD;
> +    uint64_t GCSR_PRMD;
> +    uint64_t GCSR_EUEN;
> +    uint64_t GCSR_MISC;
> +    uint64_t GCSR_ECFG;
> +    uint64_t GCSR_ESTAT;
> +    uint64_t GCSR_ERA;
> +    uint64_t GCSR_BADV;
> +    uint64_t GCSR_BADI;
> +    uint64_t GCSR_EENTRY;
> +    uint64_t GCSR_TLBIDX;
> +    uint64_t GCSR_TLBEHI;
> +    uint64_t GCSR_TLBELO0;
> +    uint64_t GCSR_TLBELO1;
> +    uint64_t GCSR_ASID;
> +    uint64_t GCSR_PGDL;
> +    uint64_t GCSR_PGDH;
> +    uint64_t GCSR_PGD;
> +    uint64_t GCSR_PWCL;
> +    uint64_t GCSR_PWCH;
> +    uint64_t GCSR_STLBPS;
> +    uint64_t GCSR_RVACFG;
> +    uint64_t GCSR_CPUID;
> +    uint64_t GCSR_PRCFG1;
> +    uint64_t GCSR_PRCFG2;
> +    uint64_t GCSR_PRCFG3;
> +    uint64_t GCSR_SAVE[16];
> +    uint64_t GCSR_TID;
> +    uint64_t GCSR_TCFG;
> +    uint64_t GCSR_TVAL;
> +    uint64_t GCSR_CNTC;
> +    uint64_t GCSR_TICLR;
> +    uint64_t GCSR_LLBCTL;
> +    uint64_t GCSR_IMPCTL1;
> +    uint64_t GCSR_IMPCTL2;
> +    uint64_t GCSR_TLBRENTRY;
> +    uint64_t GCSR_TLBRBADV;
> +    uint64_t GCSR_TLBRERA;
> +    uint64_t GCSR_TLBRSAVE;
> +    uint64_t GCSR_TLBRELO0;
> +    uint64_t GCSR_TLBRELO1;
> +    uint64_t GCSR_TLBREHI;
> +    uint64_t GCSR_TLBRPRMD;
> +    uint64_t GCSR_MERRCTL;
> +    uint64_t GCSR_MERRINFO1;
> +    uint64_t GCSR_MERRINFO2;
> +    uint64_t GCSR_MERRENTRY;
> +    uint64_t GCSR_MERRERA;
> +    uint64_t GCSR_MERRSAVE;
> +    uint64_t GCSR_CTAG;
> +    uint64_t GCSR_DMW[4];
> +    uint64_t GCSR_DBG;
> +    uint64_t GCSR_DERA;
> +    uint64_t GCSR_DSAVE;
> +    uint64_t GCSR_GSTAT;
> +    uint64_t GCSR_GCFG;
> +    uint64_t GCSR_GINTC;
> +    uint64_t GCSR_GCNTC;
> +    uint64_t GCSR_GTLBC;
> +    uint64_t GCSR_TRGP;
> +
> +    bool guest;
> +    bool vm_exit;
>       /* Msg interrupt registers */
>       uint64_t CSR_MSGIS[N_MSGIS];
>       uint64_t CSR_MSGIR;
> @@ -410,6 +502,7 @@ typedef struct CPUArchState {
>   #ifndef CONFIG_USER_ONLY
>   #ifdef CONFIG_TCG
>       LoongArchTLB  tlb[LOONGARCH_TLB_MAX];
> +    LoongArchTLB gtlb[LOONGARCH_TLB_MAX];
>   #endif
>   
>       AddressSpace *address_space_iocsr;
> @@ -434,9 +527,11 @@ struct ArchCPU {
>   
>       CPULoongArchState env;
>       QEMUTimer timer;
> +    QEMUTimer guest_timer;
>       uint32_t  phy_id;
>       OnOffAuto lbt;
>       OnOffAuto pmu;
> +    OnOffAuto lvz;
>       OnOffAuto ptw;
>       OnOffAuto lsx;
>       OnOffAuto lasx;
> @@ -480,6 +575,24 @@ struct LoongArchCPUClass {
>   #define MMU_KERNEL_IDX   MMU_PLV_KERNEL
>   #define MMU_USER_IDX     MMU_PLV_USER
>   #define MMU_DA_IDX       4
> +#define MMU_GUEST_IDX    5
> +#define MMU_GUEST_DA_IDX 9
> +
> +static inline bool is_guest_mmu_idx(int mmu_idx)
> +{
> +    return mmu_idx >= MMU_GUEST_IDX;
> +}
> +
> +static inline int mmu_idx_to_plv(int mmu_idx)
> +{
> +    if (mmu_idx == MMU_DA_IDX || mmu_idx == MMU_GUEST_DA_IDX) {
> +        return 0;
> +    }
> +    if (is_guest_mmu_idx(mmu_idx)) {
> +        return mmu_idx - MMU_GUEST_IDX;
> +    }
> +    return mmu_idx;
> +}
>   
>   static inline bool is_la64(CPULoongArchState *env)
>   {
> @@ -490,8 +603,9 @@ static inline bool is_va32(CPULoongArchState *env)
>   {
>       /* VA32 if !LA64 or VA32L[1-3] */
>       bool va32 = !is_la64(env);
> -    uint64_t plv = FIELD_EX64(env->CSR_CRMD, CSR_CRMD, PLV);
> -    if (plv >= 1 && (FIELD_EX64(env->CSR_MISC, CSR_MISC, VA32) & (1 << plv))) {
> +    uint64_t plv = FIELD_EX64(GET_CSR_IF(env->guest, CRMD), CSR_CRMD, PLV);
> +    if (plv >= 1 &&
> +        (FIELD_EX64(GET_CSR_IF(env->guest, MISC), CSR_MISC, VA32) & BIT(plv))) {
>           va32 = true;
>       }
>       return va32;
> @@ -515,6 +629,13 @@ static inline void set_pc(CPULoongArchState *env, uint64_t value)
>   #define HW_FLAGS_CRMD_PG    R_CSR_CRMD_PG_MASK   /* 0x10 */
>   #define HW_FLAGS_VA32       0x20
>   #define HW_FLAGS_EUEN_ASXE  0x40
> +#define HW_FLAGS_GUEST_MODE 0x80
> +
> +bool has_lvz_capability(CPULoongArchState *env);
> +bool will_return_to_guest(CPULoongArchState *env);
> +uint8_t get_gid(CPULoongArchState *env);
> +uint8_t get_tgid(CPULoongArchState *env);
> +void trigger_vm_exit(CPULoongArchState *env);
>   
>   #define CPU_RESOLVING_TYPE TYPE_LOONGARCH_CPU
>   
> diff --git a/target/loongarch/cpu_helper.c b/target/loongarch/cpu_helper.c
> index eb9684a4a1..bad6062ac9 100644
> --- a/target/loongarch/cpu_helper.c
> +++ b/target/loongarch/cpu_helper.c
> @@ -17,46 +17,56 @@
>   #include "cpu-mmu.h"
>   #include "tcg/tcg_loongarch.h"
>   
> -void get_dir_base_width(CPULoongArchState *env, uint64_t *dir_base,
> -                        uint64_t *dir_width, unsigned int level)
> +static void get_dir_base_width_csr(CPULoongArchState *env, uint64_t *dir_base,
> +                                   uint64_t *dir_width, unsigned int level,
> +                                   bool guest)
>   {
> +    uint64_t pwcl = GET_CSR_IF(guest, PWCL);
> +    uint64_t pwch = GET_CSR_IF(guest, PWCH);
> +
>       switch (level) {
>       case 1:
> -        *dir_base = FIELD_EX64(env->CSR_PWCL, CSR_PWCL, DIR1_BASE);
> -        *dir_width = FIELD_EX64(env->CSR_PWCL, CSR_PWCL, DIR1_WIDTH);
> +        *dir_base = FIELD_EX64(pwcl, CSR_PWCL, DIR1_BASE);
> +        *dir_width = FIELD_EX64(pwcl, CSR_PWCL, DIR1_WIDTH);
>           break;
>       case 2:
> -        *dir_base = FIELD_EX64(env->CSR_PWCL, CSR_PWCL, DIR2_BASE);
> -        *dir_width = FIELD_EX64(env->CSR_PWCL, CSR_PWCL, DIR2_WIDTH);
> +        *dir_base = FIELD_EX64(pwcl, CSR_PWCL, DIR2_BASE);
> +        *dir_width = FIELD_EX64(pwcl, CSR_PWCL, DIR2_WIDTH);
>           break;
>       case 3:
> -        *dir_base = FIELD_EX64(env->CSR_PWCH, CSR_PWCH, DIR3_BASE);
> -        *dir_width = FIELD_EX64(env->CSR_PWCH, CSR_PWCH, DIR3_WIDTH);
> +        *dir_base = FIELD_EX64(pwch, CSR_PWCH, DIR3_BASE);
> +        *dir_width = FIELD_EX64(pwch, CSR_PWCH, DIR3_WIDTH);
>           break;
>       case 4:
> -        *dir_base = FIELD_EX64(env->CSR_PWCH, CSR_PWCH, DIR4_BASE);
> -        *dir_width = FIELD_EX64(env->CSR_PWCH, CSR_PWCH, DIR4_WIDTH);
> +        *dir_base = FIELD_EX64(pwch, CSR_PWCH, DIR4_BASE);
> +        *dir_width = FIELD_EX64(pwch, CSR_PWCH, DIR4_WIDTH);
>           break;
>       default:
>           /* level may be zero for ldpte */
> -        *dir_base = FIELD_EX64(env->CSR_PWCL, CSR_PWCL, PTBASE);
> -        *dir_width = FIELD_EX64(env->CSR_PWCL, CSR_PWCL, PTWIDTH);
> +        *dir_base = FIELD_EX64(pwcl, CSR_PWCL, PTBASE);
> +        *dir_width = FIELD_EX64(pwcl, CSR_PWCL, PTWIDTH);
>           break;
>       }
>   }
>   
> +void get_dir_base_width(CPULoongArchState *env, uint64_t *dir_base,
> +                        uint64_t *dir_width, unsigned int level, bool guest)
> +{
> +    get_dir_base_width_csr(env, dir_base, dir_width, level, guest);
> +}
> +
>   TLBRet loongarch_check_pte(CPULoongArchState *env, MMUContext *context,
> -                           MMUAccessType access_type, int mmu_idx)
> +                           MMUAccessType access_type, int mmu_idx, bool guest)
>   {
> -    uint64_t plv = mmu_idx;
> +    uint64_t plv = mmu_idx_to_plv(mmu_idx);
>       uint64_t tlb_entry, tlb_ppn;
>       uint8_t tlb_ps, tlb_plv, tlb_nx, tlb_nr, tlb_rplv;
>       bool tlb_v, tlb_d;
>   
>       tlb_entry = context->pte;
>       tlb_ps = context->ps;
> -    tlb_v = pte_present(env, tlb_entry);
> -    tlb_d = pte_write(env, tlb_entry);
> +    tlb_v = pte_present(env, tlb_entry, guest);
> +    tlb_d = pte_write(env, tlb_entry, guest);
>       tlb_plv = FIELD_EX64(tlb_entry, TLBENTRY, PLV);
>       if (is_la64(env)) {
>           tlb_ppn = FIELD_EX64(tlb_entry, TLBENTRY_64, PPN);
> @@ -98,7 +108,7 @@ TLBRet loongarch_check_pte(CPULoongArchState *env, MMUContext *context,
>       context->physical = (tlb_ppn << R_TLBENTRY_64_PPN_SHIFT) |
>                           (context->addr & MAKE_64BIT_MASK(0, tlb_ps));
>       context->prot = PAGE_READ;
> -    context->mmu_index = tlb_plv;
> +    context->mmu_index = mmu_idx;
>       if (tlb_d) {
>           context->prot |= PAGE_WRITE;
>       }
> @@ -144,7 +154,8 @@ static MemTxResult loongarch_cmpxchg_phys(CPUState *cs, hwaddr phys,
>   }
>   
>   TLBRet loongarch_ptw(CPULoongArchState *env, MMUContext *context,
> -                     int access_type, int mmu_idx, int debug)
> +                     int access_type, int mmu_idx, int debug, bool guest,
> +                     uintptr_t retaddr)
>   {
>       const MemTxAttrs attrs = MEMTXATTRS_UNSPECIFIED;
>       CPUState *cs = env_cpu(env);
> @@ -160,14 +171,14 @@ TLBRet loongarch_ptw(CPULoongArchState *env, MMUContext *context,
>   
>       address = context->addr;
>       if ((address >> 63) & 0x1) {
> -        base = env->CSR_PGDH;
> +        base = GET_CSR_IF(guest, PGDH);
>       } else {
> -        base = env->CSR_PGDL;
> +        base = GET_CSR_IF(guest, PGDL);
>       }
>       base &= palen_mask;
>   
>       for (level = 4; level >= 0; level--) {
> -        get_dir_base_width(env, &dir_base, &dir_width, level);
> +        get_dir_base_width(env, &dir_base, &dir_width, level, guest);
>   
>           if (dir_width == 0) {
>               continue;
> @@ -176,7 +187,10 @@ TLBRet loongarch_ptw(CPULoongArchState *env, MMUContext *context,
>           /* get next level page directory */
>           index = (address >> dir_base) & ((1 << dir_width) - 1);
>           phys = base | index << 3;
> -        base = address_space_ldq_le(cs->as, phys, attrs, NULL);
> +        base = address_space_ldq_le(
> +            cs->as,
> +            (guest ? loongarch_get_host_address(env, phys, retaddr) : phys),
> +            attrs, NULL);
>           if (level) {
>               if (FIELD_EX64(base, TLBENTRY, HUGE)) {
>                   /* base is a huge pte */
> @@ -205,19 +219,22 @@ restart:
>           context->pte_buddy[index] = base;
>           context->pte_buddy[1 - index] = base + BIT_ULL(dir_base);
>           base += (BIT_ULL(dir_base) & address);
> -    } else if (cpu_has_ptw(env)) {
> +    } else if (cpu_has_ptw(env, guest)) {
>           uint64_t val;
>   
>           index &= 1;
>           context->pte_buddy[index] = base;
> -        val = address_space_ldq_le(cs->as, phys + 8 * (1 - 2 * index),
> -                                   attrs, NULL);
> +        val = address_space_ldq_le(
> +            cs->as,
> +            (guest ? loongarch_get_host_address(env, phys, retaddr) : phys) +
> +                8 * (1 - 2 * index),
> +            attrs, NULL);
>           context->pte_buddy[1 - index] = val;
>       }
>   
>       context->ps = dir_base;
>       context->pte = base;
> -    ret = loongarch_check_pte(env, context, access_type, mmu_idx);
> +    ret = loongarch_check_pte(env, context, access_type, mmu_idx, guest);
>       if (debug) {
>           return ret;
>       }
> @@ -228,7 +245,7 @@ restart:
>        * Need atomic compchxg operation with pte update, other vCPUs may
>        * update pte at the same time.
>        */
> -    if (ret == TLBRET_MATCH && cpu_has_ptw(env)) {
> +    if (ret == TLBRET_MATCH && cpu_has_ptw(env, guest)) {
>           if (access_type == MMU_DATA_STORE && pte_dirty(base)) {
>               return ret;
>           }
> @@ -241,10 +258,15 @@ restart:
>           if (access_type == MMU_DATA_STORE) {
>               base = pte_mkdirty(base);
>           }
> -        ret1 = loongarch_cmpxchg_phys(cs, phys, pte, base);
> +        ret1 = loongarch_cmpxchg_phys(
> +            cs, (guest ? loongarch_get_host_address(env, phys, retaddr) : phys),
> +            pte, base);
>           /* PTE updated by other CPU, reload PTE entry */
>           if (ret1 == MEMTX_DECODE_ERROR) {
> -            base = address_space_ldq_le(cs->as, phys, attrs, NULL);
> +            base = address_space_ldq_le(
> +                cs->as,
> +                (guest ? loongarch_get_host_address(env, phys, retaddr) : phys),
> +                attrs, NULL);
>               goto restart;
>           }
>   
> @@ -270,15 +292,15 @@ restart:
>       return ret;
>   }
>   
> -static TLBRet loongarch_map_address(CPULoongArchState *env,
> -                                    MMUContext *context,
> -                                    MMUAccessType access_type, int mmu_idx,
> -                                    int is_debug)
> +TLBRet loongarch_map_address(CPULoongArchState *env, MMUContext *context,
> +                             MMUAccessType access_type, int mmu_idx,
> +                             int is_debug, bool guest, uintptr_t retaddr)
>   {
>       TLBRet ret;
>   
>       if (tcg_enabled()) {
> -        ret = loongarch_get_addr_from_tlb(env, context, access_type, mmu_idx);
> +        ret = loongarch_get_addr_from_tlb(env, context, access_type, mmu_idx,
> +                                          guest);
>           if (ret != TLBRET_NOMATCH) {
>               return ret;
>           }
> @@ -290,7 +312,8 @@ static TLBRet loongarch_map_address(CPULoongArchState *env,
>            * legal mapping, even if the mapping is not yet in TLB. return 0 if
>            * there is a valid map, else none zero.
>            */
> -        return loongarch_ptw(env, context, access_type, mmu_idx, is_debug);
> +        return loongarch_ptw(env, context, access_type, mmu_idx, is_debug,
> +                             guest, retaddr);
>       }
>   
>       return TLBRET_NOMATCH;
> @@ -309,14 +332,14 @@ static hwaddr dmw_va2pa(CPULoongArchState *env, vaddr va, uint64_t dmw)
>   
>   TLBRet get_physical_address(CPULoongArchState *env, MMUContext *context,
>                               MMUAccessType access_type, int mmu_idx,
> -                            int is_debug)
> +                            int is_debug, uintptr_t retaddr)
>   {
> -    int user_mode = mmu_idx == MMU_USER_IDX;
> -    int kernel_mode = mmu_idx == MMU_KERNEL_IDX;
> +    int user_mode = mmu_idx_to_plv(mmu_idx) == MMU_USER_IDX;
> +    int kernel_mode = mmu_idx_to_plv(mmu_idx) == MMU_KERNEL_IDX;
>       uint32_t plv, base_c, base_v;
>       int64_t addr_high;
> -    uint8_t da = FIELD_EX64(env->CSR_CRMD, CSR_CRMD, DA);
> -    uint8_t pg = FIELD_EX64(env->CSR_CRMD, CSR_CRMD, PG);
> +    uint8_t da = FIELD_EX64(GET_CSR_IF(env->guest, CRMD), CSR_CRMD, DA);
> +    uint8_t pg = FIELD_EX64(GET_CSR_IF(env->guest, CRMD), CSR_CRMD, PG);
>       vaddr address;
>   
>       /* Check PG and DA */
> @@ -337,12 +360,15 @@ TLBRet get_physical_address(CPULoongArchState *env, MMUContext *context,
>       /* Check direct map window */
>       for (int i = 0; i < 4; i++) {
>           if (is_la64(env)) {
> -            base_c = FIELD_EX64(env->CSR_DMW[i], CSR_DMW_64, VSEG);
> +            base_c =
> +                FIELD_EX64(GET_CSR_IF(env->guest, DMW[i]), CSR_DMW_64, VSEG);
>           } else {
> -            base_c = FIELD_EX64(env->CSR_DMW[i], CSR_DMW_32, VSEG);
> +            base_c =
> +                FIELD_EX64(GET_CSR_IF(env->guest, DMW[i]), CSR_DMW_32, VSEG);
>           }
> -        if ((plv & env->CSR_DMW[i]) && (base_c == base_v)) {
> -            context->physical = dmw_va2pa(env, address, env->CSR_DMW[i]);
> +        if ((plv & GET_CSR_IF(env->guest, DMW[i])) && (base_c == base_v)) {
> +            context->physical =
> +                dmw_va2pa(env, address, GET_CSR_IF(env->guest, DMW[i]));
>               context->prot = PAGE_READ | PAGE_WRITE | PAGE_EXEC;
>               context->mmu_index = MMU_DA_IDX;
>               return TLBRET_MATCH;
> @@ -356,7 +382,8 @@ TLBRet get_physical_address(CPULoongArchState *env, MMUContext *context,
>       }
>   
>       /* Mapped address */
> -    return loongarch_map_address(env, context, access_type, mmu_idx, is_debug);
> +    return loongarch_map_address(env, context, access_type, mmu_idx, is_debug,
> +                                 env->guest, retaddr);
>   }
>   
>   hwaddr loongarch_cpu_get_phys_addr_debug(CPUState *cs, vaddr addr)
> @@ -366,7 +393,7 @@ hwaddr loongarch_cpu_get_phys_addr_debug(CPUState *cs, vaddr addr)
>   
>       context.addr = addr;
>       if (get_physical_address(env, &context, MMU_DATA_LOAD,
> -                             cpu_mmu_index(cs, false), 1) != TLBRET_MATCH) {
> +                             cpu_mmu_index(cs, false), 1, 0) != TLBRET_MATCH) {
>           return -1;
>       }
>       return context.physical;
> diff --git a/target/loongarch/csr.c b/target/loongarch/csr.c
> index fff2312f87..1c4505536d 100644
> --- a/target/loongarch/csr.c
> +++ b/target/loongarch/csr.c
> @@ -20,8 +20,27 @@
>           .flags = 0, .readfn = NULL, .writefn = NULL           \
>       }
>   
> +#define GCSR_OFF_FUNCS(NAME, FL, RD, WR)                              \
> +    [LOONGARCH_CSR_##                                                 \
> +        NAME] = { .name = (stringify(GCSR_##NAME)),                   \
> +                  .offset = offsetof(CPULoongArchState, GCSR_##NAME), \
> +                  .flags = FL,                                        \
> +                  .readfn = RD,                                       \
> +                  .writefn = WR }
> +
> +#define GCSR_OFF_ARRAY(NAME, N)                                         \
> +    [LOONGARCH_CSR_##NAME(N)] = { .name = (stringify(GCSR_##NAME##N)),  \
> +                                  .offset = offsetof(CPULoongArchState, \
> +                                                     GCSR_##NAME[N]),   \
> +                                  .flags = 0,                           \
> +                                  .readfn = NULL,                       \
> +                                  .writefn = NULL }
> +
>   #define CSR_OFF_FLAGS(NAME, FL)   CSR_OFF_FUNCS(NAME, FL, NULL, NULL)
>   #define CSR_OFF(NAME)             CSR_OFF_FLAGS(NAME, 0)
> +#define GCSR_OFF_FLAGS(NAME, FL) GCSR_OFF_FUNCS(NAME, FL, NULL, NULL)
> +#define GCSR_OFF(NAME) GCSR_OFF_FLAGS(NAME, 0)
> +#define GCSR_GSPR(NAME) GCSR_OFF_FUNCS(NAME, CSRFL_GSPR, NULL, NULL)
>   
>   static CSRInfo csr_info[] = {
>       CSR_OFF_FLAGS(CRMD, CSRFL_EXITTB),
> @@ -35,6 +54,8 @@ static CSRInfo csr_info[] = {
>       CSR_OFF_FLAGS(BADI, CSRFL_READONLY),
>       CSR_OFF(EENTRY),
>       CSR_OFF(TLBIDX),
> +    CSR_OFF(GTLBC),
> +    CSR_OFF(TRGP),
>       CSR_OFF(TLBEHI),
>       CSR_OFF(TLBELO0),
>       CSR_OFF(TLBELO1),
> @@ -71,6 +92,10 @@ static CSRInfo csr_info[] = {
>       CSR_OFF_FLAGS(TVAL, CSRFL_READONLY | CSRFL_IO),
>       CSR_OFF(CNTC),
>       CSR_OFF_FLAGS(TICLR, CSRFL_IO),
> +    CSR_OFF(GSTAT),
> +    CSR_OFF(GCFG),
> +    CSR_OFF_FLAGS(GINTC, CSRFL_IO),
> +    CSR_OFF(GCNTC),
>       CSR_OFF(LLBCTL),
>       CSR_OFF(IMPCTL1),
>       CSR_OFF(IMPCTL2),
> @@ -135,6 +160,87 @@ static CSRInfo csr_info[] = {
>       CSR_OFF(MSGIR),
>   };
>   
> +static CSRInfo gcsr_info[] = {
> +    GCSR_OFF_FLAGS(CRMD, CSRFL_EXITTB),
> +    GCSR_OFF(PRMD),
> +    GCSR_OFF_FLAGS(EUEN, CSRFL_EXITTB),
> +    GCSR_OFF_FLAGS(MISC, CSRFL_GUEST_READONLY),
> +    GCSR_OFF(ECFG),
> +    GCSR_OFF_FLAGS(ESTAT, CSRFL_EXITTB),
> +    GCSR_OFF(ERA),
> +    GCSR_OFF(BADV),
> +    GCSR_OFF_FLAGS(BADI, CSRFL_GUEST_READONLY),
> +    GCSR_OFF(EENTRY),
> +    GCSR_OFF(TLBIDX),
> +    GCSR_GSPR(GTLBC),
> +    GCSR_GSPR(TRGP),
> +    GCSR_OFF(TLBEHI),
> +    GCSR_OFF(TLBELO0),
> +    GCSR_OFF(TLBELO1),
> +    GCSR_OFF_FLAGS(ASID, CSRFL_EXITTB),
> +    GCSR_OFF(PGDL),
> +    GCSR_OFF(PGDH),
> +    GCSR_OFF_FLAGS(PGD, CSRFL_GUEST_READONLY),
> +    GCSR_OFF(PWCL),
> +    GCSR_OFF(PWCH),
> +    GCSR_OFF(STLBPS),
> +    GCSR_OFF(RVACFG),
> +    GCSR_OFF_FLAGS(CPUID, CSRFL_GUEST_READONLY),
> +    GCSR_OFF_FLAGS(PRCFG1, CSRFL_GUEST_READONLY),
> +    GCSR_OFF_FLAGS(PRCFG2, CSRFL_GUEST_READONLY),
> +    GCSR_OFF_FLAGS(PRCFG3, CSRFL_GUEST_READONLY),
> +    GCSR_OFF_ARRAY(SAVE, 0),
> +    GCSR_OFF_ARRAY(SAVE, 1),
> +    GCSR_OFF_ARRAY(SAVE, 2),
> +    GCSR_OFF_ARRAY(SAVE, 3),
> +    GCSR_OFF_ARRAY(SAVE, 4),
> +    GCSR_OFF_ARRAY(SAVE, 5),
> +    GCSR_OFF_ARRAY(SAVE, 6),
> +    GCSR_OFF_ARRAY(SAVE, 7),
> +    GCSR_OFF_ARRAY(SAVE, 8),
> +    GCSR_OFF_ARRAY(SAVE, 9),
> +    GCSR_OFF_ARRAY(SAVE, 10),
> +    GCSR_OFF_ARRAY(SAVE, 11),
> +    GCSR_OFF_ARRAY(SAVE, 12),
> +    GCSR_OFF_ARRAY(SAVE, 13),
> +    GCSR_OFF_ARRAY(SAVE, 14),
> +    GCSR_OFF_ARRAY(SAVE, 15),
> +    GCSR_OFF(TID),
> +    GCSR_OFF_FLAGS(TCFG, CSRFL_IO),
> +    GCSR_OFF_FLAGS(TVAL, CSRFL_GUEST_READONLY | CSRFL_IO),
> +    GCSR_OFF(CNTC),
> +    GCSR_OFF_FLAGS(TICLR, CSRFL_IO),
> +    GCSR_GSPR(GSTAT),
> +    GCSR_GSPR(GCFG),
> +    GCSR_GSPR(GINTC),
> +    GCSR_GSPR(GCNTC),
> +    GCSR_OFF(LLBCTL),
> +    GCSR_GSPR(IMPCTL1),
> +    GCSR_GSPR(IMPCTL2),
> +    GCSR_OFF(TLBRENTRY),
> +    GCSR_OFF(TLBRBADV),
> +    GCSR_OFF(TLBRERA),
> +    GCSR_OFF(TLBRSAVE),
> +    GCSR_OFF(TLBRELO0),
> +    GCSR_OFF(TLBRELO1),
> +    GCSR_OFF(TLBREHI),
> +    GCSR_OFF(TLBRPRMD),
> +    GCSR_GSPR(MERRCTL),
> +    GCSR_GSPR(MERRINFO1),
> +    GCSR_GSPR(MERRINFO2),
> +    GCSR_GSPR(MERRENTRY),
> +    GCSR_GSPR(MERRERA),
> +    GCSR_GSPR(MERRSAVE),
> +    GCSR_GSPR(CTAG),
> +    GCSR_OFF_ARRAY(DMW, 0),
> +    GCSR_OFF_ARRAY(DMW, 1),
> +    GCSR_OFF_ARRAY(DMW, 2),
> +    GCSR_OFF_ARRAY(DMW, 3),
> +    GCSR_GSPR(DBG),
> +    GCSR_GSPR(DERA),
> +    GCSR_GSPR(DSAVE),
> +};
> +
>   CSRInfo *get_csr(unsigned int csr_num)
>   {
>       CSRInfo *csr;
> @@ -151,6 +257,22 @@ CSRInfo *get_csr(unsigned int csr_num)
>       return csr;
>   }
>   
> +CSRInfo *get_gcsr(unsigned int csr_num)
> +{
> +    CSRInfo *csr;
> +
> +    if (csr_num >= ARRAY_SIZE(gcsr_info)) {
> +        return NULL;
> +    }
> +
> +    csr = &gcsr_info[csr_num];
> +    if (csr->offset == 0) {
> +        return NULL;
> +    }
> +
> +    return csr;
> +}
> +
>   bool set_csr_flag(unsigned int csr_num, int flag)
>   {
>       CSRInfo *csr;
> diff --git a/target/loongarch/csr.h b/target/loongarch/csr.h
> index 81a656baae..a0846353de 100644
> --- a/target/loongarch/csr.h
> +++ b/target/loongarch/csr.h
> @@ -14,6 +14,8 @@ enum {
>       CSRFL_EXITTB   = (1 << 1),
>       CSRFL_IO       = (1 << 2),
>       CSRFL_UNUSED   = (1 << 3),
> +    CSRFL_GUEST_READONLY = (1 << 4),
> +    CSRFL_GSPR     = (1 << 5),
>   };
>   
>   typedef struct {
> @@ -25,5 +27,6 @@ typedef struct {
>   } CSRInfo;
>   
>   CSRInfo *get_csr(unsigned int csr_num);
> +CSRInfo *get_gcsr(unsigned int csr_num);
>   bool set_csr_flag(unsigned int csr_num, int flag);
>   #endif /* TARGET_LOONGARCH_CSR_H */
> diff --git a/target/loongarch/disas.c b/target/loongarch/disas.c
> index 3249ab7ac6..b282da6ea6 100644
> --- a/target/loongarch/disas.c
> +++ b/target/loongarch/disas.c
> @@ -698,6 +698,16 @@ INSN(tlbfill,      empty)
>   INSN(tlbclr,       empty)
>   INSN(tlbflush,     empty)
>   INSN(invtlb,       i_rr)
> +INSN(gcsrrd,       r_csr)
> +INSN(gcsrwr,       r_csr)
> +INSN(gcsrxchg,     rr_csr)
> +INSN(gtlbclr,      empty)
> +INSN(gtlbflush,    empty)
> +INSN(gtlbsrch,     empty)
> +INSN(gtlbrd,       empty)
> +INSN(gtlbwr,       empty)
> +INSN(gtlbfill,     empty)
> +INSN(hvcl,         i)
>   INSN(cacop,        cop_r_i)
>   INSN(lddir,        rr_i)
>   INSN(ldpte,        j_i)
> diff --git a/target/loongarch/insns.decode b/target/loongarch/insns.decode
> index 3089d42044..b19c40b423 100644
> --- a/target/loongarch/insns.decode
> +++ b/target/loongarch/insns.decode
> @@ -493,6 +493,23 @@ bgeu            0110 11 ................ ..... .....     @rr_offs16
>     csrxchg           0000 0100 .............. ..... .....     @rr_csr
>   }
>   
> +#
> +# LVZ (LoongArch Virtualization) instructions
> +#
> +{
> +  gcsrrd            0000 0101 .............. 00000 .....     @r_csr
> +  gcsrwr            0000 0101 .............. 00001 .....     @r_csr
> +  gcsrxchg          0000 0101 .............. ..... .....     @rr_csr
> +}
> +
> +gtlbclr          0000 01100100 10000 01000 00000 00001    @empty
> +gtlbflush        0000 01100100 10000 01001 00000 00001    @empty
> +gtlbsrch         0000 01100100 10000 01010 00000 00001    @empty
> +gtlbrd           0000 01100100 10000 01011 00000 00001    @empty
> +gtlbwr           0000 01100100 10000 01100 00000 00001    @empty
> +gtlbfill         0000 01100100 10000 01101 00000 00001    @empty
> +hvcl             0000 0000 0010 1011 1 ...............    @i15
> +
>   iocsrrd_b        0000 01100100 10000 00000 ..... .....    @rr
>   iocsrrd_h        0000 01100100 10000 00001 ..... .....    @rr
>   iocsrrd_w        0000 01100100 10000 00010 ..... .....    @rr
> diff --git a/target/loongarch/internals.h b/target/loongarch/internals.h
> index e01dbed40f..8a06ab9868 100644
> --- a/target/loongarch/internals.h
> +++ b/target/loongarch/internals.h
> @@ -32,14 +32,18 @@ void restore_fp_status(CPULoongArchState *env);
>   extern const VMStateDescription vmstate_loongarch_cpu;
>   
>   void loongarch_cpu_set_irq(void *opaque, int irq, int level);
> +void loongarch_cpu_set_irq_guest(void *opaque, int irq, int level);
>   
>   void loongarch_constant_timer_cb(void *opaque);
> +void loongarch_constant_timer_cb_guest(void *opaque);
>   uint64_t cpu_loongarch_get_constant_timer_counter(LoongArchCPU *cpu);
> -uint64_t cpu_loongarch_get_constant_timer_ticks(LoongArchCPU *cpu);
> +uint64_t cpu_loongarch_get_constant_timer_ticks(LoongArchCPU *cpu, bool guest);
> +void cpu_loongarch_set_guest_timer(LoongArchCPU *cpu, bool on);
>   void cpu_loongarch_store_constant_timer_config(LoongArchCPU *cpu,
> -                                               uint64_t value);
> +                                               uint64_t value, bool guest);
>   bool loongarch_cpu_has_work(CPUState *cs);
>   bool cpu_loongarch_hw_interrupts_pending(CPULoongArchState *env);
> +bool loongarch_guest_has_interrupt(CPULoongArchState *env);
>   #endif /* !CONFIG_USER_ONLY */
>   
>   uint64_t read_fcc(CPULoongArchState *env);
> diff --git a/target/loongarch/kvm/kvm.c b/target/loongarch/kvm/kvm.c
> index 9d844c4905..114f115e90 100644
> --- a/target/loongarch/kvm/kvm.c
> +++ b/target/loongarch/kvm/kvm.c
> @@ -28,6 +28,7 @@
>   #include "cpu-csr.h"
>   #include "kvm_loongarch.h"
>   #include "trace.h"
> +#include "exec/target_long.h"
>   
>   static bool cap_has_mp_state;
>   static unsigned int brk_insn;
> diff --git a/target/loongarch/machine.c b/target/loongarch/machine.c
> index 4db53fec26..df7a7c9075 100644
> --- a/target/loongarch/machine.c
> +++ b/target/loongarch/machine.c
> @@ -197,11 +197,101 @@ static const VMStateDescription vmstate_tlb = {
>       .version_id = 0,
>       .minimum_version_id = 0,
>       .needed = tlb_needed,
> -    .fields = (const VMStateField[]) {
> -        VMSTATE_STRUCT_ARRAY(env.tlb, LoongArchCPU, LOONGARCH_TLB_MAX,
> -                             0, vmstate_tlb_entry, LoongArchTLB),
> -        VMSTATE_END_OF_LIST()
> -    }
> +    .fields =
> +        (const VMStateField[]){
> +            VMSTATE_STRUCT_ARRAY(env.tlb, LoongArchCPU, LOONGARCH_TLB_MAX, 0,
> +                                 vmstate_tlb_entry, LoongArchTLB),
> +            VMSTATE_END_OF_LIST() }
> +};
> +
> +static bool lvz_needed(void *opaque)
> +{
> +    LoongArchCPU *cpu = opaque;
> +
> +    return FIELD_EX32(cpu->env.cpucfg[2], CPUCFG2, LVZ);
> +}
> +
> +static const VMStateDescription vmstate_lvz = {
> +    .name = "cpu/lvz",
> +    .version_id = 1,
> +    .minimum_version_id = 1,
> +    .needed = lvz_needed,
> +    .fields =
> +        (const VMStateField[]){
> +            VMSTATE_UINT64(env.CSR_GSTAT, LoongArchCPU),
> +            VMSTATE_UINT64(env.CSR_GCFG, LoongArchCPU),
> +            VMSTATE_UINT64(env.CSR_GINTC, LoongArchCPU),
> +            VMSTATE_UINT64(env.CSR_GCNTC, LoongArchCPU),
> +            VMSTATE_UINT64(env.CSR_GTLBC, LoongArchCPU),
> +            VMSTATE_UINT64(env.CSR_TRGP, LoongArchCPU),
> +
> +            VMSTATE_UINT64(env.GCSR_CRMD, LoongArchCPU),
> +            VMSTATE_UINT64(env.GCSR_PRMD, LoongArchCPU),
> +            VMSTATE_UINT64(env.GCSR_EUEN, LoongArchCPU),
> +            VMSTATE_UINT64(env.GCSR_MISC, LoongArchCPU),
> +            VMSTATE_UINT64(env.GCSR_ECFG, LoongArchCPU),
> +            VMSTATE_UINT64(env.GCSR_ESTAT, LoongArchCPU),
> +            VMSTATE_UINT64(env.GCSR_ERA, LoongArchCPU),
> +            VMSTATE_UINT64(env.GCSR_BADV, LoongArchCPU),
> +            VMSTATE_UINT64(env.GCSR_BADI, LoongArchCPU),
> +            VMSTATE_UINT64(env.GCSR_EENTRY, LoongArchCPU),
> +
> +            VMSTATE_UINT64(env.GCSR_TLBIDX, LoongArchCPU),
> +            VMSTATE_UINT64(env.GCSR_TLBEHI, LoongArchCPU),
> +            VMSTATE_UINT64(env.GCSR_TLBELO0, LoongArchCPU),
> +            VMSTATE_UINT64(env.GCSR_TLBELO1, LoongArchCPU),
> +            VMSTATE_UINT64(env.GCSR_ASID, LoongArchCPU),
> +            VMSTATE_UINT64(env.GCSR_PGDL, LoongArchCPU),
> +            VMSTATE_UINT64(env.GCSR_PGDH, LoongArchCPU),
> +            VMSTATE_UINT64(env.GCSR_PGD, LoongArchCPU),
> +            VMSTATE_UINT64(env.GCSR_PWCL, LoongArchCPU),
> +            VMSTATE_UINT64(env.GCSR_PWCH, LoongArchCPU),
> +            VMSTATE_UINT64(env.GCSR_STLBPS, LoongArchCPU),
> +            VMSTATE_UINT64(env.GCSR_RVACFG, LoongArchCPU),
> +
> +            VMSTATE_UINT64(env.GCSR_CPUID, LoongArchCPU),
> +            VMSTATE_UINT64(env.GCSR_PRCFG1, LoongArchCPU),
> +            VMSTATE_UINT64(env.GCSR_PRCFG2, LoongArchCPU),
> +            VMSTATE_UINT64(env.GCSR_PRCFG3, LoongArchCPU),
> +            VMSTATE_UINT64_ARRAY(env.GCSR_SAVE, LoongArchCPU, 16),
> +
> +            VMSTATE_UINT64(env.GCSR_TID, LoongArchCPU),
> +            VMSTATE_UINT64(env.GCSR_TCFG, LoongArchCPU),
> +            VMSTATE_UINT64(env.GCSR_TVAL, LoongArchCPU),
> +            VMSTATE_UINT64(env.GCSR_CNTC, LoongArchCPU),
> +            VMSTATE_UINT64(env.GCSR_TICLR, LoongArchCPU),
> +            VMSTATE_UINT64(env.GCSR_LLBCTL, LoongArchCPU),
> +
> +            VMSTATE_UINT64(env.GCSR_IMPCTL1, LoongArchCPU),
> +            VMSTATE_UINT64(env.GCSR_IMPCTL2, LoongArchCPU),
> +
> +            VMSTATE_UINT64(env.GCSR_TLBRENTRY, LoongArchCPU),
> +            VMSTATE_UINT64(env.GCSR_TLBRBADV, LoongArchCPU),
> +            VMSTATE_UINT64(env.GCSR_TLBRERA, LoongArchCPU),
> +            VMSTATE_UINT64(env.GCSR_TLBRSAVE, LoongArchCPU),
> +            VMSTATE_UINT64(env.GCSR_TLBRELO0, LoongArchCPU),
> +            VMSTATE_UINT64(env.GCSR_TLBRELO1, LoongArchCPU),
> +            VMSTATE_UINT64(env.GCSR_TLBREHI, LoongArchCPU),
> +            VMSTATE_UINT64(env.GCSR_TLBRPRMD, LoongArchCPU),
> +
> +            VMSTATE_UINT64(env.GCSR_MERRCTL, LoongArchCPU),
> +            VMSTATE_UINT64(env.GCSR_MERRINFO1, LoongArchCPU),
> +            VMSTATE_UINT64(env.GCSR_MERRINFO2, LoongArchCPU),
> +            VMSTATE_UINT64(env.GCSR_MERRENTRY, LoongArchCPU),
> +            VMSTATE_UINT64(env.GCSR_MERRERA, LoongArchCPU),
> +            VMSTATE_UINT64(env.GCSR_MERRSAVE, LoongArchCPU),
> +            VMSTATE_UINT64(env.GCSR_CTAG, LoongArchCPU),
> +
> +            VMSTATE_UINT64_ARRAY(env.GCSR_DMW, LoongArchCPU, 4),
> +
> +            VMSTATE_UINT64(env.GCSR_DBG, LoongArchCPU),
> +            VMSTATE_UINT64(env.GCSR_DERA, LoongArchCPU),
> +            VMSTATE_UINT64(env.GCSR_DSAVE, LoongArchCPU),
> +
> +            VMSTATE_BOOL(env.guest, LoongArchCPU),
> +            VMSTATE_STRUCT_ARRAY(env.gtlb, LoongArchCPU, LOONGARCH_TLB_MAX, 0,
> +                                 vmstate_tlb_entry, LoongArchTLB),
> +            VMSTATE_END_OF_LIST() },
>   };
>   #endif
>   
> @@ -210,83 +300,78 @@ const VMStateDescription vmstate_loongarch_cpu = {
>       .name = "cpu",
>       .version_id = 4,
>       .minimum_version_id = 4,
> -    .fields = (const VMStateField[]) {
> -        VMSTATE_UINT64_ARRAY(env.gpr, LoongArchCPU, 32),
> -        VMSTATE_UINT64(env.pc, LoongArchCPU),
> -
> -        /* Remaining CSRs */
> -        VMSTATE_UINT64(env.CSR_CRMD, LoongArchCPU),
> -        VMSTATE_UINT64(env.CSR_PRMD, LoongArchCPU),
> -        VMSTATE_UINT64(env.CSR_EUEN, LoongArchCPU),
> -        VMSTATE_UINT64(env.CSR_MISC, LoongArchCPU),
> -        VMSTATE_UINT64(env.CSR_ECFG, LoongArchCPU),
> -        VMSTATE_UINT64(env.CSR_ESTAT, LoongArchCPU),
> -        VMSTATE_UINT64(env.CSR_ERA, LoongArchCPU),
> -        VMSTATE_UINT64(env.CSR_BADV, LoongArchCPU),
> -        VMSTATE_UINT64(env.CSR_BADI, LoongArchCPU),
> -        VMSTATE_UINT64(env.CSR_EENTRY, LoongArchCPU),
> -        VMSTATE_UINT64(env.CSR_TLBIDX, LoongArchCPU),
> -        VMSTATE_UINT64(env.CSR_TLBEHI, LoongArchCPU),
> -        VMSTATE_UINT64(env.CSR_TLBELO0, LoongArchCPU),
> -        VMSTATE_UINT64(env.CSR_TLBELO1, LoongArchCPU),
> -        VMSTATE_UINT64(env.CSR_ASID, LoongArchCPU),
> -        VMSTATE_UINT64(env.CSR_PGDL, LoongArchCPU),
> -        VMSTATE_UINT64(env.CSR_PGDH, LoongArchCPU),
> -        VMSTATE_UINT64(env.CSR_PGD, LoongArchCPU),
> -        VMSTATE_UINT64(env.CSR_PWCL, LoongArchCPU),
> -        VMSTATE_UINT64(env.CSR_PWCH, LoongArchCPU),
> -        VMSTATE_UINT64(env.CSR_STLBPS, LoongArchCPU),
> -        VMSTATE_UINT64(env.CSR_RVACFG, LoongArchCPU),
> -        VMSTATE_UINT64(env.CSR_PRCFG1, LoongArchCPU),
> -        VMSTATE_UINT64(env.CSR_PRCFG2, LoongArchCPU),
> -        VMSTATE_UINT64(env.CSR_PRCFG3, LoongArchCPU),
> -        VMSTATE_UINT64_ARRAY(env.CSR_SAVE, LoongArchCPU, 16),
> -        VMSTATE_UINT64(env.CSR_TID, LoongArchCPU),
> -        VMSTATE_UINT64(env.CSR_TCFG, LoongArchCPU),
> -        VMSTATE_UINT64(env.CSR_TVAL, LoongArchCPU),
> -        VMSTATE_UINT64(env.CSR_CNTC, LoongArchCPU),
> -        VMSTATE_UINT64(env.CSR_TICLR, LoongArchCPU),
> -        VMSTATE_UINT64(env.CSR_LLBCTL, LoongArchCPU),
> -        VMSTATE_UINT64(env.CSR_IMPCTL1, LoongArchCPU),
> -        VMSTATE_UINT64(env.CSR_IMPCTL2, LoongArchCPU),
> -        VMSTATE_UINT64(env.CSR_TLBRENTRY, LoongArchCPU),
> -        VMSTATE_UINT64(env.CSR_TLBRBADV, LoongArchCPU),
> -        VMSTATE_UINT64(env.CSR_TLBRERA, LoongArchCPU),
> -        VMSTATE_UINT64(env.CSR_TLBRSAVE, LoongArchCPU),
> -        VMSTATE_UINT64(env.CSR_TLBRELO0, LoongArchCPU),
> -        VMSTATE_UINT64(env.CSR_TLBRELO1, LoongArchCPU),
> -        VMSTATE_UINT64(env.CSR_TLBREHI, LoongArchCPU),
> -        VMSTATE_UINT64(env.CSR_TLBRPRMD, LoongArchCPU),
> -        VMSTATE_UINT64(env.CSR_MERRCTL, LoongArchCPU),
> -        VMSTATE_UINT64(env.CSR_MERRINFO1, LoongArchCPU),
> -        VMSTATE_UINT64(env.CSR_MERRINFO2, LoongArchCPU),
> -        VMSTATE_UINT64(env.CSR_MERRENTRY, LoongArchCPU),
> -        VMSTATE_UINT64(env.CSR_MERRERA, LoongArchCPU),
> -        VMSTATE_UINT64(env.CSR_MERRSAVE, LoongArchCPU),
> -        VMSTATE_UINT64(env.CSR_CTAG, LoongArchCPU),
> -        VMSTATE_UINT64_ARRAY(env.CSR_DMW, LoongArchCPU, 4),
> -
> -        /* Debug CSRs */
> -        VMSTATE_UINT64(env.CSR_DBG, LoongArchCPU),
> -        VMSTATE_UINT64(env.CSR_DERA, LoongArchCPU),
> -        VMSTATE_UINT64(env.CSR_DSAVE, LoongArchCPU),
> -
> -        VMSTATE_UINT64(kvm_state_counter, LoongArchCPU),
> -        /* PV steal time */
> -        VMSTATE_UINT64(env.stealtime.guest_addr, LoongArchCPU),
> -
> -        VMSTATE_END_OF_LIST()
> -    },
> -    .subsections = (const VMStateDescription * const []) {
> -        &vmstate_fpu,
> -        &vmstate_lsx,
> -        &vmstate_lasx,
> +    .fields =
> +        (const VMStateField[]){
> +            VMSTATE_UINT64_ARRAY(env.gpr, LoongArchCPU, 32),
> +            VMSTATE_UINT64(env.pc, LoongArchCPU),
> +
> +            /* Remaining CSRs */
> +            VMSTATE_UINT64(env.CSR_CRMD, LoongArchCPU),
> +            VMSTATE_UINT64(env.CSR_PRMD, LoongArchCPU),
> +            VMSTATE_UINT64(env.CSR_EUEN, LoongArchCPU),
> +            VMSTATE_UINT64(env.CSR_MISC, LoongArchCPU),
> +            VMSTATE_UINT64(env.CSR_ECFG, LoongArchCPU),
> +            VMSTATE_UINT64(env.CSR_ESTAT, LoongArchCPU),
> +            VMSTATE_UINT64(env.CSR_ERA, LoongArchCPU),
> +            VMSTATE_UINT64(env.CSR_BADV, LoongArchCPU),
> +            VMSTATE_UINT64(env.CSR_BADI, LoongArchCPU),
> +            VMSTATE_UINT64(env.CSR_EENTRY, LoongArchCPU),
> +            VMSTATE_UINT64(env.CSR_TLBIDX, LoongArchCPU),
> +            VMSTATE_UINT64(env.CSR_TLBEHI, LoongArchCPU),
> +            VMSTATE_UINT64(env.CSR_TLBELO0, LoongArchCPU),
> +            VMSTATE_UINT64(env.CSR_TLBELO1, LoongArchCPU),
> +            VMSTATE_UINT64(env.CSR_ASID, LoongArchCPU),
> +            VMSTATE_UINT64(env.CSR_PGDL, LoongArchCPU),
> +            VMSTATE_UINT64(env.CSR_PGDH, LoongArchCPU),
> +            VMSTATE_UINT64(env.CSR_PGD, LoongArchCPU),
> +            VMSTATE_UINT64(env.CSR_PWCL, LoongArchCPU),
> +            VMSTATE_UINT64(env.CSR_PWCH, LoongArchCPU),
> +            VMSTATE_UINT64(env.CSR_STLBPS, LoongArchCPU),
> +            VMSTATE_UINT64(env.CSR_RVACFG, LoongArchCPU),
> +            VMSTATE_UINT64(env.CSR_PRCFG1, LoongArchCPU),
> +            VMSTATE_UINT64(env.CSR_PRCFG2, LoongArchCPU),
> +            VMSTATE_UINT64(env.CSR_PRCFG3, LoongArchCPU),
> +            VMSTATE_UINT64_ARRAY(env.CSR_SAVE, LoongArchCPU, 16),
> +            VMSTATE_UINT64(env.CSR_TID, LoongArchCPU),
> +            VMSTATE_UINT64(env.CSR_TCFG, LoongArchCPU),
> +            VMSTATE_UINT64(env.CSR_TVAL, LoongArchCPU),
> +            VMSTATE_UINT64(env.CSR_CNTC, LoongArchCPU),
> +            VMSTATE_UINT64(env.CSR_TICLR, LoongArchCPU),
> +            VMSTATE_UINT64(env.CSR_LLBCTL, LoongArchCPU),
> +            VMSTATE_UINT64(env.CSR_IMPCTL1, LoongArchCPU),
> +            VMSTATE_UINT64(env.CSR_IMPCTL2, LoongArchCPU),
> +            VMSTATE_UINT64(env.CSR_TLBRENTRY, LoongArchCPU),
> +            VMSTATE_UINT64(env.CSR_TLBRBADV, LoongArchCPU),
> +            VMSTATE_UINT64(env.CSR_TLBRERA, LoongArchCPU),
> +            VMSTATE_UINT64(env.CSR_TLBRSAVE, LoongArchCPU),
> +            VMSTATE_UINT64(env.CSR_TLBRELO0, LoongArchCPU),
> +            VMSTATE_UINT64(env.CSR_TLBRELO1, LoongArchCPU),
> +            VMSTATE_UINT64(env.CSR_TLBREHI, LoongArchCPU),
> +            VMSTATE_UINT64(env.CSR_TLBRPRMD, LoongArchCPU),
> +            VMSTATE_UINT64(env.CSR_MERRCTL, LoongArchCPU),
> +            VMSTATE_UINT64(env.CSR_MERRINFO1, LoongArchCPU),
> +            VMSTATE_UINT64(env.CSR_MERRINFO2, LoongArchCPU),
> +            VMSTATE_UINT64(env.CSR_MERRENTRY, LoongArchCPU),
> +            VMSTATE_UINT64(env.CSR_MERRERA, LoongArchCPU),
> +            VMSTATE_UINT64(env.CSR_MERRSAVE, LoongArchCPU),
> +            VMSTATE_UINT64(env.CSR_CTAG, LoongArchCPU),
> +            VMSTATE_UINT64_ARRAY(env.CSR_DMW, LoongArchCPU, 4),
> +
> +            /* Debug CSRs */
> +            VMSTATE_UINT64(env.CSR_DBG, LoongArchCPU),
> +            VMSTATE_UINT64(env.CSR_DERA, LoongArchCPU),
> +            VMSTATE_UINT64(env.CSR_DSAVE, LoongArchCPU),
> +
> +            VMSTATE_UINT64(kvm_state_counter, LoongArchCPU),
> +            /* PV steal time */
> +            VMSTATE_UINT64(env.stealtime.guest_addr, LoongArchCPU),
> +
> +            VMSTATE_END_OF_LIST() },
> +    .subsections =
> +        (const VMStateDescription *const[]) {
> +            &vmstate_fpu, &vmstate_lsx, &vmstate_lasx,
>   #if defined(CONFIG_TCG) && !defined(CONFIG_USER_ONLY)
> -        &vmstate_tlb,
> +            &vmstate_tlb, &vmstate_lvz,
>   #endif
> -        &vmstate_lbt,
> -        &vmstate_msgint,
> -        &vmstate_pmu,
> -        NULL
> -    }
> +            &vmstate_lbt, &vmstate_msgint, &vmstate_pmu, NULL }
>   };
> diff --git a/target/loongarch/tcg/constant_timer.c b/target/loongarch/tcg/constant_timer.c
> index 1851f53fd6..97892e3ff9 100644
> --- a/target/loongarch/tcg/constant_timer.c
> +++ b/target/loongarch/tcg/constant_timer.c
> @@ -20,29 +20,62 @@ uint64_t cpu_loongarch_get_constant_timer_counter(LoongArchCPU *cpu)
>       return qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) / TIMER_PERIOD;
>   }
>   
> -uint64_t cpu_loongarch_get_constant_timer_ticks(LoongArchCPU *cpu)
> +uint64_t cpu_loongarch_get_constant_timer_ticks(LoongArchCPU *cpu, bool guest)
>   {
>       uint64_t now, expire;
> +    CPULoongArchState *env = &cpu->env;
> +
> +    if (guest && !env->guest) {
> +        return env->GCSR_TVAL;
> +    }
>   
>       now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
> -    expire = timer_expire_time_ns(&cpu->timer);
> +    expire = timer_expire_time_ns(guest ? &cpu->guest_timer : &cpu->timer);
>   
>       return (expire - now) / TIMER_PERIOD;
>   }
>   
>   void cpu_loongarch_store_constant_timer_config(LoongArchCPU *cpu,
> -                                               uint64_t value)
> +                                               uint64_t value, bool guest)
>   {
>       CPULoongArchState *env = &cpu->env;
>       uint64_t now, next;
> +    QEMUTimer *timer = guest ? &cpu->guest_timer : &cpu->timer;
> +
> +    SET_CSR_IF(guest, TCFG, value);
> +
> +    if (guest && !env->guest) {
> +        return;
> +    }
>   
> -    env->CSR_TCFG = value;
>       if (value & CONSTANT_TIMER_ENABLE) {
>           now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
>           next = now + (value & CONSTANT_TIMER_TICK_MASK) * TIMER_PERIOD;
> -        timer_mod(&cpu->timer, next);
> +        timer_mod(timer, next);
>       } else {
> -        timer_del(&cpu->timer);
> +        timer_del(timer);
> +    }
> +}
> +
> +void cpu_loongarch_set_guest_timer(LoongArchCPU *cpu, bool on)
> +{
> +    CPULoongArchState *env = &cpu->env;
> +    uint64_t now, next, ticks;
> +
> +    if (!(env->GCSR_TCFG & CONSTANT_TIMER_ENABLE)) {
> +        return;
> +    }
> +
> +    if (on) {
> +        now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
> +        ticks = env->GCSR_TVAL ? env->GCSR_TVAL :
> +                                 (env->GCSR_TCFG & CONSTANT_TIMER_TICK_MASK);
> +        next = now + ticks * TIMER_PERIOD;
> +        env->GCSR_TVAL = 0;
> +        timer_mod(&cpu->guest_timer, next);
> +    } else {
> +        env->GCSR_TVAL = cpu_loongarch_get_constant_timer_ticks(cpu, true);
> +        timer_del(&cpu->guest_timer);
>       }
>   }
>   
> @@ -62,3 +95,20 @@ void loongarch_constant_timer_cb(void *opaque)
>   
>       loongarch_cpu_set_irq(opaque, IRQ_TIMER, 1);
>   }
> +
> +void loongarch_constant_timer_cb_guest(void *opaque)
> +{
> +    LoongArchCPU *cpu = opaque;
> +    CPULoongArchState *env = &cpu->env;
> +    uint64_t now, next;
> +
> +    if (FIELD_EX64(env->GCSR_TCFG, CSR_TCFG, PERIODIC)) {
> +        now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
> +        next = now + (env->GCSR_TCFG & CONSTANT_TIMER_TICK_MASK) * TIMER_PERIOD;
> +        timer_mod(&cpu->guest_timer, next);
> +    } else {
> +        env->GCSR_TCFG = FIELD_DP64(env->GCSR_TCFG, CSR_TCFG, EN, 0);
> +    }
> +
> +    loongarch_cpu_set_irq_guest(opaque, IRQ_TIMER, 1);
> +}
> diff --git a/target/loongarch/tcg/csr_helper.c b/target/loongarch/tcg/csr_helper.c
> index cd35ca93c7..a680c6cbfe 100644
> --- a/target/loongarch/tcg/csr_helper.c
> +++ b/target/loongarch/tcg/csr_helper.c
> @@ -58,6 +58,25 @@ target_ulong helper_csrrd_pgd(CPULoongArchState *env)
>       return v;
>   }
>   
> +target_ulong helper_gcsrrd_pgd(CPULoongArchState *env)
> +{
> +    int64_t v;
> +
> +    if (env->GCSR_TLBRERA & 0x1) {
> +        v = env->GCSR_TLBRBADV;
> +    } else {
> +        v = env->GCSR_BADV;
> +    }
> +
> +    if ((v >> 63) & 0x1) {
> +        v = env->GCSR_PGDH;
> +    } else {
> +        v = env->GCSR_PGDL;
> +    }
> +
> +    return v;
> +}
> +
>   target_ulong helper_csrrd_cpuid(CPULoongArchState *env)
>   {
>       LoongArchCPU *lac = env_archcpu(env);
> @@ -71,7 +90,14 @@ target_ulong helper_csrrd_tval(CPULoongArchState *env)
>   {
>       LoongArchCPU *cpu = env_archcpu(env);
>   
> -    return cpu_loongarch_get_constant_timer_ticks(cpu);
> +    return cpu_loongarch_get_constant_timer_ticks(cpu, false);
> +}
> +
> +target_ulong helper_gcsrrd_tval(CPULoongArchState *env)
> +{
> +    LoongArchCPU *cpu = env_archcpu(env);
> +
> +    return cpu_loongarch_get_constant_timer_ticks(cpu, true);
>   }
>   
>   target_ulong helper_csrrd_msgir(CPULoongArchState *env)
> @@ -105,6 +131,27 @@ target_ulong helper_csrwr_estat(CPULoongArchState *env, target_ulong val)
>       return old_v;
>   }
>   
> +target_ulong helper_gcsrwr_estat(CPULoongArchState *env, target_ulong val)
> +{
> +    int64_t old_v = env->GCSR_ESTAT;
> +
> +    env->GCSR_ESTAT = deposit64(env->GCSR_ESTAT, 0, 2, val);
> +    if (!env->guest) {
> +        env->GCSR_ESTAT =
> +            deposit64(env->GCSR_ESTAT, 2, 11, extract64(val, 2, 11));
> +        if (extract64(val, 2, 8) &
> +            FIELD_EX64(env->CSR_GINTC, CSR_GINTC, HWIC)) {
> +            env->CSR_ESTAT = deposit64(env->CSR_ESTAT, 2, 8,
> +                                       extract64(env->CSR_ESTAT, 2, 8) &
> +                                           ~extract64(val, 2, 8));
> +        }
> +        env->GCSR_ESTAT =
> +            deposit64(env->GCSR_ESTAT, 16, 15, extract64(val, 16, 15));
> +    }
> +
> +    return old_v;
> +}
> +
>   target_ulong helper_csrwr_asid(CPULoongArchState *env, target_ulong val)
>   {
>       int64_t old_v = env->CSR_ASID;
> @@ -117,12 +164,33 @@ target_ulong helper_csrwr_asid(CPULoongArchState *env, target_ulong val)
>       return old_v;
>   }
>   
> +target_ulong helper_gcsrwr_asid(CPULoongArchState *env, target_ulong val)
> +{
> +    int64_t old_v = env->GCSR_ASID;
> +
> +    env->GCSR_ASID = deposit64(env->GCSR_ASID, 0, 10, val);
> +    if (old_v != env->GCSR_ASID) {
> +        tlb_flush(env_cpu(env));
> +    }
> +    return old_v;
> +}
> +
>   target_ulong helper_csrwr_tcfg(CPULoongArchState *env, target_ulong val)
>   {
>       LoongArchCPU *cpu = env_archcpu(env);
>       int64_t old_v = env->CSR_TCFG;
>   
> -    cpu_loongarch_store_constant_timer_config(cpu, val);
> +    cpu_loongarch_store_constant_timer_config(cpu, val, false);
> +
> +    return old_v;
> +}
> +
> +target_ulong helper_gcsrwr_tcfg(CPULoongArchState *env, target_ulong val)
> +{
> +    LoongArchCPU *cpu = env_archcpu(env);
> +    int64_t old_v = env->GCSR_TCFG;
> +
> +    cpu_loongarch_store_constant_timer_config(cpu, val, true);
>   
>       return old_v;
>   }
> @@ -140,6 +208,61 @@ target_ulong helper_csrwr_ticlr(CPULoongArchState *env, target_ulong val)
>       return old_v;
>   }
>   
> +target_ulong helper_gcsrwr_ticlr(CPULoongArchState *env, target_ulong val)
> +{
> +    LoongArchCPU *cpu = env_archcpu(env);
> +    int64_t old_v = 0;
> +
> +    if (val & 0x1) {
> +        bql_lock();
> +        loongarch_cpu_set_irq_guest(cpu, IRQ_TIMER, 0);
> +        bql_unlock();
> +    }
> +    return old_v;
> +}
> +
> +target_ulong helper_csrwr_gstat(CPULoongArchState *env, target_ulong val)
> +{
> +    int64_t old_v = env->CSR_GSTAT;
> +    uint8_t old_gid = FIELD_EX64(env->CSR_GSTAT, CSR_GSTAT, GID);
> +
> +    env->CSR_GSTAT = FIELD_DP64(env->CSR_GSTAT, CSR_GSTAT, PVM,
> +                                FIELD_EX64(val, CSR_GSTAT, PVM));
> +    env->CSR_GSTAT = FIELD_DP64(env->CSR_GSTAT, CSR_GSTAT, GID,
> +                                FIELD_EX64(val, CSR_GSTAT, GID));
> +
> +    if (old_gid != FIELD_EX64(env->CSR_GSTAT, CSR_GSTAT, GID)) {
> +        tlb_flush(env_cpu(env));
> +    }
> +
> +    return old_v;
> +}
> +
> +target_ulong helper_csrwr_gtlbc(CPULoongArchState *env, target_ulong val)
> +{
> +    int64_t old_v = env->CSR_GTLBC;
> +    uint8_t old_use_tgid = FIELD_EX64(old_v, CSR_GTLBC, USETGID);
> +    uint8_t old_tgid = FIELD_EX64(old_v, CSR_GTLBC, TGID);
> +
> +    env->CSR_GTLBC = val;
> +    if (old_use_tgid != FIELD_EX64(env->CSR_GTLBC, CSR_GTLBC, USETGID) ||
> +        old_tgid != FIELD_EX64(env->CSR_GTLBC, CSR_GTLBC, TGID)) {
> +        tlb_flush(env_cpu(env));
> +    }
> +
> +    return old_v;
> +}
> +
> +target_ulong helper_csrwr_gintc(CPULoongArchState *env, target_ulong val)
> +{
> +    int64_t old_v = env->CSR_GINTC;
> +
> +    env->CSR_GINTC = val & 0xffff00;
> +    env->GCSR_ESTAT = deposit64(env->GCSR_ESTAT, 2, 8, extract64(val, 0, 8));
> +
> +    return old_v;
> +}
> +
>   target_ulong helper_csrwr_pwcl(CPULoongArchState *env, target_ulong val)
>   {
>       uint8_t shift, ptbase;
> diff --git a/target/loongarch/tcg/helper.h b/target/loongarch/tcg/helper.h
> index 8a6c62f116..648328a7ef 100644
> --- a/target/loongarch/tcg/helper.h
> +++ b/target/loongarch/tcg/helper.h
> @@ -14,7 +14,7 @@ DEF_HELPER_FLAGS_3(asrtgt_d, TCG_CALL_NO_WG, void, env, tl, tl)
>   
>   DEF_HELPER_FLAGS_3(crc32, TCG_CALL_NO_RWG_SE, tl, tl, tl, tl)
>   DEF_HELPER_FLAGS_3(crc32c, TCG_CALL_NO_RWG_SE, tl, tl, tl, tl)
> -DEF_HELPER_FLAGS_2(cpucfg, TCG_CALL_NO_RWG_SE, tl, env, tl)
> +DEF_HELPER_FLAGS_2(cpucfg, TCG_CALL_NO_WG_SE, tl, env, tl)
>   
>   /* Floating-point helper */
>   DEF_HELPER_FLAGS_3(fadd_s, TCG_CALL_NO_WG, i64, env, i64, i64)
> @@ -98,14 +98,23 @@ DEF_HELPER_1(rdtime_d, i64, env)
>   #ifndef CONFIG_USER_ONLY
>   /* CSRs helper */
>   DEF_HELPER_1(csrrd_pgd, i64, env)
> +DEF_HELPER_1(gcsrrd_pgd, i64, env)
>   DEF_HELPER_1(csrrd_cpuid, i64, env)
>   DEF_HELPER_1(csrrd_tval, i64, env)
> +DEF_HELPER_1(gcsrrd_tval, i64, env)
>   DEF_HELPER_1(csrrd_msgir, i64, env)
>   DEF_HELPER_2(csrwr_stlbps, i64, env, tl)
>   DEF_HELPER_2(csrwr_estat, i64, env, tl)
> +DEF_HELPER_2(gcsrwr_estat, i64, env, tl)
>   DEF_HELPER_2(csrwr_asid, i64, env, tl)
> +DEF_HELPER_2(gcsrwr_asid, i64, env, tl)
>   DEF_HELPER_2(csrwr_tcfg, i64, env, tl)
> +DEF_HELPER_2(gcsrwr_tcfg, i64, env, tl)
>   DEF_HELPER_2(csrwr_ticlr, i64, env, tl)
> +DEF_HELPER_2(gcsrwr_ticlr, i64, env, tl)
> +DEF_HELPER_2(csrwr_gstat, i64, env, tl)
> +DEF_HELPER_2(csrwr_gtlbc, i64, env, tl)
> +DEF_HELPER_2(csrwr_gintc, i64, env, tl)
>   DEF_HELPER_2(csrwr_pwcl, i64, env, tl)
>   DEF_HELPER_2(csrwr_pwch, i64, env, tl)
>   DEF_HELPER_2(iocsrrd_b, i64, env, tl)
> @@ -124,16 +133,25 @@ DEF_HELPER_1(tlbsrch, void, env)
>   DEF_HELPER_1(tlbrd, void, env)
>   DEF_HELPER_1(tlbclr, void, env)
>   DEF_HELPER_1(tlbflush, void, env)
> -DEF_HELPER_1(invtlb_all, void, env)
> -DEF_HELPER_2(invtlb_all_g, void, env, i32)
> -DEF_HELPER_2(invtlb_all_asid, void, env, tl)
> -DEF_HELPER_3(invtlb_page_asid, void, env, tl, tl)
> -DEF_HELPER_3(invtlb_page_asid_or_g, void, env, tl, tl)
> +DEF_HELPER_4(invtlb_all, void, env, tl, i32, i32)
> +DEF_HELPER_4(invtlb_all_g, void, env, tl, i32, i32)
> +DEF_HELPER_3(invtlb_all_asid, void, env, tl, i32)
> +DEF_HELPER_4(invtlb_page_asid, void, env, tl, tl, i32)
> +DEF_HELPER_4(invtlb_page_asid_or_g, void, env, tl, tl, i32)
> +
> +DEF_HELPER_1(gtlbwr, void, env)
> +DEF_HELPER_1(gtlbfill, void, env)
> +DEF_HELPER_1(gtlbsrch, void, env)
> +DEF_HELPER_1(gtlbrd, void, env)
> +DEF_HELPER_1(gtlbclr, void, env)
> +DEF_HELPER_1(gtlbflush, void, env)
>   
>   DEF_HELPER_4(lddir, tl, env, tl, i32, i32)
>   DEF_HELPER_4(ldpte, void, env, tl, tl, i32)
>   DEF_HELPER_1(ertn, void, env)
>   DEF_HELPER_1(idle, void, env)
> +DEF_HELPER_2(hvcl, void, env, i32)
> +DEF_HELPER_1(gspr, void, env)
>   #endif
>   
>   /* LoongArch LSX  */
> diff --git a/target/loongarch/tcg/insn_trans/trans_privileged.c.inc b/target/loongarch/tcg/insn_trans/trans_privileged.c.inc
> index 2094d182ac..19b02e6a78 100644
> --- a/target/loongarch/tcg/insn_trans/trans_privileged.c.inc
> +++ b/target/loongarch/tcg/insn_trans/trans_privileged.c.inc
> @@ -39,6 +39,16 @@ GEN_FALSE_TRANS(lddir)
>   GEN_FALSE_TRANS(ertn)
>   GEN_FALSE_TRANS(dbcl)
>   GEN_FALSE_TRANS(idle)
> +GEN_FALSE_TRANS(gcsrrd)
> +GEN_FALSE_TRANS(gcsrwr)
> +GEN_FALSE_TRANS(gcsrxchg)
> +GEN_FALSE_TRANS(gtlbclr)
> +GEN_FALSE_TRANS(gtlbflush)
> +GEN_FALSE_TRANS(gtlbsrch)
> +GEN_FALSE_TRANS(gtlbrd)
> +GEN_FALSE_TRANS(gtlbwr)
> +GEN_FALSE_TRANS(gtlbfill)
> +GEN_FALSE_TRANS(hvcl)
>   
>   #else
>   
> @@ -69,8 +79,25 @@ static bool set_csr_trans_func(unsigned int csr_num, GenCSRRead readfn,
>       return true;
>   }
>   
> +static bool set_gcsr_trans_func(unsigned int csr_num, GenCSRRead readfn,
> +                                GenCSRWrite writefn)
> +{
> +    CSRInfo *csr;
> +
> +    csr = get_gcsr(csr_num);
> +    if (!csr) {
> +        return false;
> +    }
> +
> +    csr->readfn = (GenCSRFunc)readfn;
> +    csr->writefn = (GenCSRFunc)writefn;
> +    return true;
> +}
> +
>   #define SET_CSR_FUNC(NAME, read, write)                 \
>           set_csr_trans_func(LOONGARCH_CSR_##NAME, read, write)
> +#define SET_GCSR_FUNC(NAME, read, write)                \
> +        set_gcsr_trans_func(LOONGARCH_CSR_##NAME, read, write)
>   
>   void loongarch_csr_translate_init(void)
>   {
> @@ -85,14 +112,28 @@ void loongarch_csr_translate_init(void)
>       SET_CSR_FUNC(TVAL,  gen_helper_csrrd_tval, NULL);
>       SET_CSR_FUNC(TICLR, NULL, gen_helper_csrwr_ticlr);
>       SET_CSR_FUNC(MSGIR, gen_helper_csrrd_msgir, NULL);
> +    SET_CSR_FUNC(GSTAT, NULL, gen_helper_csrwr_gstat);
> +    SET_CSR_FUNC(GTLBC, NULL, gen_helper_csrwr_gtlbc);
> +    SET_CSR_FUNC(GINTC, NULL, gen_helper_csrwr_gintc);
> +
> +    SET_GCSR_FUNC(ESTAT, NULL, gen_helper_gcsrwr_estat);
> +    SET_GCSR_FUNC(ASID, NULL, gen_helper_gcsrwr_asid);
> +    SET_GCSR_FUNC(PGD, gen_helper_gcsrrd_pgd, NULL);
> +    SET_GCSR_FUNC(TCFG, NULL, gen_helper_gcsrwr_tcfg);
> +    SET_GCSR_FUNC(TVAL, gen_helper_gcsrrd_tval, NULL);
> +    SET_GCSR_FUNC(TICLR, NULL, gen_helper_gcsrwr_ticlr);
>   }
>   #undef SET_CSR_FUNC
> +#undef SET_GCSR_FUNC
>   
>   static bool check_csr_flags(DisasContext *ctx, const CSRInfo *csr, bool write)
>   {
>       if ((csr->flags & CSRFL_READONLY) && write) {
>           return false;
>       }
> +    if ((csr->flags & CSRFL_GUEST_READONLY) && ctx->guest_mode && write) {
> +        return false;
> +    }
>       if ((csr->flags & CSRFL_IO) && translator_io_start(&ctx->base)) {
>           ctx->base.is_jmp = DISAS_EXIT_UPDATE;
>       } else if ((csr->flags & CSRFL_EXITTB) && write) {
> @@ -110,12 +151,17 @@ static bool trans_csrrd(DisasContext *ctx, arg_csrrd *a)
>       if (check_plv(ctx)) {
>           return false;
>       }
> -    csr = get_csr(a->csr);
> +    csr = ctx->guest_mode ? get_gcsr(a->csr) : get_csr(a->csr);
>       if (csr == NULL) {
>           /* CSR is undefined: read as 0. */
>           dest = tcg_constant_tl(0);
>       } else {
>           check_csr_flags(ctx, csr, false);
> +        if (ctx->guest_mode && (csr->flags & CSRFL_GSPR)) {
> +            gen_helper_gspr(tcg_env);
> +            gen_set_gpr(a->rd, tcg_constant_tl(0), EXT_NONE);
> +            return true;
> +        }
>           dest = gpr_dst(ctx, a->rd, EXT_NONE);
>           readfn = (GenCSRRead)csr->readfn;
>           if (readfn) {
> @@ -137,12 +183,17 @@ static bool trans_csrwr(DisasContext *ctx, arg_csrwr *a)
>       if (check_plv(ctx)) {
>           return false;
>       }
> -    csr = get_csr(a->csr);
> +    csr = ctx->guest_mode ? get_gcsr(a->csr) : get_csr(a->csr);
>       if (csr == NULL) {
>           /* CSR is undefined: write ignored, read old_value as 0. */
>           gen_set_gpr(a->rd, tcg_constant_tl(0), EXT_NONE);
>           return true;
>       }
> +    if (ctx->guest_mode && (csr->flags & CSRFL_GSPR)) {
> +        gen_helper_gspr(tcg_env);
> +        gen_set_gpr(a->rd, tcg_constant_tl(0), EXT_NONE);
> +        return true;
> +    }
>       if (!check_csr_flags(ctx, csr, true)) {
>           /* CSR is readonly: trap. */
>           return false;
> @@ -170,28 +221,35 @@ static bool trans_csrxchg(DisasContext *ctx, arg_csrxchg *a)
>       if (check_plv(ctx)) {
>           return false;
>       }
> -    csr = get_csr(a->csr);
> +    csr = ctx->guest_mode ? get_gcsr(a->csr) : get_csr(a->csr);
>       if (csr == NULL) {
>           /* CSR is undefined: write ignored, read old_value as 0. */
>           gen_set_gpr(a->rd, tcg_constant_tl(0), EXT_NONE);
>           return true;
>       }
> +    if (ctx->guest_mode && (csr->flags & CSRFL_GSPR)) {
> +        gen_helper_gspr(tcg_env);
> +        gen_set_gpr(a->rd, tcg_constant_tl(0), EXT_NONE);
> +        return true;
> +    }
>   
>       if (!check_csr_flags(ctx, csr, true)) {
>           /* CSR is readonly: trap. */
>           return false;
>       }
>   
> -    /* So far only readonly csrs have readfn. */
> -    assert(csr->readfn == NULL);
> -
>       src1 = gpr_src(ctx, a->rd, EXT_NONE);
>       mask = gpr_src(ctx, a->rj, EXT_NONE);
>       oldv = tcg_temp_new();
>       newv = tcg_temp_new();
>       temp = tcg_temp_new();
>   
> -    tcg_gen_ld_tl(oldv, tcg_env, csr->offset);
> +    if (csr->readfn) {
> +        GenCSRRead readfn = (GenCSRRead)csr->readfn;
> +        readfn(oldv, tcg_env);
> +    } else {
> +        tcg_gen_ld_tl(oldv, tcg_env, csr->offset);
> +    }
>       tcg_gen_and_tl(newv, src1, mask);
>       tcg_gen_andc_tl(temp, oldv, mask);
>       tcg_gen_or_tl(newv, newv, temp);
> @@ -212,6 +270,11 @@ static bool gen_iocsrrd(DisasContext *ctx, arg_rr *a,
>       TCGv dest = gpr_dst(ctx, a->rd, EXT_NONE);
>       TCGv src1 = gpr_src(ctx, a->rj, EXT_NONE);
>   
> +    if (ctx->guest_mode) {
> +        gen_helper_gspr(tcg_env);
> +        return true;
> +    }
> +
>       if (check_plv(ctx)) {
>           return false;
>       }
> @@ -225,6 +288,11 @@ static bool gen_iocsrwr(DisasContext *ctx, arg_rr *a,
>       TCGv val = gpr_src(ctx, a->rd, EXT_NONE);
>       TCGv addr = gpr_src(ctx, a->rj, EXT_NONE);
>   
> +    if (ctx->guest_mode) {
> +        gen_helper_gspr(tcg_env);
> +        return true;
> +    }
> +
>       if (check_plv(ctx)) {
>           return false;
>       }
> @@ -243,7 +311,7 @@ TRANS64(iocsrwr_d, IOCSR, gen_iocsrwr, gen_helper_iocsrwr_d)
>   
>   static void check_mmu_idx(DisasContext *ctx)
>   {
> -    if (ctx->mem_idx != MMU_DA_IDX) {
> +    if (ctx->mem_idx != MMU_DA_IDX && ctx->mem_idx != MMU_GUEST_DA_IDX) {
>           tcg_gen_movi_tl(cpu_pc, ctx->base.pc_next + 4);
>           ctx->base.is_jmp = DISAS_EXIT;
>       }
> @@ -316,25 +384,61 @@ static bool trans_invtlb(DisasContext *ctx, arg_invtlb *a)
>           return false;
>       }
>   
> +    if (!avail_LVZ(ctx) && a->imm > 0x6) {
> +        return false;
> +    }
> +
> +    //TODO: futher refinement of op 0x9 and 0x10-0x16
>       switch (a->imm) {
> -    case 0:
> -    case 1:
> -        gen_helper_invtlb_all(tcg_env);
> +    case 0x0:
> +    case 0x1:
> +        gen_helper_invtlb_all(tcg_env, rj, tcg_constant_i32(a->imm),
> +                              tcg_constant_i32(0));
> +        break;
> +    case 0x2:
> +        gen_helper_invtlb_all_g(tcg_env, rj, tcg_constant_i32(1),
> +                                tcg_constant_i32(0));
> +        break;
> +    case 0x3:
> +        gen_helper_invtlb_all_g(tcg_env, rj, tcg_constant_i32(0),
> +                                tcg_constant_i32(0));
> +        break;
> +    case 0x4:
> +        gen_helper_invtlb_all_asid(tcg_env, rj, tcg_constant_i32(0));
> +        break;
> +    case 0x5:
> +        gen_helper_invtlb_page_asid(tcg_env, rj, rk, tcg_constant_i32(0));
>           break;
> -    case 2:
> -        gen_helper_invtlb_all_g(tcg_env, tcg_constant_i32(1));
> +    case 0x6:
> +        gen_helper_invtlb_page_asid_or_g(tcg_env, rj, rk, tcg_constant_i32(0));
>           break;
> -    case 3:
> -        gen_helper_invtlb_all_g(tcg_env, tcg_constant_i32(0));
> +    case 0x9:
> +    case 0x10:
> +    case 0x11:
> +    case 0x12:
> +    case 0x13:
> +    case 0x14:
> +    case 0x15:
> +    case 0x16:
> +        gen_helper_invtlb_all(tcg_env, rj, tcg_constant_i32(0),
> +                              tcg_constant_i32(0));
>           break;
> -    case 4:
> -        gen_helper_invtlb_all_asid(tcg_env, rj);
> +    case 0xa:
> +        gen_helper_invtlb_all_g(tcg_env, rj, tcg_constant_i32(1),
> +                                tcg_constant_i32(1));
>           break;
> -    case 5:
> -        gen_helper_invtlb_page_asid(tcg_env, rj, rk);
> +    case 0xb:
> +        gen_helper_invtlb_all_g(tcg_env, rj, tcg_constant_i32(0),
> +                                tcg_constant_i32(1));
>           break;
> -    case 6:
> -        gen_helper_invtlb_page_asid_or_g(tcg_env, rj, rk);
> +    case 0xc:
> +        gen_helper_invtlb_all_asid(tcg_env, rj, tcg_constant_i32(1));
> +        break;
> +    case 0xd:
> +        gen_helper_invtlb_page_asid(tcg_env, rj, rk, tcg_constant_i32(1));
> +        break;
> +    case 0xe:
> +        gen_helper_invtlb_page_asid_or_g(tcg_env, rj, rk, tcg_constant_i32(1));
>           break;
>       default:
>           return false;
> @@ -400,6 +504,10 @@ static bool trans_dbcl(DisasContext *ctx, arg_dbcl *a)
>       if (check_plv(ctx)) {
>           return false;
>       }
> +    if (ctx->guest_mode) {
> +        gen_helper_gspr(tcg_env);
> +        return true;
> +    }
>       generate_exception(ctx, EXCCODE_DBP);
>       return true;
>   }
> @@ -410,9 +518,212 @@ static bool trans_idle(DisasContext *ctx, arg_idle *a)
>           return false;
>       }
>   
> +    if (ctx->guest_mode) {
> +        gen_helper_gspr(tcg_env);
> +        return true;
> +    }
> +
>       tcg_gen_movi_tl(cpu_pc, ctx->base.pc_next + 4);
>       gen_helper_idle(tcg_env);
>       ctx->base.is_jmp = DISAS_NORETURN;
>       return true;
>   }
> +
> +static bool trans_gcsrrd(DisasContext *ctx, arg_gcsrrd *a)
> +{
> +    TCGv dest;
> +    const CSRInfo *csr;
> +    GenCSRRead readfn;
> +
> +    if (check_plv(ctx)) {
> +        return false;
> +    }
> +    if (!avail_LVZ(ctx) || ctx->guest_mode) {
> +        return false;
> +    }
> +
> +    csr = get_gcsr(a->csr);
> +    if (csr == NULL) {
> +        dest = tcg_constant_tl(0);
> +    } else {
> +        dest = gpr_dst(ctx, a->rd, EXT_NONE);
> +        if (csr->flags & CSRFL_GSPR) {
> +            tcg_gen_movi_tl(dest, 0);
> +        } else {
> +            readfn = (GenCSRRead)csr->readfn;
> +            if (readfn) {
> +                readfn(dest, tcg_env);
> +            } else {
> +                tcg_gen_ld_tl(dest, tcg_env, csr->offset);
> +            }
> +        }
> +    }
> +    gen_set_gpr(a->rd, dest, EXT_NONE);
> +    return true;
> +}
> +
> +static bool trans_gcsrwr(DisasContext *ctx, arg_gcsrwr *a)
> +{
> +    TCGv dest, src1;
> +    const CSRInfo *csr;
> +    GenCSRWrite writefn;
> +
> +    if (check_plv(ctx)) {
> +        return false;
> +    }
> +    if (!avail_LVZ(ctx) || ctx->guest_mode) {
> +        return false;
> +    }
> +
> +    csr = get_gcsr(a->csr);
> +    if (csr == NULL) {
> +        gen_set_gpr(a->rd, tcg_constant_tl(0), EXT_NONE);
> +        return true;
> +    }
> +    if (!check_csr_flags(ctx, csr, true)) {
> +        return false;
> +    }
> +    if (csr->flags & CSRFL_GSPR) {
> +        gen_set_gpr(a->rd, tcg_constant_tl(0), EXT_NONE);
> +        return true;
> +    }
> +
> +    src1 = gpr_src(ctx, a->rd, EXT_NONE);
> +    writefn = (GenCSRWrite)csr->writefn;
> +    if (writefn) {
> +        dest = gpr_dst(ctx, a->rd, EXT_NONE);
> +        writefn(dest, tcg_env, src1);
> +    } else {
> +        dest = tcg_temp_new();
> +        tcg_gen_ld_tl(dest, tcg_env, csr->offset);
> +        tcg_gen_st_tl(src1, tcg_env, csr->offset);
> +    }
> +    gen_set_gpr(a->rd, dest, EXT_NONE);
> +    return true;
> +}
> +
> +static bool trans_gcsrxchg(DisasContext *ctx, arg_gcsrxchg *a)
> +{
> +    TCGv src1, mask, oldv, newv, temp;
> +    const CSRInfo *csr;
> +    GenCSRRead readfn;
> +    GenCSRWrite writefn;
> +
> +    if (check_plv(ctx)) {
> +        return false;
> +    }
> +    if (!avail_LVZ(ctx) || ctx->guest_mode) {
> +        return false;
> +    }
> +
> +    csr = get_gcsr(a->csr);
> +    if (csr == NULL) {
> +        gen_set_gpr(a->rd, tcg_constant_tl(0), EXT_NONE);
> +        return true;
> +    }
> +    if (!check_csr_flags(ctx, csr, true)) {
> +        return false;
> +    }
> +    if (csr->flags & CSRFL_GSPR) {
> +        gen_set_gpr(a->rd, tcg_constant_tl(0), EXT_NONE);
> +        return true;
> +    }
> +
> +    src1 = gpr_src(ctx, a->rd, EXT_NONE);
> +    mask = gpr_src(ctx, a->rj, EXT_NONE);
> +    oldv = tcg_temp_new();
> +    newv = tcg_temp_new();
> +    temp = tcg_temp_new();
> +
> +    readfn = (GenCSRRead)csr->readfn;
> +    if (readfn) {
> +        readfn(oldv, tcg_env);
> +    } else {
> +        tcg_gen_ld_tl(oldv, tcg_env, csr->offset);
> +    }
> +    tcg_gen_and_tl(newv, src1, mask);
> +    tcg_gen_andc_tl(temp, oldv, mask);
> +    tcg_gen_or_tl(newv, newv, temp);
> +
> +    writefn = (GenCSRWrite)csr->writefn;
> +    if (writefn) {
> +        writefn(oldv, tcg_env, newv);
> +    } else {
> +        tcg_gen_st_tl(newv, tcg_env, csr->offset);
> +    }
> +    gen_set_gpr(a->rd, oldv, EXT_NONE);
> +    return true;
> +}
> +
> +static bool trans_gtlbsrch(DisasContext *ctx, arg_gtlbsrch *a)
> +{
> +    if (check_plv(ctx) || !avail_LVZ(ctx) || ctx->guest_mode) {
> +        return false;
> +    }
> +    gen_helper_gtlbsrch(tcg_env);
> +    return true;
> +}
> +
> +static bool trans_gtlbrd(DisasContext *ctx, arg_gtlbrd *a)
> +{
> +    if (check_plv(ctx) || !avail_LVZ(ctx) || ctx->guest_mode) {
> +        return false;
> +    }
> +    gen_helper_gtlbrd(tcg_env);
> +    return true;
> +}
> +
> +static bool trans_gtlbwr(DisasContext *ctx, arg_gtlbwr *a)
> +{
> +    if (check_plv(ctx) || !avail_LVZ(ctx) || ctx->guest_mode) {
> +        return false;
> +    }
> +    gen_helper_gtlbwr(tcg_env);
> +    check_mmu_idx(ctx);
> +    return true;
> +}
> +
> +static bool trans_gtlbfill(DisasContext *ctx, arg_gtlbfill *a)
> +{
> +    if (check_plv(ctx) || !avail_LVZ(ctx) || ctx->guest_mode) {
> +        return false;
> +    }
> +    gen_helper_gtlbfill(tcg_env);
> +    check_mmu_idx(ctx);
> +    return true;
> +}
> +
> +static bool trans_gtlbclr(DisasContext *ctx, arg_gtlbclr *a)
> +{
> +    if (check_plv(ctx) || !avail_LVZ(ctx) || ctx->guest_mode) {
> +        return false;
> +    }
> +    gen_helper_gtlbclr(tcg_env);
> +    check_mmu_idx(ctx);
> +    return true;
> +}
> +
> +static bool trans_gtlbflush(DisasContext *ctx, arg_gtlbflush *a)
> +{
> +    if (check_plv(ctx) || !avail_LVZ(ctx) || ctx->guest_mode) {
> +        return false;
> +    }
> +    gen_helper_gtlbflush(tcg_env);
> +    check_mmu_idx(ctx);
> +    return true;
> +}
> +
> +static bool trans_hvcl(DisasContext *ctx, arg_hvcl *a)
> +{
> +    if (!avail_LVZ(ctx)) {
> +        return false;
> +    }
> +    if (!ctx->guest_mode) {
> +        return false;
> +    }
> +    tcg_gen_movi_tl(cpu_pc, ctx->base.pc_next);
> +    gen_helper_hvcl(tcg_env, tcg_constant_i32(a->imm));
> +    ctx->base.is_jmp = DISAS_NORETURN;
> +    return true;
> +}
>   #endif
> diff --git a/target/loongarch/tcg/op_helper.c b/target/loongarch/tcg/op_helper.c
> index 16ac0d43bc..28043697d8 100644
> --- a/target/loongarch/tcg/op_helper.c
> +++ b/target/loongarch/tcg/op_helper.c
> @@ -15,6 +15,7 @@
>   #include "qemu/crc32c.h"
>   #include <zlib.h> /* for crc32 */
>   #include "cpu-csr.h"
> +#include "qemu/main-loop.h"
>   
>   /* Exceptions helpers */
>   void helper_raise_exception(CPULoongArchState *env, uint32_t exception)
> @@ -81,6 +82,10 @@ target_ulong helper_crc32c(target_ulong val, target_ulong m, uint64_t sz)
>   
>   target_ulong helper_cpucfg(CPULoongArchState *env, target_ulong rj)
>   {
> +    if (env->guest) {
> +        trigger_vm_exit(env);
> +        do_raise_exception(env, EXCCODE_GSPR, GETPC());
> +    }
>       return rj >= ARRAY_SIZE(env->cpucfg) ? 0 : env->cpucfg[rj];
>   }
>   
> @@ -92,8 +97,9 @@ uint64_t helper_rdtime_d(CPULoongArchState *env)
>       uint64_t plv;
>       LoongArchCPU *cpu = env_archcpu(env);
>   
> -    plv = FIELD_EX64(env->CSR_CRMD, CSR_CRMD, PLV);
> -    if (extract64(env->CSR_MISC, R_CSR_MISC_DRDTL_SHIFT + plv, 1)) {
> +    plv = FIELD_EX64(GET_CSR_IF(env->guest, CRMD), CSR_CRMD, PLV);
> +    if (extract64(GET_CSR_IF(env->guest, MISC), R_CSR_MISC_DRDTL_SHIFT + plv,
> +                  1)) {
>           do_raise_exception(env, EXCCODE_IPE, GETPC());
>       }
>   
> @@ -105,28 +111,50 @@ uint64_t helper_rdtime_d(CPULoongArchState *env)
>   void helper_ertn(CPULoongArchState *env)
>   {
>       uint64_t csr_pplv, csr_pie;
> -    if (FIELD_EX64(env->CSR_TLBRERA, CSR_TLBRERA, ISTLBR)) {
> -        csr_pplv = FIELD_EX64(env->CSR_TLBRPRMD, CSR_TLBRPRMD, PPLV);
> -        csr_pie = FIELD_EX64(env->CSR_TLBRPRMD, CSR_TLBRPRMD, PIE);
> -
> -        env->CSR_TLBRERA = FIELD_DP64(env->CSR_TLBRERA, CSR_TLBRERA, ISTLBR, 0);
> -        env->CSR_CRMD = FIELD_DP64(env->CSR_CRMD, CSR_CRMD, DA, 0);
> -        env->CSR_CRMD = FIELD_DP64(env->CSR_CRMD, CSR_CRMD, PG, 1);
> -        set_pc(env, env->CSR_TLBRERA);
> -        qemu_log_mask(CPU_LOG_INT, "%s: TLBRERA " TARGET_FMT_lx "\n",
> -                      __func__, env->CSR_TLBRERA);
> +
> +    if (FIELD_EX64(GET_CSR_IF(env->guest, TLBRERA), CSR_TLBRERA, ISTLBR)) {
> +        csr_pplv =
> +            FIELD_EX64(GET_CSR_IF(env->guest, TLBRPRMD), CSR_TLBRPRMD, PPLV);
> +        csr_pie =
> +            FIELD_EX64(GET_CSR_IF(env->guest, TLBRPRMD), CSR_TLBRPRMD, PIE);
> +
> +        SET_CSR_IF(env->guest, TLBRERA,
> +                   FIELD_DP64(GET_CSR_IF(env->guest, TLBRERA), CSR_TLBRERA,
> +                              ISTLBR, 0));
> +        SET_CSR_IF(env->guest, CRMD,
> +                   FIELD_DP64(GET_CSR_IF(env->guest, CRMD), CSR_CRMD, DA, 0));
> +        SET_CSR_IF(env->guest, CRMD,
> +                   FIELD_DP64(GET_CSR_IF(env->guest, CRMD), CSR_CRMD, PG, 1));
> +        set_pc(env, GET_CSR_IF(env->guest, TLBRERA));
> +        qemu_log_mask(CPU_LOG_INT, "%s: TLBRERA " TARGET_FMT_lx "\n", __func__,
> +                      GET_CSR_IF(env->guest, TLBRERA));
>       } else {
> -        csr_pplv = FIELD_EX64(env->CSR_PRMD, CSR_PRMD, PPLV);
> -        csr_pie = FIELD_EX64(env->CSR_PRMD, CSR_PRMD, PIE);
> +        csr_pplv = FIELD_EX64(GET_CSR_IF(env->guest, PRMD), CSR_PRMD, PPLV);
> +        csr_pie = FIELD_EX64(GET_CSR_IF(env->guest, PRMD), CSR_PRMD, PIE);
>   
> -        set_pc(env, env->CSR_ERA);
> -        qemu_log_mask(CPU_LOG_INT, "%s: ERA " TARGET_FMT_lx "\n",
> -                      __func__, env->CSR_ERA);
> +        set_pc(env, GET_CSR_IF(env->guest, ERA));
> +        qemu_log_mask(CPU_LOG_INT, "%s: ERA " TARGET_FMT_lx "\n", __func__,
> +                      GET_CSR_IF(env->guest, ERA));
>       }
> -    env->CSR_CRMD = FIELD_DP64(env->CSR_CRMD, CSR_CRMD, PLV, csr_pplv);
> -    env->CSR_CRMD = FIELD_DP64(env->CSR_CRMD, CSR_CRMD, IE, csr_pie);
> +    SET_CSR_IF(
> +        env->guest, CRMD,
> +        FIELD_DP64(GET_CSR_IF(env->guest, CRMD), CSR_CRMD, PLV, csr_pplv));
> +    SET_CSR_IF(env->guest, CRMD,
> +               FIELD_DP64(GET_CSR_IF(env->guest, CRMD), CSR_CRMD, IE, csr_pie));
>   
>       env->lladdr = 1;
> +    if (will_return_to_guest(env)) {
> +        env->guest = true;
> +        env->CSR_GSTAT = FIELD_DP64(env->CSR_GSTAT, CSR_GSTAT, VM, 1);
> +        cpu_loongarch_set_guest_timer(env_archcpu(env), true);
> +        bql_lock();
> +        if (loongarch_guest_has_interrupt(env)) {
> +            cpu_interrupt(env_cpu(env), CPU_INTERRUPT_GUEST);
> +        } else {
> +            cpu_reset_interrupt(env_cpu(env), CPU_INTERRUPT_GUEST);
> +        }
> +        bql_unlock();
> +    }
>   }
>   
>   void helper_idle(CPULoongArchState *env)
> @@ -136,4 +164,21 @@ void helper_idle(CPULoongArchState *env)
>       cs->halted = 1;
>       do_raise_exception(env, EXCP_HLT, 0);
>   }
> +
> +void helper_hvcl(CPULoongArchState *env, uint32_t code)
> +{
> +    if (!env->guest) {
> +        do_raise_exception(env, EXCCODE_INE, GETPC());
> +        return;
> +    }
> +
> +    trigger_vm_exit(env);
> +    do_raise_exception(env, EXCCODE_HVC, GETPC());
> +}
> +
> +void helper_gspr(CPULoongArchState *env)
> +{
> +    trigger_vm_exit(env);
> +    do_raise_exception(env, EXCCODE_GSPR, GETPC());
> +}
>   #endif
> diff --git a/target/loongarch/tcg/tcg_cpu.c b/target/loongarch/tcg/tcg_cpu.c
> index 31d3db6e8e..7bc4911524 100644
> --- a/target/loongarch/tcg/tcg_cpu.c
> +++ b/target/loongarch/tcg/tcg_cpu.c
> @@ -43,6 +43,10 @@ static const struct TypeExcp excp_names[] = {
>       {EXCCODE_BCE, "Bound Check Exception"},
>       {EXCCODE_SXD, "128 bit vector instructions Disable exception"},
>       {EXCCODE_ASXD, "256 bit vector instructions Disable exception"},
> +    {EXCCODE_GSPR, "Guest Sensitive and Privileged Resources"},
> +    {EXCCODE_HVC, "Hypervisor call"},
> +    {EXCCODE_GCSC, "Guest CSR visited by Software"},
> +    {EXCCODE_GCHC, "Guest CSR visited by Hardware"},
>       {EXCP_HLT, "EXCP_HLT"},
>   };
>   
> @@ -79,9 +83,12 @@ static void loongarch_cpu_do_interrupt(CPUState *cs)
>       CPULoongArchState *env = cpu_env(cs);
>       bool update_badinstr = 1;
>       int cause = -1;
> -    bool tlbfill = FIELD_EX64(env->CSR_TLBRERA, CSR_TLBRERA, ISTLBR);
> -    uint32_t vec_size = FIELD_EX64(env->CSR_ECFG, CSR_ECFG, VS);
> +    bool real_guest = !env->vm_exit && env->guest;
> +    bool tlbfill =
> +        FIELD_EX64(GET_CSR_IF(real_guest, TLBRERA), CSR_TLBRERA, ISTLBR);
> +    uint32_t vec_size = FIELD_EX64(GET_CSR_IF(real_guest, ECFG), CSR_ECFG, VS);
>       uint64_t last_pc = env->pc;
> +    uint32_t badinstr;
>   
>       if (cs->exception_index != EXCCODE_INT) {
>           qemu_log_mask(CPU_LOG_INT,
> @@ -115,7 +122,11 @@ static void loongarch_cpu_do_interrupt(CPUState *cs)
>           update_badinstr = 0;
>           break;
>       case EXCCODE_BCE:
> -        env->CSR_BADV = env->pc;
> +    case EXCCODE_GSPR:
> +    case EXCCODE_GCHC:
> +    case EXCCODE_GCSC:
> +    case EXCCODE_HVC:
> +        SET_CSR_IF(real_guest, BADV, env->pc);
>           QEMU_FALLTHROUGH;
>       case EXCCODE_SYS:
>       case EXCCODE_BRK:
> @@ -142,35 +153,51 @@ static void loongarch_cpu_do_interrupt(CPUState *cs)
>       if (update_badinstr) {
>           MemOpIdx oi = make_memop_idx(MO_LEUL, cpu_mmu_index(cs, true));
>   
> -        env->CSR_BADI = cpu_ldl_code_mmu(env, env->pc, oi, 0);
> +        badinstr = cpu_ldl_code_mmu(env, env->pc, oi, 0);
> +        SET_CSR_IF(real_guest, BADI, badinstr);
>       }
>   
>       /* Save PLV and IE */
>       if (tlbfill) {
> -        env->CSR_TLBRPRMD = FIELD_DP64(env->CSR_TLBRPRMD, CSR_TLBRPRMD, PPLV,
> -                                       FIELD_EX64(env->CSR_CRMD,
> -                                       CSR_CRMD, PLV));
> -        env->CSR_TLBRPRMD = FIELD_DP64(env->CSR_TLBRPRMD, CSR_TLBRPRMD, PIE,
> -                                       FIELD_EX64(env->CSR_CRMD, CSR_CRMD, IE));
> +        SET_CSR_IF(real_guest, TLBRPRMD,
> +                   FIELD_DP64(GET_CSR_IF(real_guest, TLBRPRMD), CSR_TLBRPRMD,
> +                              PPLV,
> +                              FIELD_EX64(GET_CSR_IF(real_guest, CRMD), CSR_CRMD,
> +                                         PLV)));
> +        SET_CSR_IF(
> +            real_guest, TLBRPRMD,
> +            FIELD_DP64(GET_CSR_IF(real_guest, TLBRPRMD), CSR_TLBRPRMD, PIE,
> +                       FIELD_EX64(GET_CSR_IF(real_guest, CRMD), CSR_CRMD, IE)));
>           /* set the DA mode */
> -        env->CSR_CRMD = FIELD_DP64(env->CSR_CRMD, CSR_CRMD, DA, 1);
> -        env->CSR_CRMD = FIELD_DP64(env->CSR_CRMD, CSR_CRMD, PG, 0);
> -        env->CSR_TLBRERA = FIELD_DP64(env->CSR_TLBRERA, CSR_TLBRERA,
> -                                      PC, (env->pc >> 2));
> +        SET_CSR_IF(real_guest, CRMD,
> +                   FIELD_DP64(GET_CSR_IF(real_guest, CRMD), CSR_CRMD, DA, 1));
> +        SET_CSR_IF(real_guest, CRMD,
> +                   FIELD_DP64(GET_CSR_IF(real_guest, CRMD), CSR_CRMD, PG, 0));
> +        SET_CSR_IF(real_guest, TLBRERA,
> +                   FIELD_DP64(GET_CSR_IF(real_guest, TLBRERA), CSR_TLBRERA, PC,
> +                              (env->pc >> 2)));
>       } else {
> -        env->CSR_ESTAT = FIELD_DP64(env->CSR_ESTAT, CSR_ESTAT, ECODE,
> -                                    EXCODE_MCODE(cause));
> -        env->CSR_ESTAT = FIELD_DP64(env->CSR_ESTAT, CSR_ESTAT, ESUBCODE,
> -                                    EXCODE_SUBCODE(cause));
> -        env->CSR_PRMD = FIELD_DP64(env->CSR_PRMD, CSR_PRMD, PPLV,
> -                                   FIELD_EX64(env->CSR_CRMD, CSR_CRMD, PLV));
> -        env->CSR_PRMD = FIELD_DP64(env->CSR_PRMD, CSR_PRMD, PIE,
> -                                   FIELD_EX64(env->CSR_CRMD, CSR_CRMD, IE));
> -        env->CSR_ERA = env->pc;
> +        SET_CSR_IF(real_guest, ESTAT,
> +                   FIELD_DP64(GET_CSR_IF(real_guest, ESTAT), CSR_ESTAT, ECODE,
> +                              EXCODE_MCODE(cause)));
> +        SET_CSR_IF(real_guest, ESTAT,
> +                   FIELD_DP64(GET_CSR_IF(real_guest, ESTAT), CSR_ESTAT,
> +                              ESUBCODE, EXCODE_SUBCODE(cause)));
> +        SET_CSR_IF(real_guest, PRMD,
> +                   FIELD_DP64(GET_CSR_IF(real_guest, PRMD), CSR_PRMD, PPLV,
> +                              FIELD_EX64(GET_CSR_IF(real_guest, CRMD), CSR_CRMD,
> +                                         PLV)));
> +        SET_CSR_IF(
> +            real_guest, PRMD,
> +            FIELD_DP64(GET_CSR_IF(real_guest, PRMD), CSR_PRMD, PIE,
> +                       FIELD_EX64(GET_CSR_IF(real_guest, CRMD), CSR_CRMD, IE)));
> +        SET_CSR_IF(real_guest, ERA, env->pc);
>       }
>   
> -    env->CSR_CRMD = FIELD_DP64(env->CSR_CRMD, CSR_CRMD, PLV, 0);
> -    env->CSR_CRMD = FIELD_DP64(env->CSR_CRMD, CSR_CRMD, IE, 0);
> +    SET_CSR_IF(real_guest, CRMD,
> +               FIELD_DP64(GET_CSR_IF(real_guest, CRMD), CSR_CRMD, PLV, 0));
> +    SET_CSR_IF(real_guest, CRMD,
> +               FIELD_DP64(GET_CSR_IF(real_guest, CRMD), CSR_CRMD, IE, 0));
>   
>       if (vec_size) {
>           vec_size = (1 << vec_size) * 4;
> @@ -179,43 +206,54 @@ static void loongarch_cpu_do_interrupt(CPUState *cs)
>       if  (cs->exception_index == EXCCODE_INT) {
>           /* Interrupt */
>           uint32_t vector = 0;
> -        uint32_t pending = FIELD_EX64(env->CSR_ESTAT, CSR_ESTAT, IS);
> -        pending &= FIELD_EX64(env->CSR_ECFG, CSR_ECFG, LIE);
> +        uint32_t pending =
> +            FIELD_EX64(GET_CSR_IF(real_guest, ESTAT), CSR_ESTAT, IS);
> +        pending &= FIELD_EX64(GET_CSR_IF(real_guest, ECFG), CSR_ECFG, LIE);
>   
>           /* Find the highest-priority interrupt. */
>           vector = 31 - clz32(pending);
> -        set_pc(env, env->CSR_EENTRY + \
> -               (EXCCODE_EXTERNAL_INT + vector) * vec_size);
> -        qemu_log_mask(CPU_LOG_INT,
> -                      "%s: PC " TARGET_FMT_lx " ERA " TARGET_FMT_lx
> -                      " cause %d\n" "    A " TARGET_FMT_lx " D "
> -                      TARGET_FMT_lx " vector = %d ExC " TARGET_FMT_lx "ExS"
> -                      TARGET_FMT_lx "\n",
> -                      __func__, env->pc, env->CSR_ERA,
> -                      cause, env->CSR_BADV, env->CSR_DERA, vector,
> -                      env->CSR_ECFG, env->CSR_ESTAT);
> +        set_pc(env, GET_CSR_IF(real_guest, EENTRY) +
> +                        (EXCCODE_EXTERNAL_INT + vector) * vec_size);
> +        qemu_log_mask(
> +            CPU_LOG_INT,
> +            "%s: PC " TARGET_FMT_lx " ERA " TARGET_FMT_lx " cause %d\n"
> +            "    A " TARGET_FMT_lx " D " TARGET_FMT_lx
> +            " vector = %d ExC " TARGET_FMT_lx "ExS" TARGET_FMT_lx "\n",
> +            __func__, env->pc, GET_CSR_IF(real_guest, ERA), cause,
> +            GET_CSR_IF(real_guest, BADV), GET_CSR_IF(real_guest, DERA), vector,
> +            GET_CSR_IF(real_guest, ECFG), GET_CSR_IF(real_guest, ESTAT));
>           qemu_plugin_vcpu_interrupt_cb(cs, last_pc);
>       } else {
>           if (tlbfill) {
> -            set_pc(env, env->CSR_TLBRENTRY);
> +            set_pc(env, GET_CSR_IF(real_guest, TLBRENTRY));
>           } else {
> -            set_pc(env, env->CSR_EENTRY + EXCODE_MCODE(cause) * vec_size);
> +            set_pc(env, GET_CSR_IF(real_guest, EENTRY) +
> +                            EXCODE_MCODE(cause) * vec_size);
>           }
> -        qemu_log_mask(CPU_LOG_INT,
> -                      "%s: PC " TARGET_FMT_lx " ERA " TARGET_FMT_lx
> -                      " cause %d%s\n, ESTAT " TARGET_FMT_lx
> -                      " EXCFG " TARGET_FMT_lx " BADVA " TARGET_FMT_lx
> -                      "BADI " TARGET_FMT_lx " SYS_NUM " TARGET_FMT_lu
> -                      " cpu %d asid " TARGET_FMT_lx "\n", __func__, env->pc,
> -                      tlbfill ? env->CSR_TLBRERA : env->CSR_ERA,
> -                      cause, tlbfill ? "(refill)" : "", env->CSR_ESTAT,
> -                      env->CSR_ECFG,
> -                      tlbfill ? env->CSR_TLBRBADV : env->CSR_BADV,
> -                      env->CSR_BADI, env->gpr[11], cs->cpu_index,
> -                      env->CSR_ASID);
> +        qemu_log_mask(
> +            CPU_LOG_INT,
> +            "%s: PC " TARGET_FMT_lx " ERA " TARGET_FMT_lx
> +            " cause %d%s\n, ESTAT " TARGET_FMT_lx " EXCFG " TARGET_FMT_lx
> +            " BADVA " TARGET_FMT_lx "BADI " TARGET_FMT_lx
> +            " SYS_NUM " TARGET_FMT_lu " cpu %d asid " TARGET_FMT_lx "\n",
> +            __func__, env->pc,
> +            tlbfill ? GET_CSR_IF(real_guest, TLBRERA) :
> +                      GET_CSR_IF(real_guest, ERA),
> +            cause, tlbfill ? "(refill)" : "", GET_CSR_IF(real_guest, ESTAT),
> +            GET_CSR_IF(real_guest, ECFG),
> +            tlbfill ? GET_CSR_IF(real_guest, TLBRBADV) :
> +                      GET_CSR_IF(real_guest, BADV),
> +            GET_CSR_IF(real_guest, BADI), env->gpr[11], cs->cpu_index,
> +            GET_CSR_IF(real_guest, ASID));
>           qemu_plugin_vcpu_exception_cb(cs, last_pc);
>       }
>       cs->exception_index = -1;
> +    if (env->vm_exit) {
> +        env->CSR_GSTAT = FIELD_DP64(env->CSR_GSTAT, CSR_GSTAT, VM, 0);
> +        env->guest = false;
> +        cpu_reset_interrupt(cs, CPU_INTERRUPT_GUEST);
> +    }
> +    env->vm_exit = false;
>   }
>   
>   static void loongarch_cpu_do_transaction_failed(CPUState *cs, hwaddr physaddr,
> @@ -247,16 +285,25 @@ static inline bool cpu_loongarch_hw_interrupts_enabled(CPULoongArchState *env)
>   
>   static bool loongarch_cpu_exec_interrupt(CPUState *cs, int interrupt_request)
>   {
> -    if (interrupt_request & CPU_INTERRUPT_HARD) {
> -        CPULoongArchState *env = cpu_env(cs);
> +    CPULoongArchState *env = cpu_env(cs);
> +    bool has_interrupt = false;
>   
> +    if (interrupt_request & CPU_INTERRUPT_HARD) {
>           if (cpu_loongarch_hw_interrupts_enabled(env) &&
>               cpu_loongarch_hw_interrupts_pending(env)) {
> -            /* Raise it */
> -            cs->exception_index = EXCCODE_INT;
> -            loongarch_cpu_do_interrupt(cs);
> -            return true;
> +            if (env->guest) {
> +                trigger_vm_exit(env);
> +            }
> +            has_interrupt = true;
>           }
> +    } else if (interrupt_request & CPU_INTERRUPT_GUEST) {
> +        has_interrupt = loongarch_guest_has_interrupt(env);
> +    }
> +
> +    if (has_interrupt) {
> +        cs->exception_index = EXCCODE_INT;
> +        loongarch_cpu_do_interrupt(cs);
> +        return true;
>       }
>       return false;
>   }
> @@ -273,10 +320,18 @@ static TCGTBCPUState loongarch_get_tb_cpu_state(CPUState *cs)
>       CPULoongArchState *env = cpu_env(cs);
>       uint32_t flags;
>   
> -    flags = env->CSR_CRMD & (R_CSR_CRMD_PLV_MASK | R_CSR_CRMD_PG_MASK);
> -    flags |= FIELD_EX64(env->CSR_EUEN, CSR_EUEN, FPE) * HW_FLAGS_EUEN_FPE;
> -    flags |= FIELD_EX64(env->CSR_EUEN, CSR_EUEN, SXE) * HW_FLAGS_EUEN_SXE;
> -    flags |= FIELD_EX64(env->CSR_EUEN, CSR_EUEN, ASXE) * HW_FLAGS_EUEN_ASXE;
> +    if (env->guest) {
> +        flags = env->GCSR_CRMD & (R_CSR_CRMD_PLV_MASK | R_CSR_CRMD_PG_MASK);
> +        flags |= HW_FLAGS_GUEST_MODE;
> +    } else {
> +        flags = env->CSR_CRMD & (R_CSR_CRMD_PLV_MASK | R_CSR_CRMD_PG_MASK);
> +    }
> +    flags |= FIELD_EX64(GET_CSR_IF(env->guest, EUEN), CSR_EUEN, FPE) *
> +             HW_FLAGS_EUEN_FPE;
> +    flags |= FIELD_EX64(GET_CSR_IF(env->guest, EUEN), CSR_EUEN, SXE) *
> +             HW_FLAGS_EUEN_SXE;
> +    flags |= FIELD_EX64(GET_CSR_IF(env->guest, EUEN), CSR_EUEN, ASXE) *
> +             HW_FLAGS_EUEN_ASXE;
>       flags |= is_va32(env) * HW_FLAGS_VA32;
>   
>       return (TCGTBCPUState){ .pc = env->pc, .flags = flags };
> @@ -300,6 +355,13 @@ static int loongarch_cpu_mmu_index(CPUState *cs, bool ifetch)
>   {
>       CPULoongArchState *env = cpu_env(cs);
>   
> +    if (env->guest) {
> +        if (FIELD_EX64(env->GCSR_CRMD, CSR_CRMD, PG)) {
> +            return MMU_GUEST_IDX + FIELD_EX64(env->GCSR_CRMD, CSR_CRMD, PLV);
> +        }
> +        return MMU_GUEST_DA_IDX;
> +    }
> +
>       if (FIELD_EX64(env->CSR_CRMD, CSR_CRMD, PG)) {
>           return FIELD_EX64(env->CSR_CRMD, CSR_CRMD, PLV);
>       }
> diff --git a/target/loongarch/tcg/tcg_loongarch.h b/target/loongarch/tcg/tcg_loongarch.h
> index 7fb627f2d6..ceba1e4062 100644
> --- a/target/loongarch/tcg/tcg_loongarch.h
> +++ b/target/loongarch/tcg/tcg_loongarch.h
> @@ -16,8 +16,8 @@ bool loongarch_cpu_tlb_fill(CPUState *cs, vaddr address, int size,
>                               MMUAccessType access_type, int mmu_idx,
>                               bool probe, uintptr_t retaddr);
>   
> -TLBRet loongarch_get_addr_from_tlb(CPULoongArchState *env,
> -                                   MMUContext *context,
> -                                   MMUAccessType access_type, int mmu_idx);
> +TLBRet loongarch_get_addr_from_tlb(CPULoongArchState *env, MMUContext *context,
> +                                   MMUAccessType access_type, int mmu_idx,
> +                                   bool guest);
>   
>   #endif  /* TARGET_LOONGARCH_TCG_LOONGARCH_H */
> diff --git a/target/loongarch/tcg/tlb_helper.c b/target/loongarch/tcg/tlb_helper.c
> index 892e0eb473..fec0aeeb57 100644
> --- a/target/loongarch/tcg/tlb_helper.c
> +++ b/target/loongarch/tcg/tlb_helper.c
> @@ -34,6 +34,13 @@ static bool tlb_match_asid(bool global, int asid, int tlb_asid)
>       return !global && tlb_asid == asid;
>   }
>   
> +static inline bool tlb_entry_matches_gid(LoongArchTLB *tlb, uint8_t gid)
> +{
> +    uint8_t entry_gid = FIELD_EX64(tlb->tlb_misc, TLB_MISC, GID);
> +
> +    return entry_gid == gid;
> +}
> +
>   bool check_ps(CPULoongArchState *env, uint8_t tlb_ps)
>   {
>       if (tlb_ps >= 64) {
> @@ -46,14 +53,22 @@ static void raise_mmu_exception(CPULoongArchState *env, vaddr address,
>                                   MMUAccessType access_type, TLBRet tlb_error)
>   {
>       CPUState *cs = env_cpu(env);
> +    bool real_guest;
> +
> +    if (env->guest && tlb_error > TLBRET_HOST_MATCH) {
> +        trigger_vm_exit(env);
> +    }
> +    real_guest = !env->vm_exit && env->guest;
>   
>       switch (tlb_error) {
>       default:
>       case TLBRET_BADADDR:
> +    case TLBRET_HOST_BADADDR:
>           cs->exception_index = access_type == MMU_INST_FETCH
>                                 ? EXCCODE_ADEF : EXCCODE_ADEM;
>           break;
>       case TLBRET_NOMATCH:
> +    case TLBRET_HOST_NOMATCH:
>           /* No TLB match for a mapped address */
>           if (access_type == MMU_DATA_LOAD) {
>               cs->exception_index = EXCCODE_PIL;
> @@ -62,9 +77,12 @@ static void raise_mmu_exception(CPULoongArchState *env, vaddr address,
>           } else if (access_type == MMU_INST_FETCH) {
>               cs->exception_index = EXCCODE_PIF;
>           }
> -        env->CSR_TLBRERA = FIELD_DP64(env->CSR_TLBRERA, CSR_TLBRERA, ISTLBR, 1);
> +        SET_CSR_IF(real_guest, TLBRERA,
> +                   FIELD_DP64(GET_CSR_IF(real_guest, TLBRERA), CSR_TLBRERA,
> +                              ISTLBR, 1));
>           break;
>       case TLBRET_INVALID:
> +    case TLBRET_HOST_INVALID:
>           /* TLB match with no valid bit */
>           if (access_type == MMU_DATA_LOAD) {
>               cs->exception_index = EXCCODE_PIL;
> @@ -75,46 +93,58 @@ static void raise_mmu_exception(CPULoongArchState *env, vaddr address,
>           }
>           break;
>       case TLBRET_DIRTY:
> +    case TLBRET_HOST_DIRTY:
>           /* TLB match but 'D' bit is cleared */
>           cs->exception_index = EXCCODE_PME;
>           break;
>       case TLBRET_XI:
> +    case TLBRET_HOST_XI:
>           /* Execute-Inhibit Exception */
>           cs->exception_index = EXCCODE_PNX;
>           break;
>       case TLBRET_RI:
> +    case TLBRET_HOST_RI:
>           /* Read-Inhibit Exception */
>           cs->exception_index = EXCCODE_PNR;
>           break;
>       case TLBRET_PE:
> +    case TLBRET_HOST_PE:
>           /* Privileged Exception */
>           cs->exception_index = EXCCODE_PPI;
>           break;
>       }
>   
> -    if (tlb_error == TLBRET_NOMATCH) {
> -        env->CSR_TLBRBADV = address;
> +    if (tlb_error == TLBRET_NOMATCH || tlb_error == TLBRET_HOST_NOMATCH) {
> +        SET_CSR_IF(real_guest, TLBRBADV, address);
>           if (is_la64(env)) {
> -            env->CSR_TLBREHI = FIELD_DP64(env->CSR_TLBREHI, CSR_TLBREHI_64,
> -                                        VPPN, extract64(address, 13, 35));
> +            SET_CSR_IF(real_guest, TLBREHI,
> +                       FIELD_DP64(GET_CSR_IF(real_guest, TLBREHI),
> +                                  CSR_TLBREHI_64, VPPN,
> +                                  extract64(address, 13, 35)));
>           } else {
> -            env->CSR_TLBREHI = FIELD_DP64(env->CSR_TLBREHI, CSR_TLBREHI_32,
> -                                        VPPN, extract64(address, 13, 19));
> +            SET_CSR_IF(real_guest, TLBREHI,
> +                       FIELD_DP64(GET_CSR_IF(real_guest, TLBREHI),
> +                                  CSR_TLBREHI_32, VPPN,
> +                                  extract64(address, 13, 19)));
>           }
>       } else {
>           if (!FIELD_EX64(env->CSR_DBG, CSR_DBG, DST)) {
> -            env->CSR_BADV = address;
> +            SET_CSR_IF(real_guest, BADV, address);
>           }
> -        env->CSR_TLBEHI = address & (TARGET_PAGE_MASK << 1);
> -   }
> +        SET_CSR_IF(real_guest, TLBEHI, address & (TARGET_PAGE_MASK << 1));
> +    }
>   }
>   
> -static void invalidate_tlb_entry(CPULoongArchState *env, int index)
> +static void invalidate_tlb_entry(CPULoongArchState *env, int index, bool guest)
>   {
>       target_ulong addr, mask, pagesize;
>       uint8_t tlb_ps;
> -    LoongArchTLB *tlb = &env->tlb[index];
> -    int idxmap = BIT(MMU_KERNEL_IDX) | BIT(MMU_USER_IDX);
> +    LoongArchTLB *tlb = guest ? &env->gtlb[index] : &env->tlb[index];
> +    int idxmap =
> +        guest ? (BIT(MMU_GUEST_IDX) | BIT(MMU_GUEST_IDX + 1) |
> +                 BIT(MMU_GUEST_IDX + 2) | BIT(MMU_GUEST_IDX + 3) |
> +                 BIT(MMU_GUEST_DA_IDX)) :
> +                (BIT(MMU_KERNEL_IDX) | BIT(MMU_USER_IDX) | BIT(MMU_DA_IDX));
>       uint64_t tlb_vppn = FIELD_EX64(tlb->tlb_misc, TLB_MISC, VPPN);
>       bool tlb_v;
>   
> @@ -124,27 +154,27 @@ static void invalidate_tlb_entry(CPULoongArchState *env, int index)
>       addr = (tlb_vppn << R_TLB_MISC_VPPN_SHIFT) & ~mask;
>       addr = sextract64(addr, 0, TARGET_VIRT_ADDR_SPACE_BITS);
>   
> -    tlb_v = pte_present(env, tlb->tlb_entry0);
> +    tlb_v = pte_present(env, tlb->tlb_entry0, guest);
>       if (tlb_v) {
>           tlb_flush_range_by_mmuidx(env_cpu(env), addr, pagesize,
>                                     idxmap, TARGET_LONG_BITS);
>       }
>   
> -    tlb_v = pte_present(env, tlb->tlb_entry1);
> +    tlb_v = pte_present(env, tlb->tlb_entry1, guest);
>       if (tlb_v) {
>           tlb_flush_range_by_mmuidx(env_cpu(env), addr + pagesize, pagesize,
>                                     idxmap, TARGET_LONG_BITS);
>       }
>   }
>   
> -static void invalidate_tlb(CPULoongArchState *env, int index)
> +static void invalidate_tlb(CPULoongArchState *env, int index, bool guest)
>   {
>       LoongArchTLB *tlb;
>       uint16_t csr_asid, tlb_asid, tlb_g;
>       uint8_t tlb_e;
>   
> -    csr_asid = FIELD_EX64(env->CSR_ASID, CSR_ASID, ASID);
> -    tlb = &env->tlb[index];
> +    csr_asid = FIELD_EX64(GET_CSR_IF(guest, ASID), CSR_ASID, ASID);
> +    tlb = guest ? &env->gtlb[index] : &env->tlb[index];
>       tlb_e = FIELD_EX64(tlb->tlb_misc, TLB_MISC, E);
>       if (!tlb_e) {
>           return;
> @@ -157,33 +187,38 @@ static void invalidate_tlb(CPULoongArchState *env, int index)
>       if (tlb_g == 0 && tlb_asid != csr_asid) {
>           return;
>       }
> -    invalidate_tlb_entry(env, index);
> +    invalidate_tlb_entry(env, index, guest);
>   }
>   
>   /* Prepare tlb entry information in software PTW mode */
> -static void sptw_prepare_context(CPULoongArchState *env, MMUContext *context)
> +static void sptw_prepare_context(CPULoongArchState *env, MMUContext *context,
> +                                 bool guest)
>   {
>       uint64_t lo0, lo1, csr_vppn;
>       uint8_t csr_ps;
>   
> -    if (FIELD_EX64(env->CSR_TLBRERA, CSR_TLBRERA, ISTLBR)) {
> -        csr_ps = FIELD_EX64(env->CSR_TLBREHI, CSR_TLBREHI, PS);
> +    if (FIELD_EX64(GET_CSR_IF(guest, TLBRERA), CSR_TLBRERA, ISTLBR)) {
> +        csr_ps = FIELD_EX64(GET_CSR_IF(guest, TLBREHI), CSR_TLBREHI, PS);
>           if (is_la64(env)) {
> -            csr_vppn = FIELD_EX64(env->CSR_TLBREHI, CSR_TLBREHI_64, VPPN);
> +            csr_vppn =
> +                FIELD_EX64(GET_CSR_IF(guest, TLBREHI), CSR_TLBREHI_64, VPPN);
>           } else {
> -            csr_vppn = FIELD_EX64(env->CSR_TLBREHI, CSR_TLBREHI_32, VPPN);
> +            csr_vppn =
> +                FIELD_EX64(GET_CSR_IF(guest, TLBREHI), CSR_TLBREHI_32, VPPN);
>           }
> -        lo0 = env->CSR_TLBRELO0;
> -        lo1 = env->CSR_TLBRELO1;
> +        lo0 = GET_CSR_IF(guest, TLBRELO0);
> +        lo1 = GET_CSR_IF(guest, TLBRELO1);
>       } else {
> -        csr_ps = FIELD_EX64(env->CSR_TLBIDX, CSR_TLBIDX, PS);
> +        csr_ps = FIELD_EX64(GET_CSR_IF(guest, TLBIDX), CSR_TLBIDX, PS);
>           if (is_la64(env)) {
> -            csr_vppn = FIELD_EX64(env->CSR_TLBEHI, CSR_TLBEHI_64, VPPN);
> +            csr_vppn =
> +                FIELD_EX64(GET_CSR_IF(guest, TLBEHI), CSR_TLBEHI_64, VPPN);
>           } else {
> -            csr_vppn = FIELD_EX64(env->CSR_TLBEHI, CSR_TLBEHI_32, VPPN);
> +            csr_vppn =
> +                FIELD_EX64(GET_CSR_IF(guest, TLBEHI), CSR_TLBEHI_32, VPPN);
>           }
> -        lo0 = env->CSR_TLBELO0;
> -        lo1 = env->CSR_TLBELO1;
> +        lo0 = GET_CSR_IF(guest, TLBELO0);
> +        lo1 = GET_CSR_IF(guest, TLBELO1);
>       }
>   
>       context->ps = csr_ps;
> @@ -193,7 +228,7 @@ static void sptw_prepare_context(CPULoongArchState *env, MMUContext *context)
>   }
>   
>   static void fill_tlb_entry(CPULoongArchState *env, LoongArchTLB *tlb,
> -                           MMUContext *context)
> +                           MMUContext *context, bool guest)
>   {
>       uint64_t lo0, lo1, csr_vppn;
>       uint16_t csr_asid;
> @@ -208,8 +243,9 @@ static void fill_tlb_entry(CPULoongArchState *env, LoongArchTLB *tlb,
>       tlb->tlb_misc = FIELD_DP64(tlb->tlb_misc, TLB_MISC, PS, csr_ps);
>       tlb->tlb_misc = FIELD_DP64(tlb->tlb_misc, TLB_MISC, VPPN, csr_vppn);
>       tlb->tlb_misc = FIELD_DP64(tlb->tlb_misc, TLB_MISC, E, 1);
> -    csr_asid = FIELD_EX64(env->CSR_ASID, CSR_ASID, ASID);
> +    csr_asid = FIELD_EX64(GET_CSR_IF(guest, ASID), CSR_ASID, ASID);
>       tlb->tlb_misc = FIELD_DP64(tlb->tlb_misc, TLB_MISC, ASID, csr_asid);
> +    tlb->tlb_misc = FIELD_DP64(tlb->tlb_misc, TLB_MISC, GID, get_tgid(env));
>   
>       tlb->tlb_entry0 = lo0;
>       tlb->tlb_entry1 = lo1;
> @@ -233,7 +269,8 @@ static uint32_t get_random_tlb(uint32_t low, uint32_t high)
>    */
>   static LoongArchTLB *loongarch_tlb_search_cb(CPULoongArchState *env,
>                                                vaddr vaddr, int csr_asid,
> -                                             tlb_match func)
> +                                             tlb_match func, bool guest,
> +                                             uint8_t gid)
>   {
>       LoongArchTLB *tlb;
>       uint16_t tlb_asid, stlb_idx;
> @@ -242,14 +279,15 @@ static LoongArchTLB *loongarch_tlb_search_cb(CPULoongArchState *env,
>       int i, compare_shift;
>       uint64_t vpn, tlb_vppn;
>   
> -    stlb_ps = FIELD_EX64(env->CSR_STLBPS, CSR_STLBPS, PS);
> +    stlb_ps = FIELD_EX64(GET_CSR_IF(guest, STLBPS), CSR_STLBPS, PS);
>       vpn = (vaddr & TARGET_VIRT_MASK) >> (stlb_ps + 1);
>       stlb_idx = vpn & 0xff; /* VA[25:15] <==> TLBIDX.index for 16KiB Page */
>       compare_shift = stlb_ps + 1 - R_TLB_MISC_VPPN_SHIFT;
>   
>       /* Search STLB */
>       for (i = 0; i < 8; ++i) {
> -        tlb = &env->tlb[i * 256 + stlb_idx];
> +        tlb = guest ? &env->gtlb[i * 256 + stlb_idx] :
> +                      &env->tlb[i * 256 + stlb_idx];
>           tlb_e = FIELD_EX64(tlb->tlb_misc, TLB_MISC, E);
>           if (tlb_e) {
>               tlb_vppn = FIELD_EX64(tlb->tlb_misc, TLB_MISC, VPPN);
> @@ -257,6 +295,7 @@ static LoongArchTLB *loongarch_tlb_search_cb(CPULoongArchState *env,
>               tlb_g = !!FIELD_EX64(tlb->tlb_entry0, TLBENTRY, G);
>   
>               if (func(tlb_g, csr_asid, tlb_asid) &&
> +                tlb_entry_matches_gid(tlb, gid) &&
>                   (vpn == (tlb_vppn >> compare_shift))) {
>                   return tlb;
>               }
> @@ -265,7 +304,7 @@ static LoongArchTLB *loongarch_tlb_search_cb(CPULoongArchState *env,
>   
>       /* Search MTLB */
>       for (i = LOONGARCH_STLB; i < LOONGARCH_TLB_MAX; ++i) {
> -        tlb = &env->tlb[i];
> +        tlb = guest ? &env->gtlb[i] : &env->tlb[i];
>           tlb_e = FIELD_EX64(tlb->tlb_misc, TLB_MISC, E);
>           if (tlb_e) {
>               tlb_vppn = FIELD_EX64(tlb->tlb_misc, TLB_MISC, VPPN);
> @@ -275,6 +314,7 @@ static LoongArchTLB *loongarch_tlb_search_cb(CPULoongArchState *env,
>               compare_shift = tlb_ps + 1 - R_TLB_MISC_VPPN_SHIFT;
>               vpn = (vaddr & TARGET_VIRT_MASK) >> (tlb_ps + 1);
>               if (func(tlb_g, csr_asid, tlb_asid) &&
> +                tlb_entry_matches_gid(tlb, gid) &&
>                   (vpn == (tlb_vppn >> compare_shift))) {
>                   return tlb;
>               }
> @@ -284,17 +324,17 @@ static LoongArchTLB *loongarch_tlb_search_cb(CPULoongArchState *env,
>   }
>   
>   static bool loongarch_tlb_search(CPULoongArchState *env, vaddr vaddr,
> -                                 int *index)
> +                                 int *index, bool guest, uint8_t gid)
>   {
>       int csr_asid;
>       tlb_match func;
>       LoongArchTLB *tlb;
>   
>       func = tlb_match_any;
> -    csr_asid = FIELD_EX64(env->CSR_ASID, CSR_ASID, ASID);
> -    tlb = loongarch_tlb_search_cb(env, vaddr, csr_asid, func);
> +    csr_asid = FIELD_EX64(GET_CSR_IF(guest, ASID), CSR_ASID, ASID);
> +    tlb = loongarch_tlb_search_cb(env, vaddr, csr_asid, func, guest, gid);
>       if (tlb) {
> -        *index = tlb - env->tlb;
> +        *index = guest ? (tlb - env->gtlb) : (tlb - env->tlb);
>           return true;
>       }
>   
> @@ -304,66 +344,112 @@ static bool loongarch_tlb_search(CPULoongArchState *env, vaddr vaddr,
>   void helper_tlbsrch(CPULoongArchState *env)
>   {
>       int index, match;
> +    vaddr search_ehi;
>   
> -    if (FIELD_EX64(env->CSR_TLBRERA, CSR_TLBRERA, ISTLBR)) {
> -        match = loongarch_tlb_search(env, env->CSR_TLBREHI, &index);
> +    if (FIELD_EX64(GET_CSR_IF(env->guest, TLBRERA), CSR_TLBRERA, ISTLBR)) {
> +        search_ehi = GET_CSR_IF(env->guest, TLBREHI);
>       } else {
> -        match = loongarch_tlb_search(env, env->CSR_TLBEHI, &index);
> +        search_ehi = GET_CSR_IF(env->guest, TLBEHI);
>       }
>   
> +    match = loongarch_tlb_search(env, search_ehi, &index, env->guest,
> +                                 get_tgid(env));
> +
>       if (match) {
> -        env->CSR_TLBIDX = FIELD_DP64(env->CSR_TLBIDX, CSR_TLBIDX, INDEX, index);
> -        env->CSR_TLBIDX = FIELD_DP64(env->CSR_TLBIDX, CSR_TLBIDX, NE, 0);
> +        SET_CSR_IF(env->guest, TLBIDX,
> +                   FIELD_DP64(GET_CSR_IF(env->guest, TLBIDX), CSR_TLBIDX, INDEX,
> +                              index));
> +        SET_CSR_IF(
> +            env->guest, TLBIDX,
> +            FIELD_DP64(GET_CSR_IF(env->guest, TLBIDX), CSR_TLBIDX, NE, 0));
>           return;
>       }
>   
> -    env->CSR_TLBIDX = FIELD_DP64(env->CSR_TLBIDX, CSR_TLBIDX, NE, 1);
> +    SET_CSR_IF(env->guest, TLBIDX,
> +               FIELD_DP64(GET_CSR_IF(env->guest, TLBIDX), CSR_TLBIDX, NE, 1));
>   }
>   
> -void helper_tlbrd(CPULoongArchState *env)
> +static void read_tlb(CPULoongArchState *env, bool guest)
>   {
>       LoongArchTLB *tlb;
>       int index;
>       uint8_t tlb_ps, tlb_e;
>   
> -    index = FIELD_EX64(env->CSR_TLBIDX, CSR_TLBIDX, INDEX);
> -    tlb = &env->tlb[index];
> +    index = FIELD_EX64(GET_CSR_IF(guest, TLBIDX), CSR_TLBIDX, INDEX);
> +    tlb = guest ? &env->gtlb[index] : &env->tlb[index];
>       tlb_ps = FIELD_EX64(tlb->tlb_misc, TLB_MISC, PS);
>       tlb_e = FIELD_EX64(tlb->tlb_misc, TLB_MISC, E);
>   
>       if (!tlb_e) {
>           /* Invalid TLB entry */
> -        env->CSR_TLBIDX = FIELD_DP64(env->CSR_TLBIDX, CSR_TLBIDX, NE, 1);
> -        env->CSR_ASID  = FIELD_DP64(env->CSR_ASID, CSR_ASID, ASID, 0);
> -        env->CSR_TLBEHI = 0;
> -        env->CSR_TLBELO0 = 0;
> -        env->CSR_TLBELO1 = 0;
> -        env->CSR_TLBIDX = FIELD_DP64(env->CSR_TLBIDX, CSR_TLBIDX, PS, 0);
> +        SET_CSR_IF(guest, TLBIDX,
> +                   FIELD_DP64(GET_CSR_IF(guest, TLBIDX), CSR_TLBIDX, NE, 1));
> +        SET_CSR_IF(guest, ASID,
> +                   FIELD_DP64(GET_CSR_IF(guest, ASID), CSR_ASID, ASID, 0));
> +        SET_CSR_IF(guest, TLBEHI, 0);
> +        SET_CSR_IF(guest, TLBELO0, 0);
> +        SET_CSR_IF(guest, TLBELO1, 0);
> +        SET_CSR_IF(guest, TLBIDX,
> +                   FIELD_DP64(GET_CSR_IF(guest, TLBIDX), CSR_TLBIDX, PS, 0));
>       } else {
>           /* Valid TLB entry */
> -        env->CSR_TLBIDX = FIELD_DP64(env->CSR_TLBIDX, CSR_TLBIDX, NE, 0);
> -        env->CSR_TLBIDX = FIELD_DP64(env->CSR_TLBIDX, CSR_TLBIDX,
> -                                     PS, (tlb_ps & 0x3f));
> -        env->CSR_TLBEHI = FIELD_EX64(tlb->tlb_misc, TLB_MISC, VPPN) <<
> -                                     R_TLB_MISC_VPPN_SHIFT;
> -        env->CSR_TLBELO0 = tlb->tlb_entry0;
> -        env->CSR_TLBELO1 = tlb->tlb_entry1;
> +        SET_CSR_IF(guest, TLBIDX,
> +                   FIELD_DP64(GET_CSR_IF(guest, TLBIDX), CSR_TLBIDX, NE, 0));
> +        SET_CSR_IF(guest, TLBIDX,
> +                   FIELD_DP64(GET_CSR_IF(guest, TLBIDX), CSR_TLBIDX, PS,
> +                              tlb_ps & 0x3f));
> +        SET_CSR_IF(guest, TLBEHI,
> +                   FIELD_EX64(tlb->tlb_misc, TLB_MISC, VPPN)
> +                       << R_TLB_MISC_VPPN_SHIFT);
> +        SET_CSR_IF(guest, TLBELO0, tlb->tlb_entry0);
> +        SET_CSR_IF(guest, TLBELO1, tlb->tlb_entry1);
> +    }
> +}
> +
> +void helper_tlbrd(CPULoongArchState *env)
> +{
> +    read_tlb(env, env->guest);
> +}
> +
> +void helper_gtlbsrch(CPULoongArchState *env)
> +{
> +    int index, match;
> +    vaddr search_ehi;
> +
> +    if (FIELD_EX64(env->GCSR_TLBRERA, CSR_TLBRERA, ISTLBR)) {
> +        search_ehi = env->GCSR_TLBREHI;
> +    } else {
> +        search_ehi = env->GCSR_TLBEHI;
> +    }
> +
> +    match = loongarch_tlb_search(env, search_ehi, &index, true, get_tgid(env));
> +    if (match) {
> +        env->GCSR_TLBIDX =
> +            FIELD_DP64(env->GCSR_TLBIDX, CSR_TLBIDX, INDEX, index);
> +        env->GCSR_TLBIDX = FIELD_DP64(env->GCSR_TLBIDX, CSR_TLBIDX, NE, 0);
> +        return;
>       }
> +    env->GCSR_TLBIDX = FIELD_DP64(env->GCSR_TLBIDX, CSR_TLBIDX, NE, 1);
> +}
> +
> +void helper_gtlbrd(CPULoongArchState *env)
> +{
> +    read_tlb(env, true);
>   }
>   
>   static void update_tlb_index(CPULoongArchState *env, MMUContext *context,
> -                             int index)
> +                             int index, bool guest)
>   {
>       LoongArchTLB *old, new = {};
>       bool skip_inv = false, tlb_v0, tlb_v1;
>   
> -    old = env->tlb + index;
> -    fill_tlb_entry(env, &new, context);
> +    old = guest ? env->gtlb + index : env->tlb + index;
> +    fill_tlb_entry(env, &new, context, guest);
>       /* Check whether ASID/VPPN is the same */
>       if (old->tlb_misc == new.tlb_misc) {
>           /* Check whether both even/odd pages is the same or invalid */
> -        tlb_v0 = pte_present(env, old->tlb_entry0);
> -        tlb_v1 = pte_present(env, old->tlb_entry1);
> +        tlb_v0 = pte_present(env, old->tlb_entry0, guest);
> +        tlb_v1 = pte_present(env, old->tlb_entry1, guest);
>           if ((!tlb_v0 || new.tlb_entry0 == old->tlb_entry0) &&
>               (!tlb_v1 || new.tlb_entry1 == old->tlb_entry1)) {
>               skip_inv = true;
> @@ -372,7 +458,7 @@ static void update_tlb_index(CPULoongArchState *env, MMUContext *context,
>   
>       /* flush tlb before updating the entry */
>       if (!skip_inv) {
> -        invalidate_tlb(env, index);
> +        invalidate_tlb(env, index, guest);
>       }
>   
>       *old = new;
> @@ -380,20 +466,34 @@ static void update_tlb_index(CPULoongArchState *env, MMUContext *context,
>   
>   void helper_tlbwr(CPULoongArchState *env)
>   {
> -    int index = FIELD_EX64(env->CSR_TLBIDX, CSR_TLBIDX, INDEX);
> +    int index = FIELD_EX64(GET_CSR_IF(env->guest, TLBIDX), CSR_TLBIDX, INDEX);
> +    MMUContext context;
> +
> +    if (FIELD_EX64(GET_CSR_IF(env->guest, TLBIDX), CSR_TLBIDX, NE)) {
> +        invalidate_tlb(env, index, env->guest);
> +        return;
> +    }
> +
> +    sptw_prepare_context(env, &context, env->guest);
> +    update_tlb_index(env, &context, index, env->guest);
> +}
> +
> +void helper_gtlbwr(CPULoongArchState *env)
> +{
> +    int index = FIELD_EX64(env->GCSR_TLBIDX, CSR_TLBIDX, INDEX);
>       MMUContext context;
>   
> -    if (FIELD_EX64(env->CSR_TLBIDX, CSR_TLBIDX, NE)) {
> -        invalidate_tlb(env, index);
> +    if (FIELD_EX64(env->GCSR_TLBIDX, CSR_TLBIDX, NE)) {
> +        invalidate_tlb(env, index, true);
>           return;
>       }
>   
> -    sptw_prepare_context(env, &context);
> -    update_tlb_index(env, &context, index);
> +    sptw_prepare_context(env, &context, true);
> +    update_tlb_index(env, &context, index, true);
>   }
>   
>   static int get_tlb_random_index(CPULoongArchState *env, vaddr addr,
> -                                int pagesize)
> +                                int pagesize, bool guest)
>   {
>       uint64_t address;
>       int index, set, i, stlb_idx;
> @@ -402,15 +502,16 @@ static int get_tlb_random_index(CPULoongArchState *env, vaddr addr,
>       uint8_t tlb_e, tlb_g;
>   
>       /* Validity of stlb_ps is checked in helper_csrwr_stlbps() */
> -    stlb_ps = FIELD_EX64(env->CSR_STLBPS, CSR_STLBPS, PS);
> -    asid = FIELD_EX64(env->CSR_ASID, CSR_ASID, ASID);
> +    stlb_ps = FIELD_EX64(GET_CSR_IF(guest, STLBPS), CSR_STLBPS, PS);
> +    asid = FIELD_EX64(GET_CSR_IF(guest, ASID), CSR_ASID, ASID);
>       if (pagesize == stlb_ps) {
>           /* Only write into STLB bits [47:13] */
>           address = addr & ~MAKE_64BIT_MASK(0, R_CSR_TLBEHI_64_VPPN_SHIFT);
>           set = -1;
>           stlb_idx = (address >> (stlb_ps + 1)) & 0xff; /* [0,255] */
>           for (i = 0; i < 8; ++i) {
> -            tlb = &env->tlb[i * 256 + stlb_idx];
> +            tlb = guest ? &env->gtlb[i * 256 + stlb_idx] :
> +                          &env->tlb[i * 256 + stlb_idx];
>               tlb_e = FIELD_EX64(tlb->tlb_misc, TLB_MISC, E);
>               if (!tlb_e) {
>                   set = i;
> @@ -419,7 +520,8 @@ static int get_tlb_random_index(CPULoongArchState *env, vaddr addr,
>   
>               tlb_asid = FIELD_EX64(tlb->tlb_misc, TLB_MISC, ASID);
>               tlb_g = FIELD_EX64(tlb->tlb_entry0, TLBENTRY, G);
> -            if (tlb_g == 0 && asid != tlb_asid) {
> +            if (tlb_g == 0 && asid != tlb_asid &&
> +                tlb_entry_matches_gid(tlb, get_tgid(env))) {
>                   set = i;
>               }
>           }
> @@ -433,7 +535,7 @@ static int get_tlb_random_index(CPULoongArchState *env, vaddr addr,
>           /* Only write into MTLB */
>           index = -1;
>           for (i = LOONGARCH_STLB; i < LOONGARCH_TLB_MAX; i++) {
> -            tlb = &env->tlb[i];
> +            tlb = guest ? &env->gtlb[i] : &env->tlb[i];
>               tlb_e = FIELD_EX64(tlb->tlb_misc, TLB_MISC, E);
>   
>               if (!tlb_e) {
> @@ -443,7 +545,8 @@ static int get_tlb_random_index(CPULoongArchState *env, vaddr addr,
>   
>               tlb_asid = FIELD_EX64(tlb->tlb_misc, TLB_MISC, ASID);
>               tlb_g = FIELD_EX64(tlb->tlb_entry0, TLBENTRY, G);
> -            if (tlb_g == 0 && asid != tlb_asid) {
> +            if (tlb_g == 0 && asid != tlb_asid &&
> +                tlb_entry_matches_gid(tlb, get_tgid(env))) {
>                   index = i;
>               }
>           }
> @@ -462,48 +565,70 @@ void helper_tlbfill(CPULoongArchState *env)
>       int index, pagesize;
>       MMUContext context;
>   
> -    if (FIELD_EX64(env->CSR_TLBRERA, CSR_TLBRERA, ISTLBR)) {
> -        entryhi = env->CSR_TLBREHI;
> +    if (FIELD_EX64(GET_CSR_IF(env->guest, TLBRERA), CSR_TLBRERA, ISTLBR)) {
> +        entryhi = GET_CSR_IF(env->guest, TLBREHI);
>           /* Validity of pagesize is checked in helper_ldpte() */
> -        pagesize = FIELD_EX64(env->CSR_TLBREHI, CSR_TLBREHI, PS);
> +        pagesize = FIELD_EX64(GET_CSR_IF(env->guest, TLBREHI), CSR_TLBREHI, PS);
>       } else {
> -        entryhi = env->CSR_TLBEHI;
> +        entryhi = GET_CSR_IF(env->guest, TLBEHI);
>           /* Validity of pagesize is checked in helper_tlbrd() */
> -        pagesize = FIELD_EX64(env->CSR_TLBIDX, CSR_TLBIDX, PS);
> +        pagesize = FIELD_EX64(GET_CSR_IF(env->guest, TLBIDX), CSR_TLBIDX, PS);
>       }
>   
> -    sptw_prepare_context(env, &context);
> -    index = get_tlb_random_index(env, entryhi, pagesize);
> -    invalidate_tlb(env, index);
> -    fill_tlb_entry(env, env->tlb + index, &context);
> +    sptw_prepare_context(env, &context, env->guest);
> +    index = get_tlb_random_index(env, entryhi, pagesize, env->guest);
> +    invalidate_tlb(env, index, env->guest);
> +    fill_tlb_entry(env, env->guest ? env->gtlb + index : env->tlb + index,
> +                   &context, env->guest);
>   }
>   
> -void helper_tlbclr(CPULoongArchState *env)
> +void helper_gtlbfill(CPULoongArchState *env)
> +{
> +    vaddr entryhi;
> +    int index, pagesize;
> +    MMUContext context;
> +
> +    if (FIELD_EX64(env->GCSR_TLBRERA, CSR_TLBRERA, ISTLBR)) {
> +        entryhi = env->GCSR_TLBREHI;
> +        pagesize = FIELD_EX64(env->GCSR_TLBREHI, CSR_TLBREHI, PS);
> +    } else {
> +        entryhi = env->GCSR_TLBEHI;
> +        pagesize = FIELD_EX64(env->GCSR_TLBIDX, CSR_TLBIDX, PS);
> +    }
> +
> +    sptw_prepare_context(env, &context, true);
> +    index = get_tlb_random_index(env, entryhi, pagesize, true);
> +    invalidate_tlb(env, index, true);
> +    fill_tlb_entry(env, env->gtlb + index, &context, true);
> +}
> +
> +static void clear_tlb_by_index(CPULoongArchState *env, bool guest)
>   {
>       LoongArchTLB *tlb;
>       int i, index;
>       uint16_t csr_asid, tlb_asid, tlb_g;
>   
> -    csr_asid = FIELD_EX64(env->CSR_ASID, CSR_ASID, ASID);
> -    index = FIELD_EX64(env->CSR_TLBIDX, CSR_TLBIDX, INDEX);
> +    csr_asid = FIELD_EX64(GET_CSR_IF(guest, ASID), CSR_ASID, ASID);
> +    index = FIELD_EX64(GET_CSR_IF(guest, TLBIDX), CSR_TLBIDX, INDEX);
>   
>       if (index < LOONGARCH_STLB) {
> -        /* STLB. One line per operation */
>           for (i = 0; i < 8; i++) {
> -            tlb = &env->tlb[i * 256 + (index % 256)];
> +            tlb = guest ? &env->gtlb[i * 256 + (index % 256)] :
> +                          &env->tlb[i * 256 + (index % 256)];
>               tlb_asid = FIELD_EX64(tlb->tlb_misc, TLB_MISC, ASID);
>               tlb_g = FIELD_EX64(tlb->tlb_entry0, TLBENTRY, G);
> -            if (!tlb_g && tlb_asid == csr_asid) {
> +            if (!tlb_g && tlb_asid == csr_asid &&
> +                tlb_entry_matches_gid(tlb, get_tgid(env))) {
>                   tlb->tlb_misc = FIELD_DP64(tlb->tlb_misc, TLB_MISC, E, 0);
>               }
>           }
>       } else if (index < LOONGARCH_TLB_MAX) {
> -        /* All MTLB entries */
>           for (i = LOONGARCH_STLB; i < LOONGARCH_TLB_MAX; i++) {
> -            tlb = &env->tlb[i];
> +            tlb = guest ? &env->gtlb[i] : &env->tlb[i];
>               tlb_asid = FIELD_EX64(tlb->tlb_misc, TLB_MISC, ASID);
>               tlb_g = FIELD_EX64(tlb->tlb_entry0, TLBENTRY, G);
> -            if (!tlb_g && tlb_asid == csr_asid) {
> +            if (!tlb_g && tlb_asid == csr_asid &&
> +                tlb_entry_matches_gid(tlb, get_tgid(env))) {
>                   tlb->tlb_misc = FIELD_DP64(tlb->tlb_misc, TLB_MISC, E, 0);
>               }
>           }
> @@ -512,62 +637,116 @@ void helper_tlbclr(CPULoongArchState *env)
>       tlb_flush(env_cpu(env));
>   }
>   
> -void helper_tlbflush(CPULoongArchState *env)
> +void helper_tlbclr(CPULoongArchState *env)
> +{
> +    clear_tlb_by_index(env, env->guest);
> +}
> +
> +void helper_gtlbclr(CPULoongArchState *env)
> +{
> +    clear_tlb_by_index(env, true);
> +}
> +
> +static void flush_tlb_by_index(CPULoongArchState *env, bool guest)
>   {
>       int i, index;
>   
> -    index = FIELD_EX64(env->CSR_TLBIDX, CSR_TLBIDX, INDEX);
> +    index = FIELD_EX64(GET_CSR_IF(guest, TLBIDX), CSR_TLBIDX, INDEX);
>   
>       if (index < LOONGARCH_STLB) {
> -        /* STLB. One line per operation */
>           for (i = 0; i < 8; i++) {
>               int s_idx = i * 256 + (index % 256);
> -            env->tlb[s_idx].tlb_misc = FIELD_DP64(env->tlb[s_idx].tlb_misc,
> -                                                  TLB_MISC, E, 0);
> +            LoongArchTLB *tlb = guest ? &env->gtlb[s_idx] : &env->tlb[s_idx];
> +
> +            tlb->tlb_misc = FIELD_DP64(tlb->tlb_misc, TLB_MISC, E, 0);
>           }
>       } else if (index < LOONGARCH_TLB_MAX) {
> -        /* All MTLB entries */
>           for (i = LOONGARCH_STLB; i < LOONGARCH_TLB_MAX; i++) {
> -            env->tlb[i].tlb_misc = FIELD_DP64(env->tlb[i].tlb_misc,
> -                                              TLB_MISC, E, 0);
> +            LoongArchTLB *tlb = guest ? &env->gtlb[i] : &env->tlb[i];
> +
> +            tlb->tlb_misc = FIELD_DP64(tlb->tlb_misc, TLB_MISC, E, 0);
>           }
>       }
>   
>       tlb_flush(env_cpu(env));
>   }
>   
> -void helper_invtlb_all(CPULoongArchState *env)
> +void helper_tlbflush(CPULoongArchState *env)
> +{
> +    flush_tlb_by_index(env, env->guest);
> +}
> +
> +void helper_gtlbflush(CPULoongArchState *env)
> +{
> +    flush_tlb_by_index(env, true);
> +}
> +
> +void helper_invtlb_all(CPULoongArchState *env, target_ulong info, uint32_t op,
> +                       uint32_t to_guest)
>   {
> +    uint16_t gid = to_guest ? (info & 0xff) : get_tgid(env);
> +
> +    if (to_guest && env->guest) {
> +        do_raise_exception(env, EXCCODE_IPE, GETPC());
> +    }
> +
> +    to_guest |= env->guest;
> +
>       for (int i = 0; i < LOONGARCH_TLB_MAX; i++) {
> -        env->tlb[i].tlb_misc = FIELD_DP64(env->tlb[i].tlb_misc,
> -                                          TLB_MISC, E, 0);
> +        LoongArchTLB *tlb = &env->tlb[i];
> +        LoongArchTLB *gtlb = &env->gtlb[i];
> +
> +        if (!to_guest && (op == 0 || tlb_entry_matches_gid(tlb, 0))) {
> +            tlb->tlb_misc = FIELD_DP64(tlb->tlb_misc, TLB_MISC, E, 0);
> +        }
> +        if ((!to_guest && op == 0) ||
> +            (to_guest && tlb_entry_matches_gid(gtlb, gid))) {
> +            gtlb->tlb_misc = FIELD_DP64(gtlb->tlb_misc, TLB_MISC, E, 0);
> +        }
>       }
>       tlb_flush(env_cpu(env));
>   }
>   
> -void helper_invtlb_all_g(CPULoongArchState *env, uint32_t g)
> +void helper_invtlb_all_g(CPULoongArchState *env, target_ulong info, uint32_t g,
> +                         uint32_t to_guest)
>   {
> +    uint16_t gid = to_guest ? (info & 0xff) : get_tgid(env);
> +
> +    if (to_guest && env->guest) {
> +        do_raise_exception(env, EXCCODE_IPE, GETPC());
> +    }
> +
> +    to_guest |= env->guest;
> +
>       for (int i = 0; i < LOONGARCH_TLB_MAX; i++) {
> -        LoongArchTLB *tlb = &env->tlb[i];
> +        LoongArchTLB *tlb = to_guest ? &env->gtlb[i] : &env->tlb[i];
>           uint8_t tlb_g = FIELD_EX64(tlb->tlb_entry0, TLBENTRY, G);
>   
> -        if (tlb_g == g) {
> +        if (tlb_g == g && tlb_entry_matches_gid(tlb, gid)) {
>               tlb->tlb_misc = FIELD_DP64(tlb->tlb_misc, TLB_MISC, E, 0);
>           }
>       }
>       tlb_flush(env_cpu(env));
>   }
>   
> -void helper_invtlb_all_asid(CPULoongArchState *env, target_ulong info)
> +void helper_invtlb_all_asid(CPULoongArchState *env, target_ulong info,
> +                            uint32_t to_guest)
>   {
>       uint16_t asid = info & R_CSR_ASID_ASID_MASK;
> +    uint16_t gid = to_guest ? ((info >> 16) & 0xff) : get_tgid(env);
> +
> +    if (to_guest && env->guest) {
> +        do_raise_exception(env, EXCCODE_IPE, GETPC());
> +    }
> +
> +    to_guest |= env->guest;
>   
>       for (int i = 0; i < LOONGARCH_TLB_MAX; i++) {
> -        LoongArchTLB *tlb = &env->tlb[i];
> +        LoongArchTLB *tlb = to_guest ? &env->gtlb[i] : &env->tlb[i];
>           uint8_t tlb_g = FIELD_EX64(tlb->tlb_entry0, TLBENTRY, G);
>           uint16_t tlb_asid = FIELD_EX64(tlb->tlb_misc, TLB_MISC, ASID);
>   
> -        if (!tlb_g && (tlb_asid == asid)) {
> +        if (!tlb_g && tlb_asid == asid && tlb_entry_matches_gid(tlb, gid)) {
>               tlb->tlb_misc = FIELD_DP64(tlb->tlb_misc, TLB_MISC, E, 0);
>           }
>       }
> @@ -575,66 +754,82 @@ void helper_invtlb_all_asid(CPULoongArchState *env, target_ulong info)
>   }
>   
>   void helper_invtlb_page_asid(CPULoongArchState *env, target_ulong info,
> -                             target_ulong addr)
> +                             target_ulong addr, uint32_t to_guest)
>   {
> -    int asid = info & 0x3ff;
> +    uint16_t asid = info & R_CSR_ASID_ASID_MASK;
> +    uint16_t gid = to_guest ? ((info >> 16) & 0xff) : get_tgid(env);
>       LoongArchTLB *tlb;
> -    tlb_match func;
> +    int index;
> +
> +    if (to_guest && env->guest) {
> +        do_raise_exception(env, EXCCODE_IPE, GETPC());
> +    }
> +    to_guest |= env->guest;
>   
> -    func = tlb_match_asid;
> -    tlb = loongarch_tlb_search_cb(env, addr, asid, func);
> +    tlb =
> +        loongarch_tlb_search_cb(env, addr, asid, tlb_match_asid, to_guest, gid);
>       if (tlb) {
> -        invalidate_tlb(env, tlb - env->tlb);
> +        index = to_guest ? (tlb - env->gtlb) : (tlb - env->tlb);
> +        invalidate_tlb(env, index, to_guest);
>       }
>   }
>   
> -void helper_invtlb_page_asid_or_g(CPULoongArchState *env,
> -                                  target_ulong info, target_ulong addr)
> +void helper_invtlb_page_asid_or_g(CPULoongArchState *env, target_ulong info,
> +                                  target_ulong addr, uint32_t to_guest)
>   {
> -    int asid = info & 0x3ff;
> +    uint16_t asid = info & R_CSR_ASID_ASID_MASK;
> +    uint16_t gid = to_guest ? ((info >> 16) & 0xff) : get_tgid(env);
>       LoongArchTLB *tlb;
> -    tlb_match func;
> +    int index;
>   
> -    func = tlb_match_any;
> -    tlb = loongarch_tlb_search_cb(env, addr, asid, func);
> +    if (to_guest && env->guest) {
> +        do_raise_exception(env, EXCCODE_IPE, GETPC());
> +    }
> +
> +    to_guest |= env->guest;
> +
> +    tlb =
> +        loongarch_tlb_search_cb(env, addr, asid, tlb_match_any, to_guest, gid);
>       if (tlb) {
> -        invalidate_tlb(env, tlb - env->tlb);
> +        index = to_guest ? (tlb - env->gtlb) : (tlb - env->tlb);
> +        invalidate_tlb(env, index, to_guest);
>       }
>   }
>   
> -static void ptw_update_tlb(CPULoongArchState *env, MMUContext *context)
> +static void ptw_update_tlb(CPULoongArchState *env, MMUContext *context,
> +                           bool guest)
>   {
>       int index;
>   
>       index = context->tlb_index;
>       if (index < 0) {
> -        index = get_tlb_random_index(env, context->addr, context->ps);
> +        index = get_tlb_random_index(env, context->addr, context->ps, guest);
>       }
>   
> -    update_tlb_index(env, context, index);
> +    update_tlb_index(env, context, index, guest);
>   }
>   
> -bool loongarch_cpu_tlb_fill(CPUState *cs, vaddr address, int size,
> -                            MMUAccessType access_type, int mmu_idx,
> -                            bool probe, uintptr_t retaddr)
> +TLBRet loongarch_map_host_address(CPULoongArchState *env, MMUContext *context,
> +                                  MMUAccessType access_type, uintptr_t retaddr)
>   {
> -    CPULoongArchState *env = cpu_env(cs);
> -    hwaddr physical;
> -    int prot;
> -    MMUContext context;
>       TLBRet ret;
> +    ret = loongarch_map_address(env, context, access_type, MMU_KERNEL_IDX,
> +                                false, false, retaddr);
> +    return TLBRET_HOST_MATCH + ret;
> +}
>   
> -    /* Data access */
> -    context.addr = address;
> -    context.tlb_index = -1;
> -    ret = get_physical_address(env, &context, access_type, mmu_idx, 0);
> -    if (ret == TLBRET_MATCH && context.mmu_index != MMU_DA_IDX
> -        && cpu_has_ptw(env)) {
> +static void loongarch_try_ptw(CPULoongArchState *env, MMUContext *context,
> +                              MMUAccessType access_type, int mmu_index,
> +                              TLBRet *status, bool guest, uintptr_t retaddr)
> +{
> +    if ((*status == TLBRET_MATCH || *status == TLBRET_HOST_MATCH) &&
> +        context->mmu_index != MMU_DA_IDX &&
> +        context->mmu_index != MMU_GUEST_DA_IDX && cpu_has_ptw(env, guest)) {
>           bool need_update = true;
>   
> -        if (access_type == MMU_DATA_STORE && pte_dirty(context.pte)) {
> +        if (access_type == MMU_DATA_STORE && pte_dirty(context->pte)) {
>               need_update = false;
> -        } else if (access_type != MMU_DATA_STORE && pte_access(context.pte)) {
> +        } else if (access_type != MMU_DATA_STORE && pte_access(context->pte)) {
>               need_update = false;
>   
>               /*
> @@ -649,31 +844,77 @@ bool loongarch_cpu_tlb_fill(CPUState *cs, vaddr address, int size,
>   
>           if (need_update) {
>               /* Need update bit A/D in PTE entry, take PTW again */
> -            ret = TLBRET_NOMATCH;
> +            *status =
> +                (env->guest && !guest) ? TLBRET_HOST_NOMATCH : TLBRET_NOMATCH;
>           }
>       }
>   
> -    if (ret != TLBRET_MATCH && cpu_has_ptw(env)) {
> +    if (*status != TLBRET_MATCH && *status != TLBRET_HOST_MATCH &&
> +        cpu_has_ptw(env, guest)) {
>           /* Take HW PTW if TLB missed or bit P is zero */
> -        if (ret == TLBRET_NOMATCH || ret == TLBRET_INVALID) {
> -            ret = loongarch_ptw(env, &context, access_type, mmu_idx, 0);
> -            if (ret == TLBRET_MATCH) {
> -                ptw_update_tlb(env, &context);
> +        if (*status == TLBRET_NOMATCH || *status == TLBRET_INVALID ||
> +            *status == TLBRET_HOST_NOMATCH || *status == TLBRET_HOST_INVALID) {
> +            *status =
> +                ((env->guest && !guest) ? TLBRET_HOST_MATCH : TLBRET_MATCH) +
> +                loongarch_ptw(env, context, access_type, mmu_index, 0, guest,
> +                              retaddr);
> +            if (*status == TLBRET_MATCH || *status == TLBRET_HOST_MATCH) {
> +                ptw_update_tlb(env, context, guest);
>               }
> -        } else if (context.tlb_index >= 0) {
> -            invalidate_tlb(env, context.tlb_index);
> +        } else if (context->tlb_index >= 0) {
> +            invalidate_tlb(env, context->tlb_index, guest);
>           }
>       }
> +}
> +
> +bool loongarch_cpu_tlb_fill(CPUState *cs, vaddr address, int size,
> +                            MMUAccessType access_type, int mmu_idx, bool probe,
> +                            uintptr_t retaddr)
> +{
> +    CPULoongArchState *env = cpu_env(cs);
> +    MMUContext host_context;
> +    hwaddr physical;
> +    int prot, host_prot;
> +    MMUContext context;
> +    TLBRet ret;
> +
> +    /* Data access */
> +    context.addr = address;
> +    context.tlb_index = -1;
> +    ret = get_physical_address(env, &context, access_type, mmu_idx, 0, retaddr);
> +    loongarch_try_ptw(env, &context, access_type, mmu_idx, &ret, env->guest,
> +                      retaddr);
>   
>       if (ret == TLBRET_MATCH) {
>           physical = context.physical;
>           prot = context.prot;
> +        if (env->guest) {
> +            host_context.addr = physical;
> +            host_context.tlb_index = -1;
> +            ret = loongarch_map_host_address(env, &host_context, access_type,
> +                                             retaddr);
> +            loongarch_try_ptw(env, &host_context, access_type, MMU_KERNEL_IDX,
> +                              &ret, false, retaddr);
> +            if (ret != TLBRET_HOST_MATCH) {
> +                if (probe) {
> +                    return false;
> +                }
> +                raise_mmu_exception(env, physical, access_type, ret);
> +                cpu_loop_exit_restore(cs, retaddr);
> +                return false;
> +            }
> +            physical = host_context.physical;
> +            host_prot = host_context.prot;
> +            prot &= host_prot;
> +        }
>           tlb_set_page(cs, address & TARGET_PAGE_MASK,
>                        physical & TARGET_PAGE_MASK, prot,
>                        mmu_idx, TARGET_PAGE_SIZE);
>           qemu_log_mask(CPU_LOG_MMU,
>                         "%s address=%" VADDR_PRIx " physical " HWADDR_FMT_plx
> -                      " prot %d\n", __func__, address, physical, prot);
> +                      " prot %d guest %d\n",
> +                      __func__, address, physical, prot,
> +                      is_guest_mmu_idx(mmu_idx));
>           return true;
>       } else {
>           qemu_log_mask(CPU_LOG_MMU,
> @@ -702,6 +943,31 @@ static inline uint64_t loongarch_sanitize_hw_pte(CPULoongArchState *env,
>       return (pte & ~ppn_mask) | ((pte & ppn_mask) & palen_mask);
>   }
>   
> +hwaddr loongarch_get_host_address(CPULoongArchState *env, hwaddr gpa,
> +                                  uintptr_t retaddr)
> +{
> +    MMUContext host_context;
> +    TLBRet ret;
> +
> +    if (!env->guest) {
> +        return gpa;
> +    }
> +
> +    host_context.addr = gpa;
> +    host_context.tlb_index = -1;
> +    ret =
> +        loongarch_map_host_address(env, &host_context, MMU_DATA_LOAD, retaddr);
> +    loongarch_try_ptw(env, &host_context, MMU_DATA_LOAD, MMU_KERNEL_IDX, &ret,
> +                      false, retaddr);
> +
> +    if (ret != TLBRET_HOST_MATCH) {
> +        raise_mmu_exception(env, gpa, MMU_DATA_LOAD, ret);
> +        cpu_loop_exit_restore(env_cpu(env), retaddr);
> +    }
> +
> +    return host_context.physical;
> +}
> +
>   target_ulong helper_lddir(CPULoongArchState *env, target_ulong base,
>                             uint32_t level, uint32_t mem_idx)
>   {
> @@ -732,12 +998,15 @@ target_ulong helper_lddir(CPULoongArchState *env, target_ulong base,
>           }
>       }
>   
> -    badvaddr = env->CSR_TLBRBADV;
> +    badvaddr = GET_CSR_IF(env->guest, TLBRBADV);
>       base = base & palen_mask;
> -    get_dir_base_width(env, &dir_base, &dir_width, level);
> +    get_dir_base_width(env, &dir_base, &dir_width, level, env->guest);
>       index = (badvaddr >> dir_base) & ((1 << dir_width) - 1);
>       phys = base | index << 3;
> -    val = address_space_ldq_le(cs->as, phys, MEMTXATTRS_UNSPECIFIED, NULL);
> +    val = address_space_ldq_le(
> +        cs->as,
> +        (env->guest ? loongarch_get_host_address(env, phys, GETPC()) : phys),
> +        MEMTXATTRS_UNSPECIFIED, NULL);
>   
>       return val & palen_mask;
>   }
> @@ -749,8 +1018,10 @@ void helper_ldpte(CPULoongArchState *env, target_ulong base, target_ulong odd,
>       hwaddr phys, tmp0, ptindex, ptoffset0, ptoffset1;
>       uint64_t pte_raw;
>       uint64_t badv;
> -    uint64_t ptbase = FIELD_EX64(env->CSR_PWCL, CSR_PWCL, PTBASE);
> -    uint64_t ptwidth = FIELD_EX64(env->CSR_PWCL, CSR_PWCL, PTWIDTH);
> +    uint64_t ptbase =
> +        FIELD_EX64(GET_CSR_IF(env->guest, PWCL), CSR_PWCL, PTBASE);
> +    uint64_t ptwidth =
> +        FIELD_EX64(GET_CSR_IF(env->guest, PWCL), CSR_PWCL, PTWIDTH);
>       uint64_t palen_mask = loongarch_palen_mask(env);
>       uint64_t dir_base, dir_width;
>       uint8_t  ps;
> @@ -771,7 +1042,7 @@ void helper_ldpte(CPULoongArchState *env, target_ulong base, target_ulong odd,
>            * Move HGLOBAL bit to GLOBAL bit.
>            */
>           get_dir_base_width(env, &dir_base, &dir_width,
> -                           FIELD_EX64(base, TLBENTRY, LEVEL));
> +                           FIELD_EX64(base, TLBENTRY, LEVEL), env->guest);
>   
>           base = FIELD_DP64(base, TLBENTRY, LEVEL, 0);
>           base = FIELD_DP64(base, TLBENTRY, HUGE, 0);
> @@ -796,7 +1067,7 @@ void helper_ldpte(CPULoongArchState *env, target_ulong base, target_ulong odd,
>               return;
>           }
>       } else {
> -        badv = env->CSR_TLBRBADV;
> +        badv = GET_CSR_IF(env->guest, TLBRBADV);
>   
>           base = base & palen_mask;
>   
> @@ -805,26 +1076,31 @@ void helper_ldpte(CPULoongArchState *env, target_ulong base, target_ulong odd,
>           ptoffset0 = ptindex << 3;
>           ptoffset1 = (ptindex + 1) << 3;
>           phys = base | (odd ? ptoffset1 : ptoffset0);
> -        pte_raw = address_space_ldq_le(cs->as, phys,
> -                                       MEMTXATTRS_UNSPECIFIED, NULL);
> +        pte_raw = address_space_ldq_le(
> +            cs->as,
> +            (env->guest ? loongarch_get_host_address(env, phys, GETPC()) :
> +                          phys),
> +            MEMTXATTRS_UNSPECIFIED, NULL);
>           tmp0 = loongarch_sanitize_hw_pte(env, pte_raw);
>           ps = ptbase;
>       }
>   
>       if (odd) {
> -        env->CSR_TLBRELO1 = tmp0;
> +        SET_CSR_IF(env->guest, TLBRELO1, tmp0);
>       } else {
> -        env->CSR_TLBRELO0 = tmp0;
> +        SET_CSR_IF(env->guest, TLBRELO0, tmp0);
>       }
> -    env->CSR_TLBREHI = FIELD_DP64(env->CSR_TLBREHI, CSR_TLBREHI, PS, ps);
> +    SET_CSR_IF(
> +        env->guest, TLBREHI,
> +        FIELD_DP64(GET_CSR_IF(env->guest, TLBREHI), CSR_TLBREHI, PS, ps));
>   }
>   
>   static TLBRet loongarch_map_tlb_entry(CPULoongArchState *env,
>                                         MMUContext *context,
>                                         MMUAccessType access_type, int index,
> -                                      int mmu_idx)
> +                                      int mmu_idx, bool guest)
>   {
> -    LoongArchTLB *tlb = &env->tlb[index];
> +    LoongArchTLB *tlb = guest ? &env->gtlb[index] : &env->tlb[index];
>       uint8_t tlb_ps, n;
>   
>       tlb_ps = FIELD_EX64(tlb->tlb_misc, TLB_MISC, PS);
> @@ -832,19 +1108,20 @@ static TLBRet loongarch_map_tlb_entry(CPULoongArchState *env,
>       context->pte = n ? tlb->tlb_entry1 : tlb->tlb_entry0;
>       context->ps = tlb_ps;
>       context->tlb_index = index;
> -    return loongarch_check_pte(env, context, access_type, mmu_idx);
> +    return loongarch_check_pte(env, context, access_type, mmu_idx, guest);
>   }
>   
> -TLBRet loongarch_get_addr_from_tlb(CPULoongArchState *env,
> -                                   MMUContext *context,
> -                                   MMUAccessType access_type, int mmu_idx)
> +TLBRet loongarch_get_addr_from_tlb(CPULoongArchState *env, MMUContext *context,
> +                                   MMUAccessType access_type, int mmu_idx,
> +                                   bool guest)
>   {
>       int index, match;
>   
> -    match = loongarch_tlb_search(env, context->addr, &index);
> +    match =
> +        loongarch_tlb_search(env, context->addr, &index, guest, get_tgid(env));
>       if (match) {
>           return loongarch_map_tlb_entry(env, context, access_type, index,
> -                                       mmu_idx);
> +                                       mmu_idx, guest);
>       }
>   
>       return TLBRET_NOMATCH;
> diff --git a/target/loongarch/tcg/translate.c b/target/loongarch/tcg/translate.c
> index 124dce6269..15c83ef72d 100644
> --- a/target/loongarch/tcg/translate.c
> +++ b/target/loongarch/tcg/translate.c
> @@ -122,12 +122,16 @@ static void loongarch_tr_init_disas_context(DisasContextBase *dcbase,
>       CPULoongArchState *env = cpu_env(cs);
>       DisasContext *ctx = container_of(dcbase, DisasContext, base);
>   
> +    ctx->guest_mode = (ctx->base.tb->flags & HW_FLAGS_GUEST_MODE) != 0;
>       ctx->page_start = ctx->base.pc_first & TARGET_PAGE_MASK;
>       ctx->plv = ctx->base.tb->flags & HW_FLAGS_PLV_MASK;
>       if (ctx->base.tb->flags & HW_FLAGS_CRMD_PG) {
>           ctx->mem_idx = ctx->plv;
> +        if (ctx->guest_mode) {
> +            ctx->mem_idx += MMU_GUEST_IDX;
> +        }
>       } else {
> -        ctx->mem_idx = MMU_DA_IDX;
> +        ctx->mem_idx = ctx->guest_mode ? MMU_GUEST_DA_IDX : MMU_DA_IDX;
>       }
>   
>       /* Bound the number of insns to execute to those left on the page.  */
> diff --git a/target/loongarch/translate.h b/target/loongarch/translate.h
> index 8aa8325dc6..db0650e713 100644
> --- a/target/loongarch/translate.h
> +++ b/target/loongarch/translate.h
> @@ -26,6 +26,7 @@
>   #define avail_FP_DP(C)  (FIELD_EX32((C)->cpucfg2, CPUCFG2, FP_DP))
>   #define avail_LSPW(C)   (FIELD_EX32((C)->cpucfg2, CPUCFG2, LSPW))
>   #define avail_LAM(C)    (FIELD_EX32((C)->cpucfg2, CPUCFG2, LAM))
> +#define avail_LVZ(C)    (FIELD_EX32((C)->cpucfg2, CPUCFG2, LVZ))
>   #define avail_LAM_BH(C) (FIELD_EX32((C)->cpucfg2, CPUCFG2, LAM_BH))
>   #define avail_LAMCAS(C) (FIELD_EX32((C)->cpucfg2, CPUCFG2, LAMCAS))
>   #define avail_LSX(C)    (FIELD_EX32((C)->cpucfg2, CPUCFG2, LSX))
> @@ -66,6 +67,7 @@ typedef struct DisasContext {
>       TCGv zero;
>       bool la64; /* LoongArch64 mode */
>       bool va32; /* 32-bit virtual address */
> +    bool guest_mode;
>       uint32_t cpucfg1;
>       uint32_t cpucfg2;
>       uint32_t cpucfg3;
>