#include "pci.h"
#include "qemu-timer.h"
#include "usb-uhci.h"
+#include "iov.h"
+#include "dma.h"
//#define DEBUG
//#define DEBUG_DUMP_DATA
#endif
#ifdef DEBUG_DUMP_DATA
-static void dump_data(const uint8_t *data, int len)
+static void dump_data(USBPacket *p, int ret)
{
- int i;
-
- printf("uhci: data: ");
- for(i = 0; i < len; i++)
- printf(" %02x", data[i]);
- printf("\n");
+ iov_hexdump(p->iov.iov, p->iov.niov, stderr, "uhci", ret);
}
#else
-static void dump_data(const uint8_t *data, int len) {}
+static void dump_data(USBPacket *p, int ret) {}
#endif
+typedef struct UHCIState UHCIState;
+
/*
* Pending async transaction.
* 'packet' must be the first field because completion
*/
typedef struct UHCIAsync {
USBPacket packet;
- struct UHCIAsync *next;
+ QEMUSGList sgl;
+ UHCIState *uhci;
+ QTAILQ_ENTRY(UHCIAsync) next;
uint32_t td;
uint32_t token;
int8_t valid;
uint8_t isoc;
uint8_t done;
- uint8_t buffer[2048];
} UHCIAsync;
typedef struct UHCIPort {
uint16_t ctrl;
} UHCIPort;
-typedef struct UHCIState {
+struct UHCIState {
PCIDevice dev;
- USBBus bus;
+ MemoryRegion io_bar;
+ USBBus bus; /* Note unused when we're a companion controller */
uint16_t cmd; /* cmd register */
uint16_t status;
uint16_t intr; /* interrupt enable register */
uint32_t pending_int_mask;
/* Active packets */
- UHCIAsync *async_pending;
- UHCIAsync *async_pool;
+ QTAILQ_HEAD(,UHCIAsync) async_pending;
uint8_t num_ports_vmstate;
-} UHCIState;
+
+ /* Properties */
+ char *masterbus;
+ uint32_t firstport;
+};
typedef struct UHCI_TD {
uint32_t link;
static UHCIAsync *uhci_async_alloc(UHCIState *s)
{
- UHCIAsync *async = qemu_malloc(sizeof(UHCIAsync));
+ UHCIAsync *async = g_malloc(sizeof(UHCIAsync));
memset(&async->packet, 0, sizeof(async->packet));
+ async->uhci = s;
async->valid = 0;
async->td = 0;
async->token = 0;
async->done = 0;
async->isoc = 0;
- async->next = NULL;
+ usb_packet_init(&async->packet);
+ qemu_sglist_init(&async->sgl, 1);
return async;
}
static void uhci_async_free(UHCIState *s, UHCIAsync *async)
{
- qemu_free(async);
+ usb_packet_cleanup(&async->packet);
+ qemu_sglist_destroy(&async->sgl);
+ g_free(async);
}
static void uhci_async_link(UHCIState *s, UHCIAsync *async)
{
- async->next = s->async_pending;
- s->async_pending = async;
+ QTAILQ_INSERT_HEAD(&s->async_pending, async, next);
}
static void uhci_async_unlink(UHCIState *s, UHCIAsync *async)
{
- UHCIAsync *curr = s->async_pending;
- UHCIAsync **prev = &s->async_pending;
-
- while (curr) {
- if (curr == async) {
- *prev = curr->next;
- return;
- }
-
- prev = &curr->next;
- curr = curr->next;
- }
+ QTAILQ_REMOVE(&s->async_pending, async, next);
}
static void uhci_async_cancel(UHCIState *s, UHCIAsync *async)
*/
static UHCIAsync *uhci_async_validate_begin(UHCIState *s)
{
- UHCIAsync *async = s->async_pending;
+ UHCIAsync *async;
- while (async) {
+ QTAILQ_FOREACH(async, &s->async_pending, next) {
async->valid--;
- async = async->next;
}
return NULL;
}
*/
static void uhci_async_validate_end(UHCIState *s)
{
- UHCIAsync *curr = s->async_pending;
- UHCIAsync **prev = &s->async_pending;
- UHCIAsync *next;
+ UHCIAsync *curr, *n;
- while (curr) {
+ QTAILQ_FOREACH_SAFE(curr, &s->async_pending, next, n) {
if (curr->valid > 0) {
- prev = &curr->next;
- curr = curr->next;
continue;
}
+ uhci_async_unlink(s, curr);
+ uhci_async_cancel(s, curr);
+ }
+}
- next = curr->next;
-
- /* Unlink */
- *prev = next;
+static void uhci_async_cancel_device(UHCIState *s, USBDevice *dev)
+{
+ UHCIAsync *curr, *n;
+ QTAILQ_FOREACH_SAFE(curr, &s->async_pending, next, n) {
+ if (curr->packet.owner != dev) {
+ continue;
+ }
+ uhci_async_unlink(s, curr);
uhci_async_cancel(s, curr);
-
- curr = next;
}
}
static void uhci_async_cancel_all(UHCIState *s)
{
- UHCIAsync *curr = s->async_pending;
- UHCIAsync *next;
-
- while (curr) {
- next = curr->next;
+ UHCIAsync *curr, *n;
+ QTAILQ_FOREACH_SAFE(curr, &s->async_pending, next, n) {
+ uhci_async_unlink(s, curr);
uhci_async_cancel(s, curr);
-
- curr = next;
}
-
- s->async_pending = NULL;
}
static UHCIAsync *uhci_async_find_td(UHCIState *s, uint32_t addr, uint32_t token)
{
- UHCIAsync *async = s->async_pending;
+ UHCIAsync *async;
UHCIAsync *match = NULL;
int count = 0;
* If we ever do we'd want to optimize this algorithm.
*/
- while (async) {
+ QTAILQ_FOREACH(async, &s->async_pending, next) {
if (async->token == token) {
/* Good match */
match = async;
break;
}
}
-
- async = async->next;
count++;
}
for(i = 0; i < NB_PORTS; i++) {
port = &s->ports[i];
port->ctrl = 0x0080;
- if (port->port.dev) {
- usb_attach(&port->port, port->port.dev);
+ if (port->port.dev && port->port.dev->attached) {
+ usb_attach(&port->port);
}
}
case 0x00:
if ((val & UHCI_CMD_RS) && !(s->cmd & UHCI_CMD_RS)) {
/* start frame processing */
- qemu_mod_timer(s->frame_timer, qemu_get_clock(vm_clock));
+ s->expire_time = qemu_get_clock_ns(vm_clock) +
+ (get_ticks_per_sec() / FRAME_TIMER_FREQ);
+ qemu_mod_timer(s->frame_timer, qemu_get_clock_ns(vm_clock));
s->status &= ~UHCI_STS_HCHALTED;
} else if (!(val & UHCI_CMD_RS)) {
s->status |= UHCI_STS_HCHALTED;
for(i = 0; i < NB_PORTS; i++) {
port = &s->ports[i];
dev = port->port.dev;
- if (dev) {
+ if (dev && dev->attached) {
usb_send_msg(dev, USB_MSG_RESET);
}
}
return;
port = &s->ports[n];
dev = port->port.dev;
- if (dev) {
+ if (dev && dev->attached) {
/* port reset */
if ( (val & UHCI_PORT_RESET) &&
!(port->ctrl & UHCI_PORT_RESET) ) {
UHCIState *s = port1->opaque;
UHCIPort *port = &s->ports[port1->index];
+ uhci_async_cancel_device(s, port1->dev);
+
/* set connect status */
if (port->ctrl & UHCI_PORT_CCS) {
port->ctrl &= ~UHCI_PORT_CCS;
uhci_resume(s);
}
-static void uhci_wakeup(USBDevice *dev)
+static void uhci_child_detach(USBPort *port1, USBDevice *child)
{
- USBBus *bus = usb_bus_from_device(dev);
- UHCIState *s = container_of(bus, UHCIState, bus);
- UHCIPort *port = s->ports + dev->port->index;
+ UHCIState *s = port1->opaque;
+
+ uhci_async_cancel_device(s, child);
+}
+
+static void uhci_wakeup(USBPort *port1)
+{
+ UHCIState *s = port1->opaque;
+ UHCIPort *port = &s->ports[port1->index];
if (port->ctrl & UHCI_PORT_SUSPEND && !(port->ctrl & UHCI_PORT_RD)) {
port->ctrl |= UHCI_PORT_RD;
{
int i, ret;
- DPRINTF("uhci: packet enter. pid %s addr 0x%02x ep %d len %d\n",
- pid2str(p->pid), p->devaddr, p->devep, p->len);
+ DPRINTF("uhci: packet enter. pid %s addr 0x%02x ep %d len %zd\n",
+ pid2str(p->pid), p->devaddr, p->devep, p->iov.size);
if (p->pid == USB_TOKEN_OUT || p->pid == USB_TOKEN_SETUP)
- dump_data(p->data, p->len);
+ dump_data(p, 0);
ret = USB_RET_NODEV;
for (i = 0; i < NB_PORTS && ret == USB_RET_NODEV; i++) {
UHCIPort *port = &s->ports[i];
USBDevice *dev = port->port.dev;
- if (dev && (port->ctrl & UHCI_PORT_EN))
- ret = dev->info->handle_packet(dev, p);
+ if (dev && dev->attached && (port->ctrl & UHCI_PORT_EN)) {
+ ret = usb_handle_packet(dev, p);
+ }
}
- DPRINTF("uhci: packet exit. ret %d len %d\n", ret, p->len);
+ DPRINTF("uhci: packet exit. ret %d len %zd\n", ret, p->iov.size);
if (p->pid == USB_TOKEN_IN && ret > 0)
- dump_data(p->data, ret);
+ dump_data(p, ret);
return ret;
}
-static void uhci_async_complete(USBPacket * packet, void *opaque);
+static void uhci_async_complete(USBPort *port, USBPacket *packet);
static void uhci_process_frame(UHCIState *s);
/* return -1 if fatal error (frame must be stopped)
max_len = ((td->token >> 21) + 1) & 0x7ff;
pid = td->token & 0xff;
- ret = async->packet.len;
+ ret = async->packet.result;
if (td->ctrl & TD_CTRL_IOS)
td->ctrl &= ~TD_CTRL_ACTIVE;
if (ret < 0)
goto out;
- len = async->packet.len;
+ len = async->packet.result;
td->ctrl = (td->ctrl & ~0x7ff) | ((len - 1) & 0x7ff);
/* The NAK bit may have been set by a previous frame, so clear it
goto out;
}
- if (len > 0) {
- /* write the data back */
- cpu_physical_memory_write(td->buffer, async->buffer, len);
- }
-
if ((td->ctrl & TD_CTRL_SPD) && len < max_len) {
*int_mask |= 0x02;
/* short packet: do not update QH */
case USB_RET_STALL:
td->ctrl |= TD_CTRL_STALL;
td->ctrl &= ~TD_CTRL_ACTIVE;
+ s->status |= UHCI_STS_USBERR;
+ if (td->ctrl & TD_CTRL_IOC) {
+ *int_mask |= 0x01;
+ }
+ uhci_update_irq(s);
return 1;
case USB_RET_BABBLE:
td->ctrl |= TD_CTRL_BABBLE | TD_CTRL_STALL;
td->ctrl &= ~TD_CTRL_ACTIVE;
+ s->status |= UHCI_STS_USBERR;
+ if (td->ctrl & TD_CTRL_IOC) {
+ *int_mask |= 0x01;
+ }
+ uhci_update_irq(s);
/* frame interrupted */
return -1;
max_len = ((td->token >> 21) + 1) & 0x7ff;
pid = td->token & 0xff;
- async->packet.pid = pid;
- async->packet.devaddr = (td->token >> 8) & 0x7f;
- async->packet.devep = (td->token >> 15) & 0xf;
- async->packet.data = async->buffer;
- async->packet.len = max_len;
- async->packet.complete_cb = uhci_async_complete;
- async->packet.complete_opaque = s;
+ usb_packet_setup(&async->packet, pid, (td->token >> 8) & 0x7f,
+ (td->token >> 15) & 0xf);
+ qemu_sglist_add(&async->sgl, td->buffer, max_len);
+ usb_packet_map(&async->packet, &async->sgl);
switch(pid) {
case USB_TOKEN_OUT:
case USB_TOKEN_SETUP:
- cpu_physical_memory_read(td->buffer, async->buffer, max_len);
len = uhci_broadcast_packet(s, &async->packet);
if (len >= 0)
len = max_len;
return 2;
}
- async->packet.len = len;
+ async->packet.result = len;
done:
len = uhci_complete_td(s, td, async, int_mask);
+ usb_packet_unmap(&async->packet);
uhci_async_free(s, async);
return len;
}
-static void uhci_async_complete(USBPacket *packet, void *opaque)
+static void uhci_async_complete(USBPort *port, USBPacket *packet)
{
- UHCIState *s = opaque;
- UHCIAsync *async = (UHCIAsync *) packet;
+ UHCIAsync *async = container_of(packet, UHCIAsync, packet);
+ UHCIState *s = async->uhci;
DPRINTF("uhci: async complete. td 0x%x token 0x%x\n", async->td, async->token);
qemu_mod_timer(s->frame_timer, s->expire_time);
}
-static void uhci_map(PCIDevice *pci_dev, int region_num,
- pcibus_t addr, pcibus_t size, int type)
-{
- UHCIState *s = (UHCIState *)pci_dev;
-
- register_ioport_write(addr, 32, 2, uhci_ioport_writew, s);
- register_ioport_read(addr, 32, 2, uhci_ioport_readw, s);
- register_ioport_write(addr, 32, 4, uhci_ioport_writel, s);
- register_ioport_read(addr, 32, 4, uhci_ioport_readl, s);
- register_ioport_write(addr, 32, 1, uhci_ioport_writeb, s);
- register_ioport_read(addr, 32, 1, uhci_ioport_readb, s);
-}
+static const MemoryRegionPortio uhci_portio[] = {
+ { 0, 32, 2, .write = uhci_ioport_writew, },
+ { 0, 32, 2, .read = uhci_ioport_readw, },
+ { 0, 32, 4, .write = uhci_ioport_writel, },
+ { 0, 32, 4, .read = uhci_ioport_readl, },
+ { 0, 32, 1, .write = uhci_ioport_writeb, },
+ { 0, 32, 1, .read = uhci_ioport_readb, },
+ PORTIO_END_OF_LIST()
+};
+
+static const MemoryRegionOps uhci_ioport_ops = {
+ .old_portio = uhci_portio,
+};
static USBPortOps uhci_port_ops = {
.attach = uhci_attach,
.detach = uhci_detach,
+ .child_detach = uhci_child_detach,
.wakeup = uhci_wakeup,
+ .complete = uhci_async_complete,
};
-static int usb_uhci_common_initfn(UHCIState *s)
+static USBBusOps uhci_bus_ops = {
+};
+
+static int usb_uhci_common_initfn(PCIDevice *dev)
{
+ UHCIState *s = DO_UPCAST(UHCIState, dev, dev);
uint8_t *pci_conf = s->dev.config;
int i;
- pci_conf[PCI_REVISION_ID] = 0x01; // revision number
pci_conf[PCI_CLASS_PROG] = 0x00;
- pci_config_set_class(pci_conf, PCI_CLASS_SERIAL_USB);
/* TODO: reset value should be 0. */
pci_conf[PCI_INTERRUPT_PIN] = 4; // interrupt pin 3
- pci_conf[0x60] = 0x10; // release number
+ pci_conf[USB_SBRN] = USB_RELEASE_1; // release number
- usb_bus_new(&s->bus, &s->dev.qdev);
- for(i = 0; i < NB_PORTS; i++) {
- usb_register_port(&s->bus, &s->ports[i].port, s, i, &uhci_port_ops,
- USB_SPEED_MASK_LOW | USB_SPEED_MASK_FULL);
- usb_port_location(&s->ports[i].port, NULL, i+1);
+ if (s->masterbus) {
+ USBPort *ports[NB_PORTS];
+ for(i = 0; i < NB_PORTS; i++) {
+ ports[i] = &s->ports[i].port;
+ }
+ if (usb_register_companion(s->masterbus, ports, NB_PORTS,
+ s->firstport, s, &uhci_port_ops,
+ USB_SPEED_MASK_LOW | USB_SPEED_MASK_FULL) != 0) {
+ return -1;
+ }
+ } else {
+ usb_bus_new(&s->bus, &uhci_bus_ops, &s->dev.qdev);
+ for (i = 0; i < NB_PORTS; i++) {
+ usb_register_port(&s->bus, &s->ports[i].port, s, i, &uhci_port_ops,
+ USB_SPEED_MASK_LOW | USB_SPEED_MASK_FULL);
+ }
}
- s->frame_timer = qemu_new_timer(vm_clock, uhci_frame_timer, s);
- s->expire_time = qemu_get_clock(vm_clock) +
- (get_ticks_per_sec() / FRAME_TIMER_FREQ);
+ s->frame_timer = qemu_new_timer_ns(vm_clock, uhci_frame_timer, s);
s->num_ports_vmstate = NB_PORTS;
+ QTAILQ_INIT(&s->async_pending);
qemu_register_reset(uhci_reset, s);
+ memory_region_init_io(&s->io_bar, &uhci_ioport_ops, s, "uhci", 0x20);
/* Use region 4 for consistency with real hardware. BSD guests seem
to rely on this. */
- pci_register_bar(&s->dev, 4, 0x20,
- PCI_BASE_ADDRESS_SPACE_IO, uhci_map);
+ pci_register_bar(&s->dev, 4, PCI_BASE_ADDRESS_SPACE_IO, &s->io_bar);
return 0;
}
-static int usb_uhci_piix3_initfn(PCIDevice *dev)
-{
- UHCIState *s = DO_UPCAST(UHCIState, dev, dev);
- uint8_t *pci_conf = s->dev.config;
-
- pci_config_set_vendor_id(pci_conf, PCI_VENDOR_ID_INTEL);
- pci_config_set_device_id(pci_conf, PCI_DEVICE_ID_INTEL_82371SB_2);
- return usb_uhci_common_initfn(s);
-}
-
-static int usb_uhci_piix4_initfn(PCIDevice *dev)
-{
- UHCIState *s = DO_UPCAST(UHCIState, dev, dev);
- uint8_t *pci_conf = s->dev.config;
-
- pci_config_set_vendor_id(pci_conf, PCI_VENDOR_ID_INTEL);
- pci_config_set_device_id(pci_conf, PCI_DEVICE_ID_INTEL_82371AB_2);
- return usb_uhci_common_initfn(s);
-}
-
static int usb_uhci_vt82c686b_initfn(PCIDevice *dev)
{
UHCIState *s = DO_UPCAST(UHCIState, dev, dev);
uint8_t *pci_conf = s->dev.config;
- pci_config_set_vendor_id(pci_conf, PCI_VENDOR_ID_VIA);
- pci_config_set_device_id(pci_conf, PCI_DEVICE_ID_VIA_UHCI);
-
/* USB misc control 1/2 */
pci_set_long(pci_conf + 0x40,0x00001000);
/* PM capability */
/* USB legacy support */
pci_set_long(pci_conf + 0xc0,0x00002000);
- return usb_uhci_common_initfn(s);
+ return usb_uhci_common_initfn(dev);
+}
+
+static int usb_uhci_exit(PCIDevice *dev)
+{
+ UHCIState *s = DO_UPCAST(UHCIState, dev, dev);
+
+ memory_region_destroy(&s->io_bar);
+ return 0;
}
+static Property uhci_properties[] = {
+ DEFINE_PROP_STRING("masterbus", UHCIState, masterbus),
+ DEFINE_PROP_UINT32("firstport", UHCIState, firstport, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
static PCIDeviceInfo uhci_info[] = {
{
.qdev.name = "piix3-usb-uhci",
.qdev.size = sizeof(UHCIState),
.qdev.vmsd = &vmstate_uhci,
- .init = usb_uhci_piix3_initfn,
+ .init = usb_uhci_common_initfn,
+ .exit = usb_uhci_exit,
+ .vendor_id = PCI_VENDOR_ID_INTEL,
+ .device_id = PCI_DEVICE_ID_INTEL_82371SB_2,
+ .revision = 0x01,
+ .class_id = PCI_CLASS_SERIAL_USB,
+ .qdev.props = uhci_properties,
},{
.qdev.name = "piix4-usb-uhci",
.qdev.size = sizeof(UHCIState),
.qdev.vmsd = &vmstate_uhci,
- .init = usb_uhci_piix4_initfn,
+ .init = usb_uhci_common_initfn,
+ .exit = usb_uhci_exit,
+ .vendor_id = PCI_VENDOR_ID_INTEL,
+ .device_id = PCI_DEVICE_ID_INTEL_82371AB_2,
+ .revision = 0x01,
+ .class_id = PCI_CLASS_SERIAL_USB,
+ .qdev.props = uhci_properties,
},{
.qdev.name = "vt82c686b-usb-uhci",
.qdev.size = sizeof(UHCIState),
.qdev.vmsd = &vmstate_uhci,
.init = usb_uhci_vt82c686b_initfn,
+ .exit = usb_uhci_exit,
+ .vendor_id = PCI_VENDOR_ID_VIA,
+ .device_id = PCI_DEVICE_ID_VIA_UHCI,
+ .revision = 0x01,
+ .class_id = PCI_CLASS_SERIAL_USB,
+ .qdev.props = uhci_properties,
+ },{
+ .qdev.name = "ich9-usb-uhci1",
+ .qdev.size = sizeof(UHCIState),
+ .qdev.vmsd = &vmstate_uhci,
+ .init = usb_uhci_common_initfn,
+ .vendor_id = PCI_VENDOR_ID_INTEL,
+ .device_id = PCI_DEVICE_ID_INTEL_82801I_UHCI1,
+ .revision = 0x03,
+ .class_id = PCI_CLASS_SERIAL_USB,
+ .qdev.props = uhci_properties,
+ },{
+ .qdev.name = "ich9-usb-uhci2",
+ .qdev.size = sizeof(UHCIState),
+ .qdev.vmsd = &vmstate_uhci,
+ .init = usb_uhci_common_initfn,
+ .vendor_id = PCI_VENDOR_ID_INTEL,
+ .device_id = PCI_DEVICE_ID_INTEL_82801I_UHCI2,
+ .revision = 0x03,
+ .class_id = PCI_CLASS_SERIAL_USB,
+ .qdev.props = uhci_properties,
+ },{
+ .qdev.name = "ich9-usb-uhci3",
+ .qdev.size = sizeof(UHCIState),
+ .qdev.vmsd = &vmstate_uhci,
+ .init = usb_uhci_common_initfn,
+ .vendor_id = PCI_VENDOR_ID_INTEL,
+ .device_id = PCI_DEVICE_ID_INTEL_82801I_UHCI3,
+ .revision = 0x03,
+ .class_id = PCI_CLASS_SERIAL_USB,
+ .qdev.props = uhci_properties,
},{
/* end of list */
}