X-Git-Url: http://vcs.maemo.org/git/?a=blobdiff_plain;f=hw%2Fusb-uhci.c;h=b55fd849aef58082423b9434321488d95af4b4eb;hb=d66846a14e191c75f4aa373623dd9a7aaa843ade;hp=3936fe03f2c0f71a4c374c4446128f867c297cf5;hpb=afcc3cdfc4f6291ae6cb803339c794029bf6c99a;p=qemu diff --git a/hw/usb-uhci.c b/hw/usb-uhci.c index 3936fe0..b55fd84 100644 --- a/hw/usb-uhci.c +++ b/hw/usb-uhci.c @@ -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 @@ -21,10 +21,14 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -#include "vl.h" +#include "hw.h" +#include "usb.h" +#include "pci.h" +#include "qemu-timer.h" //#define DEBUG //#define DEBUG_PACKET +//#define DEBUG_ISOCH #define UHCI_CMD_FGR (1 << 4) #define UHCI_CMD_EGSM (1 << 3) @@ -84,10 +88,11 @@ 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[2048]; } UHCIState; @@ -146,10 +151,64 @@ static void uhci_reset(UHCIState *s) } } +#if 0 +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; +} +#endif + static void uhci_ioport_writeb(void *opaque, uint32_t addr, uint32_t val) { UHCIState *s = opaque; - + addr &= 0x1f; switch(addr) { case 0x0c: @@ -178,7 +237,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); @@ -243,7 +302,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); } @@ -280,7 +339,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; @@ -449,23 +508,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, uint32_t *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; @@ -481,7 +542,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; @@ -519,6 +581,7 @@ static int uhci_handle_td(UHCIState *s, UHCI_TD *td, int *int_mask) return -1; } } + if (ret == USB_RET_ASYNC) { return 2; } @@ -526,8 +589,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; @@ -552,7 +618,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: @@ -581,8 +647,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 @@ -594,19 +694,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) @@ -618,8 +719,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; @@ -632,8 +733,8 @@ static void uhci_frame_timer(void *opaque) { UHCIState *s = opaque; int64_t expire_time; - uint32_t frame_addr, link, old_td_ctrl, val; - int int_mask, cnt, ret; + uint32_t frame_addr, link, old_td_ctrl, val, int_mask; + int cnt, ret; UHCI_TD td; UHCI_QH qh; uint32_t old_async_qh; @@ -687,19 +788,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) @@ -710,8 +812,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 */ @@ -728,27 +830,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; } @@ -764,13 +862,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; @@ -804,7 +903,7 @@ void usb_uhci_piix3_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); } @@ -814,7 +913,7 @@ void usb_uhci_piix3_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); } @@ -852,4 +951,3 @@ void usb_uhci_piix4_init(PCIBus *bus, int devfn) pci_register_io_region(&s->dev, 4, 0x20, PCI_ADDRESS_SPACE_IO, uhci_map); } -