Add PowerPC power-management state check callback.
[qemu] / hw / usb-uhci.c
index 29809f9..95dd4a0 100644 (file)
@@ -1,8 +1,8 @@
 /*
  * USB UHCI controller emulation
- * 
+ *
  * Copyright (c) 2005 Fabrice Bellard
- * 
+ *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
  * in the Software without restriction, including without limitation the rights
 
 //#define DEBUG
 //#define DEBUG_PACKET
+//#define DEBUG_ISOCH
 
+#define UHCI_CMD_FGR      (1 << 4)
+#define UHCI_CMD_EGSM     (1 << 3)
 #define UHCI_CMD_GRESET   (1 << 2)
 #define UHCI_CMD_HCRESET  (1 << 1)
 #define UHCI_CMD_RS       (1 << 0)
@@ -82,12 +85,13 @@ typedef struct UHCIState {
     /* For simplicity of implementation we only allow a single pending USB
        request.  This means all usb traffic on this controller is effectively
        suspended until that transfer completes.  When the transfer completes
-       the next transfer from that queue will be processed.  However 
+       the next transfer from that queue will be processed.  However
        other queues will not be processed until the next frame.  The solution
        is to allow multiple pending requests.  */
     uint32_t async_qh;
+    uint32_t async_frame_addr;
     USBPacket usb_packet;
-    uint8_t usb_buf[1280];
+    uint8_t usb_buf[2048];
 } UHCIState;
 
 typedef struct UHCI_TD {
@@ -117,7 +121,7 @@ static void uhci_update_irq(UHCIState *s)
     } else {
         level = 0;
     }
-    pci_set_irq(&s->dev, 3, level);
+    qemu_set_irq(s->dev.irq[3], level);
 }
 
 static void uhci_reset(UHCIState *s)
@@ -144,10 +148,62 @@ static void uhci_reset(UHCIState *s)
     }
 }
 
