Blame 0015-fw_cfg-fix-endianness-in-fw_cfg_data_mem_read-_write.patch

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