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 at0x50001000
and the T2 counter high byte at0x50001200
-
The guest tries to read both registers at once by issuing a
movew %a0@(4607),%d0
instruction which is a 16-bit read at0x500011ff
-
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.