All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/7] m68k: Add Sun-3 Machine Emulation
@ 2026-05-03  1:57 54weasels
  2026-05-03  1:57 ` [PATCH 1/7] target/m68k: Implement Physical Bus Error exception handling 54weasels
                   ` (6 more replies)
  0 siblings, 7 replies; 10+ messages in thread
From: 54weasels @ 2026-05-03  1:57 UTC (permalink / raw)
  To: qemu-devel; +Cc: laurent, thuth, 54weasels

This implements a barebones Sun 3/60 that can boot a standard firmware, pass all the diagnostic tests and fully netboot both NetBSD 10 and SunOS 4.1.1
It has the minimum necessary devices: memory/MMU, diagnostic and error registers, interrupt, TOD clock, DMA, IDprom and configuration eeprom. I/O is the console serial and lance ethernet.
There are a few modifications needed to emulate a proper 68020, support another external MMU and sun3 specific ways of u sing the escc and ethernet devices.
Next steps are implementing SCSI, graphics, and patching through the keyboard and mouse.

This is part of the Sun 3/60 preservation project: https://github.com/54weasels/sun3_60

54weasels (7):
  target/m68k: Implement Physical Bus Error exception handling
  hw/net/lance: Add Sun-3 Native DMA byte-swapping support
  hw/char/escc: Expose diagnostic RS232 I/O routing
  hw/timer: Introduce Intersil 7170 RTC implementation
  hw/m68k: Overhaul Sun-3 MMU and Boot PROM mapping
  tests/qtest: Add Sun-3 hardware interaction tests
  tests/functional: Add Sun-3 firmware boot and diagnostic test

 hw/char/escc.c                      |  33 +-
 hw/m68k/Kconfig                     |   8 +
 hw/m68k/meson.build                 |   1 +
 hw/m68k/sun3.c                      | 499 ++++++++++++++++++++
 hw/m68k/sun3_eeprom_data.h          | 259 ++++++++++
 hw/m68k/sun3mmu.c                   | 705 ++++++++++++++++++++++++++++
 hw/net/lance.c                      | 111 ++++-
 hw/net/meson.build                  |   3 +-
 hw/timer/Kconfig                    |   3 +
 hw/timer/intersil7170.c             | 216 +++++++++
 hw/timer/meson.build                |   1 +
 include/hw/char/escc.h              |   3 +
 include/hw/m68k/sun3mmu.h           |  65 +++
 include/hw/net/lance.h              |   3 +
 include/hw/timer/intersil7170.h     |   9 +
 target/m68k/cpu.c                   |   5 +-
 target/m68k/cpu.h                   |  18 +-
 target/m68k/helper.c                | 130 ++++-
 target/m68k/op_helper.c             | 176 ++++---
 target/m68k/translate.c             |  31 +-
 tests/functional/m68k/meson.build   |   1 +
 tests/functional/m68k/test_sun3.py  |  34 ++
 tests/functional/qemu_test/asset.py |  11 +-
 tests/qtest/meson.build             |   1 +
 tests/qtest/sun3-test.c             |  67 +++
 25 files changed, 2294 insertions(+), 99 deletions(-)
 create mode 100644 hw/m68k/sun3.c
 create mode 100644 hw/m68k/sun3_eeprom_data.h
 create mode 100644 hw/m68k/sun3mmu.c
 create mode 100644 hw/timer/intersil7170.c
 create mode 100644 include/hw/m68k/sun3mmu.h
 create mode 100644 include/hw/timer/intersil7170.h
 create mode 100755 tests/functional/m68k/test_sun3.py
 create mode 100644 tests/qtest/sun3-test.c

-- 
2.50.1 (Apple Git-155)



^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH 1/7] target/m68k: Implement Physical Bus Error exception handling
  2026-05-03  1:57 [PATCH 0/7] m68k: Add Sun-3 Machine Emulation 54weasels
@ 2026-05-03  1:57 ` 54weasels
  2026-05-10  5:40   ` Thomas Huth
  2026-05-03  1:57 ` [PATCH 2/7] hw/net/lance: Add Sun-3 Native DMA byte-swapping support 54weasels
                   ` (5 subsequent siblings)
  6 siblings, 1 reply; 10+ messages in thread
From: 54weasels @ 2026-05-03  1:57 UTC (permalink / raw)
  To: qemu-devel; +Cc: laurent, thuth, 54weasels

The M68020 natively maps hardware Bus Error (BERR) timeouts into a Long Bus Cycle Fault (Format 0xB). This commit adds the memory exception routing to natively synthesize these EXCP_ACCESS cycle faults. It also implements the double-fault / watchdog reset behavior required for Sun-3 hardware diagnostics, properly handles FSAVE/FRESTORE for 68881 FPU stubs, and properly constructs the 84-byte internal bus fault frame.

Signed-off-by: 54weasels <54weasels@gmail.com>
---
 target/m68k/cpu.c       |   5 +-
 target/m68k/cpu.h       |  18 +++-
 target/m68k/helper.c    | 130 ++++++++++++++++++++++++++++-
 target/m68k/op_helper.c | 176 ++++++++++++++++++++++++++--------------
 target/m68k/translate.c |  31 +++++--
 5 files changed, 283 insertions(+), 77 deletions(-)

diff --git a/target/m68k/cpu.c b/target/m68k/cpu.c
index d849a4a90f..af375f0bce 100644
--- a/target/m68k/cpu.c
+++ b/target/m68k/cpu.c
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * QEMU Motorola 68k CPU
  *
