mvadkert / rpms / qemu

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