+static void uhci_save(QEMUFile *f, void *opaque)
+{
+    UHCIState *s = opaque;
+    uint8_t num_ports = NB_PORTS;
+    int i;
+
+    pci_device_save(&s->dev, f);
+
+    qemu_put_8s(f, &num_ports);
+    for (i = 0; i < num_ports; ++i)
+        qemu_put_be16s(f, &s->ports[i].ctrl);
+    qemu_put_be16s(f, &s->cmd);
+    qemu_put_be16s(f, &s->status);
+    qemu_put_be16s(f, &s->intr);
+    qemu_put_be16s(f, &s->frnum);
+    qemu_put_be32s(f, &s->fl_base_addr);
+    qemu_put_8s(f, &s->sof_timing);
+    qemu_put_8s(f, &s->status2);
+    qemu_put_timer(f, s->frame_timer);
+}
+
+static int uhci_load(QEMUFile *f, void *opaque, int version_id)
+{
+    UHCIState *s = opaque;
+    uint8_t num_ports;
+    int i, ret;
+
+    if (version_id > 1)
+        return -EINVAL;
+
+    ret = pci_device_load(&s->dev, f);
+    if (ret < 0)
+        return ret;
+
+    qemu_get_8s(f, &num_ports);
+    if (num_ports != NB_PORTS)
+        return -EINVAL;
+
+    for (i = 0; i < num_ports; ++i)
+        qemu_get_be16s(f, &s->ports[i].ctrl);
+    qemu_get_be16s(f, &s->cmd);
+    qemu_get_be16s(f, &s->status);
+    qemu_get_be16s(f, &s->intr);
+    qemu_get_be16s(f, &s->frnum);
+    qemu_get_be32s(f, &s->fl_base_addr);
+    qemu_get_8s(f, &s->sof_timing);
+    qemu_get_8s(f, &s->status2);
+    qemu_get_timer(f, s->frame_timer);
+
+    return 0;
+}
+
 static void uhci_ioport_writeb(void *opaque, uint32_t addr, uint32_t val)
 {
     UHCIState *s = opaque;
-    
+
     addr &= 0x1f;
     switch(addr) {
     case 0x0c:
@@ -176,7 +232,7 @@ static uint32_t uhci_ioport_readb(void *opaque, uint32_t addr)
 static void uhci_ioport_writew(void *opaque, uint32_t addr, uint32_t val)
 {
     UHCIState *s = opaque;
-    
+
     addr &= 0x1f;
 #ifdef DEBUG
     printf("uhci writew port=0x%04x val=0x%04x\n", addr, val);
@@ -241,7 +297,7 @@ static void uhci_ioport_writew(void *opaque, uint32_t addr, uint32_t val)
             dev = port->port.dev;
             if (dev) {
                 /* port reset */
-                if ( (val & UHCI_PORT_RESET) && 
+                if ( (val & UHCI_PORT_RESET) &&
                      !(port->ctrl & UHCI_PORT_RESET) ) {
                     usb_send_msg(dev, USB_MSG_RESET);
                 }
@@ -278,7 +334,7 @@ static uint32_t uhci_ioport_readw(void *opaque, uint32_t addr)
             UHCIPort *port;
             int n;
             n = (addr >> 1) & 7;
-            if (n >= NB_PORTS) 
+            if (n >= NB_PORTS)
                 goto read_default;
             port = &s->ports[n];
             val = port->ctrl;
@@ -327,6 +383,21 @@ static uint32_t uhci_ioport_readl(void *opaque, uint32_t addr)
     return val;
 }
 
+/* signal resume if controller suspended */
+static void uhci_resume (void *opaque)
+{
+    UHCIState *s = (UHCIState *)opaque;
+
+    if (!s)
+        return;
+
+    if (s->cmd & UHCI_CMD_EGSM) {
+        s->cmd |= UHCI_CMD_FGR;
+        s->status |= UHCI_STS_RD;
+        uhci_update_irq(s);
+    }
+}
+
 static void uhci_attach(USBPort *port1, USBDevice *dev)
 {
     UHCIState *s = port1->opaque;
@@ -344,6 +415,9 @@ static void uhci_attach(USBPort *port1, USBDevice *dev)
             port->ctrl |= UHCI_PORT_LSDA;
         else
             port->ctrl &= ~UHCI_PORT_LSDA;
+
+        uhci_resume(s);
+
         port->port.dev = dev;
         /* send the attach message */
         usb_send_msg(dev, USB_MSG_ATTACH);
@@ -358,6 +432,9 @@ static void uhci_attach(USBPort *port1, USBDevice *dev)
             port->ctrl &= ~UHCI_PORT_EN;
             port->ctrl |= UHCI_PORT_ENC;
         }
+
+        uhci_resume(s);
+
         dev = port->port.dev;
         if (dev) {
             /* send the detach message */
@@ -426,23 +503,25 @@ static void uhci_async_complete_packet(USBPacket * packet, void *opaque);
           0 if TD successful
           1 if TD unsuccessful or inactive
 */
-static int uhci_handle_td(UHCIState *s, UHCI_TD *td, int *int_mask)
+static int uhci_handle_td(UHCIState *s, UHCI_TD *td, int *int_mask,
+                          int completion)
 {
     uint8_t pid;
-    int len, max_len, err, ret;
+    int len = 0, max_len, err, ret = 0;
 
     /* ??? This is wrong for async completion.  */
     if (td->ctrl & TD_CTRL_IOC) {
         *int_mask |= 0x01;
     }
-    
+
     if (!(td->ctrl & TD_CTRL_ACTIVE))
         return 1;
 
     /* TD is active */
     max_len = ((td->token >> 21) + 1) & 0x7ff;
     pid = td->token & 0xff;
-    if (s->async_qh) {
+
+    if (completion && (s->async_qh || s->async_frame_addr)) {
         ret = s->usb_packet.len;
         if (ret >= 0) {
             len = ret;
@@ -458,7 +537,8 @@ static int uhci_handle_td(UHCIState *s, UHCI_TD *td, int *int_mask)
             len = 0;
         }
         s->async_qh = 0;
-    } else {
+        s->async_frame_addr = 0;
+    } else if (!completion) {
         s->usb_packet.pid = pid;
         s->usb_packet.devaddr = (td->token >> 8) & 0x7f;
         s->usb_packet.devep = (td->token >> 15) & 0xf;
@@ -496,6 +576,7 @@ static int uhci_handle_td(UHCIState *s, UHCI_TD *td, int *int_mask)
             return -1;
         }
     }
+
     if (ret == USB_RET_ASYNC) {
         return 2;
     }
@@ -503,8 +584,11 @@ static int uhci_handle_td(UHCIState *s, UHCI_TD *td, int *int_mask)
         td->ctrl &= ~TD_CTRL_ACTIVE;
     if (ret >= 0) {
         td->ctrl = (td->ctrl & ~0x7ff) | ((len - 1) & 0x7ff);
-        td->ctrl &= ~TD_CTRL_ACTIVE;
-        if (pid == USB_TOKEN_IN && 
+        /* The NAK bit may have been set by a previous frame, so clear it
+           here.  The docs are somewhat unclear, but win2k relies on this
+           behavior.  */
+        td->ctrl &= ~(TD_CTRL_ACTIVE | TD_CTRL_NAK);
+        if (pid == USB_TOKEN_IN &&
             (td->ctrl & TD_CTRL_SPD) &&
             len < max_len) {
             *int_mask |= 0x02;
@@ -529,7 +613,7 @@ static int uhci_handle_td(UHCIState *s, UHCI_TD *td, int *int_mask)
                     uhci_update_irq(s);
                 }
             }
-            td->ctrl = (td->ctrl & ~(3 << TD_CTRL_ERROR_SHIFT)) | 
+            td->ctrl = (td->ctrl & ~(3 << TD_CTRL_ERROR_SHIFT)) |
                 (err << TD_CTRL_ERROR_SHIFT);
             return 1;
         case USB_RET_NAK:
@@ -558,8 +642,42 @@ static void uhci_async_complete_packet(USBPacket * packet, void *opaque)
     uint32_t link;
     uint32_t old_td_ctrl;
     uint32_t val;
+    uint32_t frame_addr;
     int ret;
 
+    /* Handle async isochronous packet completion */
+    frame_addr = s->async_frame_addr;
+    if (frame_addr) {
+        cpu_physical_memory_read(frame_addr, (uint8_t *)&link, 4);
+        le32_to_cpus(&link);
+
+        cpu_physical_memory_read(link & ~0xf, (uint8_t *)&td, sizeof(td));
+        le32_to_cpus(&td.link);
+        le32_to_cpus(&td.ctrl);
+        le32_to_cpus(&td.token);
+        le32_to_cpus(&td.buffer);
+        old_td_ctrl = td.ctrl;
+        ret = uhci_handle_td(s, &td, &s->pending_int_mask, 1);
+
+        /* update the status bits of the TD */
+        if (old_td_ctrl != td.ctrl) {
+            val = cpu_to_le32(td.ctrl);
+            cpu_physical_memory_write((link & ~0xf) + 4,
+                                      (const uint8_t *)&val,
+                                      sizeof(val));
+        }
+        if (ret == 2) {
+            s->async_frame_addr = frame_addr;
+        } else if (ret == 0) {
+            /* update qh element link */
+            val = cpu_to_le32(td.link);
+            cpu_physical_memory_write(frame_addr,
+                                      (const uint8_t *)&val,
+                                      sizeof(val));
+        }
+        return;
+    }
+
     link = s->async_qh;
     if (!link) {
         /* This should never happen. It means a TD somehow got removed
@@ -571,19 +689,20 @@ static void uhci_async_complete_packet(USBPacket * packet, void *opaque)
     le32_to_cpus(&qh.el_link);
     /* Re-process the queue containing the async packet.  */
     while (1) {
-        cpu_physical_memory_read(qh.el_link & ~0xf, 
+        cpu_physical_memory_read(qh.el_link & ~0xf,
                                  (uint8_t *)&td, sizeof(td));
         le32_to_cpus(&td.link);
         le32_to_cpus(&td.ctrl);
         le32_to_cpus(&td.token);
         le32_to_cpus(&td.buffer);
         old_td_ctrl = td.ctrl;
-        ret = uhci_handle_td(s, &td, &s->pending_int_mask);
+        ret = uhci_handle_td(s, &td, &s->pending_int_mask, 1);
+
         /* update the status bits of the TD */
         if (old_td_ctrl != td.ctrl) {
             val = cpu_to_le32(td.ctrl);
-            cpu_physical_memory_write((qh.el_link & ~0xf) + 4, 
-                                      (const uint8_t *)&val, 
+            cpu_physical_memory_write((qh.el_link & ~0xf) + 4,
+                                      (const uint8_t *)&val,
                                       sizeof(val));
         }
         if (ret < 0)
@@ -595,8 +714,8 @@ static void uhci_async_complete_packet(USBPacket * packet, void *opaque)
             /* update qh element link */
             qh.el_link = td.link;
             val = cpu_to_le32(qh.el_link);
-            cpu_physical_memory_write((link & ~0xf) + 4, 
-                                      (const uint8_t *)&val, 
+            cpu_physical_memory_write((link & ~0xf) + 4,
+                                      (const uint8_t *)&val,
                                       sizeof(val));
             if (!(qh.el_link & 4))
                 break;
@@ -664,19 +783,20 @@ static void uhci_frame_timer(void *opaque)
                 /* TD */
                 if (--cnt == 0)
                     break;
-                cpu_physical_memory_read(qh.el_link & ~0xf, 
+                cpu_physical_memory_read(qh.el_link & ~0xf,
                                          (uint8_t *)&td, sizeof(td));
                 le32_to_cpus(&td.link);
                 le32_to_cpus(&td.ctrl);
                 le32_to_cpus(&td.token);
                 le32_to_cpus(&td.buffer);
                 old_td_ctrl = td.ctrl;
-                ret = uhci_handle_td(s, &td, &int_mask);
+                ret = uhci_handle_td(s, &td, &int_mask, 0);
+
                 /* update the status bits of the TD */
                 if (old_td_ctrl != td.ctrl) {
                     val = cpu_to_le32(td.ctrl);
-                    cpu_physical_memory_write((qh.el_link & ~0xf) + 4, 
-                                              (const uint8_t *)&val, 
+                    cpu_physical_memory_write((qh.el_link & ~0xf) + 4,
+                                              (const uint8_t *)&val,
                                               sizeof(val));
                 }
                 if (ret < 0)
@@ -687,8 +807,8 @@ static void uhci_frame_timer(void *opaque)
                     /* update qh element link */
                     qh.el_link = td.link;
                     val = cpu_to_le32(qh.el_link);
-                    cpu_physical_memory_write((link & ~0xf) + 4, 
-                                              (const uint8_t *)&val, 
+                    cpu_physical_memory_write((link & ~0xf) + 4,
+                                              (const uint8_t *)&val,
                                               sizeof(val));
                     if (qh.el_link & 4) {
                         /* depth first */
@@ -705,27 +825,23 @@ static void uhci_frame_timer(void *opaque)
             le32_to_cpus(&td.ctrl);
             le32_to_cpus(&td.token);
             le32_to_cpus(&td.buffer);
-            /* Ignore isochonous transfers while there is an async packet
-               pending.  This is wrong, but we don't implement isochronous
-               transfers anyway.  */
-            if (s->async_qh == 0) {
-                old_td_ctrl = td.ctrl;
-                ret = uhci_handle_td(s, &td, &int_mask);
-                /* update the status bits of the TD */
-                if (old_td_ctrl != td.ctrl) {
-                    val = cpu_to_le32(td.ctrl);
-                    cpu_physical_memory_write((link & ~0xf) + 4, 
-                                              (const uint8_t *)&val, 
-                                              sizeof(val));
-                }
-                if (ret < 0)
-                    break; /* interrupted frame */
-                if (ret == 2) {
-                    /* We can't handle async isochronous transfers.
-                       Cancel The packet.  */
-                    fprintf(stderr, "usb-uhci: Unimplemented async packet\n");
-                    usb_cancel_packet(&s->usb_packet);
-                }
+
+            /* Handle isochonous transfer.  */
+            /* FIXME: might be more than one isoc in frame */
+            old_td_ctrl = td.ctrl;
+            ret = uhci_handle_td(s, &td, &int_mask, 0);
+
+            /* update the status bits of the TD */
+            if (old_td_ctrl != td.ctrl) {
+                val = cpu_to_le32(td.ctrl);
+                cpu_physical_memory_write((link & ~0xf) + 4,
+                                          (const uint8_t *)&val,
+                                          sizeof(val));
+            }
+            if (ret < 0)
+                break; /* interrupted frame */
+            if (ret == 2) {
+                s->async_frame_addr = frame_addr;
             }
             link = td.link;
         }
@@ -741,13 +857,14 @@ static void uhci_frame_timer(void *opaque)
         usb_cancel_packet(&s->usb_packet);
         s->async_qh = 0;
     }
+
     /* prepare the timer for the next frame */
-    expire_time = qemu_get_clock(vm_clock) + 
+    expire_time = qemu_get_clock(vm_clock) +
         (ticks_per_sec / FRAME_TIMER_FREQ);
     qemu_mod_timer(s->frame_timer, expire_time);
 }
 
-static void uhci_map(PCIDevice *pci_dev, int region_num, 
+static void uhci_map(PCIDevice *pci_dev, int region_num,
                     uint32_t addr, uint32_t size, int type)
 {
     UHCIState *s = (UHCIState *)pci_dev;
@@ -760,7 +877,7 @@ static void uhci_map(PCIDevice *pci_dev, int region_num,
     register_ioport_read(addr, 32, 1, uhci_ioport_readb, s);
 }
 
-void usb_uhci_init(PCIBus *bus, int devfn)
+void usb_uhci_piix3_init(PCIBus *bus, int devfn)
 {
     UHCIState *s;
     uint8_t *pci_conf;
@@ -781,7 +898,42 @@ void usb_uhci_init(PCIBus *bus, int devfn)
     pci_conf[0x0e] = 0x00; // header_type
     pci_conf[0x3d] = 4; // interrupt pin 3
     pci_conf[0x60] = 0x10; // release number
-    
+
+    for(i = 0; i < NB_PORTS; i++) {
+        qemu_register_usb_port(&s->ports[i].port, s, i, uhci_attach);
+    }
+    s->frame_timer = qemu_new_timer(vm_clock, uhci_frame_timer, s);
+
+    uhci_reset(s);
+
+    /* Use region 4 for consistency with real hardware.  BSD guests seem
+       to rely on this.  */
+    pci_register_io_region(&s->dev, 4, 0x20,
+                           PCI_ADDRESS_SPACE_IO, uhci_map);
+}
+
+void usb_uhci_piix4_init(PCIBus *bus, int devfn)
+{
+    UHCIState *s;
+    uint8_t *pci_conf;
+    int i;
+
+    s = (UHCIState *)pci_register_device(bus,
+                                        "USB-UHCI", sizeof(UHCIState),
+                                        devfn, NULL, NULL);
+    pci_conf = s->dev.config;
+    pci_conf[0x00] = 0x86;
+    pci_conf[0x01] = 0x80;
+    pci_conf[0x02] = 0x12;
+    pci_conf[0x03] = 0x71;
+    pci_conf[0x08] = 0x01; // revision number
+    pci_conf[0x09] = 0x00;
+    pci_conf[0x0a] = 0x03;
+    pci_conf[0x0b] = 0x0c;
+    pci_conf[0x0e] = 0x00; // header_type
+    pci_conf[0x3d] = 4; // interrupt pin 3
+    pci_conf[0x60] = 0x10; // release number
+
     for(i = 0; i < NB_PORTS; i++) {
         qemu_register_usb_port(&s->ports[i].port, s, i, uhci_attach);
     }
@@ -791,6 +943,6 @@ void usb_uhci_init(PCIBus *bus, int devfn)
 
     /* Use region 4 for consistency with real hardware.  BSD guests seem
        to rely on this.  */
-    pci_register_io_region(&s->dev, 4, 0x20, 
+    pci_register_io_region(&s->dev, 4, 0x20,
                            PCI_ADDRESS_SPACE_IO, uhci_map);
 }