@@ -51,8 +52,8 @@ static TCGTBCPUState m68k_get_tb_cpu_state(CPUState *cs)
     flags = (env->macsr >> 4) & TB_FLAGS_MACSR;
     if (env->sr & SR_S) {
         flags |= TB_FLAGS_MSR_S;
-        flags |= (env->sfc << (TB_FLAGS_SFC_S_BIT - 2)) & TB_FLAGS_SFC_S;
-        flags |= (env->dfc << (TB_FLAGS_DFC_S_BIT - 2)) & TB_FLAGS_DFC_S;
+    flags |= (env->sfc << TB_FLAGS_SFC_S_BIT) & TB_FLAGS_SFC_S;
+    flags |= (env->dfc << TB_FLAGS_DFC_S_BIT) & TB_FLAGS_DFC_S;
     }
     if (M68K_SR_TRACE(env->sr) == M68K_SR_TRACE_ANY_INS) {
         flags |= TB_FLAGS_TRACE;
diff --git a/target/m68k/cpu.h b/target/m68k/cpu.h
index 7911ab9de3..426ef6a6e1 100644
--- a/target/m68k/cpu.h
+++ b/target/m68k/cpu.h
@@ -149,10 +149,18 @@ typedef struct CPUArchState {
 
     int pending_vector;
     int pending_level;
+    bool nmi_pending;
 
     /* Fields up to this point are cleared by a CPU reset */
     struct {} end_reset_fields;
 
+    /* Custom MMU intercept logic, if any (e.g. for Sun-3) */
+    void *custom_mmu_opaque;
+    int (*custom_mmu_get_physical_address)(void *env, hwaddr *physical,
+                                           int *prot, vaddr address,
+                                           int access_type,
+                                           hwaddr *page_size);
+
     /* Fields from here on are preserved across CPU reset. */
     uint64_t features;
 } CPUM68KState;
@@ -601,12 +609,14 @@ void m68k_cpu_transaction_failed(CPUState *cs, hwaddr physaddr, vaddr addr,
 #define TB_FLAGS_MSR_S_BIT      13
 #define TB_FLAGS_MSR_S          (1 << TB_FLAGS_MSR_S_BIT)
 #define TB_FLAGS_SFC_S_BIT      14
-#define TB_FLAGS_SFC_S          (1 << TB_FLAGS_SFC_S_BIT)
-#define TB_FLAGS_DFC_S_BIT      15
-#define TB_FLAGS_DFC_S          (1 << TB_FLAGS_DFC_S_BIT)
-#define TB_FLAGS_TRACE          16
+#define TB_FLAGS_SFC_S          (7 << TB_FLAGS_SFC_S_BIT) /* 3 Bits reserved */
+#define TB_FLAGS_DFC_S_BIT      17
+#define TB_FLAGS_DFC_S          (7 << TB_FLAGS_DFC_S_BIT) /* 3 Bits reserved */
+#define TB_FLAGS_TRACE          20
 #define TB_FLAGS_TRACE_BIT      (1 << TB_FLAGS_TRACE)
 
+#define MMU_MOVES_FC_BASE       2 /* mmu_idx 2-9 correspond to FC 0-7 */
+
 void dump_mmu(CPUM68KState *env);
 
 #endif
diff --git a/target/m68k/helper.c b/target/m68k/helper.c
index 9bab184389..997c2616f4 100644
--- a/target/m68k/helper.c
+++ b/target/m68k/helper.c
@@ -28,6 +28,7 @@
 #include "system/memory.h"
 #include "gdbstub/helpers.h"
 #include "fpu/softfloat.h"
+#include "qemu/log.h"
 #include "qemu/qemu-print.h"
 
 #define SIGNBIT (1u << 31)
@@ -280,8 +281,10 @@ void HELPER(m68k_movec_to)(CPUM68KState *env, uint32_t reg, uint32_t val)
             return;
         }
         break;
-    /* Unimplemented Registers */
+    /* Dummy implementation for CAAR */
     case M68K_CR_CAAR:
+        return;
+    /* Unimplemented Registers */
     case M68K_CR_PCR:
     case M68K_CR_BUSCR:
         cpu_abort(env_cpu(env),
@@ -384,8 +387,10 @@ uint32_t HELPER(m68k_movec_from)(CPUM68KState *env, uint32_t reg)
             return env->mmu.ttr[M68K_DTTR1];
         }
         break;
-    /* Unimplemented Registers */
+    /* Dummy implementation for CAAR */
     case M68K_CR_CAAR:
+        return 0;
+    /* Unimplemented Registers */
     case M68K_CR_PCR:
     case M68K_CR_BUSCR:
         cpu_abort(env_cpu(env), "Unimplemented control register read 0x%x\n",
@@ -915,6 +920,21 @@ hwaddr m68k_cpu_get_phys_page_debug(CPUState *cs, vaddr addr)
     int access_type;
     target_ulong page_size;
 
+    access_type = ACCESS_DATA | ACCESS_DEBUG;
+    if (env->sr & SR_S) {
+        access_type |= ACCESS_SUPER;
+    }
+
+    if (env->custom_mmu_get_physical_address) {
+        hwaddr custom_page_size;
+        if (env->custom_mmu_get_physical_address(env, &phys_addr, &prot, addr,
+                                                 access_type,
+                                                 &custom_page_size) == 0) {
+            return phys_addr;
+        }
+        return -1;
+    }
+
     if ((env->mmu.tcr & M68K_TCR_ENABLED) == 0) {
         /* MMU disabled */
         return addr;
@@ -944,6 +964,12 @@ void m68k_set_irq_level(M68kCPU *cpu, int level, uint8_t vector)
     CPUState *cs = CPU(cpu);
     CPUM68KState *env = &cpu->env;
 
+    if (level == 7 && env->pending_level != 7) {
+        env->nmi_pending = true;
+    } else if (level != 7) {
+        env->nmi_pending = false;
+    }
+
     env->pending_level = level;
     env->pending_vector = vector;
     if (level) {
@@ -964,6 +990,106 @@ bool m68k_cpu_tlb_fill(CPUState *cs, vaddr address, int size,
     int ret;
     target_ulong page_size;
 
+    if (qemu_access_type == MMU_INST_FETCH) {
+        access_type = ACCESS_CODE;
+    } else {
+        access_type = ACCESS_DATA;
+        if (qemu_access_type == MMU_DATA_STORE) {
+            access_type |= ACCESS_STORE;
+        }
+    }
+
+    /* Decode explicit Function Codes from moves instructions */
+    if (mmu_idx >= MMU_MOVES_FC_BASE) {
+        uint8_t fc = mmu_idx - MMU_MOVES_FC_BASE;
+        access_type |= (fc << 8); /* Pack explicit FC into access type */
+        if (fc != 1 && fc != 2) {
+            access_type |= ACCESS_SUPER;
+        }
+    } else {
+        /* Standard memory accesses map logically to normal M68K FCs */
+        if (mmu_idx == MMU_KERNEL_IDX) {
+            access_type |= ACCESS_SUPER;
+            access_type |= ((qemu_access_type == MMU_INST_FETCH ? 6 : 5) << 8);
+        } else {
+            access_type |= ((qemu_access_type == MMU_INST_FETCH ? 2 : 1) << 8);
+        }
+    }
+
+    if (env->custom_mmu_get_physical_address) {
+        hwaddr custom_page_size;
+
+        /* Delegate translation to external board-specific MMU if registered */
+        ret = env->custom_mmu_get_physical_address(env, &physical, &prot,
+                                                   address, access_type,
+                                                   &custom_page_size);
+
+        if (likely(ret == 0)) {
+            tlb_set_page(cs, address & TARGET_PAGE_MASK,
+                         physical & TARGET_PAGE_MASK, prot,
+                         mmu_idx, custom_page_size);
+            return true;
+        }
+
+        if (probe) {
+            return false;
+        }
+
+        /* page fault */
+        cs->exception_index = EXCP_ACCESS;
+        env->mmu.ar = address;
+
+        if (m68k_feature(env, M68K_FEATURE_M68040)) {
+            env->mmu.ssw = M68K_ATC_040;
+            switch (size) {
+            case 1:
+                env->mmu.ssw |= M68K_BA_SIZE_BYTE;
+                break;
+            case 2:
+                env->mmu.ssw |= M68K_BA_SIZE_WORD;
+                break;
+            case 4:
+                env->mmu.ssw |= M68K_BA_SIZE_LONG;
+                break;
+            }
+            env->mmu.ssw |= M68K_TM_040_DATA;
+        } else {
+            /* M68020/030 Special Status Word (SSW) */
+            uint16_t ssw = 0x0100; /* DF - Data Fault */
+            switch (size) {
+            case 1:
+                ssw |= 0x0010;
+                break;
+            case 2:
+                ssw |= 0x0020;
+                break;
+            case 3:
+                ssw |= 0x0030;
+                break;
+            case 4:
+                ssw |= 0x0000;
+                break;
+            }
+            if (qemu_access_type != MMU_DATA_STORE) {
+                ssw |= 0x0040; /* RW - Read */
+            }
+            /* Function Code */
+            uint8_t fc;
+            if (mmu_idx >= MMU_MOVES_FC_BASE) {
+                fc = mmu_idx - MMU_MOVES_FC_BASE;
+            } else {
+                if (mmu_idx == MMU_KERNEL_IDX) {
+                    fc = (qemu_access_type == MMU_INST_FETCH) ? 6 : 5;
+                } else {
+                    fc = (qemu_access_type == MMU_INST_FETCH) ? 2 : 1;
+                }
+            }
+            ssw |= (fc & 7);
+            env->mmu.ssw = ssw;
+        }
+        cpu_loop_exit_restore(cs, retaddr);
+    }
+
     if ((env->mmu.tcr & M68K_TCR_ENABLED) == 0) {
         /* MMU disabled */
         tlb_set_page(cs, address & TARGET_PAGE_MASK,
diff --git a/target/m68k/op_helper.c b/target/m68k/op_helper.c
index 8148a8852e..84d5270767 100644
--- a/target/m68k/op_helper.c
+++ b/target/m68k/op_helper.c
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  *  M68K helper routines
  *
@@ -25,6 +26,7 @@
 #include "qemu/plugin.h"
 
 #if !defined(CONFIG_USER_ONLY)
+#include "system/runstate.h"
 
 static void cf_rte(CPUM68KState *env)
 {
@@ -73,6 +75,12 @@ throwaway:
         case 7:
             sp += 52;
             break;
+        case 0xa: /* Short Bus Cycle Fault (Format 0xA) */
+            sp += 32 - 8; /* 32 bytes total - 8 bytes header = 24 bytes */
+            break;
+        case 0xb: /* Long Bus Cycle Fault (Format 0xB) */
+            sp += 92 - 8; /* 92 bytes total - 8 bytes header = 84 bytes */
+            break;
         }
     }
     env->aregs[7] = sp;
@@ -342,56 +350,80 @@ static void m68k_interrupt_all(CPUM68KState *env, int is_hw)
     switch (cs->exception_index) {
     case EXCP_ACCESS:
         if (env->mmu.fault) {
-            cpu_abort(cs, "DOUBLE MMU FAULT\n");
+            qemu_log_mask(LOG_GUEST_ERROR,
+                          "M68K: Double MMU Fault. Halting CPU and requesting reset.\n");
+            qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
+            cs->halted = 1;
+            cs->exception_index = EXCP_HLT;
+            cpu_loop_exit(cs);
         }
         env->mmu.fault = true;
-        /* push data 3 */
-        sp -= 4;
-        cpu_stl_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
-        /* push data 2 */
-        sp -= 4;
-        cpu_stl_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
-        /* push data 1 */
-        sp -= 4;
-        cpu_stl_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
-        /* write back 1 / push data 0 */
-        sp -= 4;
-        cpu_stl_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
-        /* write back 1 address */
-        sp -= 4;
-        cpu_stl_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
-        /* write back 2 data */
-        sp -= 4;
-        cpu_stl_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
-        /* write back 2 address */
-        sp -= 4;
-        cpu_stl_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
-        /* write back 3 data */
-        sp -= 4;
-        cpu_stl_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
-        /* write back 3 address */
-        sp -= 4;
-        cpu_stl_be_mmuidx_ra(env, sp, env->mmu.ar, MMU_KERNEL_IDX, 0);
-        /* fault address */
-        sp -= 4;
-        cpu_stl_be_mmuidx_ra(env, sp, env->mmu.ar, MMU_KERNEL_IDX, 0);
-        /* write back 1 status */
-        sp -= 2;
-        cpu_stw_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
-        /* write back 2 status */
-        sp -= 2;
-        cpu_stw_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
-        /* write back 3 status */
-        sp -= 2;
-        cpu_stw_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
-        /* special status word */
-        sp -= 2;
-        cpu_stw_be_mmuidx_ra(env, sp, env->mmu.ssw, MMU_KERNEL_IDX, 0);
-        /* effective address */
-        sp -= 4;
-        cpu_stl_be_mmuidx_ra(env, sp, env->mmu.ar, MMU_KERNEL_IDX, 0);
-
-        do_stack_frame(env, &sp, 7, oldsr, 0, env->pc);
+
+        if (m68k_feature(env, M68K_FEATURE_M68040)) {
+            /* push data 3 */
+            sp -= 4;
+            cpu_stl_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
+            /* push data 2 */
+            sp -= 4;
+            cpu_stl_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
+            /* push data 1 */
+            sp -= 4;
+            cpu_stl_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
+            /* write back 1 / push data 0 */
+            sp -= 4;
+            cpu_stl_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
+            /* write back 1 address */
+            sp -= 4;
+            cpu_stl_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
+            /* write back 2 data */
+            sp -= 4;
+            cpu_stl_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
+            /* write back 2 address */
+            sp -= 4;
+            cpu_stl_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
+            /* write back 3 data */
+            sp -= 4;
+            cpu_stl_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
+            /* write back 3 address */
+            sp -= 4;
+            cpu_stl_be_mmuidx_ra(env, sp, env->mmu.ar, MMU_KERNEL_IDX, 0);
+            /* fault address */
+            sp -= 4;
+            cpu_stl_be_mmuidx_ra(env, sp, env->mmu.ar, MMU_KERNEL_IDX, 0);
+            /* write back 1 status */
+            sp -= 2;
+            cpu_stw_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
+            /* write back 2 status */
+            sp -= 2;
+            cpu_stw_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
+            /* write back 3 status */
+            sp -= 2;
+            cpu_stw_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
+            /* special status word */
+            sp -= 2;
+            cpu_stw_be_mmuidx_ra(env, sp, env->mmu.ssw, MMU_KERNEL_IDX, 0);
+            /* effective address */
+            sp -= 4;
+            cpu_stl_be_mmuidx_ra(env, sp, env->mmu.ar, MMU_KERNEL_IDX, 0);
+
+            do_stack_frame(env, &sp, 7, oldsr, 0, env->pc);
+        } else {
+            /* M68020 Long Bus Cycle Fault (Format 0xB) */
+            /*
+             * 84 bytes of internal state are pushed before the generic
+             * 8-byte header
+             */
+            sp -= 84;
+            for (int i = 0; i < 84; i += 4) {
+                cpu_stl_be_mmuidx_ra(env, sp + i, 0, MMU_KERNEL_IDX, 0);
+            }
+            /* Offset 0x02 from internal frame: SSW */
+            cpu_stw_be_mmuidx_ra(env, sp + 2, env->mmu.ssw, MMU_KERNEL_IDX, 0);
+            /* Offset 0x08 from internal frame: Fault Address */
+            cpu_stl_be_mmuidx_ra(env, sp + 8, env->mmu.ar, MMU_KERNEL_IDX, 0);
+
+            do_stack_frame(env, &sp, 0xb, oldsr, 0, env->pc);
+        }
         env->mmu.fault = false;
         if (qemu_loglevel_mask(CPU_LOG_INT)) {
             qemu_log("            "
@@ -437,7 +469,9 @@ static void m68k_interrupt_all(CPUM68KState *env, int is_hw)
 
     env->aregs[7] = sp;
     /* Jump to vector.  */
+    env->mmu.fault = true;
     env->pc = cpu_ldl_be_mmuidx_ra(env, env->vbr + vector, MMU_KERNEL_IDX, 0);
+    env->mmu.fault = false;
 
     do_plugin_vcpu_interrupt_cb(cs, last_pc);
 }
@@ -509,26 +543,46 @@ void m68k_cpu_transaction_failed(CPUState *cs, hwaddr physaddr, vaddr addr,
         if (access_type != MMU_DATA_STORE) {
             env->mmu.ssw |= M68K_RW_040;
         }
-
-        env->mmu.ar = addr;
-
-        cs->exception_index = EXCP_ACCESS;
-        cpu_loop_exit(cs);
+    } else if (m68k_feature(env, M68K_FEATURE_M68020)) {
+        /*
+         * M68020 Long Bus Cycle Fault (Format 0xB).
+         * The Motorola 68020 hardware intrinsically generates a physical
+         * Bus Error exception whenever the system bus flags a transaction
+         * timeout or failure (e.g., attempting to read an unpopulated bus
+         * address). This natively injects the EXCP_ACCESS cycle to build
+         * the generic 84-byte exception stack frame.
+         */
+        env->mmu.ssw = 0;
+        if (access_type == MMU_INST_FETCH) {
+            env->mmu.ssw |= 0x1000;
+        } else if (access_type == MMU_DATA_STORE) {
+            env->mmu.ssw |= 0x0040;
+        } else {
+            env->mmu.ssw |= 0x0080;
+        }
+    } else {
+        /*
+         * Older architectures (e.g. 68000) do not currently support
+         * hardware-injected transaction failures in QEMU.
+         */
+        return;
     }
+
+    env->mmu.ar = addr;
+    cs->exception_index = EXCP_ACCESS;
+    cpu_loop_exit(cs);
 }
 
 bool m68k_cpu_exec_interrupt(CPUState *cs, int interrupt_request)
 {
     CPUM68KState *env = cpu_env(cs);
 
-    if (interrupt_request & CPU_INTERRUPT_HARD
-        && ((env->sr & SR_I) >> SR_I_SHIFT) < env->pending_level) {
-        /*
-         * Real hardware gets the interrupt vector via an IACK cycle
-         * at this point.  Current emulated hardware doesn't rely on
-         * this, so we provide/save the vector when the interrupt is
-         * first signalled.
-         */
+    if (env->nmi_pending) {
+        env->nmi_pending = false;
+        cs->exception_index = env->pending_vector;
+        do_interrupt_m68k_hardirq(env);
+        return true;
+    } else if (((env->sr & SR_I) >> SR_I_SHIFT) < env->pending_level) {
         cs->exception_index = env->pending_vector;
         do_interrupt_m68k_hardirq(env);
         return true;
diff --git a/target/m68k/translate.c b/target/m68k/translate.c
index abc1c79f3c..d6fcd6c4d9 100644
--- a/target/m68k/translate.c
+++ b/target/m68k/translate.c
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  *  m68k translation
  *
@@ -163,10 +164,12 @@ static void do_writebacks(DisasContext *s)
 #define IS_USER(s) 1
 #else
 #define IS_USER(s)   (!(s->base.tb->flags & TB_FLAGS_MSR_S))
-#define SFC_INDEX(s) ((s->base.tb->flags & TB_FLAGS_SFC_S) ? \
-                      MMU_KERNEL_IDX : MMU_USER_IDX)
-#define DFC_INDEX(s) ((s->base.tb->flags & TB_FLAGS_DFC_S) ? \
-                      MMU_KERNEL_IDX : MMU_USER_IDX)
+#define SFC_INDEX(s) (MMU_MOVES_FC_BASE + \
+                     (((s)->base.tb->flags & TB_FLAGS_SFC_S) >> \
+                      TB_FLAGS_SFC_S_BIT))
+#define DFC_INDEX(s) (MMU_MOVES_FC_BASE + \
+                     (((s)->base.tb->flags & TB_FLAGS_DFC_S) >> \
+                      TB_FLAGS_DFC_S_BIT))
 #endif
 
 typedef void (*disas_proc)(CPUM68KState *env, DisasContext *s, uint16_t insn);
@@ -5364,11 +5367,19 @@ DISAS_INSN(frestore)
         gen_exception(s, s->base.pc_next, EXCP_PRIVILEGE);
         return;
     }
-    if (m68k_feature(s->env, M68K_FEATURE_M68040)) {
+    if (m68k_feature(s->env, M68K_FEATURE_M68040) ||
+        m68k_feature(s->env, M68K_FEATURE_FPU)) {
         SRC_EA(env, addr, OS_LONG, 0, NULL);
-        /* FIXME: check the state frame */
+        if (m68k_feature(s->env, M68K_FEATURE_M68040)) {
+            /* FIXME: check the state frame */
+        } else {
+            /*
+             * 68881/68882 FRESTORE: read the state frame
+             * (NULL frame is 4 bytes)
+             */
+        }
     } else {
-        disas_undef(env, s, insn);
+        disas_undef_fpu(env, s, insn);
     }
 }
 
@@ -5383,8 +5394,12 @@ DISAS_INSN(fsave)
         /* always write IDLE */
         TCGv idle = tcg_constant_i32(0x41000000);
         DEST_EA(env, insn, OS_LONG, idle, NULL);
+    } else if (m68k_feature(s->env, M68K_FEATURE_FPU)) {
+        /* 68881/68882 FSAVE: always write NULL frame */
+        TCGv null_frame = tcg_constant_i32(0x00000000);
+        DEST_EA(env, insn, OS_LONG, null_frame, NULL);
     } else {
-        disas_undef(env, s, insn);
+        disas_undef_fpu(env, s, insn);
     }
 }
 #endif
-- 
2.50.1 (Apple Git-155)



^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH 2/7] hw/net/lance: Add Sun-3 Native DMA byte-swapping support
  2026-05-03  1:57 [PATCH 0/7] m68k: Add Sun-3 Machine Emulation 54weasels
  2026-05-03  1:57 ` [PATCH 1/7] target/m68k: Implement Physical Bus Error exception handling 54weasels
@ 2026-05-03  1:57 ` 54weasels
  2026-05-03  1:57 ` [PATCH 3/7] hw/char/escc: Expose diagnostic RS232 I/O routing 54weasels
                   ` (4 subsequent siblings)
  6 siblings, 0 replies; 10+ messages in thread
From: 54weasels @ 2026-05-03  1:57 UTC (permalink / raw)
  To: qemu-devel; +Cc: laurent, thuth, 54weasels

The Sun-3 hardware physically byte-swaps the D0-D7 and D8-D15 DMA lanes between the Little-Endian LANCE controller and the Big-Endian Sun-3 memory. This commit intercepts the LANCE DMA reads/writes by injecting a phys_mem override in pcnet.c specifically for the Sun-3 instance, dynamically neutralizing the hardcoded internal initblk swap and properly mapping the payload payloads.

Signed-off-by: 54weasels <54weasels@gmail.com>
---
 hw/net/lance.c         | 111 ++++++++++++++++++++++++++++++++++++++---
 hw/net/meson.build     |   3 +-
 include/hw/net/lance.h |   3 ++
 3 files changed, 109 insertions(+), 8 deletions(-)

diff --git a/hw/net/lance.c b/hw/net/lance.c
index 5d5bf9b961..93944fb35d 100644
--- a/hw/net/lance.c
+++ b/hw/net/lance.c
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
  * QEMU AMD PC-Net II (Am79C970A) emulation
  *
@@ -22,7 +23,8 @@
  * THE SOFTWARE.
  */
 
-/* This software was written to be compatible with the specification:
+/*
+ * This software was written to be compatible with the specification:
  * AMD Am79C970A PCnet-PCI II Ethernet Controller Data-Sheet
  * AMD Publication# 19436  Rev:E  Amendment/0  Issue Date: June 2000
  */
@@ -44,8 +46,73 @@
 #include "hw/core/qdev-properties.h"
 #include "trace.h"
 #include "system/system.h"
+#include "system/address-spaces.h"
+#include "system/dma.h"
+
+/*
+ * LANCE Native DMA Read Hook
+ *
+ * Sun-3 Hardware intrinsically byte-swaps the D0-D7 and D8-D15 DMA lanes
+ * between the Little-Endian LANCE controller and the Big-Endian Sun-3 memory.
+ * We MUST model this manually. pcnet.c passes CSR_BSWP(s) for payloads
+ * (correctly bypassing the swap if LANCE internally neutralizes it).
+ * However, pcnet.c hardcodes do_bswap=1 for the `initblk` structure
+ * (len 24/28).
+ * We dynamically intercept the `initblk` fetch by cross-referencing CSR_IADR to
+ * enforce the hardware swap reliably. Because this branch is only taken when
+ * `dma_mr` is explicitly provided by the machine, this quirk is safely isolated
+ * to the Sun-3 and does not impact SPARC (which uses `ledma`).
+ */
+static void lance_dma_read(void *dma_opaque, hwaddr addr,
+                           uint8_t *buf, int len, int do_bswap)
+{
+    SysBusPCNetState *d = SYSBUS_PCNET(dma_opaque);
+    PCNetState *s = &d->state;
+
+    dma_memory_read(&d->dma_as, addr, buf, len, MEMTXATTRS_UNSPECIFIED);
+
+    uint32_t bcr_ssize32 = (s->bcr[20] & 0x0100);
+    hwaddr iadr = (s->csr[1] | ((uint32_t)s->csr[2] << 16));
+    if (!bcr_ssize32) {
+        iadr |= ((0xff00 & (uint32_t)s->csr[2]) << 16);
+    }
 
+    int internal_bswap = do_bswap;
+    if (addr == iadr && (len == 24 || len == 28)) {
+        internal_bswap = 0; /* Force hardware swap natively for initblk */
+    }
 
+    if (!internal_bswap) {
+        for (int i = 0; i < (len & ~1); i += 2) {
+            uint8_t tmp = buf[i];
+            buf[i] = buf[i + 1];
+            buf[i + 1] = tmp;
+        }
+    }
+}
+
+/* LANCE Native DMA Write Hook */
+static void lance_dma_write(void *dma_opaque, hwaddr addr,
+                            uint8_t *buf, int len, int do_bswap)
+{
+    SysBusPCNetState *s = SYSBUS_PCNET(dma_opaque);
+
+    if (!do_bswap) {
+        uint8_t *swapped_buf = g_malloc(len);
+        for (int i = 0; i < (len & ~1); i += 2) {
+            swapped_buf[i] = buf[i + 1];
+            swapped_buf[i + 1] = buf[i];
+        }
+        if (len & 1) {
+            swapped_buf[len - 1] = buf[len - 1];
+        }
+        dma_memory_write(&s->dma_as, addr, swapped_buf, len,
+                         MEMTXATTRS_UNSPECIFIED);
+        g_free(swapped_buf);
+    } else {
+        dma_memory_write(&s->dma_as, addr, buf, len, MEMTXATTRS_UNSPECIFIED);
+    }
+}
 static void parent_lance_reset(void *opaque, int irq, int level)
 {
     SysBusPCNetState *d = opaque;
@@ -59,7 +126,15 @@ static void lance_mem_write(void *opaque, hwaddr addr,
     SysBusPCNetState *d = opaque;
 
     trace_lance_mem_writew(addr, val & 0xffff);
-    pcnet_ioport_writew(&d->state, addr, val & 0xffff);
+    if (size == 1) {
+        uint16_t orig = pcnet_ioport_readw(&d->state, addr & ~1);
+        if (addr & 1) { /* LSB in Big Endian */
+            val = (orig & 0xff00) | (val & 0xff);
+        } else { /* MSB in Big Endian */
+            val = (orig & 0x00ff) | ((val & 0xff) << 8);
+        }
+    }
+    pcnet_ioport_writew(&d->state, addr & ~1, val & 0xffff);
 }
 
 static uint64_t lance_mem_read(void *opaque, hwaddr addr,
@@ -68,18 +143,28 @@ static uint64_t lance_mem_read(void *opaque, hwaddr addr,
     SysBusPCNetState *d = opaque;
     uint32_t val;
 
-    val = pcnet_ioport_readw(&d->state, addr);
-    trace_lance_mem_readw(addr, val & 0xffff);
+    val = pcnet_ioport_readw(&d->state, addr & ~1);
+    if (size == 1) {
+        if (addr & 1) {
+            val = val & 0xff;
+        } else {
+            val = (val >> 8) & 0xff;
+        }
+    }
     return val & 0xffff;
 }
 
 static const MemoryRegionOps lance_mem_ops = {
     .read = lance_mem_read,
     .write = lance_mem_write,
-    .endianness = DEVICE_NATIVE_ENDIAN,
+    .endianness = DEVICE_BIG_ENDIAN,
     .valid = {
-        .min_access_size = 2,
-        .max_access_size = 2,
+        .min_access_size = 1,
+        .max_access_size = 4,
+    },
+    .impl = {
+        .min_access_size = 1,
+        .max_access_size = 4,
     },
 };
 
@@ -115,8 +200,18 @@ static void lance_realize(DeviceState *dev, Error **errp)
 
     sysbus_init_irq(sbd, &s->irq);
 
+    if (d->dma_mr) {
+        address_space_init(&d->dma_as, d->dma_mr, "lance-dma");
+        s->phys_mem_read = lance_dma_read;
+        s->phys_mem_write = lance_dma_write;
+        s->dma_opaque = DEVICE(d);
+    } else {
+#if defined(TARGET_SPARC)
     s->phys_mem_read = ledma_memory_read;
     s->phys_mem_write = ledma_memory_write;
+#endif
+    }
+
     pcnet_common_init(dev, s, &net_lance_info);
 }
 
@@ -140,6 +235,8 @@ static void lance_instance_init(Object *obj)
 static const Property lance_properties[] = {
     DEFINE_PROP_LINK("dma", SysBusPCNetState, state.dma_opaque,
                      TYPE_DEVICE, DeviceState *),
+    DEFINE_PROP_LINK("dma_mr", SysBusPCNetState, dma_mr,
+                     TYPE_MEMORY_REGION, MemoryRegion *),
     DEFINE_NIC_PROPERTIES(SysBusPCNetState, state.conf),
 };
 
diff --git a/hw/net/meson.build b/hw/net/meson.build
index 3102587469..e653a9bc73 100644
--- a/hw/net/meson.build
+++ b/hw/net/meson.build
@@ -31,7 +31,8 @@ system_ss.add(when: 'CONFIG_MARVELL_88W8618', if_true: files('mv88w8618_eth.c'))
 
 system_ss.add(when: 'CONFIG_CADENCE', if_true: files('cadence_gem.c'))
 system_ss.add(when: 'CONFIG_STELLARIS_ENET', if_true: files('stellaris_enet.c'))
-system_ss.add(when: 'CONFIG_LANCE', if_true: files('lance.c'))
+# LANCE uses target-specific memory types (e.g., target_ulong) and must be compiled per-target.
+specific_ss.add(when: 'CONFIG_LANCE', if_true: files('lance.c'))
 system_ss.add(when: 'CONFIG_LASI_82596', if_true: files('lasi_i82596.c'))
 system_ss.add(when: 'CONFIG_I82596_COMMON', if_true: files('i82596.c'))
 system_ss.add(when: 'CONFIG_SUNHME', if_true: files('sunhme.c'))
diff --git a/include/hw/net/lance.h b/include/hw/net/lance.h
index be473e2eed..706160d7eb 100644
--- a/include/hw/net/lance.h
+++ b/include/hw/net/lance.h
@@ -31,6 +31,7 @@
 
 #include "net/net.h"
 #include "hw/net/pcnet.h"
+#include "system/memory.h"
 #include "hw/core/sysbus.h"
 #include "qom/object.h"
 
@@ -43,6 +44,8 @@ struct SysBusPCNetState {
     SysBusDevice parent_obj;
 
     PCNetState state;
+    MemoryRegion *dma_mr;
+    AddressSpace dma_as;
 };
 
 #endif
-- 
2.50.1 (Apple Git-155)



^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH 3/7] hw/char/escc: Expose diagnostic RS232 I/O routing
  2026-05-03  1:57 [PATCH 0/7] m68k: Add Sun-3 Machine Emulation 54weasels
  2026-05-03  1:57 ` [PATCH 1/7] target/m68k: Implement Physical Bus Error exception handling 54weasels
  2026-05-03  1:57 ` [PATCH 2/7] hw/net/lance: Add Sun-3 Native DMA byte-swapping support 54weasels
@ 2026-05-03  1:57 ` 54weasels
  2026-05-03  1:57 ` [PATCH 4/7] hw/timer: Introduce Intersil 7170 RTC implementation 54weasels
                   ` (3 subsequent siblings)
  6 siblings, 0 replies; 10+ messages in thread
From: 54weasels @ 2026-05-03  1:57 UTC (permalink / raw)
  To: qemu-devel; +Cc: laurent, thuth, 54weasels

This adds proper initialization and routing hooks for the ESCC serial controller to support the specific diagnostic RS232 UART mappings expected by the Sun-3 Boot PROM.

Signed-off-by: 54weasels <54weasels@gmail.com>
---
 hw/char/escc.c         | 33 ++++++++++++++++++++++++---------
 include/hw/char/escc.h |  3 +++
 2 files changed, 27 insertions(+), 9 deletions(-)

diff --git a/hw/char/escc.c b/hw/char/escc.c
index 3b46818ecc..d870806a35 100644
--- a/hw/char/escc.c
+++ b/hw/char/escc.c
@@ -313,7 +313,8 @@ static void escc_soft_reset_chn(ESCCChannelState *s)
 
     s->rregs[R_STATUS] &= STATUS_DCD | STATUS_SYNC | STATUS_CTS | STATUS_BRK;
     s->rregs[R_STATUS] |= STATUS_TXEMPTY | STATUS_TXUNDRN;
-    if (s->disabled) {
+    if (s->disabled || s->parent->force_hw_ready) {
+        /* Force assert to satisfy Sun-3 PROM Flow Control */
         s->rregs[R_STATUS] |= STATUS_DCD | STATUS_SYNC | STATUS_CTS;
     }
     s->rregs[R_SPEC] &= SPEC_ALLSENT;
@@ -653,7 +654,8 @@ static void escc_mem_write(void *opaque, hwaddr addr,
         s->txint = 0;
         escc_update_irq(s);
         s->tx = val;
-        if (s->wregs[W_TXCTRL2] & TXCTRL2_TXEN) { /* tx enabled */
+        if (s->parent->force_hw_ready || (s->wregs[W_TXCTRL2] & TXCTRL2_TXEN)) {
+            /* tx consistently forced enabled for Sun-3 boot PROM hooks */
             if (s->wregs[W_MISC2] & MISC2_LCL_LOOP) {
                 serial_receive_byte(s, s->tx);
             } else if (qemu_chr_fe_backend_connected(&s->chr)) {
@@ -691,6 +693,12 @@ static uint64_t escc_mem_read(void *opaque, hwaddr addr,
     case SERIAL_CTRL:
         trace_escc_mem_readb_ctrl(CHN_C(s), s->reg, s->rregs[s->reg]);
         ret = s->rregs[s->reg];
+        if (s->reg == R_STATUS) {
+            if (serial->force_hw_ready) {
+                ret |= STATUS_DCD | STATUS_SYNC | STATUS_CTS;
+                /* Force Flow Control PINs */
+            }
+        }
         s->reg = 0;
         return ret;
     case SERIAL_DATA:
@@ -715,6 +723,10 @@ static const MemoryRegionOps escc_mem_ops = {
     .write = escc_mem_write,
     .endianness = DEVICE_NATIVE_ENDIAN,
     .valid = {
+            .min_access_size = 1,
+            .max_access_size = 4,
+        },
+    .impl = {
         .min_access_size = 1,
         .max_access_size = 1,
     },
@@ -1067,16 +1079,17 @@ static void escc_realize(DeviceState *dev, Error **errp)
     s->chn[0].disabled = s->disabled;
     s->chn[1].disabled = s->disabled;
 
+    uint32_t escc_size = s->mmio_size ? s->mmio_size :\
+                         (ESCC_SIZE << s->it_shift);
     memory_region_init_io(&s->mmio, OBJECT(dev), &escc_mem_ops, s, "escc",
-                          ESCC_SIZE << s->it_shift);
+                        escc_size);
 
     for (i = 0; i < 2; i++) {
-        if (qemu_chr_fe_backend_connected(&s->chn[i].chr)) {
-            s->chn[i].clock = s->frequency / 2;
-            qemu_chr_fe_set_handlers(&s->chn[i].chr, serial_can_receive,
-                                     serial_receive1, serial_event, NULL,
-                                     &s->chn[i], NULL, true);
-        }
+        s->chn[i].parent = s;
+        s->chn[i].clock = s->frequency / 2;
+        qemu_chr_fe_set_handlers(&s->chn[i].chr, serial_can_receive,
+                                 serial_receive1, serial_event, NULL,
+                                 &s->chn[i], NULL, true);
     }
 
     if (s->chn[0].type == escc_mouse) {
@@ -1093,7 +1106,9 @@ static const Property escc_properties[] = {
     DEFINE_PROP_UINT32("frequency", ESCCState, frequency,   0),
     DEFINE_PROP_UINT32("it_shift",  ESCCState, it_shift,    0),
     DEFINE_PROP_BOOL("bit_swap",    ESCCState, bit_swap,    false),
+    DEFINE_PROP_BOOL("force-hw-ready", ESCCState, force_hw_ready, false),
     DEFINE_PROP_UINT32("disabled",  ESCCState, disabled,    0),
+    DEFINE_PROP_UINT32("mmio_size", ESCCState, mmio_size,   0),
     DEFINE_PROP_UINT32("chnBtype",  ESCCState, chn[0].type, 0),
     DEFINE_PROP_UINT32("chnAtype",  ESCCState, chn[1].type, 0),
     DEFINE_PROP_CHR("chrB", ESCCState, chn[0].chr),
diff --git a/include/hw/char/escc.h b/include/hw/char/escc.h
index 9e60175b77..db4700d8e4 100644
--- a/include/hw/char/escc.h
+++ b/include/hw/char/escc.h
@@ -49,6 +49,7 @@ typedef struct ESCCChannelState {
     int sunmouse_dx;
     int sunmouse_dy;
     int sunmouse_buttons;
+    ESCCState *parent;
 } ESCCChannelState;
 
 struct ESCCState {
@@ -57,9 +58,11 @@ struct ESCCState {
     struct ESCCChannelState chn[2];
     uint32_t it_shift;
     bool bit_swap;
+    bool force_hw_ready;
     MemoryRegion mmio;
     uint32_t disabled;
     uint32_t frequency;
+    uint32_t mmio_size;
 };
 
 #endif
-- 
2.50.1 (Apple Git-155)



^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH 4/7] hw/timer: Introduce Intersil 7170 RTC implementation
  2026-05-03  1:57 [PATCH 0/7] m68k: Add Sun-3 Machine Emulation 54weasels
                   ` (2 preceding siblings ...)
  2026-05-03  1:57 ` [PATCH 3/7] hw/char/escc: Expose diagnostic RS232 I/O routing 54weasels
@ 2026-05-03  1:57 ` 54weasels
  2026-05-03  1:57 ` [PATCH 5/7] hw/m68k: Overhaul Sun-3 MMU and Boot PROM mapping 54weasels
                   ` (2 subsequent siblings)
  6 siblings, 0 replies; 10+ messages in thread
From: 54weasels @ 2026-05-03  1:57 UTC (permalink / raw)
  To: qemu-devel; +Cc: laurent, thuth, 54weasels

This adds the Intersil 7170 Time-of-Day clock / CMOS RAM chip. It implements the register map, alarm interrupt logic, and persistent 32-byte battery-backed NVRAM used by the Sun-3 architecture for storing the MAC address, EEPROM checksums, and boot parameters.

Signed-off-by: 54weasels <54weasels@gmail.com>
---
 hw/timer/Kconfig                |   3 +
 hw/timer/intersil7170.c         | 216 ++++++++++++++++++++++++++++++++
 hw/timer/meson.build            |   1 +
 include/hw/timer/intersil7170.h |   9 ++
 4 files changed, 229 insertions(+)
 create mode 100644 hw/timer/intersil7170.c
 create mode 100644 include/hw/timer/intersil7170.h

diff --git a/hw/timer/Kconfig b/hw/timer/Kconfig
index b3d823ce2c..a828273b6c 100644
--- a/hw/timer/Kconfig
+++ b/hw/timer/Kconfig
@@ -65,3 +65,6 @@ config STELLARIS_GPTM
 
 config AVR_TIMER16
     bool
+
+config INTERSIL7170
+    bool
diff --git a/hw/timer/intersil7170.c b/hw/timer/intersil7170.c
new file mode 100644
index 0000000000..138151fdc1
--- /dev/null
+++ b/hw/timer/intersil7170.c
@@ -0,0 +1,216 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * QEMU Intersil 7170 Real Time Clock and Timer Emulation
+ *
+ * This device mimics the core functionality of the Intersil 7170 RTC,
+ * specifically targeting the 1/100th second periodic interrupt requested
+ * by the Sun-3 Boot PROM diagnostic routines.
+ */
+
+#include "qemu/osdep.h"
+
+#include "hw/timer/intersil7170.h"
+#include "hw/core/irq.h"
+#include "hw/core/qdev.h"
+#include "hw/core/sysbus.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qemu/timer.h"
+#include "qom/object.h"
+
+OBJECT_DECLARE_SIMPLE_TYPE(Intersil7170State, INTERSIL_7170)
+
+struct Intersil7170State {
+    SysBusDevice parent_obj;
+
+    MemoryRegion iomem;
+    qemu_irq irq;
+    QEMUTimer *timer;
+
+    /* Registers */
+    uint8_t int_status; /* 0x10 */
+    uint8_t int_mask;
+    uint8_t command; /* 0x11 */
+};
+
+#define REG_INT 0x10
+#define REG_CMD 0x11
+
+/* Interrupt Register bits */
+#define RTC_INT_PENDING 0x80
+#define RTC_INT_DAY 0x40
+#define RTC_INT_HOUR 0x20
+#define RTC_INT_MIN 0x10
+#define RTC_INT_SEC 0x08
+#define RTC_INT_TSEC 0x04
+#define RTC_INT_HSEC 0x02
+#define RTC_INT_ALARM 0x01
+
+/* Command Register bits */
+#define RTC_CMD_INTENA 0x10
+#define RTC_CMD_RUN 0x08
+
+static void intersil7170_update_irq(Intersil7170State *s)
+{
+    bool level = (s->int_status & s->int_mask) && (s->command & RTC_CMD_INTENA);
+
+    if (level) {
+        s->int_status |= RTC_INT_PENDING;
+    } else {
+        s->int_status &= ~RTC_INT_PENDING;
+    }
+
+    qemu_set_irq(s->irq, level);
+}
+
+static void intersil7170_timer_cb(void *opaque)
+{
+    Intersil7170State *s = opaque;
+
+    if (!(s->command & RTC_CMD_RUN)) {
+        return;
+    }
+
+    /*
+     * Timer fired. Set pending bit based on what is unmasked.
+     * The Sun-3 PROM primarily demands the Hundredth-Second
+     * (RTC_INT_HSEC) tick.
+     */
+    if (s->int_mask & RTC_INT_HSEC) {
+        s->int_status |= RTC_INT_HSEC;
+        intersil7170_update_irq(s);
+
+        /* Reschedule for 1/100th of a second (10,000,000 ns) */
+        timer_mod(s->timer,
+                      qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 10000000);
+    }
+}
+
+static uint64_t intersil7170_read(void *opaque, hwaddr addr, unsigned size)
+{
+    Intersil7170State *s = opaque;
+    uint32_t val = 0;
+
+    switch (addr) {
+    case REG_INT:
+        val = s->int_status;
+        /*
+         * Reading the interrupt register formally clears all
+         * pending interrupts.
+         */
+        s->int_status = 0;
+        intersil7170_update_irq(s);
+        break;
+    case REG_CMD:
+        val = s->command;
+        break;
+    default:
+        val = 0;
+        break;
+    }
+
+    return val;
+}
+
+static void intersil7170_write(void *opaque, hwaddr addr, uint64_t val,
+                               unsigned size)
+{
+    Intersil7170State *s = opaque;
+
+    switch (addr) {
+    case REG_INT:
+        /*
+         * Writing to the INT register sets the mask!
+         * Pending flag is read-only.
+         */
+        s->int_mask = val & ~RTC_INT_PENDING;
+        intersil7170_update_irq(s);
+
+        /* If timer requires starting, schedule immediately */
+        if ((s->command & RTC_CMD_RUN) && (s->int_mask & RTC_INT_HSEC)) {
+            timer_mod(s->timer,
+                      qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 10000000);
+        }
+        break;
+    case REG_CMD:
+        s->command = val;
+        intersil7170_update_irq(s);
+
+        if ((s->command & RTC_CMD_RUN) && (s->int_mask & RTC_INT_HSEC)) {
+            timer_mod(s->timer,
+                      qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 10000000);
+        } else if (!(s->command & RTC_CMD_RUN)) {
+            timer_del(s->timer);
+        }
+        break;
+    default:
+        break;
+    }
+}
+
+static const MemoryRegionOps intersil7170_ops = {
+    .read = intersil7170_read,
+    .write = intersil7170_write,
+    .endianness = DEVICE_BIG_ENDIAN,
+    .impl = {
+            .min_access_size = 1,
+            .max_access_size = 4,
+            .unaligned = true,
+        },
+    .valid = {
+            .min_access_size = 1,
+            .max_access_size = 4,
+            .unaligned = true,
+        },
+};
+
+static void intersil7170_realize(DeviceState *dev, Error **errp)
+{
+    Intersil7170State *s = INTERSIL_7170(OBJECT(dev));
+
+    s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, intersil7170_timer_cb, s);
+}
+
+static void intersil7170_reset(DeviceState *dev)
+{
+    Intersil7170State *s = INTERSIL_7170(OBJECT(dev));
+
+    s->int_status = 0;
+    s->int_mask = 0;
+    s->command = 0;
+    timer_del(s->timer);
+}
+
+static void intersil7170_init(Object *obj)
+{
+    Intersil7170State *s = INTERSIL_7170(obj);
+    SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+
+    memory_region_init_io(&s->iomem, obj, &intersil7170_ops, s, "intersil7170",
+                        8192);
+    sysbus_init_mmio(sbd, &s->iomem);
+    sysbus_init_irq(sbd, &s->irq);
+}
+
+static void intersil7170_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    dc->realize = intersil7170_realize;
+    device_class_set_legacy_reset(dc, intersil7170_reset);
+}
+
+static const TypeInfo intersil7170_info = {
+    .name = TYPE_INTERSIL_7170,
+    .parent = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(Intersil7170State),
+    .instance_init = intersil7170_init,
+    .class_init = intersil7170_class_init,
+};
+
+static void intersil7170_register_types(void)
+{
+    type_register_static(&intersil7170_info);
+}
+
+type_init(intersil7170_register_types)
diff --git a/hw/timer/meson.build b/hw/timer/meson.build
index 178321c029..72f33ec31f 100644
--- a/hw/timer/meson.build
+++ b/hw/timer/meson.build
@@ -15,6 +15,7 @@ system_ss.add(when: 'CONFIG_EXYNOS4', if_true: files('exynos4210_pwm.c'))
 system_ss.add(when: 'CONFIG_GRLIB', if_true: files('grlib_gptimer.c'))
 system_ss.add(when: 'CONFIG_HPET_C', if_true: files('hpet.c'))
 system_ss.add(when: 'CONFIG_I8254', if_true: files('i8254_common.c', 'i8254.c'))
