Patchwork usb: Fix host-side endian bugs in hcd-ehci

login
register
mail settings
Submitter David Gibson
Date Aug. 22, 2012, 3:04 a.m.
Message ID <1345604699-24851-1-git-send-email-david@gibson.dropbear.id.au>
Download mbox | patch
Permalink /patch/179222/
State New
Headers show

Comments

David Gibson - Aug. 22, 2012, 3:04 a.m.
The EHCI device model is horribly broken for big-endian hosts.  It uses a
union of 'mmio' a byte array which is as-is as the device's MMIO space
with the various internal registers.  The IO routines assume that mmio is
laid out in little-endian order, but everything else in the code accesses
the register variables directly assuming (host) native endian.

This fix is fairly ugly - a nicer approach would involve converting
hcd-ehci to use the new-style memory region .read and .write functions -
but it's the most minimal fix I can see to apply for the qemu 1.2 release.

NB: It might look like this patch breaks support for unaligned accesses,
however these already didn't work as they would be rejected by the code in
memory.c before even reaching hcd-ehci.c.

Cc: Gerd Hoffman <kraxel@redhat.com>

Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
---
 hw/usb/hcd-ehci.c |   94 +++++++++++++++++++++++++++++++++--------------------
 1 file changed, 58 insertions(+), 36 deletions(-)
Gerd Hoffmann - Sept. 6, 2012, 9:53 a.m.
On 08/22/12 05:04, David Gibson wrote:
> The EHCI device model is horribly broken for big-endian hosts.  It uses a
> union of 'mmio' a byte array which is as-is as the device's MMIO space
> with the various internal registers.  The IO routines assume that mmio is
> laid out in little-endian order, but everything else in the code accesses
> the register variables directly assuming (host) native endian.
> 
> This fix is fairly ugly - a nicer approach would involve converting
> hcd-ehci to use the new-style memory region .read and .write functions -
> but it's the most minimal fix I can see to apply for the qemu 1.2 release.

Oops.  This one slipped through.  Just found it -- to late for 1.2 --
while wading through my inbox looking for unprocessed stuff.

Doing the real thing instead (i.e. convert memory regions) doesn't look
that horrible though, I think we can put that into stable-1.2.  I'll
send out the patch in a few moments.

cheers,
  Gerd

Patch

diff --git a/hw/usb/hcd-ehci.c b/hw/usb/hcd-ehci.c
index 104c21d..bf73a78 100644
--- a/hw/usb/hcd-ehci.c
+++ b/hw/usb/hcd-ehci.c
@@ -54,6 +54,7 @@ 
 #define HCSPPORTROUTE2   CAPREGBASE + 0x0010
 
 #define OPREGBASE        0x0020        // Operational Registers Base Address
+#define OPREGNUM         ((MMIO_SIZE - OPREGBASE) / sizeof(uint32_t))
 
 #define USBCMD           OPREGBASE + 0x0000
 #define USBCMD_RUNSTOP   (1 << 0)      // run / Stop
@@ -393,10 +394,10 @@  struct EHCIState {
      *  EHCI spec version 1.0 Section 2.3
      *  Host Controller Operational Registers
      */
+    uint8_t cap[OPREGBASE];
     union {
-        uint8_t mmio[MMIO_SIZE];
+        uint32_t opreg[OPREGNUM];
         struct {
-            uint8_t cap[OPREGBASE];
             uint32_t usbcmd;
             uint32_t usbsts;
             uint32_t usbintr;
@@ -962,7 +963,7 @@  static int ehci_register_companion(USBBus *bus, USBPort *ports[],
     }
 
     s->companion_count++;
-    s->mmio[0x05] = (s->companion_count << 4) | portcount;
+    s->cap[0x05] = (s->companion_count << 4) | portcount;
 
     return 0;
 }
@@ -1007,7 +1008,7 @@  static void ehci_reset(void *opaque)
         }
     }
 
-    memset(&s->mmio[OPREGBASE], 0x00, MMIO_SIZE - OPREGBASE);
+    memset(&s->opreg, 0, sizeof(s->opreg));
 
     s->usbcmd = NB_MAXINTRATE << USBCMD_ITC_SH;
     s->usbsts = USBSTS_HALT;
@@ -1034,22 +1035,37 @@  static void ehci_reset(void *opaque)
     qemu_bh_cancel(s->async_bh);
 }
 
+static uint32_t ehci_mem_readl(void *ptr, target_phys_addr_t addr);
+
 static uint32_t ehci_mem_readb(void *ptr, target_phys_addr_t addr)
 {
     EHCIState *s = ptr;
     uint32_t val;
 
-    val = s->mmio[addr];
+    if (addr < OPREGBASE) {
+        val = s->cap[addr];
+    } else {
+        /* Registers present as little-endian, regardless of host endianness */
+        val = ehci_mem_readl(ptr, addr & ~3);
+        val = (val >> (8*(addr & 3))) & 0xff;
+    }
 
     return val;
 }
 
 static uint32_t ehci_mem_readw(void *ptr, target_phys_addr_t addr)
 {
-    EHCIState *s = ptr;
     uint32_t val;
 
-    val = s->mmio[addr] | (s->mmio[addr+1] << 8);
+    /* Unaligned access not possible, since .valid.unaligned is not
+     * set in mem region ops */
+    if (addr < OPREGBASE) {
+        /* Registers present as little-endian, regardless of host endianness */
+        val = ehci_mem_readb(ptr, addr) | (ehci_mem_readb(ptr, addr + 1) << 8);
+    } else {
+        val = ehci_mem_readl(ptr, addr & ~3);
+        val = (val >> (8*(addr & 3))) & 0xffff;
+    }
 
     return val;
 }
