Skip to content

load_helper() do_unaligned_access path doesn't return correct result with MMIO

Basic information

I discovered this issue when trying to understand why a timer calibration routine wasn't working correctly on the QEMU Q800 machine. The basic setup is described below:

  • The Q800 machine VIA1 (a 6522 device) is mapped at 0x50000000 with a stride of 0x200, placing the T2 counter low byte at 0x50001000 and the T2 counter high byte at 0x50001200

  • The guest tries to read both registers at once by issuing a movew %a0@(4607),%d0 instruction which is a 16-bit read at 0x500011ff

  • Since 0x500011ff is unaligned, load_helper() in cputlb.c jumps to the do_unaligned_access path and performs 2 separate accesses

  • The resulting values are combined incorrectly resulting in the loss of the LSB

Detail

Tracing through load_helper() for this particular case on an x86_64 host gives the following:

    do_unaligned_access:
        addr1 = addr & ~((target_ulong)size - 1);     // addr1 = 0x50f011fe
        addr2 = addr1 + size;                         // addr2 = 0x50f01200
        r1 = full_load(env, addr1, oi, retaddr);      // r1 = 0x3100
        r2 = full_load(env, addr2, oi, retaddr);      // r2 = 0xfc00
        shift = (addr & (size - 1)) * 8;              // shift = 8, size = 2

        if (memop_big_endian(op)) {
            /* Big-endian combine.  */
            res = (r1 << shift) | (r2 >> ((size * 8) - shift));
        } else {
            /* Little-endian combine.  */
            // res = (0x3100 >> 8) | (0xfc00 << ((2 * 8) - 8)) = 0x00fc0031
            res = (r1 >> shift) | (r2 << ((size * 8) - shift));
        }
        // res & 0xffff = 0x0031 (r2 component is missing)
        return res & MAKE_64BIT_MASK(0, size * 8);

Reproducer

I've put together a hacked-up version of q800.c that can reproduce the issue at https://gitlab.com/mcayland/qemu/-/commits/gitlab-do-unaligned-access-issue. With this branch the issue can be reproduced on an x86_64 host with an m68k gdbstub easily:

  • QEMU command line
$ ./qemu-system-m68k -M q800 -trace 'mos6522_read' -s -S
mos6522_read reg=0x8 val=0x31
mos6522_read reg=0x9 val=0xfc
  • m68k gdbstub
GNU gdb 6.4.90-debian
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "m68k-linux-gnu".
(gdb) target remote :1234
Remote debugging using :1234
[New thread 1]
0x40800000 in ?? ()
(gdb) stepi
0x40800006 in ?? ()
(gdb) 
0x4080000a in ?? ()
(gdb) i r $a0
a0             0x50000000       0x50000000
(gdb) i r $d0
d0             0x3100   12544

Here the value returned in register d0 is 0x3100 and not 0x31fc as expected.

To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information