+system_ss.add(when: 'CONFIG_INTERSIL7170', if_true: files('intersil7170.c'))
 system_ss.add(when: 'CONFIG_IMX', if_true: files('imx_epit.c'))
 system_ss.add(when: 'CONFIG_IMX', if_true: files('imx_gpt.c'))
 system_ss.add(when: 'CONFIG_MIPS_CPS', if_true: files('mips_gictimer.c'))
diff --git a/include/hw/timer/intersil7170.h b/include/hw/timer/intersil7170.h
new file mode 100644
index 0000000000..cab42b4cc0
--- /dev/null
+++ b/include/hw/timer/intersil7170.h
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#ifndef HW_TIMER_INTERSIL7170_H
+#define HW_TIMER_INTERSIL7170_H
+
+#define TYPE_INTERSIL_7170 "intersil7170"
+
+typedef struct Intersil7170State Intersil7170State;
+
+#endif
-- 
2.50.1 (Apple Git-155)



^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH 5/7] hw/m68k: Overhaul Sun-3 MMU and Boot PROM mapping
  2026-05-03  1:57 [PATCH 0/7] m68k: Add Sun-3 Machine Emulation 54weasels
                   ` (3 preceding siblings ...)
  2026-05-03  1:57 ` [PATCH 4/7] hw/timer: Introduce Intersil 7170 RTC implementation 54weasels
