summary refs log tree commit diff stats
path: root/target/mips/system
diff options
context:
space:
mode:
Diffstat (limited to 'target/mips/system')
-rw-r--r--target/mips/system/addr.c44
-rw-r--r--target/mips/system/cp0.c123
-rw-r--r--target/mips/system/cp0_timer.c147
-rw-r--r--target/mips/system/machine.c336
-rw-r--r--target/mips/system/meson.build8
-rw-r--r--target/mips/system/mips-qmp-cmds.c38
-rw-r--r--target/mips/system/physaddr.c243
7 files changed, 939 insertions, 0 deletions
diff --git a/target/mips/system/addr.c b/target/mips/system/addr.c
new file mode 100644
index 0000000000..4f025be44a
--- /dev/null
+++ b/target/mips/system/addr.c
@@ -0,0 +1,44 @@
+/*
+ * QEMU MIPS address translation support
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "cpu.h"
+
+uint64_t cpu_mips_kseg0_to_phys(void *opaque, uint64_t addr)
+{
+    return addr & 0x1fffffffll;
+}
+
+uint64_t cpu_mips_phys_to_kseg0(void *opaque, uint64_t addr)
+{
+    return addr | ~0x7fffffffll;
+}
+
+uint64_t cpu_mips_kseg1_to_phys(void *opaque, uint64_t addr)
+{
+    return addr & 0x1fffffffll;
+}
+
+uint64_t cpu_mips_phys_to_kseg1(void *opaque, uint64_t addr)
+{
+    return (addr & 0x1fffffffll) | 0xffffffffa0000000ll;
+}
diff --git a/target/mips/system/cp0.c b/target/mips/system/cp0.c
new file mode 100644
index 0000000000..bae37f515b
--- /dev/null
+++ b/target/mips/system/cp0.c
@@ -0,0 +1,123 @@
+/*
+ * QEMU MIPS CPU
+ *
+ * Copyright (c) 2012 SUSE LINUX Products GmbH
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/lgpl-2.1.html>
+ */
+
+#include "qemu/osdep.h"
+#include "cpu.h"
+#include "internal.h"
+#include "exec/exec-all.h"
+
+/* Called for updates to CP0_Status.  */
+void sync_c0_status(CPUMIPSState *env, CPUMIPSState *cpu, int tc)
+{
+    int32_t tcstatus, *tcst;
+    uint32_t v = cpu->CP0_Status;
+    uint32_t cu, mx, asid, ksu;
+    uint32_t mask = ((1 << CP0TCSt_TCU3)
+                       | (1 << CP0TCSt_TCU2)
+                       | (1 << CP0TCSt_TCU1)
+                       | (1 << CP0TCSt_TCU0)
+                       | (1 << CP0TCSt_TMX)
+                       | (3 << CP0TCSt_TKSU)
+                       | (0xff << CP0TCSt_TASID));
+
+    cu = (v >> CP0St_CU0) & 0xf;
+    mx = (v >> CP0St_MX) & 0x1;
+    ksu = (v >> CP0St_KSU) & 0x3;
+    asid = env->CP0_EntryHi & env->CP0_EntryHi_ASID_mask;
+
+    tcstatus = cu << CP0TCSt_TCU0;
+    tcstatus |= mx << CP0TCSt_TMX;
+    tcstatus |= ksu << CP0TCSt_TKSU;
+    tcstatus |= asid;
+
+    if (tc == cpu->current_tc) {
+        tcst = &cpu->active_tc.CP0_TCStatus;
+    } else {
+        tcst = &cpu->tcs[tc].CP0_TCStatus;
+    }
+
+    *tcst &= ~mask;
+    *tcst |= tcstatus;
+    compute_hflags(cpu);
+}
+
+void cpu_mips_store_status(CPUMIPSState *env, target_ulong val)
+{
+    uint32_t mask = env->CP0_Status_rw_bitmask;
+    target_ulong old = env->CP0_Status;
+
+    if (env->insn_flags & ISA_MIPS_R6) {
+        bool has_supervisor = extract32(mask, CP0St_KSU, 2) == 0x3;
+#if defined(TARGET_MIPS64)
+        uint32_t ksux = (1 << CP0St_KX) & val;
+        ksux |= (ksux >> 1) & val; /* KX = 0 forces SX to be 0 */
+        ksux |= (ksux >> 1) & val; /* SX = 0 forces UX to be 0 */
+        val = (val & ~(7 << CP0St_UX)) | ksux;
+#endif
+        if (has_supervisor && extract32(val, CP0St_KSU, 2) == 0x3) {
+            mask &= ~(3 << CP0St_KSU);
+        }
+        mask &= ~(((1 << CP0St_SR) | (1 << CP0St_NMI)) & val);
+    }
+
+    env->CP0_Status = (old & ~mask) | (val & mask);
+#if defined(TARGET_MIPS64)
+    if ((env->CP0_Status ^ old) & (old & (7 << CP0St_UX))) {
+        /* Access to at least one of the 64-bit segments has been disabled */
+        tlb_flush(env_cpu(env));
+    }
+#endif
+    if (ase_mt_available(env)) {
+        sync_c0_status(env, env, env->current_tc);
+    } else {
+        compute_hflags(env);
+    }
+}
+
+void cpu_mips_store_cause(CPUMIPSState *env, target_ulong val)
+{
+    uint32_t mask = 0x00C00300;
+    uint32_t old = env->CP0_Cause;
+    int i;
+
+    if (env->insn_flags & ISA_MIPS_R2) {
+        mask |= 1 << CP0Ca_DC;
+    }
+    if (env->insn_flags & ISA_MIPS_R6) {
+        mask &= ~((1 << CP0Ca_WP) & val);
+    }
+
+    env->CP0_Cause = (env->CP0_Cause & ~mask) | (val & mask);
+
+    if ((old ^ env->CP0_Cause) & (1 << CP0Ca_DC)) {
+        if (env->CP0_Cause & (1 << CP0Ca_DC)) {
+            cpu_mips_stop_count(env);
+        } else {
+            cpu_mips_start_count(env);
+        }
+    }
+
+    /* Set/reset software interrupts */
+    for (i = 0 ; i < 2 ; i++) {
+        if ((old ^ env->CP0_Cause) & (1 << (CP0Ca_IP + i))) {
+            cpu_mips_soft_irq(env, i, env->CP0_Cause & (1 << (CP0Ca_IP + i)));
+        }
+    }
+}
diff --git a/target/mips/system/cp0_timer.c b/target/mips/system/cp0_timer.c
new file mode 100644
index 0000000000..ca16945cee
--- /dev/null
+++ b/target/mips/system/cp0_timer.c
@@ -0,0 +1,147 @@
+/*
+ * QEMU MIPS timer support
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/irq.h"
+#include "qemu/timer.h"
+#include "system/kvm.h"
+#include "internal.h"
+
+/* MIPS R4K timer */
+static uint32_t cpu_mips_get_count_val(CPUMIPSState *env)
+{
+    int64_t now_ns;
+    now_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+    return env->CP0_Count +
+            (uint32_t)clock_ns_to_ticks(env->count_clock, now_ns);
+}
+
+static void cpu_mips_timer_update(CPUMIPSState *env)
+{
+    uint64_t now_ns, next_ns;
+    uint32_t wait;
+
+    now_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+    wait = env->CP0_Compare - cpu_mips_get_count_val(env);
+    /* Clamp interval to overflow if virtual time had not progressed */
+    if (!wait) {
+        wait = UINT32_MAX;
+    }
+    next_ns = now_ns + clock_ticks_to_ns(env->count_clock, wait);
+    timer_mod(env->timer, next_ns);
+}
+
+/* Expire the timer.  */
+static void cpu_mips_timer_expire(CPUMIPSState *env)
+{
+    cpu_mips_timer_update(env);
+    if (env->insn_flags & ISA_MIPS_R2) {
+        env->CP0_Cause |= 1 << CP0Ca_TI;
+    }
+    qemu_irq_raise(env->irq[(env->CP0_IntCtl >> CP0IntCtl_IPTI) & 0x7]);
+}
+
+uint32_t cpu_mips_get_count(CPUMIPSState *env)
+{
+    if (env->CP0_Cause & (1 << CP0Ca_DC)) {
+        return env->CP0_Count;
+    } else {
+        uint64_t now_ns;
+
+        now_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+        if (timer_pending(env->timer)
+            && timer_expired(env->timer, now_ns)) {
+            /* The timer has already expired.  */
+            cpu_mips_timer_expire(env);
+        }
+
+        return cpu_mips_get_count_val(env);
+    }
+}
+
+void cpu_mips_store_count(CPUMIPSState *env, uint32_t count)
+{
+    /*
+     * This gets called from cpu_state_reset(), potentially before timer init.
+     * So env->timer may be NULL, which is also the case with KVM enabled so
+     * treat timer as disabled in that case.
+     */
+    if (env->CP0_Cause & (1 << CP0Ca_DC) || !env->timer) {
+        env->CP0_Count = count;
+    } else {
+        /* Store new count register */
+        env->CP0_Count = count - (uint32_t)clock_ns_to_ticks(env->count_clock,
+                        qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL));
+        /* Update timer timer */
+        cpu_mips_timer_update(env);
+    }
+}
+
+void cpu_mips_store_compare(CPUMIPSState *env, uint32_t value)
+{
+    env->CP0_Compare = value;
+    if (!(env->CP0_Cause & (1 << CP0Ca_DC))) {
+        cpu_mips_timer_update(env);
+    }
+    if (env->insn_flags & ISA_MIPS_R2) {
+        env->CP0_Cause &= ~(1 << CP0Ca_TI);
+    }
+    qemu_irq_lower(env->irq[(env->CP0_IntCtl >> CP0IntCtl_IPTI) & 0x7]);
+}
+
+void cpu_mips_start_count(CPUMIPSState *env)
+{
+    cpu_mips_store_count(env, env->CP0_Count);
+}
+
+void cpu_mips_stop_count(CPUMIPSState *env)
+{
+    /* Store the current value */
+    env->CP0_Count += (uint32_t)clock_ns_to_ticks(env->count_clock,
+                        qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL));
+}
+
+static void mips_timer_cb(void *opaque)
+{
+    CPUMIPSState *env;
+
+    env = opaque;
+
+    if (env->CP0_Cause & (1 << CP0Ca_DC)) {
+        return;
+    }
+
+    cpu_mips_timer_expire(env);
+}
+
+void cpu_mips_clock_init(MIPSCPU *cpu)
+{
+    CPUMIPSState *env = &cpu->env;
+
+    /*
+     * If we're in KVM mode, don't create the periodic timer, that is handled in
+     * kernel.
+     */
+    if (!kvm_enabled()) {
+        env->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, &mips_timer_cb, env);
+    }
+}
diff --git a/target/mips/system/machine.c b/target/mips/system/machine.c
new file mode 100644
index 0000000000..8af11fd896
--- /dev/null
+++ b/target/mips/system/machine.c
@@ -0,0 +1,336 @@
+#include "qemu/osdep.h"
+#include "cpu.h"
+#include "internal.h"
+#include "migration/cpu.h"
+#include "fpu_helper.h"
+
+static int cpu_post_load(void *opaque, int version_id)
+{
+    MIPSCPU *cpu = opaque;
+    CPUMIPSState *env = &cpu->env;
+
+    restore_fp_status(env);
+    restore_msa_fp_status(env);
+    compute_hflags(env);
+    restore_pamask(env);
+
+    return 0;
+}
+
+/* FPU state */
+
+static int get_fpr(QEMUFile *f, void *pv, size_t size,
+                   const VMStateField *field)
+{
+    int i;
+    fpr_t *v = pv;
+    /* Restore entire MSA vector register */
+    for (i = 0; i < MSA_WRLEN / 64; i++) {
+        qemu_get_sbe64s(f, &v->wr.d[i]);
+    }
+    return 0;
+}
+
+static int put_fpr(QEMUFile *f, void *pv, size_t size,
+                   const VMStateField *field, JSONWriter *vmdesc)
+{
+    int i;
+    fpr_t *v = pv;
+    /* Save entire MSA vector register */
+    for (i = 0; i < MSA_WRLEN / 64; i++) {
+        qemu_put_sbe64s(f, &v->wr.d[i]);
+    }
+
+    return 0;
+}
+
+static const VMStateInfo vmstate_info_fpr = {
+    .name = "fpr",
+    .get  = get_fpr,
+    .put  = put_fpr,
+};
+
+#define VMSTATE_FPR_ARRAY_V(_f, _s, _n, _v)                     \
+    VMSTATE_ARRAY(_f, _s, _n, _v, vmstate_info_fpr, fpr_t)
+
+#define VMSTATE_FPR_ARRAY(_f, _s, _n)                           \
+    VMSTATE_FPR_ARRAY_V(_f, _s, _n, 0)
+
+static const VMStateField vmstate_fpu_fields[] = {
+    VMSTATE_FPR_ARRAY(fpr, CPUMIPSFPUContext, 32),
+    VMSTATE_UINT32(fcr0, CPUMIPSFPUContext),
+    VMSTATE_UINT32(fcr31, CPUMIPSFPUContext),
+    VMSTATE_END_OF_LIST()
+};
+
+static const VMStateDescription vmstate_fpu = {
+    .name = "cpu/fpu",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = vmstate_fpu_fields
+};
+
+static const VMStateDescription vmstate_inactive_fpu = {
+    .name = "cpu/inactive_fpu",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = vmstate_fpu_fields
+};
+
+/* TC state */
+
+static const VMStateField vmstate_tc_fields[] = {
+    VMSTATE_UINTTL_ARRAY(gpr, TCState, 32),
+#if defined(TARGET_MIPS64)
+    VMSTATE_UINT64_ARRAY(gpr_hi, TCState, 32),
+#endif /* TARGET_MIPS64 */
+    VMSTATE_UINTTL(PC, TCState),
+    VMSTATE_UINTTL_ARRAY(HI, TCState, MIPS_DSP_ACC),
+    VMSTATE_UINTTL_ARRAY(LO, TCState, MIPS_DSP_ACC),
+    VMSTATE_UINTTL_ARRAY(ACX, TCState, MIPS_DSP_ACC),
+    VMSTATE_UINTTL(DSPControl, TCState),
+    VMSTATE_INT32(CP0_TCStatus, TCState),
+    VMSTATE_INT32(CP0_TCBind, TCState),
+    VMSTATE_UINTTL(CP0_TCHalt, TCState),
+    VMSTATE_UINTTL(CP0_TCContext, TCState),
+    VMSTATE_UINTTL(CP0_TCSchedule, TCState),
+    VMSTATE_UINTTL(CP0_TCScheFBack, TCState),
+    VMSTATE_INT32(CP0_Debug_tcstatus, TCState),
+    VMSTATE_UINTTL(CP0_UserLocal, TCState),
+    VMSTATE_INT32(msacsr, TCState),
+    VMSTATE_UINTTL_ARRAY(mxu_gpr, TCState, NUMBER_OF_MXU_REGISTERS - 1),
+    VMSTATE_UINTTL(mxu_cr, TCState),
+    VMSTATE_END_OF_LIST()
+};
+
+static const VMStateDescription vmstate_tc = {
+    .name = "cpu/tc",
+    .version_id = 2,
+    .minimum_version_id = 2,
+    .fields = vmstate_tc_fields
+};
+
+static const VMStateDescription vmstate_inactive_tc = {
+    .name = "cpu/inactive_tc",
+    .version_id = 2,
+    .minimum_version_id = 2,
+    .fields = vmstate_tc_fields
+};
+
+/* MVP state */
+
+static const VMStateDescription vmstate_mvp = {
+    .name = "cpu/mvp",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (const VMStateField[]) {
+        VMSTATE_INT32(CP0_MVPControl, CPUMIPSMVPContext),
+        VMSTATE_INT32(CP0_MVPConf0, CPUMIPSMVPContext),
+        VMSTATE_INT32(CP0_MVPConf1, CPUMIPSMVPContext),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+/* TLB state */
+
+static int get_tlb(QEMUFile *f, void *pv, size_t size,
+                   const VMStateField *field)
+{
+    r4k_tlb_t *v = pv;
+    uint16_t flags;
+
+    qemu_get_betls(f, &v->VPN);
+    qemu_get_be32s(f, &v->PageMask);
+    qemu_get_be16s(f, &v->ASID);
+    qemu_get_be32s(f, &v->MMID);
+    qemu_get_be16s(f, &flags);
+    v->G = (flags >> 10) & 1;
+    v->C0 = (flags >> 7) & 3;
+    v->C1 = (flags >> 4) & 3;
+    v->V0 = (flags >> 3) & 1;
+    v->V1 = (flags >> 2) & 1;
+    v->D0 = (flags >> 1) & 1;
+    v->D1 = (flags >> 0) & 1;
+    v->EHINV = (flags >> 15) & 1;
+    v->RI1 = (flags >> 14) & 1;
+    v->RI0 = (flags >> 13) & 1;
+    v->XI1 = (flags >> 12) & 1;
+    v->XI0 = (flags >> 11) & 1;
+    qemu_get_be64s(f, &v->PFN[0]);
+    qemu_get_be64s(f, &v->PFN[1]);
+
+    return 0;
+}
+
+static int put_tlb(QEMUFile *f, void *pv, size_t size,
+                   const VMStateField *field, JSONWriter *vmdesc)
+{
+    r4k_tlb_t *v = pv;
+
+    uint16_t asid = v->ASID;
+    uint32_t mmid = v->MMID;
+    uint16_t flags = ((v->EHINV << 15) |
+                      (v->RI1 << 14) |
+                      (v->RI0 << 13) |
+                      (v->XI1 << 12) |
+                      (v->XI0 << 11) |
+                      (v->G << 10) |
+                      (v->C0 << 7) |
+                      (v->C1 << 4) |
+                      (v->V0 << 3) |
+                      (v->V1 << 2) |
+                      (v->D0 << 1) |
+                      (v->D1 << 0));
+
+    qemu_put_betls(f, &v->VPN);
+    qemu_put_be32s(f, &v->PageMask);
+    qemu_put_be16s(f, &asid);
+    qemu_put_be32s(f, &mmid);
+    qemu_put_be16s(f, &flags);
+    qemu_put_be64s(f, &v->PFN[0]);
+    qemu_put_be64s(f, &v->PFN[1]);
+
+    return 0;
+}
+
+static const VMStateInfo vmstate_info_tlb = {
+    .name = "tlb_entry",
+    .get  = get_tlb,
+    .put  = put_tlb,
+};
+
+#define VMSTATE_TLB_ARRAY_V(_f, _s, _n, _v)                     \
+    VMSTATE_ARRAY(_f, _s, _n, _v, vmstate_info_tlb, r4k_tlb_t)
+
+#define VMSTATE_TLB_ARRAY(_f, _s, _n)                           \
+    VMSTATE_TLB_ARRAY_V(_f, _s, _n, 0)
+
+static const VMStateDescription vmstate_tlb = {
+    .name = "cpu/tlb",
+    .version_id = 3,
+    .minimum_version_id = 3,
+    .fields = (const VMStateField[]) {
+        VMSTATE_UINT32(nb_tlb, CPUMIPSTLBContext),
+        VMSTATE_UINT32(tlb_in_use, CPUMIPSTLBContext),
+        VMSTATE_TLB_ARRAY(mmu.r4k.tlb, CPUMIPSTLBContext, MIPS_TLB_MAX),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+/* MIPS CPU state */
+
+const VMStateDescription vmstate_mips_cpu = {
+    .name = "cpu",
+    .version_id = 21,
+    .minimum_version_id = 21,
+    .post_load = cpu_post_load,
+    .fields = (const VMStateField[]) {
+        /* Active TC */
+        VMSTATE_STRUCT(env.active_tc, MIPSCPU, 1, vmstate_tc, TCState),
+
+        /* Active FPU */
+        VMSTATE_STRUCT(env.active_fpu, MIPSCPU, 1, vmstate_fpu,
+                       CPUMIPSFPUContext),
+
+        /* MVP */
+        VMSTATE_STRUCT_POINTER(env.mvp, MIPSCPU, vmstate_mvp,
+                               CPUMIPSMVPContext),
+
+        /* TLB */
+        VMSTATE_STRUCT_POINTER(env.tlb, MIPSCPU, vmstate_tlb,
+                               CPUMIPSTLBContext),
+
+        /* CPU metastate */
+        VMSTATE_UINT32(env.current_tc, MIPSCPU),
+        VMSTATE_UNUSED(sizeof(uint32_t)), /* was current_fpu */
+        VMSTATE_INT32(env.error_code, MIPSCPU),
+        VMSTATE_UINTTL(env.btarget, MIPSCPU),
+        VMSTATE_UINTTL(env.bcond, MIPSCPU),
+
+        /* Remaining CP0 registers */
+        VMSTATE_INT32(env.CP0_Index, MIPSCPU),
+        VMSTATE_INT32(env.CP0_VPControl, MIPSCPU),
+        VMSTATE_INT32(env.CP0_Random, MIPSCPU),
+        VMSTATE_INT32(env.CP0_VPEControl, MIPSCPU),
+        VMSTATE_INT32(env.CP0_VPEConf0, MIPSCPU),
+        VMSTATE_INT32(env.CP0_VPEConf1, MIPSCPU),
+        VMSTATE_UINTTL(env.CP0_YQMask, MIPSCPU),
+        VMSTATE_UINTTL(env.CP0_VPESchedule, MIPSCPU),
+        VMSTATE_UINTTL(env.CP0_VPEScheFBack, MIPSCPU),
+        VMSTATE_INT32(env.CP0_VPEOpt, MIPSCPU),
+        VMSTATE_UINT64(env.CP0_EntryLo0, MIPSCPU),
+        VMSTATE_UINT64(env.CP0_EntryLo1, MIPSCPU),
+        VMSTATE_INT32(env.CP0_GlobalNumber, MIPSCPU),
+        VMSTATE_UINTTL(env.CP0_Context, MIPSCPU),
+        VMSTATE_INT32(env.CP0_MemoryMapID, MIPSCPU),
+        VMSTATE_INT32(env.CP0_PageMask, MIPSCPU),
+        VMSTATE_INT32(env.CP0_PageGrain, MIPSCPU),
+        VMSTATE_UINTTL(env.CP0_SegCtl0, MIPSCPU),
+        VMSTATE_UINTTL(env.CP0_SegCtl1, MIPSCPU),
+        VMSTATE_UINTTL(env.CP0_SegCtl2, MIPSCPU),
+        VMSTATE_UINTTL(env.CP0_PWBase, MIPSCPU),
+        VMSTATE_UINTTL(env.CP0_PWField, MIPSCPU),
+        VMSTATE_UINTTL(env.CP0_PWSize, MIPSCPU),
+        VMSTATE_INT32(env.CP0_Wired, MIPSCPU),
+        VMSTATE_INT32(env.CP0_PWCtl, MIPSCPU),
+        VMSTATE_INT32(env.CP0_SRSConf0, MIPSCPU),
+        VMSTATE_INT32(env.CP0_SRSConf1, MIPSCPU),
+        VMSTATE_INT32(env.CP0_SRSConf2, MIPSCPU),
+        VMSTATE_INT32(env.CP0_SRSConf3, MIPSCPU),
+        VMSTATE_INT32(env.CP0_SRSConf4, MIPSCPU),
+        VMSTATE_INT32(env.CP0_HWREna, MIPSCPU),
+        VMSTATE_UINTTL(env.CP0_BadVAddr, MIPSCPU),
+        VMSTATE_UINT32(env.CP0_BadInstr, MIPSCPU),
+        VMSTATE_UINT32(env.CP0_BadInstrP, MIPSCPU),
+        VMSTATE_UINT32(env.CP0_BadInstrX, MIPSCPU),
+        VMSTATE_INT32(env.CP0_Count, MIPSCPU),
+        VMSTATE_UNUSED(sizeof(uint32_t)), /* was CP0_SAARI */
+        VMSTATE_UNUSED(2 * sizeof(uint64_t)), /* was CP0_SAAR[2] */
+        VMSTATE_UINTTL(env.CP0_EntryHi, MIPSCPU),
+        VMSTATE_INT32(env.CP0_Compare, MIPSCPU),
+        VMSTATE_INT32(env.CP0_Status, MIPSCPU),
+        VMSTATE_INT32(env.CP0_IntCtl, MIPSCPU),
+        VMSTATE_INT32(env.CP0_SRSCtl, MIPSCPU),
+        VMSTATE_INT32(env.CP0_SRSMap, MIPSCPU),
+        VMSTATE_INT32(env.CP0_Cause, MIPSCPU),
+        VMSTATE_UINTTL(env.CP0_EPC, MIPSCPU),
+        VMSTATE_INT32(env.CP0_PRid, MIPSCPU),
+        VMSTATE_UINTTL(env.CP0_EBase, MIPSCPU),
+        VMSTATE_UINTTL(env.CP0_CMGCRBase, MIPSCPU),
+        VMSTATE_INT32(env.CP0_Config0, MIPSCPU),
+        VMSTATE_INT32(env.CP0_Config1, MIPSCPU),
+        VMSTATE_INT32(env.CP0_Config2, MIPSCPU),
+        VMSTATE_INT32(env.CP0_Config3, MIPSCPU),
+        VMSTATE_INT32(env.CP0_Config4, MIPSCPU),
+        VMSTATE_INT32(env.CP0_Config5, MIPSCPU),
+        VMSTATE_INT32(env.CP0_Config6, MIPSCPU),
+        VMSTATE_INT32(env.CP0_Config7, MIPSCPU),
+        VMSTATE_UINT64(env.CP0_LLAddr, MIPSCPU),
+        VMSTATE_UINT64_ARRAY(env.CP0_MAAR, MIPSCPU, MIPS_MAAR_MAX),
+        VMSTATE_INT32(env.CP0_MAARI, MIPSCPU),
+        VMSTATE_UINTTL(env.lladdr, MIPSCPU),
+        VMSTATE_UINTTL_ARRAY(env.CP0_WatchLo, MIPSCPU, 8),
+        VMSTATE_UINT64_ARRAY(env.CP0_WatchHi, MIPSCPU, 8),
+        VMSTATE_UINTTL(env.CP0_XContext, MIPSCPU),
+        VMSTATE_INT32(env.CP0_Framemask, MIPSCPU),
+        VMSTATE_INT32(env.CP0_Debug, MIPSCPU),
+        VMSTATE_UINTTL(env.CP0_DEPC, MIPSCPU),
+        VMSTATE_INT32(env.CP0_Performance0, MIPSCPU),
+        VMSTATE_INT32(env.CP0_ErrCtl, MIPSCPU),
+        VMSTATE_UINT64(env.CP0_TagLo, MIPSCPU),
+        VMSTATE_INT32(env.CP0_DataLo, MIPSCPU),
+        VMSTATE_INT32(env.CP0_TagHi, MIPSCPU),
+        VMSTATE_INT32(env.CP0_DataHi, MIPSCPU),
+        VMSTATE_UINTTL(env.CP0_ErrorEPC, MIPSCPU),
+        VMSTATE_INT32(env.CP0_DESAVE, MIPSCPU),
+        VMSTATE_UINTTL_ARRAY(env.CP0_KScratch, MIPSCPU, MIPS_KSCRATCH_NUM),
+
+        /* Inactive TC */
+        VMSTATE_STRUCT_ARRAY(env.tcs, MIPSCPU, MIPS_SHADOW_SET_MAX, 1,
+                             vmstate_inactive_tc, TCState),
+        VMSTATE_STRUCT_ARRAY(env.fpus, MIPSCPU, MIPS_FPU_MAX, 1,
+                             vmstate_inactive_fpu, CPUMIPSFPUContext),
+
+        VMSTATE_END_OF_LIST()
+    },
+};
diff --git a/target/mips/system/meson.build b/target/mips/system/meson.build
new file mode 100644
index 0000000000..498cf289d6
--- /dev/null
+++ b/target/mips/system/meson.build
@@ -0,0 +1,8 @@
+mips_system_ss.add(files(
+  'addr.c',
+  'cp0.c',
+  'cp0_timer.c',
+  'machine.c',
+  'mips-qmp-cmds.c',
+  'physaddr.c',
+))
diff --git a/target/mips/system/mips-qmp-cmds.c b/target/mips/system/mips-qmp-cmds.c
new file mode 100644
index 0000000000..7340ac70ba
--- /dev/null
+++ b/target/mips/system/mips-qmp-cmds.c
@@ -0,0 +1,38 @@
+/*
+ * QEMU MIPS CPU (monitor definitions)
+ *
+ * SPDX-FileCopyrightText: 2012 SUSE LINUX Products GmbH
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/qapi-commands-machine-target.h"
+#include "cpu.h"
+
+static void mips_cpu_add_definition(gpointer data, gpointer user_data)
+{
+    ObjectClass *oc = data;
+    CpuDefinitionInfoList **cpu_list = user_data;
+    CpuDefinitionInfo *info;
+    const char *typename;
+
+    typename = object_class_get_name(oc);
+    info = g_malloc0(sizeof(*info));
+    info->name = cpu_model_from_type(typename);
+    info->q_typename = g_strdup(typename);
+
+    QAPI_LIST_PREPEND(*cpu_list, info);
+}
+
+CpuDefinitionInfoList *qmp_query_cpu_definitions(Error **errp)
+{
+    CpuDefinitionInfoList *cpu_list = NULL;
+    GSList *list;
+
+    list = object_class_get_list(TYPE_MIPS_CPU, false);
+    g_slist_foreach(list, mips_cpu_add_definition, &cpu_list);
+    g_slist_free(list);
+
+    return cpu_list;
+}
diff --git a/target/mips/system/physaddr.c b/target/mips/system/physaddr.c
new file mode 100644
index 0000000000..505781d84c
--- /dev/null
+++ b/target/mips/system/physaddr.c
@@ -0,0 +1,243 @@
+/*
+ * MIPS TLB (Translation lookaside buffer) helpers.
+ *
+ *  Copyright (c) 2004-2005 Jocelyn Mayer
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include "qemu/osdep.h"
+#include "cpu.h"
+#include "exec/exec-all.h"
+#include "exec/page-protection.h"
+#include "../internal.h"
+
+static int is_seg_am_mapped(unsigned int am, bool eu, int mmu_idx)
+{
+    /*
+     * Interpret access control mode and mmu_idx.
+     *           AdE?     TLB?
+     *      AM  K S U E  K S U E
+     * UK    0  0 1 1 0  0 - - 0
+     * MK    1  0 1 1 0  1 - - !eu
+     * MSK   2  0 0 1 0  1 1 - !eu
+     * MUSK  3  0 0 0 0  1 1 1 !eu
+     * MUSUK 4  0 0 0 0  0 1 1 0
+     * USK   5  0 0 1 0  0 0 - 0
+     * -     6  - - - -  - - - -
+     * UUSK  7  0 0 0 0  0 0 0 0
+     */
+    int32_t adetlb_mask;
+
+    switch (mmu_idx) {
+    case 3: /* ERL */
+        /* If EU is set, always unmapped */
+        if (eu) {
+            return 0;
+        }
+        /* fall through */
+    case MIPS_HFLAG_KM:
+        /* Never AdE, TLB mapped if AM={1,2,3} */
+        adetlb_mask = 0x70000000;
+        goto check_tlb;
+
+    case MIPS_HFLAG_SM:
+        /* AdE if AM={0,1}, TLB mapped if AM={2,3,4} */
+        adetlb_mask = 0xc0380000;
+        goto check_ade;
+
+    case MIPS_HFLAG_UM:
+        /* AdE if AM={0,1,2,5}, TLB mapped if AM={3,4} */
+        adetlb_mask = 0xe4180000;
+        /* fall through */
+    check_ade:
+        /* does this AM cause AdE in current execution mode */
+        if ((adetlb_mask << am) < 0) {
+            return TLBRET_BADADDR;
+        }
+        adetlb_mask <<= 8;
+        /* fall through */
+    check_tlb:
+        /* is this AM mapped in current execution mode */
+        return ((adetlb_mask << am) < 0);
+    default:
+        g_assert_not_reached();
+    };
+}
+
+static int get_seg_physical_address(CPUMIPSState *env, hwaddr *physical,
+                                    int *prot, target_ulong real_address,
+                                    MMUAccessType access_type, int mmu_idx,
+                                    unsigned int am, bool eu,
+                                    target_ulong segmask,
+                                    hwaddr physical_base)
+{
+    int mapped = is_seg_am_mapped(am, eu, mmu_idx);
+
+    if (mapped < 0) {
+        /* is_seg_am_mapped can report TLBRET_BADADDR */
+        return mapped;
+    } else if (mapped) {
+        /* The segment is TLB mapped */
+        return env->tlb->map_address(env, physical, prot, real_address,
+                                     access_type);
+    } else {
+        /* The segment is unmapped */
+        *physical = physical_base | (real_address & segmask);
+        *prot = PAGE_READ | PAGE_WRITE | PAGE_EXEC;
+        return TLBRET_MATCH;
+    }
+}
+
+static int get_segctl_physical_address(CPUMIPSState *env, hwaddr *physical,
+                                       int *prot, target_ulong real_address,
+                                       MMUAccessType access_type, int mmu_idx,
+                                       uint16_t segctl, target_ulong segmask)
+{
+    unsigned int am = (segctl & CP0SC_AM_MASK) >> CP0SC_AM;
+    bool eu = (segctl >> CP0SC_EU) & 1;
+    hwaddr pa = ((hwaddr)segctl & CP0SC_PA_MASK) << 20;
+
+    return get_seg_physical_address(env, physical, prot, real_address,
+                                    access_type, mmu_idx, am, eu, segmask,
+                                    pa & ~(hwaddr)segmask);
+}
+
+int get_physical_address(CPUMIPSState *env, hwaddr *physical,
+                         int *prot, target_ulong real_address,
+                         MMUAccessType access_type, int mmu_idx)
+{
+    /* User mode can only access useg/xuseg */
+#if defined(TARGET_MIPS64)
+    int user_mode = mmu_idx == MIPS_HFLAG_UM;
+    int supervisor_mode = mmu_idx == MIPS_HFLAG_SM;
+    int kernel_mode = !user_mode && !supervisor_mode;
+    int UX = (env->CP0_Status & (1 << CP0St_UX)) != 0;
+    int SX = (env->CP0_Status & (1 << CP0St_SX)) != 0;
+    int KX = (env->CP0_Status & (1 << CP0St_KX)) != 0;
+#endif
+    int ret = TLBRET_MATCH;
+    /* effective address (modified for KVM T&E kernel segments) */
+    target_ulong address = real_address;
+
+    if (address <= USEG_LIMIT) {
+        /* useg */
+        uint16_t segctl;
+
+        if (address >= 0x40000000UL) {
+            segctl = env->CP0_SegCtl2;
+        } else {
+            segctl = env->CP0_SegCtl2 >> 16;
+        }
+        ret = get_segctl_physical_address(env, physical, prot,
+                                          real_address, access_type,
+                                          mmu_idx, segctl, 0x3FFFFFFF);
+#if defined(TARGET_MIPS64)
+    } else if (address < 0x4000000000000000ULL) {
+        /* xuseg */
+        if (UX && address <= (0x3FFFFFFFFFFFFFFFULL & env->SEGMask)) {
+            ret = env->tlb->map_address(env, physical, prot,
+                                        real_address, access_type);
+        } else {
+            ret = TLBRET_BADADDR;
+        }
+    } else if (address < 0x8000000000000000ULL) {
+        /* xsseg */
+        if ((supervisor_mode || kernel_mode) &&
+            SX && address <= (0x7FFFFFFFFFFFFFFFULL & env->SEGMask)) {
+            ret = env->tlb->map_address(env, physical, prot,
+                                        real_address, access_type);
+        } else {
+            ret = TLBRET_BADADDR;
+        }
+    } else if (address < 0xC000000000000000ULL) {
+        /* xkphys */
+        if ((address & 0x07FFFFFFFFFFFFFFULL) <= env->PAMask) {
+            /* KX/SX/UX bit to check for each xkphys EVA access mode */
+            static const uint8_t am_ksux[8] = {
+                [CP0SC_AM_UK]    = (1u << CP0St_KX),
+                [CP0SC_AM_MK]    = (1u << CP0St_KX),
+                [CP0SC_AM_MSK]   = (1u << CP0St_SX),
+                [CP0SC_AM_MUSK]  = (1u << CP0St_UX),
+                [CP0SC_AM_MUSUK] = (1u << CP0St_UX),
+                [CP0SC_AM_USK]   = (1u << CP0St_SX),
+                [6]              = (1u << CP0St_KX),
+                [CP0SC_AM_UUSK]  = (1u << CP0St_UX),
+            };
+            unsigned int am = CP0SC_AM_UK;
+            unsigned int xr = (env->CP0_SegCtl2 & CP0SC2_XR_MASK) >> CP0SC2_XR;
+
+            if (xr & (1 << ((address >> 59) & 0x7))) {
+                am = (env->CP0_SegCtl1 & CP0SC1_XAM_MASK) >> CP0SC1_XAM;
+            }
+            /* Does CP0_Status.KX/SX/UX permit the access mode (am) */
+            if (env->CP0_Status & am_ksux[am]) {
+                ret = get_seg_physical_address(env, physical, prot,
+                                               real_address, access_type,
+                                               mmu_idx, am, false, env->PAMask,
+                                               0);
+            } else {
+                ret = TLBRET_BADADDR;
+            }
+        } else {
+            ret = TLBRET_BADADDR;
+        }
+    } else if (address < 0xFFFFFFFF80000000ULL) {
+        /* xkseg */
+        if (kernel_mode && KX &&
+            address <= (0xFFFFFFFF7FFFFFFFULL & env->SEGMask)) {
+            ret = env->tlb->map_address(env, physical, prot,
+                                        real_address, access_type);
+        } else {
+            ret = TLBRET_BADADDR;
+        }
+#endif
+    } else if (address < KSEG1_BASE) {
+        /* kseg0 */
+        ret = get_segctl_physical_address(env, physical, prot, real_address,
+                                          access_type, mmu_idx,
+                                          env->CP0_SegCtl1 >> 16, 0x1FFFFFFF);
+    } else if (address < KSEG2_BASE) {
+        /* kseg1 */
+        ret = get_segctl_physical_address(env, physical, prot, real_address,
+                                          access_type, mmu_idx,
+                                          env->CP0_SegCtl1, 0x1FFFFFFF);
+    } else if (address < KSEG3_BASE) {
+        /* sseg (kseg2) */
+        ret = get_segctl_physical_address(env, physical, prot, real_address,
+                                          access_type, mmu_idx,
+                                          env->CP0_SegCtl0 >> 16, 0x1FFFFFFF);
+    } else {
+        /*
+         * kseg3
+         * XXX: debug segment is not emulated
+         */
+        ret = get_segctl_physical_address(env, physical, prot, real_address,
+                                          access_type, mmu_idx,
+                                          env->CP0_SegCtl0, 0x1FFFFFFF);
+    }
+    return ret;
+}
+
+hwaddr mips_cpu_get_phys_page_debug(CPUState *cs, vaddr addr)
+{
+    CPUMIPSState *env = cpu_env(cs);
+    hwaddr phys_addr;
+    int prot;
+
+    if (get_physical_address(env, &phys_addr, &prot, addr, MMU_DATA_LOAD,
+                             mips_env_mmu_index(env)) != 0) {
+        return -1;
+    }
+    return phys_addr;
+}