@@ -1059,8 +1075,16 @@  static uint32_t ehci_mem_readl(void *ptr, target_phys_addr_t addr)
     EHCIState *s = ptr;
     uint32_t val;
 
-    val = s->mmio[addr] | (s->mmio[addr+1] << 8) |
-          (s->mmio[addr+2] << 16) | (s->mmio[addr+3] << 24);
+    /* Unaligned access not possible, since .valid.unaligned is not
+     * set in mem region ops */
+    if (addr < OPREGBASE) {
+        /* Registers present as little-endian, regardless of host endianness */
+        val = ehci_mem_readb(ptr, addr) | (ehci_mem_readb(ptr, addr+1) << 8)
+            | (ehci_mem_readb(ptr, addr+2) << 16)
+            | (ehci_mem_readb(ptr, addr+3) << 24);
+    } else {
+        val = s->opreg[(addr - OPREGBASE) / sizeof(uint32_t)];
+    }
 
     trace_usb_ehci_mmio_readl(addr, addr2str(addr), val);
     return val;
@@ -1147,32 +1171,30 @@  static void handle_port_status_write(EHCIState *s, int port, uint32_t val)
 static void ehci_mem_writel(void *ptr, target_phys_addr_t addr, uint32_t val)
 {
     EHCIState *s = ptr;
-    uint32_t *mmio = (uint32_t *)(&s->mmio[addr]);
-    uint32_t old = *mmio;
+    uint32_t *opreg = &s->opreg[(addr - OPREGBASE) / sizeof(uint32_t)];
+    uint32_t old;
     int i;
 
     trace_usb_ehci_mmio_writel(addr, addr2str(addr), val);
 
-    /* Only aligned reads are allowed on OHCI */
-    if (addr & 3) {
-        fprintf(stderr, "usb-ehci: Mis-aligned write to addr 0x"
+    /* Unaligned access not possible, since .valid.unaligned is not
+     * set in mem region ops */
+    assert(!(addr & 3));
+
+    if (addr < OPREGBASE) {
+        fprintf(stderr, "usb-ehci: write attempt to read-only register"
                 TARGET_FMT_plx "\n", addr);
         return;
     }
 
+    old = *opreg;
+
     if (addr >= PORTSC && addr < PORTSC + 4 * NB_PORTS) {
         handle_port_status_write(s, (addr-PORTSC)/4, val);
-        trace_usb_ehci_mmio_change(addr, addr2str(addr), *mmio, old);
+        trace_usb_ehci_mmio_change(addr, addr2str(addr), *opreg, old);
         return;
     }
 
-    if (addr < OPREGBASE) {
-        fprintf(stderr, "usb-ehci: write attempt to read-only register"
-                TARGET_FMT_plx "\n", addr);
-        return;
-    }
-
-
     /* Do any register specific pre-write processing here.  */
     switch(addr) {
     case USBCMD:
@@ -1240,8 +1262,8 @@  static void ehci_mem_writel(void *ptr, target_phys_addr_t addr, uint32_t val)
         break;
     }
 
-    *mmio = val;
-    trace_usb_ehci_mmio_change(addr, addr2str(addr), *mmio, old);
+    *opreg = val;
+    trace_usb_ehci_mmio_change(addr, addr2str(addr), *opreg, old);
 }
 
 
@@ -2581,18 +2603,18 @@  static int usb_ehci_initfn(PCIDevice *dev)
     pci_conf[0x6f] = 0xc0;  // USBLEFCTLSTS
 
     // 2.2 host controller interface version
-    s->mmio[0x00] = (uint8_t) OPREGBASE;
-    s->mmio[0x01] = 0x00;
-    s->mmio[0x02] = 0x00;
-    s->mmio[0x03] = 0x01;        // HC version
-    s->mmio[0x04] = NB_PORTS;    // Number of downstream ports
-    s->mmio[0x05] = 0x00;        // No companion ports at present
-    s->mmio[0x06] = 0x00;
-    s->mmio[0x07] = 0x00;
-    s->mmio[0x08] = 0x80;        // We can cache whole frame, not 64-bit capable
-    s->mmio[0x09] = 0x68;        // EECP
-    s->mmio[0x0a] = 0x00;
-    s->mmio[0x0b] = 0x00;
+    s->cap[0x00] = (uint8_t) OPREGBASE;
+    s->cap[0x01] = 0x00;
+    s->cap[0x02] = 0x00;
+    s->cap[0x03] = 0x01;        // HC version
+    s->cap[0x04] = NB_PORTS;    // Number of downstream ports
+    s->cap[0x05] = 0x00;        // No companion ports at present
+    s->cap[0x06] = 0x00;
+    s->cap[0x07] = 0x00;
+    s->cap[0x08] = 0x80;        // We can cache whole frame, not 64-bit capable
+    s->cap[0x09] = 0x68;        // EECP
+    s->cap[0x0a] = 0x00;
+    s->cap[0x0b] = 0x00;
 
     s->irq = s->dev.irq[3];