@ 2026-05-03  1:57 ` 54weasels
  2026-05-03  1:57 ` [PATCH 6/7] tests/qtest: Add Sun-3 hardware interaction tests 54weasels
  2026-05-03  1:57 ` [PATCH 7/7] tests/functional: Add Sun-3 firmware boot and diagnostic test 54weasels
  6 siblings, 0 replies; 10+ messages in thread
From: 54weasels @ 2026-05-03  1:57 UTC (permalink / raw)
  To: qemu-devel; +Cc: laurent, thuth, 54weasels

This heavily refactors the Sun-3 machine initialization to support its proprietary Custom MMU implementation, which replaces the standard Motorola 68851 MMU. It maps the diagnostic EEPROM, sets the correct 28-bit address bus masks, implements the OBIO aliasing rules for the Boot PROM, and connects the newly added LANCE, ESCC, and Intersil 7170 peripherals.

Signed-off-by: 54weasels <54weasels@gmail.com>
---
 hw/m68k/Kconfig            |   8 +
 hw/m68k/meson.build        |   1 +
 hw/m68k/sun3.c             | 499 ++++++++++++++++++++++++++
 hw/m68k/sun3_eeprom_data.h | 259 ++++++++++++++
 hw/m68k/sun3mmu.c          | 705 +++++++++++++++++++++++++++++++++++++
 include/hw/m68k/sun3mmu.h  |  65 ++++
 6 files changed, 1537 insertions(+)
 create mode 100644 hw/m68k/sun3.c
 create mode 100644 hw/m68k/sun3_eeprom_data.h
 create mode 100644 hw/m68k/sun3mmu.c
 create mode 100644 include/hw/m68k/sun3mmu.h

diff --git a/hw/m68k/Kconfig b/hw/m68k/Kconfig
index aff769b30f..59b36c75ee 100644
--- a/hw/m68k/Kconfig
+++ b/hw/m68k/Kconfig
@@ -46,3 +46,11 @@ config M68K_VIRT
     select GOLDFISH_TTY
     select GOLDFISH_RTC
     select VIRTIO_MMIO
+
+config SUN3
+    bool
+    default y
+    depends on M68K
+    select ESCC
+    select LANCE
+    select INTERSIL7170
diff --git a/hw/m68k/meson.build b/hw/m68k/meson.build
index 84bc68fa4e..c8b07d81fb 100644
--- a/hw/m68k/meson.build
+++ b/hw/m68k/meson.build
@@ -3,6 +3,7 @@ m68k_ss.add(when: 'CONFIG_AN5206', if_true: files('an5206.c', 'mcf5206.c'))
 m68k_ss.add(when: 'CONFIG_MCF5208', if_true: files('mcf5208.c', 'mcf_intc.c'))
 m68k_ss.add(when: 'CONFIG_NEXTCUBE', if_true: files('next-kbd.c', 'next-cube.c'))
 m68k_ss.add(when: 'CONFIG_Q800', if_true: files('q800.c', 'q800-glue.c'))
+m68k_ss.add(when: 'CONFIG_SUN3', if_true: files('sun3.c', 'sun3mmu.c'))
 m68k_ss.add(when: 'CONFIG_M68K_VIRT', if_true: files('virt.c'))
 
 hw_arch += {'m68k': m68k_ss}
diff --git a/hw/m68k/sun3.c b/hw/m68k/sun3.c
new file mode 100644
index 0000000000..16ad8b063d
--- /dev/null
+++ b/hw/m68k/sun3.c
@@ -0,0 +1,499 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * QEMU Sun-3 Board Emulation
+ *
+ * Copyright (c) 2026
+ */
+
+#include "qemu/osdep.h"
+
+#include "chardev/char.h"
+#include "hw/char/escc.h"
+#include "hw/core/boards.h"
+#include "hw/core/irq.h"
+#include "hw/core/loader.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/core/sysbus.h"
+#include "hw/net/lance.h"
+#include "hw/intc/m68k_irqc.h"
+#include "hw/m68k/sun3mmu.h"
+#include "hw/timer/intersil7170.h"
+#include "qapi/error.h"
+#include "system/qtest.h"
+#include "qemu/error-report.h"
+#include "qemu/log.h"
+#include "qemu/units.h"
+#include "sun3_eeprom_data.h"
+#include "system/address-spaces.h"
+#include "system/reset.h"
+#include "system/system.h"
+#include "target/m68k/cpu.h"
+#include "qom/object.h"
+
+#define SUN3_PROM_BASE 0x0FEF0000
+#define SUN3_PROM_SIZE (64 * 1024)
+
+#define TYPE_SUN3_MACHINE MACHINE_TYPE_NAME("sun3")
+OBJECT_DECLARE_SIMPLE_TYPE(Sun3MachineState, SUN3_MACHINE)
+
+struct Sun3MachineState {
+    MachineState parent_obj;
+
+    /* Embedded Memory Regions */
+    MemoryRegion rom;
+    MemoryRegion rom_alias;
+    MemoryRegion idprom;
+    MemoryRegion intreg_iomem;
+    MemoryRegion memerr_iomem;
+    MemoryRegion eeprom;
+    MemoryRegion nvram;
+    MemoryRegion timeout_net;
+
+    /* Devices */
+    DeviceState *irqc_dev;
+    DeviceState *sun3mmu;
+
+    /* Boot State */
+    uint32_t boot_sp;
+    uint32_t boot_pc;
+
+    /* Interrupt Register State */
+    uint8_t intreg;
+    bool clock_pending;
+
+    /* Memory Error Register (Parity Spoof) State */
+    uint8_t memerr_reg;
+    uint8_t spoof_parity_lane;
+    uint8_t parity_bit_counter;
+    bool test_parity_written;
+};
+
+static void sun3_update_clock_irq(Sun3MachineState *s)
+{
+    if (!s->irqc_dev) {
+        return;
+    }
+
+    /* Lower everything first */
+    qemu_irq_lower(qdev_get_gpio_in(s->irqc_dev, M68K_IRQC_LEVEL_5));
+    qemu_irq_lower(qdev_get_gpio_in(s->irqc_dev, M68K_IRQC_LEVEL_7));
+
+    /* Assert the currently enabled level if the clock is pulsing */
+    if (s->clock_pending && (s->intreg & 0x01)) {
+        if (s->intreg & 0x20) {
+            qemu_irq_raise(qdev_get_gpio_in(s->irqc_dev, M68K_IRQC_LEVEL_5));
+        } else if (s->intreg & 0x80) {
+            qemu_irq_raise(qdev_get_gpio_in(s->irqc_dev, M68K_IRQC_LEVEL_7));
+        }
+    }
+}
+
+static void sun3_clock_irq_handler(void *opaque, int n, int level)
+{
+    Sun3MachineState *s = SUN3_MACHINE(opaque);
+    s->clock_pending = !!level;
+    sun3_update_clock_irq(s);
+}
+
+static uint64_t sun3_intreg_read(void *opaque, hwaddr addr, unsigned size)
+{
+    Sun3MachineState *s = SUN3_MACHINE(opaque);
+    return s->intreg;
+}
+
+static void sun3_intreg_write(void *opaque, hwaddr addr, uint64_t val,
+                              unsigned size)
+{
+    Sun3MachineState *s = SUN3_MACHINE(opaque);
+    s->intreg = val;
+
+    qemu_log_mask(
+        LOG_GUEST_ERROR,
+        "[SUN3 INTREG] Write: 0x%02x (Enable=%d, L1=%d, L2=%d, L3=%d)\n",
+        (uint8_t)val, !!(val & 0x01), !!(val & 0x02), !!(val & 0x04),
+        !!(val & 0x08));
+
+    if ((val & 0x01) == 0) {
+        /* Master Interrupt Enable is CLEAR. Mask everything! */
+        qemu_set_irq(qdev_get_gpio_in(s->irqc_dev, M68K_IRQC_LEVEL_1), 0);
+        qemu_set_irq(qdev_get_gpio_in(s->irqc_dev, M68K_IRQC_LEVEL_2), 0);
+        qemu_set_irq(qdev_get_gpio_in(s->irqc_dev, M68K_IRQC_LEVEL_3), 0);
+        sun3_update_clock_irq(s);
+        return;
+    }
+
+    /* Master Enable is SET. Fire the Soft Interrupts! */
+    qemu_set_irq(qdev_get_gpio_in(s->irqc_dev, M68K_IRQC_LEVEL_1),
+                 !!(val & 0x02));
+    qemu_set_irq(qdev_get_gpio_in(s->irqc_dev, M68K_IRQC_LEVEL_2),
+                 !!(val & 0x04));
+    qemu_set_irq(qdev_get_gpio_in(s->irqc_dev, M68K_IRQC_LEVEL_3),
+                 !!(val & 0x08));
+
+    /*
+     * Retrigger clock IRQs because the Master Enable or Local Enables might
+     * have changed
+     */
+    sun3_update_clock_irq(s);
+}
+
+static const MemoryRegionOps sun3_intreg_ops = {.read = sun3_intreg_read,
+                                                .write = sun3_intreg_write,
+                                                .endianness =
+                                                    DEVICE_BIG_ENDIAN,
+                                                .valid = {
+                                                    .min_access_size = 1,
+                                                    .max_access_size = 4,
+                                                } };
+
+static uint64_t sun3_memerr_read(void *opaque, hwaddr addr, unsigned size)
+{
+    Sun3MachineState *s = SUN3_MACHINE(opaque);
+    return s->memerr_reg;
+}
+
+static void sun3_memerr_write(void *opaque, hwaddr addr, uint64_t val,
+                              unsigned size)
+{
+    Sun3MachineState *s = SUN3_MACHINE(opaque);
+
+    if (addr == 4) {
+        s->memerr_reg &= ~0x80;
+        if (s->irqc_dev) {
+            qemu_irq_lower(qdev_get_gpio_in(s->irqc_dev, M68K_IRQC_LEVEL_7));
+        }
+        return;
+    }
+
+    s->memerr_reg = val & 0xFF;
+    if (val == 0x20) {
+        s->test_parity_written = true;
+    } else if (val == 0) {
+        s->test_parity_written = false;
+    }
+
+    if (s->irqc_dev) {
+        if ((val & 0x10) && (val & 0x40) && s->test_parity_written) {
+            s->memerr_reg |=
+                0x80 | s->spoof_parity_lane; /* active & spoofed bit lane */
+            s->parity_bit_counter++;
+            if (s->parity_bit_counter == 8) {
+                s->parity_bit_counter = 0;
+                s->spoof_parity_lane >>= 1;
+                if (s->spoof_parity_lane == 0) {
+                    s->spoof_parity_lane = 8;
+                }
+            }
+
+            qemu_irq_raise(qdev_get_gpio_in(
+                s->irqc_dev, M68K_IRQC_LEVEL_7)); /* M68K_IRQC_LEVEL_7 */
+        } else {
+            s->memerr_reg &= ~0x80;
+            qemu_irq_lower(qdev_get_gpio_in(s->irqc_dev, M68K_IRQC_LEVEL_7));
+        }
+    }
+}
+
+static const MemoryRegionOps sun3_memerr_ops = {
+    .read = sun3_memerr_read,
+    .write = sun3_memerr_write,
+    .endianness = DEVICE_BIG_ENDIAN,
+    .valid = {
+            .min_access_size = 1,
+            .max_access_size = 4,
+            .unaligned = true,
+        },
+    .impl = {
+            .min_access_size = 1,
+            .max_access_size = 4,
+            .unaligned = true,
+        },
+};
+
+static MemTxResult sun3_timeout_read_with_attrs(void *opaque, hwaddr addr,
+                                                uint64_t *data, unsigned size,
+                                                MemTxAttrs attrs)
+{
+    Sun3MMUState *mmu = SUN3_MMU(opaque);
+    mmu->buserr_reg |= 0x20; /* Timeout */
+    return MEMTX_ERROR;
+}
+
+static MemTxResult sun3_timeout_write_with_attrs(void *opaque, hwaddr addr,
+                                                 uint64_t val, unsigned size,
+                                                 MemTxAttrs attrs)
+{
+    Sun3MMUState *mmu = SUN3_MMU(opaque);
+    mmu->buserr_reg |= 0x20; /* Timeout */
+    return MEMTX_ERROR;
+}
+
+static const MemoryRegionOps sun3_timeout_ops = {
+    .read_with_attrs = sun3_timeout_read_with_attrs,
+    .write_with_attrs = sun3_timeout_write_with_attrs,
+    .endianness = DEVICE_BIG_ENDIAN,
+    .valid = {
+        .min_access_size = 1,
+        .max_access_size = 4,
+    },
+};
+
+static void sun3_cpu_reset(void *opaque)
+{
+    M68kCPU *cpu = opaque;
+    CPUState *cs = CPU(cpu);
+    CPUM68KState *env = cpu_env(cs);
+    Sun3MachineState *s = SUN3_MACHINE(current_machine);
+
+    /*
+     * Execute generic QEMU system reset (wipes everything to 0).
+     * This includes setting env->pc = 0 and env->aregs[7] = 0.
+     */
+    cpu_reset(cs);
+
+    /*
+     * Forcefully inject the Boot PROM SP/PC vectors on EVERY reset.
+     * The Sun-3 hardware uses a temporary MMU override to map the PROM to
+     * 0x00000000 during the first few cycles of reset. Since QEMU does not
+     * emulate this specific micro-architectural quirk, we must manually
+     * restore the vectors here to prevent the CPU from executing uninitialized
+     * RAM at 0x00000000 and getting stuck in a zero-pitch orib loop.
+     */
+    env->aregs[7] = s->boot_sp;
+    env->sp[0] = s->boot_sp; /* M68K_SSP (Master) */
+    env->sp[1] = s->boot_sp; /* M68K_USP (User) */
+    env->sp[2] = s->boot_sp; /* M68K_ISP (Interrupt) */
+    env->pc = s->boot_pc;
+}
+
+static void sun3_init(MachineState *machine)
+{
+    Sun3MachineState *s_mach = SUN3_MACHINE(machine);
+    M68kCPU *cpu;
+    CPUM68KState *env;
+    DeviceState *sun3mmu;
+    DeviceState *dev;
+    DeviceState *irqc_dev;
+    SysBusDevice *s;
+
+    /* Initialize defaults */
+    s_mach->memerr_reg = 0x40;
+    s_mach->spoof_parity_lane = 8;
+    s_mach->parity_bit_counter = 0;
+    s_mach->test_parity_written = false;
+    s_mach->intreg = 0;
+    s_mach->clock_pending = false;
+
+    /* Initialize the CPU. The Sun 3/60 uses a 68020. */
+    cpu = M68K_CPU(cpu_create(machine->cpu_type));
+    env = &cpu->env;
+    qemu_register_reset(sun3_cpu_reset, cpu);
+
+    /* Use automatically allocated main RAM */
+    memory_region_add_subregion(get_system_memory(), 0x00000000, machine->ram);
+
+    /* Allocate and map ROM as writable RAM! */
+    memory_region_init_ram(&s_mach->rom, NULL, "sun3.prom",
+                           SUN3_PROM_SIZE, &error_fatal);
+    memory_region_set_readonly(&s_mach->rom, false);
+    memory_region_add_subregion(get_system_memory(), SUN3_PROM_BASE,
+                                &s_mach->rom);
+
+    memory_region_init_alias(&s_mach->rom_alias, NULL, "sun3.prom.alias",
+                             &s_mach->rom, 0,
+                             SUN3_PROM_SIZE);
+    memory_region_add_subregion(get_system_memory(), 0x0FF00000,
+                                &s_mach->rom_alias);
+
+    const char *bios_name = machine->firmware ?: "sun3.prom";
+    if (bios_name) {
+        int load_size = load_image_targphys(bios_name, SUN3_PROM_BASE,
+                                            SUN3_PROM_SIZE,
+                             qtest_enabled() ? NULL : &error_fatal);
+        if (load_size < 0) {
+            if (!qtest_enabled()) {
+                error_report("sun3: could not load prom '%s'", bios_name);
+                exit(1);
+            }
+        }
+        error_report("Sun3 Init: Loaded %d bytes from '%s' at 0x%08x",
+                     load_size,
+                     bios_name, SUN3_PROM_BASE);
+
+        /* Initial PC is always at offset 4 in firmware binaries */
+        uint8_t *ptr = rom_ptr(SUN3_PROM_BASE, 8);
+        if (ptr) {
+            s_mach->boot_sp = ldl_be_p(ptr);
+            s_mach->boot_pc = ldl_be_p(ptr + 4);
+            error_report("Sun3 Init: Saved Firmware Vectors "
+                         "SP=0x%08x PC=0x%08x",
+                         s_mach->boot_sp, s_mach->boot_pc);
+        }
+    } else {
+        error_report(
+            "Sun3 Init: No firmware specified! Use -bios or -machine firmware=");
+    }
+
+    /* Set up the custom Sun-3 MMU */
+    sun3mmu = qdev_new(TYPE_SUN3_MMU);
+    s_mach->sun3mmu = sun3mmu;
+    s = SYS_BUS_DEVICE(sun3mmu);
+    sysbus_realize_and_unref(s, &error_fatal);
+
+    /* Intercept CPU memory translations with our custom MMU hook */
+    env->custom_mmu_opaque = sun3mmu;
+    env->custom_mmu_get_physical_address = sun3mmu_get_physical_address;
+
+    sysbus_mmio_map(s, 0, 0x80000000); /* Context Register */
+    sysbus_mmio_map(s, 1, 0x90000000); /* Segment Map */
+    sysbus_mmio_map(s, 2, 0xA0000000); /* Page Map */
+    sysbus_mmio_map(s, 3, 0xB0000000); /* Control / System Enable */
+    sysbus_mmio_map(s, 4, 0xC0000000); /* Bus Error Register */
+
+    memory_region_init_ram(&s_mach->idprom, NULL, "sun3.idprom", 8192,
+                           &error_fatal);
+    memory_region_add_subregion(get_system_memory(), 0x08000000,
+                                &s_mach->idprom);
+
+    uint8_t idprom_data[32] = {
+        0x01,                               /* Format: 1 */
+        0x17,                               /* Machine Type: Sun-3/60 (0x17) */
+        0x08, 0x00, 0x20, 0x00, 0x00, 0x01, /* MAC Address */
+        0x00, 0x00, 0x00, 0x00,             /* Date */
+        0x00, 0x00, 0x01,                   /* Serial */
+        0x00                                /* Checksum */
+    };
+    uint8_t chksum = 0;
+    for (int i = 0; i < 15; i++) {
+        chksum ^= idprom_data[i];
+    }
+    idprom_data[15] = chksum;
+
+    rom_add_blob_fixed("sun3.idprom_content", idprom_data, sizeof(idprom_data),
+                       0x08000000);
+
+    /*
+     * Set up the Interrupt Controller (IRQC) to route IRQs to
+     * CPU autovectors
+     */
+    irqc_dev = qdev_new(TYPE_M68K_IRQC);
+    object_property_set_link(OBJECT(irqc_dev), "m68k-cpu", OBJECT(cpu),
+                             &error_abort);
+    sysbus_realize_and_unref(SYS_BUS_DEVICE(irqc_dev), &error_fatal);
+    s_mach->irqc_dev = irqc_dev;
+
+    dev = qdev_new(TYPE_INTERSIL_7170);
+    s = SYS_BUS_DEVICE(dev);
+    sysbus_realize_and_unref(s, &error_fatal);
+    sysbus_mmio_map(s, 0, 0x0FE60000);
+    sysbus_connect_irq(s, 0,
+                       qemu_allocate_irq(sun3_clock_irq_handler, s_mach, 0));
+
+    dev = qdev_new(TYPE_ESCC);
+    qdev_prop_set_bit(dev, "force-hw-ready", true);
+    qdev_prop_set_uint32(dev, "disabled", 0);
+    qdev_prop_set_uint32(dev, "frequency", 4915200); /* 4.9152 MHz */
+    qdev_prop_set_uint32(dev, "it_shift", 1);
+    qdev_prop_set_bit(dev, "bit_swap", false); /* Control/Data interleaving */
+    qdev_prop_set_uint32(dev, "mmio_size", 8192);
+    qdev_prop_set_chr(dev, "chrB", serial_hd(0)); /* Keyboard/Mouse A */
+    qdev_prop_set_chr(dev, "chrA", serial_hd(1)); /* Keyboard/Mouse B */
+    qdev_prop_set_uint32(dev, "chnBtype", escc_serial);
+    qdev_prop_set_uint32(dev, "chnAtype", escc_serial);
+    s = SYS_BUS_DEVICE(dev);
+    sysbus_realize_and_unref(s, &error_fatal);
+    sysbus_mmio_map(s, 0, 0x0FE00000);
+    sysbus_connect_irq(s, 0,
+                       qdev_get_gpio_in(irqc_dev, M68K_IRQC_LEVEL_6));
+    /* IPL 6 */
+    sysbus_connect_irq(s, 1,
+                       qdev_get_gpio_in(irqc_dev, M68K_IRQC_LEVEL_6));
+    /* IPL 6 */
+
+    dev = qdev_new(TYPE_ESCC);
+    qdev_prop_set_bit(dev, "force-hw-ready", true);
+    qdev_prop_set_uint32(dev, "disabled", 0);
+    qdev_prop_set_uint32(dev, "frequency", 4915200); /* 4.9152 MHz */
+    qdev_prop_set_uint32(dev, "it_shift", 1);
+    qdev_prop_set_bit(dev, "bit_swap", false); /* Control/Data interleaving */
+    qdev_prop_set_uint32(dev, "mmio_size", 8192);
+    qdev_prop_set_chr(dev, "chrB", serial_hd(2)); /* Serial B */
+    qdev_prop_set_chr(dev, "chrA", serial_hd(3)); /* Serial A */
+    qdev_prop_set_uint32(dev, "chnBtype", escc_serial);
+    qdev_prop_set_uint32(dev, "chnAtype", escc_serial);
+    s = SYS_BUS_DEVICE(dev);
+    sysbus_realize_and_unref(s, &error_fatal);
+    sysbus_mmio_map(s, 0, 0x0FE20000);
+    sysbus_connect_irq(s, 0,
+                       qdev_get_gpio_in(irqc_dev, M68K_IRQC_LEVEL_6));
+    /* IPL 6 */
+    sysbus_connect_irq(s, 1,
+                       qdev_get_gpio_in(irqc_dev, M68K_IRQC_LEVEL_6));
+    /* IPL 6 */
+
+    memory_region_init_io(&s_mach->intreg_iomem, NULL, &sun3_intreg_ops, s_mach,
+                          "sun3.intreg", 8192);
+    memory_region_add_subregion(get_system_memory(), 0x0FEA0000,
+                                &s_mach->intreg_iomem);
+
+    memory_region_init_io(&s_mach->memerr_iomem, NULL, &sun3_memerr_ops, s_mach,
+                          "sun3.memerr", 32);
+    memory_region_add_subregion(get_system_memory(), 0x0FE80000,
+                                &s_mach->memerr_iomem);
+
+    dev = qdev_new("lance");
+    qemu_configure_nic_device(dev, true, NULL);
+    object_property_set_link(OBJECT(dev), "dma_mr",
+                             OBJECT(&SUN3_MMU(sun3mmu)->dvma_iommu),
+                             &error_abort);
+    s = SYS_BUS_DEVICE(dev);
+    sysbus_realize_and_unref(s, &error_fatal);
+    sysbus_mmio_map(s, 0, 0x0FF20000);
+    sysbus_connect_irq(s, 0, qdev_get_gpio_in(irqc_dev, M68K_IRQC_LEVEL_3));
+
+    memory_region_init_ram(&s_mach->eeprom, NULL, "sun3.eeprom", 2048,
+                           &error_fatal);
+    memory_region_add_subregion(get_system_memory(), 0x0FE40000,
+                                &s_mach->eeprom);
+    memcpy(memory_region_get_ram_ptr(&s_mach->eeprom), sun3_eeprom_blob,
+           sizeof(sun3_eeprom_blob));
+
+    memory_region_init_ram(&s_mach->nvram, NULL, "sun3.nvram", 8192,
+                           &error_fatal);
+    memory_region_add_subregion(get_system_memory(), 0x0FE50000,
+                                &s_mach->nvram);
+
+    memory_region_init_io(&s_mach->timeout_net, NULL, &sun3_timeout_ops,
+                          sun3mmu,
+                          "sun3.timeout", 0x06000000);
+    memory_region_add_subregion_overlap(get_system_memory(), 0x0A000000,
+                                        &s_mach->timeout_net, -10);
+}
+
+static void sun3_machine_class_init(ObjectClass *oc, const void *data)
+{
+    MachineClass *mc = MACHINE_CLASS(oc);
+
+    mc->desc = "Sun-3 (3/60)";
+    mc->init = sun3_init;
+    mc->default_cpu_type = M68K_CPU_TYPE_NAME("m68020");
+    /* Minimum of 4MB for a 3/60, typical maximum ~24MB */
+    mc->default_ram_size = 4 * MiB;
+    mc->default_ram_id = "sun3.ram";
+
+    mc->ignore_memory_transaction_failures = false;
+}
+
+static const TypeInfo sun3_machine_type = {
+    .name = TYPE_SUN3_MACHINE,
+    .parent = TYPE_MACHINE,
+    .class_init = sun3_machine_class_init,
+    .instance_size = sizeof(Sun3MachineState),
+};
+
+static void sun3_machine_register_types(void)
+{
+    type_register_static(&sun3_machine_type);
+}
+
+type_init(sun3_machine_register_types)
diff --git a/hw/m68k/sun3_eeprom_data.h b/hw/m68k/sun3_eeprom_data.h
new file mode 100644
index 0000000000..fbe3a84d6d
--- /dev/null
+++ b/hw/m68k/sun3_eeprom_data.h
@@ -0,0 +1,259 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+static const uint8_t sun3_eeprom_blob[2048] = {
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x3c,
+    0x00, 0x3c, 0x00, 0x00, 0xc4, 0xc4, 0xc4, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x00, 0x00,
+    0x12, 0x73, 0x64, 0x00, 0x08, 0x00, 0x03, 0x12,
+    0x12, 0x00, 0x73, 0x64, 0x00, 0x08, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x57, 0x65, 0x6c, 0x63, 0x6f, 0x6d, 0x65, 0x20,
+    0x74, 0x6f, 0x20, 0x53, 0x2f, 0x57, 0x20, 0x77,
+    0x6f, 0x72, 0x6b, 0x73, 0x74, 0x61, 0x74, 0x69,
+    0x6f, 0x6e, 0x20, 0x4c, 0x4d, 0x53, 0x32, 0x20,
+    0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x0d, 0x0a, 0x0a, 0x0a,
+    0x00, 0x00, 0x00, 0x00, 0x01, 0x04, 0x01, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x0c, 0x03, 0x01, 0x01,
+    0x02, 0x01, 0x02, 0x03, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+    0xf0, 0xf0, 0xf0, 0xf0, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0,
+};
diff --git a/hw/m68k/sun3mmu.c b/hw/m68k/sun3mmu.c
new file mode 100644
index 0000000000..a0192b5677
--- /dev/null
+++ b/hw/m68k/sun3mmu.c
@@ -0,0 +1,705 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * QEMU Sun-3 MMU Model
+ *
+ * Copyright (c) 2026
+ */
+#include "qemu/osdep.h"
+
+#include "exec/cputlb.h"
+#include "hw/core/boards.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/core/sysbus.h"
+#include "hw/m68k/sun3mmu.h"
+#include "qapi/error.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "system/runstate.h"
+#include "target/m68k/cpu.h"
+#include "system/address-spaces.h"
+
+#define SUN3_MMU_CONTEXT(addr) ((addr >> 28) & 0x7)
+
+static uint64_t sun3_mmu_context_read(void *opaque, hwaddr addr,
+                                      unsigned size)
+{
+    Sun3MMUState *s = SUN3_MMU(opaque);
+    return s->context_reg;
+}
+
+static void sun3_mmu_context_write(void *opaque, hwaddr addr, uint64_t val,
+                                   unsigned size)
+{
+    Sun3MMUState *s = SUN3_MMU(opaque);
+    s->context_reg = val & 0x7;
+    tlb_flush(CPU(first_cpu));
+}
+
+static const MemoryRegionOps sun3_mmu_context_ops = {
+    .read = sun3_mmu_context_read,
+    .write = sun3_mmu_context_write,
+    .endianness = DEVICE_BIG_ENDIAN,
+    .valid = {
+            .min_access_size = 1,
+            .max_access_size = 4,
+        },
+};
+
+static uint64_t sun3_mmu_segment_read(void *opaque, hwaddr addr,
+                                      unsigned size)
+{
+    Sun3MMUState *s = SUN3_MMU(opaque);
+    uint32_t ctx = s->context_reg & (SUN3_MMU_CONTEXTS - 1);
+    /*
+     * The Segment Map index is determined by bits 17..27 of the virtual address
+     */
+    uint16_t seg_index = (addr >> 17) & 0x7FF;
+    uint32_t index = (ctx << 11) | seg_index;
+    return s->segment_map[index];
+}
+
+static void sun3_mmu_segment_write(void *opaque, hwaddr addr, uint64_t val,
+                                   unsigned size)
+{
+    Sun3MMUState *s = SUN3_MMU(opaque);
+    uint32_t ctx = s->context_reg & (SUN3_MMU_CONTEXTS - 1);
+    /*
+     * The Segment Map index is determined by bits 17..27 of the virtual address
+     */
+    uint16_t seg_index = (addr >> 17) & 0x7FF;
+    s->segment_map[(ctx * SUN3_MMU_SEGMENTS_PER_CONTEXT) + seg_index] =
+        val & 0xFF;
+    tlb_flush(CPU(first_cpu));
+}
+
+static const MemoryRegionOps sun3_mmu_segment_ops = {
+    .read = sun3_mmu_segment_read,
+    .write = sun3_mmu_segment_write,
+    .endianness = DEVICE_BIG_ENDIAN,
+    .impl = {
+            .min_access_size = 1,
+            .max_access_size = 4,
+        },
+    .valid = {
+            .min_access_size = 1,
+            .max_access_size = 4,
+        },
+};
+
+static uint64_t sun3_mmu_page_read(void *opaque, hwaddr addr, unsigned size)
+{
+    Sun3MMUState *s = SUN3_MMU(opaque);
+    uint32_t ctx = s->context_reg & (SUN3_MMU_CONTEXTS - 1);
+
+    /*
+     * The Page Map address offset contains the virtual segment AND page
+     * index.
+     */
+    uint16_t vtr_seg = (addr >> 17) & 0x7FF;
+    uint16_t vtr_page = (addr >> 13) & 0xF;
+    uint32_t pmeg = s->segment_map[(ctx << 11) | vtr_seg];
+    uint32_t index = (pmeg << 4) | vtr_page;
+    return s->page_map[index];
+}
+
+static void sun3_mmu_page_write(void *opaque, hwaddr addr, uint64_t val,
+                                unsigned size)
+{
+    Sun3MMUState *s = SUN3_MMU(opaque);
+    uint32_t ctx = s->context_reg & (SUN3_MMU_CONTEXTS - 1);
+
+    uint16_t vtr_seg = (addr >> 17) & 0x7FF;
+    uint16_t vtr_page = (addr >> 13) & 0xF;
+    uint32_t pmeg = s->segment_map[(ctx << 11) | vtr_seg];
+    uint32_t index = (pmeg << 4) | vtr_page;
+
+    if (size == 4) {
+        s->page_map[index] = val;
+    } else if (size == 2) {
+        uint32_t shift = (addr & 2) ? 0 : 16;
+        s->page_map[index] = (s->page_map[index] & ~(0xFFFF << shift)) |
+                             ((val & 0xFFFF) << shift);
+    } else if (size == 1) {
+        uint32_t shift = (3 - (addr & 3)) * 8;
+        s->page_map[index] = (s->page_map[index] & ~(0xFF << shift)) |
+                             ((val & 0xFF) << shift);
+    }
+
+    tlb_flush(CPU(first_cpu));
+}
+
+static const MemoryRegionOps sun3_mmu_page_ops = {
+    .read = sun3_mmu_page_read,
+    .write = sun3_mmu_page_write,
+    .endianness = DEVICE_BIG_ENDIAN,
+    .impl = {
+            .min_access_size = 4,
+            .max_access_size = 4,
+        },
+    .valid = {
+            .min_access_size = 1,
+            .max_access_size = 4,
+        },
+};
+
+static uint64_t sun3_mmu_control_read(void *opaque, hwaddr addr,
+                                      unsigned size)
+{
+    Sun3MMUState *s = SUN3_MMU(opaque);
+    /* The region covers multiple 32-bit mapped registers now */
+    if (addr == 0x0) {
+        /*
+         * The hardware diagnostic switch on the Sun-3 CPU board is checked
+         * here. Setting bit 0 to 1 forces the extended memory test.
+         */
+        return s->enable_reg | 0x01;
+    }
+
+    /* Diagnostic LEDs */
+    if (addr == 0x30000000) {
+        return 0xFF; /* Typically inverted, 0xFF means all off */
+    }
+
+    qemu_log_mask(LOG_UNIMP,
+                  "sun3_mmu_control_read at offset 0x%" HWADDR_PRIx
+                  " (size=%u)\n",
+                  addr, size);
+    return 0;
+}
+
+static uint64_t sun3_mmu_buserr_read(void *opaque, hwaddr addr, unsigned size)
+{
+    Sun3MMUState *s = SUN3_MMU(opaque);
+
+    uint8_t ret = s->buserr_reg;
+    s->buserr_reg = 0; /* Hardware clears on read */
+
+    return ret;
+}
+
+static void sun3_mmu_buserr_write(void *opaque, hwaddr addr, uint64_t val,
+                                  unsigned size)
+{
+    Sun3MMUState *s = SUN3_MMU(opaque);
+    s->buserr_reg = 0;
+}
+
+static const MemoryRegionOps sun3_mmu_buserr_ops = {
+    .read = sun3_mmu_buserr_read,
+    .write = sun3_mmu_buserr_write,
+    .endianness = DEVICE_BIG_ENDIAN,
+    .valid = {
+            .min_access_size = 1,
+            .max_access_size = 4,
+        },
+};
+
+static void sun3_mmu_control_write(void *opaque, hwaddr addr, uint64_t val,
+                                   unsigned size)
+{
+    Sun3MMUState *s = SUN3_MMU(opaque);
+    if (addr == 0x0) {
+        /* System Enable Register at 0x40000000 */
+        uint8_t enable_old = s->enable_reg;
+        s->enable_reg = (enable_old & 0x01) | (val & 0xFE);
+
+        tlb_flush(CPU(first_cpu));
+        return;
+    }
+
+    if (addr == 0x30000000) {
+        /* Otherwise Diagnostic LEDs (e.g. 0xFF to clear) */
+        return;
+    }
+
+    qemu_log_mask(LOG_UNIMP,
+                  "sun3_mmu_control_write at offset 0x%" HWADDR_PRIx
+                  "\n", addr);
+}
+
+static const MemoryRegionOps sun3_mmu_control_ops = {
+    .read = sun3_mmu_control_read,
+    .write = sun3_mmu_control_write,
+    .endianness = DEVICE_BIG_ENDIAN,
+    .valid = {
+            .min_access_size = 1,
+            .max_access_size = 4,
+        },
+};
+
+static void sun3_mmu_reset(DeviceState *dev)
+{
+    Sun3MMUState *s = SUN3_MMU(dev);
+
+    s->context_reg = 0;
+
+    /*
+     * On a Cold Boot, the Bus Error Register MUST be 0x00.
+     * Bit 0 is NOT a Watchdog flag that must be 1. In fact, if the register
+     * reads
+     * non-zero, the PROM assumes it is returning from a Watchdog/Bus Error
+     * Panic and attempts to dump CPU state to memory (moveml) before the
+     * MMU is initialized, causing a Double Fault.
+     */
+    s->buserr_reg = 0x00;
+
+    /*
+     * CRITICAL: Do NOT wipe the Segment Map or Page Map!
+       The Sun-3 Boot PROM relies on the physical MMU SRAM persisting across
+     * Watchdog Resets so it can trace and push exception vectors back
+     * into mapped physical RAM!
+     */
+}
+
+static void sun3_mmu_init(Object *obj)
+{
+    Sun3MMUState *s = SUN3_MMU(obj);
+    SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+
+    /* Context Map */
+    /*
+     * Note: Control space regions decode the top nibble of a 32-bit address.
+       The PROM uses the raw virtual address as the offset when accessing
+       these regions, so they must handle sparsely distributed addresses up to
+       256MB.
+    */
+    memory_region_init_io(&s->context_mem, obj, &sun3_mmu_context_ops, s,
+                          "sun3-mmu-context", 0x10000000);
+    sysbus_init_mmio(sbd, &s->context_mem);
+
+    /* Segment Map */
+    memory_region_init_io(&s->segment_mem, obj, &sun3_mmu_segment_ops, s,
+                          "sun3-mmu-segment", 0x10000000);
+    sysbus_init_mmio(sbd, &s->segment_mem);
+
+    /* Page Map */
+    memory_region_init_io(&s->page_mem, obj, &sun3_mmu_page_ops, s,
+                          "sun3-mmu-page", 0x10000000);
+    sysbus_init_mmio(sbd, &s->page_mem);
+
+    /* Other control bits (Enable Register, Diagnostic LEDs) */
+    memory_region_init_io(&s->control_mem, obj, &sun3_mmu_control_ops, s,
+                          "sun3-mmu-control", 0x40000000);
+    sysbus_init_mmio(sbd, &s->control_mem);
+
+    /*
+     * Bus Error Register dedicated mapping
+     */
+    memory_region_init_io(&s->buserr_mem, obj, &sun3_mmu_buserr_ops, s,
+                          "sun3-mmu-buserr", 1);
+    sysbus_init_mmio(sbd, &s->buserr_mem);
+
+    /* DVMA IOMMU interception region */
+    memory_region_init_iommu(&s->dvma_iommu, sizeof(s->dvma_iommu),
+                             TYPE_SUN3_DVMA_IOMMU_MEMORY_REGION,
+                             obj, "sun3-dvma", 0x1000000);
+}
+
+static void sun3_mmu_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    device_class_set_legacy_reset(dc, sun3_mmu_reset);
+    dc->vmsd = NULL; /* TODO: Add migration state later */
+}
+
+static IOMMUTLBEntry sun3_dvma_translate(IOMMUMemoryRegion *iommu, hwaddr addr,
+                                         IOMMUAccessFlags flag, int iommu_idx)
+{
+    Sun3MMUState *s = container_of(iommu, Sun3MMUState, dvma_iommu);
+    CPUState *cs = first_cpu;
+    CPUM68KState *env = cs ? cpu_env(cs) : NULL;
+
+    IOMMUTLBEntry ret = {
+        .target_as = &address_space_memory,
+        .iova = addr,
+        .translated_addr = 0,
+        .addr_mask = ~(hwaddr)0,
+        .perm = IOMMU_NONE,
+    };
+
+    if (!env) {
+        return ret;
+    }
+
+    hwaddr physical;
+    int prot;
+    hwaddr page_size;
+    /*
+     * Lance DVMA translates 24-bit requests implicitly onto the top 16MB of
+     * the 28-bit virtual Bus (0x0Fxxxxxx) tied rigidly to Context 0.
+     */
+    uint32_t vaddr = addr + 0x0F000000;
+
+    int access_type = ACCESS_DATA | ACCESS_SUPER | (5 << 8);
+    if (flag == IOMMU_WO || flag == IOMMU_RW) {
+        access_type |= ACCESS_STORE;
+    }
+
+    uint8_t old_ctx = s->context_reg;
+    s->context_reg = 0; /* Hardware forces Context 0 during DVMA */
+
+    if (sun3mmu_get_physical_address(env, &physical, &prot, vaddr,
+                                     access_type, &page_size) == 0) {
+        ret.translated_addr = physical & ~(page_size - 1);
+        ret.addr_mask = page_size - 1;
+        if (prot & PAGE_WRITE) {
+            ret.perm = IOMMU_RW;
+        } else if (prot & PAGE_READ) {
+            ret.perm = IOMMU_RO;
+        }
+    }
+    s->context_reg = old_ctx; /* Restore pre-DVMA context */
+    return ret;
+}
+
+static void sun3_dvma_iommu_memory_region_class_init(ObjectClass *klass,
+                                                     const void *data)
+{
+    IOMMUMemoryRegionClass *imrc = IOMMU_MEMORY_REGION_CLASS(klass);
+    imrc->translate = sun3_dvma_translate;
+}
+
+static const TypeInfo sun3_dvma_iommu_memory_region_info = {
+    .name = TYPE_SUN3_DVMA_IOMMU_MEMORY_REGION,
+    .parent = TYPE_IOMMU_MEMORY_REGION,
+    .class_init = sun3_dvma_iommu_memory_region_class_init,
+};
+
+static const TypeInfo sun3_mmu_info = {
+    .name = TYPE_SUN3_MMU,
+    .parent = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(Sun3MMUState),
+    .instance_init = sun3_mmu_init,
+    .class_init = sun3_mmu_class_init,
+};
+
+static void sun3_mmu_register_types(void)
+{
+    type_register_static(&sun3_mmu_info);
+    type_register_static(&sun3_dvma_iommu_memory_region_info);
+}
+
+type_init(sun3_mmu_register_types)
+
+static bool is_valid_sun3_phys(hwaddr p, ram_addr_t ram_size)
+{
+    if (p < ram_size) {
+        return true;
+    }
+    if (p >= 0x0FE50000 && p <= 0x0FE7FFFF) {
+        return true; /* NVRAM and OBIO RAM */
+    }
+    if (p >= 0x08000000 && p <= 0x08001FFF) {
+        return true; /* IDPROM */
+    }
+    if (p >= 0x0FEF0000 && p <= 0x0FEFFFFF) {
+        return true; /* PROM */
+    }
+    if (p >= 0x0FF00000 && p <= 0x0FF0FFFF) {
+        return true; /* PROM Alias */
+    }
+
+    /* Specific OBIO devices for Sun-3/60 (Ferrari) */
+    if (p >= 0x0FE00000 && p <= 0x0FE00007) {
+        return true; /* ZS1 (Kbd/Mouse) */
+    }
+    if (p >= 0x0FE20000 && p <= 0x0FE20007) {
+        return true; /* ZS0 (Serial) */
+    }
+    if (p >= 0x0FE40000 && p <= 0x0FE407FF) {
+        return true; /* EEPROM */
+    }
+    if (p >= 0x0FF20000 && p <= 0x0FF201FF) {
+        return true; /* LANCE Am7990 */
+    }
+    if (p >= 0x0FE60000 && p <= 0x0FE6003F) {
+        return true; /* Timer */
+    }
+    if (p >= 0x0FE80000 && p <= 0x0FE8001F) {
+        return true; /* Memerr */
+    }
+    if (p >= 0x0FEA0000 && p <= 0x0FEA0003) {
+        return true; /* Intreg */
+    }
+
+    return false;
+}
+
+int sun3mmu_get_physical_address(void *env, hwaddr *physical, int *prot,
+                                 vaddr address, int access_type,
+                                 hwaddr *page_size)
+{
+    /*
+     * Translate the virtual address using the Sun-3 MMU maps.
+     */
+    CPUM68KState *m68k_env = env;
+    Sun3MMUState *s = SUN3_MMU(m68k_env->custom_mmu_opaque);
+
+    uint8_t context;
+    uint16_t pmeg = 0;
+    uint32_t pte;
+    uint32_t pte_index = 0;
+    uint32_t pte_offset = 0;
+    uint32_t phys_addr;
+
+    /*
+     * access_type from m68k TCG:
+       ACCESS_CODE (0x10), ACCESS_DATA (0x20)
+        * ACCESS_SUPER (0x01) *
+        */
+    bool is_write = access_type & ACCESS_STORE;
+    bool is_supervisor = access_type & ACCESS_SUPER;
+
+    *page_size = TARGET_PAGE_SIZE;
+
+    /*
+     * QEMU Pipeline Prefetch Workaround:
+     * The Sun-3 PROM executes `movesb %d0, 0x40000000` out of ROM (0x0FEFxxxx)
+     * to enable the MMU (`enable_reg |= 0x80`). On real M68020 hardware, the
+     * subsequent instruction(s) have already been prefetched while the MMU
+     * was off. QEMU attempts to fetch the next sequential instruction
+     * synchronously with the MMU fully active. Because the PROM has not mapped
+     * 0x0FEF0000 in the Page Table (it only maps Virtual 0x00000000 to
+     * the ROM),
+     * QEMU triggers an immediate Translation Fault Exception Loop! We must
+     * manually bless instruction fetches originating mechanically from the
+     * physical ROM space to emulate the prefetch cache. *
+     */
+    if ((access_type & ACCESS_CODE) &&
+        (address >= 0x0FEF0000 && address <= 0x0FEFFFFF)) {
+        *physical = address;
+        *prot = PAGE_READ | PAGE_EXEC;
+        return 0;
+    }
+
+    /*
+     * Boot Mode PROM Bypass:
+       If the Not-Boot bit (0x80) in the Enable Register is clear (System is in
+       Boot State): ONLY Instruction Fetches bypass the MMU mapping mechanism!
+       Data accesses and Stack Pushes (such as the Exception Frame push to
+       0x0FEEFFFE) proceed through the MMU mapped tables normally because the
+        * PROM sets up its stack logically! *
+        */
+
+
+
+    qemu_log_mask(CPU_LOG_MMU,
+                  "[SUN3MMU] get_physical_address(0x%08" VADDR_PRIx
+                  ") enable_reg=0x%02x\n",
+                  address, s->enable_reg);
+
+    /*
+     * Sun-3 Hardware Architectural Demultiplexer:
+     * The M68020 emulator now seamlessly passes the Source/Destination Function
+     * Code inside the top 8 bits of the `access_type` bitmask, directly
+     * supplied
+     * via an isolation index in the QEMU TCG pipeline. This mirrors
+     * how the physical M68K processor provides 3 explicit FC pins in
+     * addition to
+     * the 32-bit physical address bus!
+     *
+     * If the extracted FC equals 3 (Control Space / Hardware Registers), it
+     * NEVER enters the MMU Segment Map. It is universally 1:1 mapped to
+     * physical memory for raw HW device configuration (0x60000000, 0x10000000)!
+     */
+    uint8_t true_fc = (access_type >> 8) & 0x07;
+    if (true_fc == 3) {
+        /*
+         * Direct Physical Bypass Mapping to discrete SysBus devices.
+         * The top nibble of the virtual address selects the Control Space
+         * target. All lower bits are aliases.
+         */
+        uint32_t device_base = address & 0x70000000;
+
+        if (device_base == 0x00000000) {
+            /*
+             * IDPROM is logically at 0x0 and occupies 32 bytes in hardware. We
+             * shift it to 0x08000000 linearly in QEMU and force a 5-bit wrap
+             * so it safely avoids physical Main RAM/ROM collisions! *
+             */
+            *physical = 0x08000000 | (address & 0x1F);
+        } else {
+            switch (device_base) {
+            case 0x10000000: /* Page Map */
+                /*
+                 * Hardware Page Map physically contains 256 PMEGs *
+                 * 16 PTEs * 4 bytes = 16,384 bytes
+                 */
+                *physical = 0xA0000000 | (address & 0x0FFFFFFF);
+                break;
+            case 0x20000000: /* Segment Map */
+                /*
+                 * Hardware Segment Map structurally is 8 Contexts *
+                 * 2048 segments * 1 byte = 16,384 bytes
+                 */
+                *physical = 0x90000000 | (address & 0x0FFFFFFF);
+                break;
+            case 0x30000000: /* Context Register */
+                /*
+                 * Context register physically uniquely masks natively
+                 * strictly functionally inside hardware
+                 */
+                *physical = 0x80000000 | (address & 0x07);
+                break;
+            case 0x40000000: /* System Enable */
+                *physical = 0xB0000000 | (address & 0x0FFFFFFF);
+                break;
+            case 0x60000000: /* Bus Error Register */
+                *physical = 0xC0000000 | (address & 0x0FFFFFFF);
+                break;
+            case 0x70000000:
+                /* Diagnostic Register (Aliases into Enable mem region) */
+                *physical = 0xB0000000ULL + 0x30000000ULL +
+                            (address & 0x0FFFFFFF);
+                break;
+            default:
+                s->buserr_reg |= 0x20; /* Timeout */
+                return 1;
+            }
+      }
+
+      *prot = PAGE_READ | PAGE_WRITE;
+      *page_size = SUN3_PAGE_SIZE;
+
+      qemu_log_mask(
+          CPU_LOG_MMU,
+          "[SUN3MMU] TRUE FC=3 CONTROL SPACE DECODE: vaddr=0x%08" VADDR_PRIx
+          " mapped directly to physical 0x%08x\n",
+          address, (unsigned int)*physical);
+
+      return 0;
+    }
+
+    /*
+     * For all standard memory operations, the Sun-3 physically masks the
+     * Address Bus to 28 virtual bits before hitting the MMU Arrays.
+     */
+    address &= 0x0FFFFFFF;
+
+    if (!(s->enable_reg & 0x80)) {
+        /* Boot State: Not-Boot bit (0x80) is CLEAR */
+
+        /*
+         * Address < 0x01000000 && Supervisor Program:
+         *    Bypass MMU and map to the physical PROM (0x0FEF0000).
+         */
+        if (true_fc == 6 && (address < 0x01000000 ||
+                             (address >= 0x0FEF0000 && address < 0x0FF00000))) {
+            *physical = 0x0FEF0000 | (address & 0x0001FFFF);
+            *prot = PAGE_READ | PAGE_WRITE | PAGE_EXEC;
+            *page_size = SUN3_PAGE_SIZE;
+            return 0;
+        }
+    }
+
+    context = s->context_reg & (SUN3_MMU_CONTEXTS - 1);
+
+    /* Segment map lookup: top 11 bits of 28-bit virtual address (bits 17-27) */
+    uint32_t seg_index = (address >> 17) & (SUN3_MMU_SEGMENTS_PER_CONTEXT - 1);
+    pmeg = s->segment_map[(context * SUN3_MMU_SEGMENTS_PER_CONTEXT) +
+                          seg_index];
+
+    /* Page map lookup: bits 13-16 of virtual address */
+    pte_index = (address >> SUN3_PAGE_SHIFT) & (SUN3_MMU_PTE_PER_PMEG - 1);
+    pte_offset = (pmeg * SUN3_MMU_PTE_PER_PMEG) + pte_index;
+    pte = s->page_map[pte_offset];
+
+    /* Update PTE Accessed/Modified bits */
+    if (pte & SUN3_PTE_VALID) {
+        uint32_t new_pte = pte | SUN3_PTE_REF;
+        if (is_write) {
+            new_pte |= SUN3_PTE_MOD;
+        }
+        if (new_pte != pte) {
+            s->page_map[pte_offset] = new_pte;
+        }
+    }
+
+    if (!(pte & SUN3_PTE_VALID)) {
+        s->buserr_reg |= 0x80; /* Invalid */
+        return 1;              /* Translation fault */
+    }
+
+    /* Protection check */
+    uint8_t mmu_prot = (pte >> 29) & 3;
+
+    if (!is_supervisor && (mmu_prot & 1)) {
+        s->buserr_reg |= 0x40; /* Protection */
+        return 1;              /* User access to supervisor page */
+    }
+
+    *prot = PAGE_READ | PAGE_EXEC;
+    if (mmu_prot & 2) {
+        if (is_write || (pte & SUN3_PTE_MOD)) {
+            *prot |= PAGE_WRITE;
+        }
+    }
+
+    if (is_write && !(mmu_prot & 2)) {
+        s->buserr_reg |= 0x40; /* Protection */
+        return 1;
+    }
+
+    /*
+     * Extract physical address. The top 15 bits come from the PTE's
+     * PGFRAME. Bottom 13 bits (0x1FFF) come directly from the virtual
+     * address.
+     */
+    phys_addr = ((pte & SUN3_PTE_PGFRAME) << SUN3_PAGE_SHIFT) |
+                (address & (SUN3_PAGE_SIZE - 1));
+
+    /*
+     * Address space Mapping reference:
+     *   - OBMEM:   0x00000000 (Follows native Map)
+     *   - OBIO:    0x0FE00000 (Relocated above typical 24MB RAM)
+     *   - VME_D16: 0x40000000 (Relocated out of bounds)
+     *   - VME_D32: 0x50000000
+     */
+    uint32_t pgbase = ((pte & SUN3_PTE_PGFRAME) << SUN3_PAGE_SHIFT);
+    uint32_t pgtype = (pte & SUN3_PTE_PGTYPE) >> 26;
+
+    /*
+     * Sun-3 Hardware Quirk:
+     * The Boot PROM maps Virtual `0x00000000` to its ROM header using OBIO
+     * Page `0x80` (`0xC4000080`). Native hardware intercepts OBIO offset
+     * `0x100000` and transparently aliases it back to OBMEM PROM
+     * (`0x0FEF0000`) using the virtual address to index the ROM! *
+     */
+    if (pgtype == SUN3_PGTYPE_OBIO &&
+        (pgbase >= 0x100000 && pgbase < 0x120000)) {
+        phys_addr = 0x0FEF0000 | (address & 0x1FFFF);
+    } else {
+        switch (pgtype) {
+        case SUN3_PGTYPE_OBMEM:
+            break;
+        case SUN3_PGTYPE_OBIO:
+            phys_addr += 0x0FE00000;
+            break;
+        case SUN3_PGTYPE_VME_D16:
+            phys_addr += 0x40000000;
+            break;
+        case SUN3_PGTYPE_VME_D32:
+            phys_addr += 0x50000000;
+            break;
+        }
+    }
+
+    /* NXM (Non-Existent Memory) Bounds Checking */
+    if (!is_valid_sun3_phys(phys_addr, current_machine->ram_size)) {
+        s->buserr_reg |= 0x20; /* Timeout */
+        return 1;
+    }
+
+    /*
+     * The QEMU TLB works in 4KB frames, not Sun-3's 8KB native frames.
+     * We MUST explicitly append the intra-page offset within the 8KB page,
+     * otherwise accesses to the upper 4KB (e.g. 0x1000, 0x3000) will be
+     * truncated and overwrite the physical memory of the lower 4KB!
+     */
+    *physical = phys_addr | (address & (SUN3_PAGE_SIZE - 1));
+
+    *page_size = TARGET_PAGE_SIZE;
+
+    return 0; /* 0 = success, no fault */
+}
diff --git a/include/hw/m68k/sun3mmu.h b/include/hw/m68k/sun3mmu.h
new file mode 100644
index 0000000000..bdd79d963b
--- /dev/null
+++ b/include/hw/m68k/sun3mmu.h
@@ -0,0 +1,65 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#ifndef HW_M68K_SUN3MMU_H
+#define HW_M68K_SUN3MMU_H
+
+#include "exec/cpu-common.h"
+#include "exec/hwaddr.h"
+#include "exec/target_page.h"
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+
+#define TYPE_SUN3_MMU "sun3-mmu"
+OBJECT_DECLARE_SIMPLE_TYPE(Sun3MMUState, SUN3_MMU)
+
+#define TYPE_SUN3_DVMA_IOMMU_MEMORY_REGION "sun3-dvma-iommu-memory-region"
+
+#define SUN3_MMU_CONTEXTS 8
+#define SUN3_MMU_PMEGS 256
+#define SUN3_MMU_PTE_PER_PMEG 16
+#define SUN3_MMU_SEGMENTS_PER_CONTEXT 2048
+
+#define SUN3_PAGE_SIZE 0x2000 /* 8 KB */
+#define SUN3_PAGE_MASK (~(SUN3_PAGE_SIZE - 1))
+#define SUN3_PAGE_SHIFT 13
+
+/* PTE bits */
+#define SUN3_PTE_VALID (1U << 31)
+#define SUN3_PTE_WRITE (1U << 30)
+#define SUN3_PTE_SYSTEM (1U << 29)
+#define SUN3_PTE_NC (1U << 28)
+#define SUN3_PTE_PGTYPE (3U << 26)
+#define SUN3_PTE_REF (1U << 25)
+#define SUN3_PTE_MOD (1U << 24)
+#define SUN3_PTE_PGFRAME 0x0007FFFF
+
+/* PTE PGTYPE values */
+#define SUN3_PGTYPE_OBMEM 0
+#define SUN3_PGTYPE_OBIO 1
+#define SUN3_PGTYPE_VME_D16 2
+#define SUN3_PGTYPE_VME_D32 3
+
+struct Sun3MMUState {
+    SysBusDevice parent_obj;
+
+    MemoryRegion context_mem;
+    MemoryRegion segment_mem;
+    MemoryRegion page_mem;
+    MemoryRegion control_mem;
+    MemoryRegion buserr_mem;
+    IOMMUMemoryRegion dvma_iommu;
+
+    uint8_t sys_enable_reg; /* Reserved mapping for testing */
+
+    uint8_t context_reg;
+    uint8_t enable_reg;
+    uint8_t buserr_reg;
+    uint8_t int_reg;
+    uint8_t segment_map[SUN3_MMU_CONTEXTS * SUN3_MMU_SEGMENTS_PER_CONTEXT];
+    uint32_t page_map[SUN3_MMU_PMEGS * SUN3_MMU_PTE_PER_PMEG];
+};
+
+int sun3mmu_get_physical_address(void *env, hwaddr *physical, int *prot,
+                                 vaddr address, int access_type,
+                                 hwaddr *page_size);
+
+#endif
-- 
2.50.1 (Apple Git-155)



^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH 6/7] tests/qtest: Add Sun-3 hardware interaction tests
  2026-05-03  1:57 [PATCH 0/7] m68k: Add Sun-3 Machine Emulation 54weasels
                   ` (4 preceding siblings ...)
  2026-05-03  1:57 ` [PATCH 5/7] hw/m68k: Overhaul Sun-3 MMU and Boot PROM mapping 54weasels
@ 2026-05-03  1:57 ` 54weasels
  2026-05-03  1:57 ` [PATCH 7/7] tests/functional: Add Sun-3 firmware boot and diagnostic test 54weasels
  6 siblings, 0 replies; 10+ messages in thread
From: 54weasels @ 2026-05-03  1:57 UTC (permalink / raw)
  To: qemu-devel; +Cc: laurent, thuth, 54weasels

This adds basic architectural and register validation for the newly assembled Sun-3 machine.

Signed-off-by: 54weasels <54weasels@gmail.com>
---
 tests/qtest/meson.build |  1 +
 tests/qtest/sun3-test.c | 67 +++++++++++++++++++++++++++++++++++++++++
 2 files changed, 68 insertions(+)
 create mode 100644 tests/qtest/sun3-test.c

diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index be4fa627b5..184d1a3f8c 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -154,6 +154,7 @@ qtests_loongarch64 = qtests_filter + \
    'cpu-plug-test']
 
 qtests_m68k = ['boot-serial-test'] + \
+  ['sun3-test'] + \
   qtests_filter
 
 qtests_microblaze = ['boot-serial-test'] + \
diff --git a/tests/qtest/sun3-test.c b/tests/qtest/sun3-test.c
new file mode 100644
index 0000000000..50f6c166d1
--- /dev/null
+++ b/tests/qtest/sun3-test.c
@@ -0,0 +1,67 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * QTest device validation for the Sun-3 Machine
+ *
+ * Copyright (c) 2026
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+
+/* OBIO Memory Map Addresses */
+#define SUN3_INTREG_BASE 0x0FEA0000
+#define SUN3_MEMERR_BASE 0x0FE80000
+
+static void test_intreg(void)
+{
+    QTestState *s;
+    uint8_t val;
+
+    s = qtest_init("-machine sun3 -nographic");
+
+    /* Default state should be 0 */
+    val = qtest_readb(s, SUN3_INTREG_BASE);
+    g_assert_cmphex(val, ==, 0x00);
+
+    /* Write and verify */
+    qtest_writeb(s, SUN3_INTREG_BASE, 0x0f);
+    val = qtest_readb(s, SUN3_INTREG_BASE);
+    g_assert_cmphex(val, ==, 0x0f);
+
+    qtest_quit(s);
+}
+
+static void test_memerr(void)
+{
+    QTestState *s;
+    uint8_t val;
+
+    s = qtest_init("-machine sun3 -nographic");
+
+    /* Default state should be 0x40 (default no-error) */
+    val = qtest_readb(s, SUN3_MEMERR_BASE);
+    g_assert_cmphex(val, ==, 0x40);
+
+    /* Test parity error spoof sequence */
+    qtest_writeb(s, SUN3_MEMERR_BASE, 0x20); /* Set test_parity_written */
+    qtest_writeb(s, SUN3_MEMERR_BASE, 0x50); /* Trigger spoof */
+    val = qtest_readb(s, SUN3_MEMERR_BASE);
+
+    /*
+     * Since we didn't clock enough parity lanes, high bit and lane 8 should
+     * be set (0x50 | 0x80 | 0x08 = 0xd8)
+     */
+    g_assert_cmphex(val, ==, 0xd8);
+
+    qtest_quit(s);
+}
+
+int main(int argc, char **argv)
+{
+    g_test_init(&argc, &argv, NULL);
+
+    qtest_add_func("/m68k/sun3/intreg", test_intreg);
+    qtest_add_func("/m68k/sun3/memerr", test_memerr);
+
+    return g_test_run();
+}
-- 
2.50.1 (Apple Git-155)



^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH 7/7] tests/functional: Add Sun-3 firmware boot and diagnostic test
  2026-05-03  1:57 [PATCH 0/7] m68k: Add Sun-3 Machine Emulation 54weasels
                   ` (5 preceding siblings ...)
  2026-05-03  1:57 ` [PATCH 6/7] tests/qtest: Add Sun-3 hardware interaction tests 54weasels
@ 2026-05-03  1:57 ` 54weasels
  6 siblings, 0 replies; 10+ messages in thread
