During the kernel timer calibration routine A/UX performs an unaligned access
across the T2CL and T2CH registers to read the entire 16-bit value in a
single memory access.

Allow unaligned accesses to the VIA1 memory region such that the unaligned
address and size are available within the MemoryRegionOps read and write
functions. This gives two advantages: i) the unaligned accesses can be
logged and ii) the original access information can be used subsequently to
detect when the A/UX timer calibration is being executed.

Signed-off-by: Mark Cave-Ayland <mark.cave-ayl...@ilande.co.uk>
---
 hw/m68k/q800.c       |   2 +
 hw/misc/mac_via.c    | 127 ++++++++++++++++++++++++++-----------------
 hw/misc/trace-events |   2 +
 3 files changed, 80 insertions(+), 51 deletions(-)

diff --git a/hw/m68k/q800.c b/hw/m68k/q800.c
index 87665c6407..eb2f11234a 100644
--- a/hw/m68k/q800.c
+++ b/hw/m68k/q800.c
@@ -198,6 +198,8 @@ static const MemoryRegionOps macio_alias_ops = {
     .valid = {
         .min_access_size = 1,
         .max_access_size = 4,
+        /* VIA1 unaligned access for A/UX timer calibration */
+        .unaligned = true,
     },
 };
 
diff --git a/hw/misc/mac_via.c b/hw/misc/mac_via.c
index 5d1adf5863..94b4f5cebc 100644
--- a/hw/misc/mac_via.c
+++ b/hw/misc/mac_via.c
@@ -995,20 +995,33 @@ static void 
via1_timer_calibration_hack(MOS6522Q800VIA1State *v1s, int addr,
 
 static uint64_t mos6522_q800_via1_read(void *opaque, hwaddr addr, unsigned 
size)
 {
-    MOS6522Q800VIA1State *s = MOS6522_Q800_VIA1(opaque);
-    MOS6522State *ms = MOS6522(s);
-    uint64_t ret;
+    MOS6522Q800VIA1State *v1s = MOS6522_Q800_VIA1(opaque);
+    MOS6522State *ms = MOS6522(v1s);
+    int64_t now;
+    uint8_t ret;
+    uint64_t val = 0;
+    int i;
+    hwaddr addr1;
+
+    /* Handle unaligned read used by A/UX timer calibration code */
+    addr1 = (addr >> 9) & 0xf;
+    for (i = 0; i < size; i++, addr1++) {
+        ret = mos6522_read(ms, addr1, size);
+        switch (addr1) {
+        case VIA_REG_A:
+        case VIA_REG_ANH:
+            /* Quadra 800 Id */
+            ret = (ret & ~VIA1A_CPUID_MASK) | VIA1A_CPUID_Q800;
+            break;
+        }
+        val |= ret << ((size - i - 1) << 3);
+    }
 
-    addr = (addr >> 9) & 0xf;
-    ret = mos6522_read(ms, addr, size);
-    switch (addr) {
-    case VIA_REG_A:
-    case VIA_REG_ANH:
-        /* Quadra 800 Id */
-        ret = (ret & ~VIA1A_CPUID_MASK) | VIA1A_CPUID_Q800;
-        break;
+    if ((addr >> 9) != ((addr + size) >> 9)) {
+        trace_via1_unaligned_read(addr, val, size);
     }
-    return ret;
+
+    return val;
 }
 
 static void mos6522_q800_via1_write(void *opaque, hwaddr addr, uint64_t val,
@@ -1018,53 +1031,63 @@ static void mos6522_q800_via1_write(void *opaque, 
hwaddr addr, uint64_t val,
     MOS6522State *ms = MOS6522(v1s);
     int oldstate, state;
     int oldsr = ms->sr;
+    hwaddr addr1;
+    uint8_t v;
+    int i;
 
-    addr = (addr >> 9) & 0xf;
+    if ((addr >> 9) != ((addr + size) >> 9)) {
+        trace_via1_unaligned_write(addr, val, size);
+    }
 
-    via1_timer_calibration_hack(v1s, addr, val, size);
+    addr1 = (addr >> 9) & 0xf;
+    via1_timer_calibration_hack(v1s, addr1, val, size);
 
-    mos6522_write(ms, addr, val, size);
+    /* Handle unaligned write used by A/UX timer calibration code */
+    for (i = 0; i < size; i++, addr1++) {
+        v = val >> ((size - i - 1) << 3);
+        mos6522_write(ms, addr1, v, size);
 
-    switch (addr) {
-    case VIA_REG_B:
-        via1_rtc_update(v1s);
-        via1_adb_update(v1s);
-        via1_auxmode_update(v1s);
+        switch (addr1) {
+        case VIA_REG_B:
+            via1_rtc_update(v1s);
+            via1_adb_update(v1s);
+            via1_auxmode_update(v1s);
 
-        v1s->last_b = ms->b;
-        break;
+            v1s->last_b = ms->b;
+            break;
 
-    case VIA_REG_SR:
-        {
-            /*
-             * NetBSD assumes it can send its first ADB command after sending
-             * the ADB_BUSRESET command in ADB_STATE_NEW without changing the
-             * state back to ADB_STATE_IDLE first as detailed in the ADB
-             * protocol.
-             *
-             * Add a workaround to detect this condition at the start of ADB
-             * enumeration and send the next command written to SR after a
-             * ADB_BUSRESET onto the bus regardless, even if we don't detect a
-             * state transition to ADB_STATE_NEW.
-             *
-             * Note that in my tests the NetBSD state machine takes one ADB
-             * operation to recover which means the probe for an ADB device at
-             * address 1 always fails. However since the first device is at
-             * address 2 then this will work fine, without having to come up
-             * with a more complicated and invasive solution.
-             */
-            oldstate = (v1s->last_b & VIA1B_vADB_StateMask) >>
-                       VIA1B_vADB_StateShift;
-            state = (ms->b & VIA1B_vADB_StateMask) >> VIA1B_vADB_StateShift;
-
-            if (oldstate == ADB_STATE_NEW && state == ADB_STATE_NEW &&
-                    (ms->acr & VIA1ACR_vShiftOut) &&
-                    oldsr == 0 /* ADB_BUSRESET */) {
-                trace_via1_adb_netbsd_enum_hack();
-                adb_via_send(v1s, state, ms->sr);
+        case VIA_REG_SR:
+            {
+                /*
+                 * NetBSD assumes it can send its first ADB command after
+                 * sending the ADB_BUSRESET command in ADB_STATE_NEW without
+                 * changing the state back to ADB_STATE_IDLE first as detailed
+                 * in the ADB protocol.
+                 *
+                 * Add a workaround to detect this condition at the start of
+                 * ADB enumeration and send the next command written to SR
+                 * after a ADB_BUSRESET onto the bus regardless, even if we
+                 * don't detect a state transition to ADB_STATE_NEW.
+                 *
+                 * Note that in my tests the NetBSD state machine takes one ADB
+                 * operation to recover which means the probe for an ADB device
+                 * at address 1 always fails. However since the first device is
+                 * at address 2 then this will work fine, without having to
+                 * come up with a more complicated and invasive solution.
+                 */
+                oldstate = (v1s->last_b & VIA1B_vADB_StateMask) >>
+                           VIA1B_vADB_StateShift;
+                state = (ms->b & VIA1B_vADB_StateMask) >> 
VIA1B_vADB_StateShift;
+
+                if (oldstate == ADB_STATE_NEW && state == ADB_STATE_NEW &&
+                        (ms->acr & VIA1ACR_vShiftOut) &&
+                        oldsr == 0 /* ADB_BUSRESET */) {
+                    trace_via1_adb_netbsd_enum_hack();
+                    adb_via_send(v1s, state, ms->sr);
+                }
             }
+            break;
         }
-        break;
     }
 }
 
@@ -1075,6 +1098,8 @@ static const MemoryRegionOps mos6522_q800_via1_ops = {
     .valid = {
         .min_access_size = 1,
         .max_access_size = 4,
+        /* VIA1 unaligned access for A/UX timer calibration */
+        .unaligned = true,
     },
 };
 
diff --git a/hw/misc/trace-events b/hw/misc/trace-events
index 3d14e1db09..81bb16cea2 100644
--- a/hw/misc/trace-events
+++ b/hw/misc/trace-events
@@ -270,6 +270,8 @@ via1_adb_poll(uint8_t data, const char *vadbint, int 
status, int index, int size
 via1_adb_netbsd_enum_hack(void) "using NetBSD enum hack"
 via1_auxmode(int mode) "setting auxmode to %d"
 via1_timer_hack_state(int state) "setting timer_hack_state to %d"
+via1_unaligned_read(uint64_t addr, uint64_t value, unsigned size) 
"addr=0x%"PRIx64" value=0x%"PRIx64" size=%u"
+via1_unaligned_write(uint64_t addr, uint64_t value, unsigned size) 
"addr=0x%"PRIx64" value=0x%"PRIx64" size=%u"
 
 # grlib_ahb_apb_pnp.c
 grlib_ahb_pnp_read(uint64_t addr, unsigned size, uint32_t value) "AHB PnP read 
addr:0x%03"PRIx64" size:%u data:0x%08x"
-- 
2.30.2


Reply via email to