|
|
391fb8 |
From b627b2d476b3677e35d06bdc9fac26678fb92484 Mon Sep 17 00:00:00 2001
|
|
|
391fb8 |
From: Laszlo Ersek <lersek@redhat.com>
|
|
|
391fb8 |
Date: Fri, 16 Jan 2015 11:54:30 +0000
|
|
|
391fb8 |
Subject: [PATCH 15/15] fw_cfg: fix endianness in fw_cfg_data_mem_read() /
|
|
|
391fb8 |
_write()
|
|
|
391fb8 |
|
|
|
391fb8 |
(1) Let's contemplate what device endianness means, for a memory mapped
|
|
|
391fb8 |
device register (independently of QEMU -- that is, on physical hardware).
|
|
|
391fb8 |
|
|
|
391fb8 |
It determines the byte order that the device will put on the data bus when
|
|
|
391fb8 |
the device is producing a *numerical value* for the CPU. This byte order
|
|
|
391fb8 |
may differ from the CPU's own byte order, therefore when software wants to
|
|
|
391fb8 |
consume the *numerical value*, it may have to swap the byte order first.
|
|
|
391fb8 |
|
|
|
391fb8 |
For example, suppose we have a device that exposes in a 2-byte register
|
|
|
391fb8 |
the number of sheep we have to count before falling asleep. If the value
|
|
|
391fb8 |
is decimal 37 (0x0025), then a big endian register will produce [0x00,
|
|
|
391fb8 |
0x25], while a little endian register will produce [0x25, 0x00].
|
|
|
391fb8 |
|
|
|
391fb8 |
If the device register is big endian, but the CPU is little endian, the
|
|
|
391fb8 |
numerical value will read as 0x2500 (decimal 9472), which software has to
|
|
|
391fb8 |
byte swap before use.
|
|
|
391fb8 |
|
|
|
391fb8 |
However... if we ask the device about who stole our herd of sheep, and it
|
|
|
391fb8 |
answers "XY", then the byte representation coming out of the register must
|
|
|
391fb8 |
be [0x58, 0x59], regardless of the device register's endianness for
|
|
|
391fb8 |
numeric values. And, software needs to copy these bytes into a string
|
|
|
391fb8 |
field regardless of the CPU's own endianness.
|
|
|
391fb8 |
|
|
|
391fb8 |
(2) QEMU's device register accessor functions work with *numerical values*
|
|
|
391fb8 |
exclusively, not strings:
|
|
|
391fb8 |
|
|
|
391fb8 |
The emulated register's read accessor function returns the numerical value
|
|
|
391fb8 |
(eg. 37 decimal, 0x0025) as a *host-encoded* uint64_t. QEMU translates
|
|
|
391fb8 |
this value for the guest to the endianness of the emulated device register
|
|
|
391fb8 |
(which is recorded in MemoryRegionOps.endianness). Then guest code must
|
|
|
391fb8 |
translate the numerical value from device register to guest CPU
|
|
|
391fb8 |
endianness, before including it in any computation (see (1)).
|
|
|
391fb8 |
|
|
|
391fb8 |
(3) However, the data register of the fw_cfg device shall transfer strings
|
|
|
391fb8 |
*only* -- that is, opaque blobs. Interpretation of any given blob is
|
|
|
391fb8 |
subject to further agreement -- it can be an integer in an independently
|
|
|
391fb8 |
determined byte order, or a genuine string, or an array of structs of
|
|
|
391fb8 |
integers (in some byte order) and fixed size strings, and so on.
|
|
|
391fb8 |
|
|
|
391fb8 |
Because register emulation in QEMU is integer-preserving, not
|
|
|
391fb8 |
string-preserving (see (2)), we have to jump through a few hoops.
|
|
|
391fb8 |
|
|
|
391fb8 |
(3a) We defined the memory mapped fw_cfg data register as
|
|
|
391fb8 |
DEVICE_BIG_ENDIAN.
|
|
|
391fb8 |
|
|
|
391fb8 |
The particular choice is not really relevant -- we picked BE only for
|
|
|
391fb8 |
consistency with the control register, which *does* transfer integers --
|
|
|
391fb8 |
but our choice affects how we must host-encode values from fw_cfg strings.
|
|
|
391fb8 |
|
|
|
391fb8 |
(3b) Since we want the fw_cfg string "XY" to appear as the [0x58, 0x59]
|
|
|
391fb8 |
array on the data register, *and* we picked DEVICE_BIG_ENDIAN, we must
|
|
|
391fb8 |
compose the host (== C language) value 0x5859 in the read accessor
|
|
|
391fb8 |
function.
|
|
|
391fb8 |
|
|
|
391fb8 |
(3c) When the guest performs the read access, the immediate uint16_t value
|
|
|
391fb8 |
will be 0x5958 (in LE guests) and 0x5859 (in BE guests). However, the
|
|
|
391fb8 |
uint16_t value does not matter. The only thing that matters is the byte
|
|
|
391fb8 |
pattern [0x58, 0x59], which the guest code must copy into the target
|
|
|
391fb8 |
string *without* any byte-swapping.
|
|
|
391fb8 |
|
|
|
391fb8 |
(4) Now I get to explain where I screwed up. :(
|
|
|
391fb8 |
|
|
|
391fb8 |
When we decided for big endian *integer* representation in the MMIO data
|
|
|
391fb8 |
register -- see (3a) --, I mindlessly added an indiscriminate
|
|
|
391fb8 |
byte-swizzling step to the (little endian) guest firmware.
|
|
|
391fb8 |
|
|
|
391fb8 |
This was a grave error -- it violates (3c) --, but I didn't realize it. I
|
|
|
391fb8 |
only saw that the code I otherwise intended for fw_cfg_data_mem_read():
|
|
|
391fb8 |
|
|
|
391fb8 |
value = 0;
|
|
|
391fb8 |
for (i = 0; i < size; ++i) {
|
|
|
391fb8 |
value = (value << 8) | fw_cfg_read(s);
|
|
|
391fb8 |
}
|
|
|
391fb8 |
|
|
|
391fb8 |
didn't produce the expected result in the guest.
|
|
|
391fb8 |
|
|
|
391fb8 |
In true facepalm style, instead of blaming my guest code (which violated
|
|
|
391fb8 |
(3c)), I blamed my host code (which was correct). Ultimately, I coded
|
|
|
391fb8 |
ldX_he_p() into fw_cfg_data_mem_read(), because that happened to work.
|
|
|
391fb8 |
|
|
|
391fb8 |
Obviously (...in retrospect) that was wrong. Only because my host happened
|
|
|
391fb8 |
to be LE, ldX_he_p() composed the (otherwise incorrect) host value 0x5958
|
|
|
391fb8 |
from the fw_cfg string "XY". And that happened to compensate for the bogus
|
|
|
391fb8 |
indiscriminate byte-swizzling in my guest code.
|
|
|
391fb8 |
|
|
|
391fb8 |
Clearly the current code leaks the host endianness through to the guest,
|
|
|
391fb8 |
which is wrong. Any device should work the same regardless of host
|
|
|
391fb8 |
endianness.
|
|
|
391fb8 |
|
|
|
391fb8 |
The solution is to compose the host-endian representation (2) of the big
|
|
|
391fb8 |
endian interpretation (3a, 3b) of the fw_cfg string, and to drop the wrong
|
|
|
391fb8 |
byte-swizzling in the guest (3c).
|
|
|
391fb8 |
|
|
|
391fb8 |
Brown paper bag time for me.
|
|
|
391fb8 |
|
|
|
391fb8 |
Signed-off-by: Laszlo Ersek <lersek@redhat.com>
|
|
|
391fb8 |
Message-id: 1420024880-15416-1-git-send-email-lersek@redhat.com
|
|
|
391fb8 |
Reviewed-by: Peter Maydell <peter.maydell@linaro.org>
|
|
|
391fb8 |
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
|
|
|
391fb8 |
(cherry picked from commit 36b62ae6a58f9a588fd33be9386e18a2b90103f5)
|
|
|
391fb8 |
---
|
|
|
391fb8 |
hw/nvram/fw_cfg.c | 41 +++++++----------------------------------
|
|
|
391fb8 |
1 file changed, 7 insertions(+), 34 deletions(-)
|
|
|
391fb8 |
|
|
|
391fb8 |
diff --git a/hw/nvram/fw_cfg.c b/hw/nvram/fw_cfg.c
|
|
|
391fb8 |
index fcdf821..78a37be 100644
|
|
|
391fb8 |
--- a/hw/nvram/fw_cfg.c
|
|
|
391fb8 |
+++ b/hw/nvram/fw_cfg.c
|
|
|
391fb8 |
@@ -287,51 +287,24 @@ static uint64_t fw_cfg_data_mem_read(void *opaque, hwaddr addr,
|
|
|
391fb8 |
unsigned size)
|
|
|
391fb8 |
{
|
|
|
391fb8 |
FWCfgState *s = opaque;
|
|
|
391fb8 |
- uint8_t buf[8];
|
|
|
391fb8 |
+ uint64_t value = 0;
|
|
|
391fb8 |
unsigned i;
|
|
|
391fb8 |
|
|
|
391fb8 |
for (i = 0; i < size; ++i) {
|
|
|
391fb8 |
- buf[i] = fw_cfg_read(s);
|
|
|
391fb8 |
+ value = (value << 8) | fw_cfg_read(s);
|
|
|
391fb8 |
}
|
|
|
391fb8 |
- switch (size) {
|
|
|
391fb8 |
- case 1:
|
|
|
391fb8 |
- return buf[0];
|
|
|
391fb8 |
- case 2:
|
|
|
391fb8 |
- return lduw_he_p(buf);
|
|
|
391fb8 |
- case 4:
|
|
|
391fb8 |
- return (uint32_t)ldl_he_p(buf);
|
|
|
391fb8 |
- case 8:
|
|
|
391fb8 |
- return ldq_he_p(buf);
|
|
|
391fb8 |
- }
|
|
|
391fb8 |
- abort();
|
|
|
391fb8 |
+ return value;
|
|
|
391fb8 |
}
|
|
|
391fb8 |
|
|
|
391fb8 |
static void fw_cfg_data_mem_write(void *opaque, hwaddr addr,
|
|
|
391fb8 |
uint64_t value, unsigned size)
|
|
|
391fb8 |
{
|
|
|
391fb8 |
FWCfgState *s = opaque;
|
|
|
391fb8 |
- uint8_t buf[8];
|
|
|
391fb8 |
- unsigned i;
|
|
|
391fb8 |
+ unsigned i = size;
|
|
|
391fb8 |
|
|
|
391fb8 |
- switch (size) {
|
|
|
391fb8 |
- case 1:
|
|
|
391fb8 |
- buf[0] = value;
|
|
|
391fb8 |
- break;
|
|
|
391fb8 |
- case 2:
|
|
|
391fb8 |
- stw_he_p(buf, value);
|
|
|
391fb8 |
- break;
|
|
|
391fb8 |
- case 4:
|
|
|
391fb8 |
- stl_he_p(buf, value);
|
|
|
391fb8 |
- break;
|
|
|
391fb8 |
- case 8:
|
|
|
391fb8 |
- stq_he_p(buf, value);
|
|
|
391fb8 |
- break;
|
|
|
391fb8 |
- default:
|
|
|
391fb8 |
- abort();
|
|
|
391fb8 |
- }
|
|
|
391fb8 |
- for (i = 0; i < size; ++i) {
|
|
|
391fb8 |
- fw_cfg_write(s, buf[i]);
|
|
|
391fb8 |
- }
|
|
|
391fb8 |
+ do {
|
|
|
391fb8 |
+ fw_cfg_write(s, value >> (8 * --i));
|
|
|
391fb8 |
+ } while (i);
|
|
|
391fb8 |
}
|
|
|
391fb8 |
|
|
|
391fb8 |
static bool fw_cfg_data_mem_valid(void *opaque, hwaddr addr,
|
|
|
391fb8 |
--
|
|
|
391fb8 |
2.1.0
|
|
|
391fb8 |
|