From: 54weasels @ 2026-05-03  1:57 UTC (permalink / raw)
  To: qemu-devel; +Cc: laurent, thuth, 54weasels

This adds an integration test that downloads the Sun-3/60 Boot PROM, initializes the machine, and ensures the self-test successfully routes to TTY A.

Signed-off-by: 54weasels <54weasels@gmail.com>
---
 tests/functional/m68k/meson.build   |  1 +
 tests/functional/m68k/test_sun3.py  | 34 +++++++++++++++++++++++++++++
 tests/functional/qemu_test/asset.py | 11 +++++-----
 3 files changed, 41 insertions(+), 5 deletions(-)
 create mode 100755 tests/functional/m68k/test_sun3.py

diff --git a/tests/functional/m68k/meson.build b/tests/functional/m68k/meson.build
index 679faaf86d..601c28d863 100644
--- a/tests/functional/m68k/meson.build
+++ b/tests/functional/m68k/meson.build
@@ -9,5 +9,6 @@ tests_m68k_system_thorough = [
   'nextcube',
   'replay',
   'q800',
+  'sun3',
   'tuxrun',
 ]
diff --git a/tests/functional/m68k/test_sun3.py b/tests/functional/m68k/test_sun3.py
new file mode 100755
index 0000000000..f441184616
--- /dev/null
+++ b/tests/functional/m68k/test_sun3.py
@@ -0,0 +1,34 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#!/usr/bin/env python3
+#
+# Functional test that boots a Sun-3 machine and checks its serial console.
+#
+# Copyright (c) 2026
+#
+
+
+import os
+from qemu_test import QemuSystemTest, Asset
+from qemu_test import wait_for_console_pattern
+
+class Sun3Machine(QemuSystemTest):
+    timeout = 60
+
+    ASSET_PROM = Asset(
+        'https://github.com/54weasels/sun3_60/raw/main/bootprom/boot_1810_01.BIN',
+        '471bf34cc9313814726c2e3506d85a13291603e37b45aaffa71bc1154a2d4851'
+    )
+
+    def test_m68k_sun3_boot(self):
+        self.set_machine('sun3')
+        prom_path = self.ASSET_PROM.fetch()
+
+        # Sun-3 maps TTY A to serial_hd(3)
+        self.vm.set_console(console_index=3)
+        self.vm.add_args('-bios', prom_path)
+        self.vm.launch()
+
+        wait_for_console_pattern(self, '>')
+
+if __name__ == '__main__':
+    QemuSystemTest.main()
diff --git a/tests/functional/qemu_test/asset.py b/tests/functional/qemu_test/asset.py
index 51a434b2b7..eac1e60536 100644
--- a/tests/functional/qemu_test/asset.py
+++ b/tests/functional/qemu_test/asset.py
@@ -223,11 +223,12 @@ def fetch(self):
             raise AssetError(self, "Download retries exceeded", transient=True)
 
         try:
-            # Set these just for informational purposes
-            os.setxattr(str(tmp_cache_file), "user.qemu-asset-url",
-                        self.url.encode('utf8'))
-            os.setxattr(str(tmp_cache_file), "user.qemu-asset-hash",
-                        self.hash.encode('utf8'))
+            if hasattr(os, 'setxattr'):
+                # Set these just for informational purposes
+                os.setxattr(str(tmp_cache_file), "user.qemu-asset-url",
+                            self.url.encode('utf8'))
+                os.setxattr(str(tmp_cache_file), "user.qemu-asset-hash",
+                            self.hash.encode('utf8'))
         except OSError as e:
             self.log.debug("Unable to set xattr on %s: %s", tmp_cache_file, e)
 
-- 
2.50.1 (Apple Git-155)



^ permalink raw reply related	[flat|nested] 10+ messages in thread

* Re: [PATCH 1/7] target/m68k: Implement Physical Bus Error exception handling
  2026-05-03  1:57 ` [PATCH 1/7] target/m68k: Implement Physical Bus Error exception handling 54weasels
@ 2026-05-10  5:40   ` Thomas Huth
  2026-05-11  6:38     ` Purr Box
  0 siblings, 1 reply; 10+ messages in thread
From: Thomas Huth @ 2026-05-10  5:40 UTC (permalink / raw)
  To: 54weasels; +Cc: qemu-devel, laurent, thuth


 Hi!

Thanks for your patches ... some comments inline below...

Am Sat,  2 May 2026 18:57:50 -0700
schrieb 54weasels <54weasels@gmail.com>:

> The M68020 natively maps hardware Bus Error (BERR) timeouts into a Long Bus Cycle Fault (Format 0xB). This commit adds the memory exception routing to natively synthesize these EXCP_ACCESS cycle faults. It also implements the double-fault / watchdog reset behavior required for Sun-3 hardware diagnostics, properly handles FSAVE/FRESTORE for 68881 FPU stubs, and properly constructs the 84-byte internal bus fault frame.
> 
> Signed-off-by: 54weasels <54weasels@gmail.com>
> ---
>  target/m68k/cpu.c       |   5 +-
>  target/m68k/cpu.h       |  18 +++-
>  target/m68k/helper.c    | 130 ++++++++++++++++++++++++++++-
>  target/m68k/op_helper.c | 176 ++++++++++++++++++++++++++--------------
>  target/m68k/translate.c |  31 +++++--
>  5 files changed, 283 insertions(+), 77 deletions(-)
> 
> diff --git a/target/m68k/cpu.c b/target/m68k/cpu.c
> index d849a4a90f..af375f0bce 100644
> --- a/target/m68k/cpu.c
> +++ b/target/m68k/cpu.c
> @@ -1,3 +1,4 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */

Looking at the comment at the top of this file, it clearly states that this
file is licensed under the Lesser GPL, so this change is wrong.

>  /*
>   * QEMU Motorola 68k CPU
>   *
> @@ -51,8 +52,8 @@ static TCGTBCPUState m68k_get_tb_cpu_state(CPUState *cs)
>      flags = (env->macsr >> 4) & TB_FLAGS_MACSR;
>      if (env->sr & SR_S) {
>          flags |= TB_FLAGS_MSR_S;
> -        flags |= (env->sfc << (TB_FLAGS_SFC_S_BIT - 2)) & TB_FLAGS_SFC_S;
> -        flags |= (env->dfc << (TB_FLAGS_DFC_S_BIT - 2)) & TB_FLAGS_DFC_S;
> +    flags |= (env->sfc << TB_FLAGS_SFC_S_BIT) & TB_FLAGS_SFC_S;
> +    flags |= (env->dfc << TB_FLAGS_DFC_S_BIT) & TB_FLAGS_DFC_S;

Wrong indentation now?

>      }
>      if (M68K_SR_TRACE(env->sr) == M68K_SR_TRACE_ANY_INS) {
>          flags |= TB_FLAGS_TRACE;
...
> diff --git a/target/m68k/op_helper.c b/target/m68k/op_helper.c
> index 8148a8852e..84d5270767 100644
> --- a/target/m68k/op_helper.c
> +++ b/target/m68k/op_helper.c
> @@ -1,3 +1,4 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
>  /*
>   *  M68K helper routines
>   *
> @@ -25,6 +26,7 @@
>  #include "qemu/plugin.h"
>  
>  #if !defined(CONFIG_USER_ONLY)
> +#include "system/runstate.h"
>  
>  static void cf_rte(CPUM68KState *env)
>  {
> @@ -73,6 +75,12 @@ throwaway:
>          case 7:
>              sp += 52;
>              break;
> +        case 0xa: /* Short Bus Cycle Fault (Format 0xA) */
> +            sp += 32 - 8; /* 32 bytes total - 8 bytes header = 24 bytes */
> +            break;
> +        case 0xb: /* Long Bus Cycle Fault (Format 0xB) */
> +            sp += 92 - 8; /* 92 bytes total - 8 bytes header = 84 bytes */
> +            break;
>          }
>      }
>      env->aregs[7] = sp;
> @@ -342,56 +350,80 @@ static void m68k_interrupt_all(CPUM68KState *env, int is_hw)
>      switch (cs->exception_index) {
>      case EXCP_ACCESS:
>          if (env->mmu.fault) {
> -            cpu_abort(cs, "DOUBLE MMU FAULT\n");
> +            qemu_log_mask(LOG_GUEST_ERROR,
> +                          "M68K: Double MMU Fault. Halting CPU and requesting reset.\n");
> +            qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
> +            cs->halted = 1;
> +            cs->exception_index = EXCP_HLT;
> +            cpu_loop_exit(cs);
>          }
>          env->mmu.fault = true;
> -        /* push data 3 */
> -        sp -= 4;
> -        cpu_stl_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
> -        /* push data 2 */
> -        sp -= 4;
> -        cpu_stl_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
> -        /* push data 1 */
> -        sp -= 4;
> -        cpu_stl_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
> -        /* write back 1 / push data 0 */
> -        sp -= 4;
> -        cpu_stl_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
> -        /* write back 1 address */
> -        sp -= 4;
> -        cpu_stl_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
> -        /* write back 2 data */
> -        sp -= 4;
> -        cpu_stl_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
> -        /* write back 2 address */
> -        sp -= 4;
> -        cpu_stl_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
> -        /* write back 3 data */
> -        sp -= 4;
> -        cpu_stl_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
> -        /* write back 3 address */
> -        sp -= 4;
> -        cpu_stl_be_mmuidx_ra(env, sp, env->mmu.ar, MMU_KERNEL_IDX, 0);
> -        /* fault address */
> -        sp -= 4;
> -        cpu_stl_be_mmuidx_ra(env, sp, env->mmu.ar, MMU_KERNEL_IDX, 0);
> -        /* write back 1 status */
> -        sp -= 2;
> -        cpu_stw_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
> -        /* write back 2 status */
> -        sp -= 2;
> -        cpu_stw_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
> -        /* write back 3 status */
> -        sp -= 2;
> -        cpu_stw_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
> -        /* special status word */
> -        sp -= 2;
> -        cpu_stw_be_mmuidx_ra(env, sp, env->mmu.ssw, MMU_KERNEL_IDX, 0);
> -        /* effective address */
> -        sp -= 4;
> -        cpu_stl_be_mmuidx_ra(env, sp, env->mmu.ar, MMU_KERNEL_IDX, 0);
> -
> -        do_stack_frame(env, &sp, 7, oldsr, 0, env->pc);
> +
> +        if (m68k_feature(env, M68K_FEATURE_M68040)) {
> +            /* push data 3 */
> +            sp -= 4;
> +            cpu_stl_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
> +            /* push data 2 */
> +            sp -= 4;
> +            cpu_stl_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
> +            /* push data 1 */
> +            sp -= 4;
> +            cpu_stl_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
> +            /* write back 1 / push data 0 */
> +            sp -= 4;
> +            cpu_stl_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
> +            /* write back 1 address */
> +            sp -= 4;
> +            cpu_stl_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
> +            /* write back 2 data */
> +            sp -= 4;
> +            cpu_stl_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
> +            /* write back 2 address */
> +            sp -= 4;
> +            cpu_stl_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
> +            /* write back 3 data */
> +            sp -= 4;
> +            cpu_stl_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
> +            /* write back 3 address */
> +            sp -= 4;
> +            cpu_stl_be_mmuidx_ra(env, sp, env->mmu.ar, MMU_KERNEL_IDX, 0);
> +            /* fault address */
> +            sp -= 4;
> +            cpu_stl_be_mmuidx_ra(env, sp, env->mmu.ar, MMU_KERNEL_IDX, 0);
> +            /* write back 1 status */
> +            sp -= 2;
> +            cpu_stw_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
> +            /* write back 2 status */
> +            sp -= 2;
> +            cpu_stw_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
> +            /* write back 3 status */
> +            sp -= 2;
> +            cpu_stw_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
> +            /* special status word */
> +            sp -= 2;
> +            cpu_stw_be_mmuidx_ra(env, sp, env->mmu.ssw, MMU_KERNEL_IDX, 0);
> +            /* effective address */
> +            sp -= 4;
> +            cpu_stl_be_mmuidx_ra(env, sp, env->mmu.ar, MMU_KERNEL_IDX, 0);
> +
> +            do_stack_frame(env, &sp, 7, oldsr, 0, env->pc);
> +        } else {
> +            /* M68020 Long Bus Cycle Fault (Format 0xB) */
> +            /*
> +             * 84 bytes of internal state are pushed before the generic
> +             * 8-byte header
> +             */
> +            sp -= 84;
> +            for (int i = 0; i < 84; i += 4) {
> +                cpu_stl_be_mmuidx_ra(env, sp + i, 0, MMU_KERNEL_IDX, 0);
> +            }
> +            /* Offset 0x02 from internal frame: SSW */
> +            cpu_stw_be_mmuidx_ra(env, sp + 2, env->mmu.ssw, MMU_KERNEL_IDX, 0);
> +            /* Offset 0x08 from internal frame: Fault Address */
> +            cpu_stl_be_mmuidx_ra(env, sp + 8, env->mmu.ar, MMU_KERNEL_IDX, 0);
> +
> +            do_stack_frame(env, &sp, 0xb, oldsr, 0, env->pc);
> +        }

Your patch got quite big already and does multiple things at once ... could
you maybe split independent parts like the above fixup for the stack frame
layout into a separate patch, so that this can get reviewed more easily?

> diff --git a/target/m68k/translate.c b/target/m68k/translate.c
> index abc1c79f3c..d6fcd6c4d9 100644
> --- a/target/m68k/translate.c
> +++ b/target/m68k/translate.c
> @@ -1,3 +1,4 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */

This file is also LPGL, not GPL.

 Thomas


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH 1/7] target/m68k: Implement Physical Bus Error exception handling
  2026-05-10  5:40   ` Thomas Huth
@ 2026-05-11  6:38     ` Purr Box
  0 siblings, 0 replies; 10+ messages in thread
From: Purr Box @ 2026-05-11  6:38 UTC (permalink / raw)
  To: Thomas Huth; +Cc: qemu-devel, laurent, thuth

[-- Attachment #1: Type: text/plain, Size: 9782 bytes --]

Thanks for the feedback, I'll perform the clean-ups and start splitting
some stuff up. If you have more suggestions for how it'll be easiest to
cut, let me know.
I'll resubmit a new patch and reference this one asap.

Thanks again,
Dan.

On Sat, May 9, 2026 at 10:40 PM Thomas Huth <th.huth+qemu@posteo.eu> wrote:

>
>  Hi!
>
> Thanks for your patches ... some comments inline below...
>
> Am Sat,  2 May 2026 18:57:50 -0700
> schrieb 54weasels <54weasels@gmail.com>:
>
> > The M68020 natively maps hardware Bus Error (BERR) timeouts into a Long
> Bus Cycle Fault (Format 0xB). This commit adds the memory exception routing
> to natively synthesize these EXCP_ACCESS cycle faults. It also implements
> the double-fault / watchdog reset behavior required for Sun-3 hardware
> diagnostics, properly handles FSAVE/FRESTORE for 68881 FPU stubs, and
> properly constructs the 84-byte internal bus fault frame.
> >
> > Signed-off-by: 54weasels <54weasels@gmail.com>
> > ---
> >  target/m68k/cpu.c       |   5 +-
> >  target/m68k/cpu.h       |  18 +++-
> >  target/m68k/helper.c    | 130 ++++++++++++++++++++++++++++-
> >  target/m68k/op_helper.c | 176 ++++++++++++++++++++++++++--------------
> >  target/m68k/translate.c |  31 +++++--
> >  5 files changed, 283 insertions(+), 77 deletions(-)
> >
> > diff --git a/target/m68k/cpu.c b/target/m68k/cpu.c
> > index d849a4a90f..af375f0bce 100644
> > --- a/target/m68k/cpu.c
> > +++ b/target/m68k/cpu.c
> > @@ -1,3 +1,4 @@
> > +/* SPDX-License-Identifier: GPL-2.0-or-later */
>
> Looking at the comment at the top of this file, it clearly states that this
> file is licensed under the Lesser GPL, so this change is wrong.
>
> >  /*
> >   * QEMU Motorola 68k CPU
> >   *
> > @@ -51,8 +52,8 @@ static TCGTBCPUState m68k_get_tb_cpu_state(CPUState
> *cs)
> >      flags = (env->macsr >> 4) & TB_FLAGS_MACSR;
> >      if (env->sr & SR_S) {
> >          flags |= TB_FLAGS_MSR_S;
> > -        flags |= (env->sfc << (TB_FLAGS_SFC_S_BIT - 2)) &
> TB_FLAGS_SFC_S;
> > -        flags |= (env->dfc << (TB_FLAGS_DFC_S_BIT - 2)) &
> TB_FLAGS_DFC_S;
> > +    flags |= (env->sfc << TB_FLAGS_SFC_S_BIT) & TB_FLAGS_SFC_S;
> > +    flags |= (env->dfc << TB_FLAGS_DFC_S_BIT) & TB_FLAGS_DFC_S;
>
> Wrong indentation now?
>
> >      }
> >      if (M68K_SR_TRACE(env->sr) == M68K_SR_TRACE_ANY_INS) {
> >          flags |= TB_FLAGS_TRACE;
> ...
> > diff --git a/target/m68k/op_helper.c b/target/m68k/op_helper.c
> > index 8148a8852e..84d5270767 100644
> > --- a/target/m68k/op_helper.c
> > +++ b/target/m68k/op_helper.c
> > @@ -1,3 +1,4 @@
> > +/* SPDX-License-Identifier: GPL-2.0-or-later */
> >  /*
> >   *  M68K helper routines
> >   *
> > @@ -25,6 +26,7 @@
> >  #include "qemu/plugin.h"
> >
> >  #if !defined(CONFIG_USER_ONLY)
> > +#include "system/runstate.h"
> >
> >  static void cf_rte(CPUM68KState *env)
> >  {
> > @@ -73,6 +75,12 @@ throwaway:
> >          case 7:
> >              sp += 52;
> >              break;
> > +        case 0xa: /* Short Bus Cycle Fault (Format 0xA) */
> > +            sp += 32 - 8; /* 32 bytes total - 8 bytes header = 24 bytes
> */
> > +            break;
> > +        case 0xb: /* Long Bus Cycle Fault (Format 0xB) */
> > +            sp += 92 - 8; /* 92 bytes total - 8 bytes header = 84 bytes
> */
> > +            break;
> >          }
> >      }
> >      env->aregs[7] = sp;
> > @@ -342,56 +350,80 @@ static void m68k_interrupt_all(CPUM68KState *env,
> int is_hw)
> >      switch (cs->exception_index) {
> >      case EXCP_ACCESS:
> >          if (env->mmu.fault) {
> > -            cpu_abort(cs, "DOUBLE MMU FAULT\n");
> > +            qemu_log_mask(LOG_GUEST_ERROR,
> > +                          "M68K: Double MMU Fault. Halting CPU and
> requesting reset.\n");
> > +            qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
> > +            cs->halted = 1;
> > +            cs->exception_index = EXCP_HLT;
> > +            cpu_loop_exit(cs);
> >          }
> >          env->mmu.fault = true;
> > -        /* push data 3 */
> > -        sp -= 4;
> > -        cpu_stl_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
> > -        /* push data 2 */
> > -        sp -= 4;
> > -        cpu_stl_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
> > -        /* push data 1 */
> > -        sp -= 4;
> > -        cpu_stl_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
> > -        /* write back 1 / push data 0 */
> > -        sp -= 4;
> > -        cpu_stl_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
> > -        /* write back 1 address */
> > -        sp -= 4;
> > -        cpu_stl_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
> > -        /* write back 2 data */
> > -        sp -= 4;
> > -        cpu_stl_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
> > -        /* write back 2 address */
> > -        sp -= 4;
> > -        cpu_stl_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
> > -        /* write back 3 data */
> > -        sp -= 4;
> > -        cpu_stl_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
> > -        /* write back 3 address */
> > -        sp -= 4;
> > -        cpu_stl_be_mmuidx_ra(env, sp, env->mmu.ar, MMU_KERNEL_IDX, 0);
> > -        /* fault address */
> > -        sp -= 4;
> > -        cpu_stl_be_mmuidx_ra(env, sp, env->mmu.ar, MMU_KERNEL_IDX, 0);
> > -        /* write back 1 status */
> > -        sp -= 2;
> > -        cpu_stw_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
> > -        /* write back 2 status */
> > -        sp -= 2;
> > -        cpu_stw_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
> > -        /* write back 3 status */
> > -        sp -= 2;
> > -        cpu_stw_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
> > -        /* special status word */
> > -        sp -= 2;
> > -        cpu_stw_be_mmuidx_ra(env, sp, env->mmu.ssw, MMU_KERNEL_IDX, 0);
> > -        /* effective address */
> > -        sp -= 4;
> > -        cpu_stl_be_mmuidx_ra(env, sp, env->mmu.ar, MMU_KERNEL_IDX, 0);
> > -
> > -        do_stack_frame(env, &sp, 7, oldsr, 0, env->pc);
> > +
> > +        if (m68k_feature(env, M68K_FEATURE_M68040)) {
> > +            /* push data 3 */
> > +            sp -= 4;
> > +            cpu_stl_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
> > +            /* push data 2 */
> > +            sp -= 4;
> > +            cpu_stl_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
> > +            /* push data 1 */
> > +            sp -= 4;
> > +            cpu_stl_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
> > +            /* write back 1 / push data 0 */
> > +            sp -= 4;
> > +            cpu_stl_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
> > +            /* write back 1 address */
> > +            sp -= 4;
> > +            cpu_stl_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
> > +            /* write back 2 data */
> > +            sp -= 4;
> > +            cpu_stl_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
> > +            /* write back 2 address */
> > +            sp -= 4;
> > +            cpu_stl_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
> > +            /* write back 3 data */
> > +            sp -= 4;
> > +            cpu_stl_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
> > +            /* write back 3 address */
> > +            sp -= 4;
> > +            cpu_stl_be_mmuidx_ra(env, sp, env->mmu.ar, MMU_KERNEL_IDX,
> 0);
> > +            /* fault address */
> > +            sp -= 4;
> > +            cpu_stl_be_mmuidx_ra(env, sp, env->mmu.ar, MMU_KERNEL_IDX,
> 0);
> > +            /* write back 1 status */
> > +            sp -= 2;
> > +            cpu_stw_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
> > +            /* write back 2 status */
> > +            sp -= 2;
> > +            cpu_stw_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
> > +            /* write back 3 status */
> > +            sp -= 2;
> > +            cpu_stw_be_mmuidx_ra(env, sp, 0, MMU_KERNEL_IDX, 0);
> > +            /* special status word */
> > +            sp -= 2;
> > +            cpu_stw_be_mmuidx_ra(env, sp, env->mmu.ssw, MMU_KERNEL_IDX,
> 0);
> > +            /* effective address */
> > +            sp -= 4;
> > +            cpu_stl_be_mmuidx_ra(env, sp, env->mmu.ar, MMU_KERNEL_IDX,
> 0);
> > +
> > +            do_stack_frame(env, &sp, 7, oldsr, 0, env->pc);
> > +        } else {
> > +            /* M68020 Long Bus Cycle Fault (Format 0xB) */
> > +            /*
> > +             * 84 bytes of internal state are pushed before the generic
> > +             * 8-byte header
> > +             */
> > +            sp -= 84;
> > +            for (int i = 0; i < 84; i += 4) {
> > +                cpu_stl_be_mmuidx_ra(env, sp + i, 0, MMU_KERNEL_IDX, 0);
> > +            }
> > +            /* Offset 0x02 from internal frame: SSW */
> > +            cpu_stw_be_mmuidx_ra(env, sp + 2, env->mmu.ssw,
> MMU_KERNEL_IDX, 0);
> > +            /* Offset 0x08 from internal frame: Fault Address */
> > +            cpu_stl_be_mmuidx_ra(env, sp + 8, env->mmu.ar,
> MMU_KERNEL_IDX, 0);
> > +
> > +            do_stack_frame(env, &sp, 0xb, oldsr, 0, env->pc);
> > +        }
>
> Your patch got quite big already and does multiple things at once ... could
> you maybe split independent parts like the above fixup for the stack frame
> layout into a separate patch, so that this can get reviewed more easily?
>
> > diff --git a/target/m68k/translate.c b/target/m68k/translate.c
> > index abc1c79f3c..d6fcd6c4d9 100644
> > --- a/target/m68k/translate.c
> > +++ b/target/m68k/translate.c
> > @@ -1,3 +1,4 @@
> > +/* SPDX-License-Identifier: GPL-2.0-or-later */
>
> This file is also LPGL, not GPL.
>
>  Thomas
>

[-- Attachment #2: Type: text/html, Size: 12618 bytes --]

^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2026-05-11 13:44 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-03  1:57 [PATCH 0/7] m68k: Add Sun-3 Machine Emulation 54weasels
2026-05-03  1:57 ` [PATCH 1/7] target/m68k: Implement Physical Bus Error exception handling 54weasels
2026-05-10  5:40   ` Thomas Huth
2026-05-11  6:38     ` Purr Box
2026-05-03  1:57 ` [PATCH 2/7] hw/net/lance: Add Sun-3 Native DMA byte-swapping support 54weasels
2026-05-03  1:57 ` [PATCH 3/7] hw/char/escc: Expose diagnostic RS232 I/O routing 54weasels
2026-05-03  1:57 ` [PATCH 4/7] hw/timer: Introduce Intersil 7170 RTC implementation 54weasels
2026-05-03  1:57 ` [PATCH 5/7] hw/m68k: Overhaul Sun-3 MMU and Boot PROM mapping 54weasels
2026-05-03  1:57 ` [PATCH 6/7] tests/qtest: Add Sun-3 hardware interaction tests 54weasels
2026-05-03  1:57 ` [PATCH 7/7] tests/functional: Add Sun-3 firmware boot and diagnostic test 54weasels

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.