mirror of
https://github.com/Motorhead1991/qemu.git
synced 2025-08-07 01:33:56 -06:00
SCSI and USB async IO support.
git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@2107 c046a42c-6fe2-441c-8c8c-71466251a162
This commit is contained in:
parent
4ca9c76f36
commit
4d611c9a2f
14 changed files with 745 additions and 292 deletions
244
hw/usb-uhci.c
244
hw/usb-uhci.c
|
@ -76,6 +76,18 @@ typedef struct UHCIState {
|
|||
uint8_t status2; /* bit 0 and 1 are used to generate UHCI_STS_USBINT */
|
||||
QEMUTimer *frame_timer;
|
||||
UHCIPort ports[NB_PORTS];
|
||||
|
||||
/* Interrupts that should be raised at the end of the current frame. */
|
||||
uint32_t pending_int_mask;
|
||||
/* 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
|
||||
other queues will not be processed until the next frame. The solution
|
||||
is to allow multiple pending requests. */
|
||||
uint32_t async_qh;
|
||||
USBPacket usb_packet;
|
||||
uint8_t usb_buf[1280];
|
||||
} UHCIState;
|
||||
|
||||
typedef struct UHCI_TD {
|
||||
|
@ -188,8 +200,7 @@ static void uhci_ioport_writew(void *opaque, uint32_t addr, uint32_t val)
|
|||
port = &s->ports[i];
|
||||
dev = port->port.dev;
|
||||
if (dev) {
|
||||
dev->handle_packet(dev,
|
||||
USB_MSG_RESET, 0, 0, NULL, 0);
|
||||
usb_send_msg(dev, USB_MSG_RESET);
|
||||
}
|
||||
}
|
||||
uhci_reset(s);
|
||||
|
@ -232,8 +243,7 @@ static void uhci_ioport_writew(void *opaque, uint32_t addr, uint32_t val)
|
|||
/* port reset */
|
||||
if ( (val & UHCI_PORT_RESET) &&
|
||||
!(port->ctrl & UHCI_PORT_RESET) ) {
|
||||
dev->handle_packet(dev,
|
||||
USB_MSG_RESET, 0, 0, NULL, 0);
|
||||
usb_send_msg(dev, USB_MSG_RESET);
|
||||
}
|
||||
}
|
||||
port->ctrl = (port->ctrl & 0x01fb) | (val & ~0x01fb);
|
||||
|
@ -336,8 +346,7 @@ static void uhci_attach(USBPort *port1, USBDevice *dev)
|
|||
port->ctrl &= ~UHCI_PORT_LSDA;
|
||||
port->port.dev = dev;
|
||||
/* send the attach message */
|
||||
dev->handle_packet(dev,
|
||||
USB_MSG_ATTACH, 0, 0, NULL, 0);
|
||||
usb_send_msg(dev, USB_MSG_ATTACH);
|
||||
} else {
|
||||
/* set connect status */
|
||||
if (port->ctrl & UHCI_PORT_CCS) {
|
||||
|
@ -352,16 +361,13 @@ static void uhci_attach(USBPort *port1, USBDevice *dev)
|
|||
dev = port->port.dev;
|
||||
if (dev) {
|
||||
/* send the detach message */
|
||||
dev->handle_packet(dev,
|
||||
USB_MSG_DETACH, 0, 0, NULL, 0);
|
||||
usb_send_msg(dev, USB_MSG_DETACH);
|
||||
}
|
||||
port->port.dev = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static int uhci_broadcast_packet(UHCIState *s, uint8_t pid,
|
||||
uint8_t devaddr, uint8_t devep,
|
||||
uint8_t *data, int len)
|
||||
static int uhci_broadcast_packet(UHCIState *s, USBPacket *p)
|
||||
{
|
||||
UHCIPort *port;
|
||||
USBDevice *dev;
|
||||
|
@ -370,18 +376,18 @@ static int uhci_broadcast_packet(UHCIState *s, uint8_t pid,
|
|||
#ifdef DEBUG_PACKET
|
||||
{
|
||||
const char *pidstr;
|
||||
switch(pid) {
|
||||
switch(p->pid) {
|
||||
case USB_TOKEN_SETUP: pidstr = "SETUP"; break;
|
||||
case USB_TOKEN_IN: pidstr = "IN"; break;
|
||||
case USB_TOKEN_OUT: pidstr = "OUT"; break;
|
||||
default: pidstr = "?"; break;
|
||||
}
|
||||
printf("frame %d: pid=%s addr=0x%02x ep=%d len=%d\n",
|
||||
s->frnum, pidstr, devaddr, devep, len);
|
||||
if (pid != USB_TOKEN_IN) {
|
||||
s->frnum, pidstr, p->devaddr, p->devep, p->len);
|
||||
if (p->pid != USB_TOKEN_IN) {
|
||||
printf(" data_out=");
|
||||
for(i = 0; i < len; i++) {
|
||||
printf(" %02x", data[i]);
|
||||
for(i = 0; i < p->len; i++) {
|
||||
printf(" %02x", p->data[i]);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
@ -391,17 +397,17 @@ static int uhci_broadcast_packet(UHCIState *s, uint8_t pid,
|
|||
port = &s->ports[i];
|
||||
dev = port->port.dev;
|
||||
if (dev && (port->ctrl & UHCI_PORT_EN)) {
|
||||
ret = dev->handle_packet(dev, pid,
|
||||
devaddr, devep,
|
||||
data, len);
|
||||
ret = dev->handle_packet(dev, p);
|
||||
if (ret != USB_RET_NODEV) {
|
||||
#ifdef DEBUG_PACKET
|
||||
{
|
||||
if (ret == USB_RET_ASYNC) {
|
||||
printf("usb-uhci: Async packet\n");
|
||||
} else {
|
||||
printf(" ret=%d ", ret);
|
||||
if (pid == USB_TOKEN_IN && ret > 0) {
|
||||
if (p->pid == USB_TOKEN_IN && ret > 0) {
|
||||
printf("data_in=");
|
||||
for(i = 0; i < ret; i++) {
|
||||
printf(" %02x", data[i]);
|
||||
printf(" %02x", p->data[i]);
|
||||
}
|
||||
}
|
||||
printf("\n");
|
||||
|
@ -414,6 +420,8 @@ static int uhci_broadcast_packet(UHCIState *s, uint8_t pid,
|
|||
return USB_RET_NODEV;
|
||||
}
|
||||
|
||||
static void uhci_async_complete_packet(USBPacket * packet, void *opaque);
|
||||
|
||||
/* return -1 if fatal error (frame must be stopped)
|
||||
0 if TD successful
|
||||
1 if TD unsuccessful or inactive
|
||||
|
@ -421,9 +429,9 @@ static int uhci_broadcast_packet(UHCIState *s, uint8_t pid,
|
|||
static int uhci_handle_td(UHCIState *s, UHCI_TD *td, int *int_mask)
|
||||
{
|
||||
uint8_t pid;
|
||||
uint8_t buf[1280];
|
||||
int len, max_len, err, ret;
|
||||
|
||||
/* ??? This is wrong for async completion. */
|
||||
if (td->ctrl & TD_CTRL_IOC) {
|
||||
*int_mask |= 0x01;
|
||||
}
|
||||
|
@ -434,21 +442,8 @@ static int uhci_handle_td(UHCIState *s, UHCI_TD *td, int *int_mask)
|
|||
/* TD is active */
|
||||
max_len = ((td->token >> 21) + 1) & 0x7ff;
|
||||
pid = td->token & 0xff;
|
||||
switch(pid) {
|
||||
case USB_TOKEN_OUT:
|
||||
case USB_TOKEN_SETUP:
|
||||
cpu_physical_memory_read(td->buffer, buf, max_len);
|
||||
ret = uhci_broadcast_packet(s, pid,
|
||||
(td->token >> 8) & 0x7f,
|
||||
(td->token >> 15) & 0xf,
|
||||
buf, max_len);
|
||||
len = max_len;
|
||||
break;
|
||||
case USB_TOKEN_IN:
|
||||
ret = uhci_broadcast_packet(s, pid,
|
||||
(td->token >> 8) & 0x7f,
|
||||
(td->token >> 15) & 0xf,
|
||||
buf, max_len);
|
||||
if (s->async_qh) {
|
||||
ret = s->usb_packet.len;
|
||||
if (ret >= 0) {
|
||||
len = ret;
|
||||
if (len > max_len) {
|
||||
|
@ -457,17 +452,52 @@ static int uhci_handle_td(UHCIState *s, UHCI_TD *td, int *int_mask)
|
|||
}
|
||||
if (len > 0) {
|
||||
/* write the data back */
|
||||
cpu_physical_memory_write(td->buffer, buf, len);
|
||||
cpu_physical_memory_write(td->buffer, s->usb_buf, len);
|
||||
}
|
||||
} else {
|
||||
len = 0;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
/* invalid pid : frame interrupted */
|
||||
s->status |= UHCI_STS_HCPERR;
|
||||
uhci_update_irq(s);
|
||||
return -1;
|
||||
s->async_qh = 0;
|
||||
} else {
|
||||
s->usb_packet.pid = pid;
|
||||
s->usb_packet.devaddr = (td->token >> 8) & 0x7f;
|
||||
s->usb_packet.devep = (td->token >> 15) & 0xf;
|
||||
s->usb_packet.data = s->usb_buf;
|
||||
s->usb_packet.len = max_len;
|
||||
s->usb_packet.complete_cb = uhci_async_complete_packet;
|
||||
s->usb_packet.complete_opaque = s;
|
||||
switch(pid) {
|
||||
case USB_TOKEN_OUT:
|
||||
case USB_TOKEN_SETUP:
|
||||
cpu_physical_memory_read(td->buffer, s->usb_buf, max_len);
|
||||
ret = uhci_broadcast_packet(s, &s->usb_packet);
|
||||
len = max_len;
|
||||
break;
|
||||
case USB_TOKEN_IN:
|
||||
ret = uhci_broadcast_packet(s, &s->usb_packet);
|
||||
if (ret >= 0) {
|
||||
len = ret;
|
||||
if (len > max_len) {
|
||||
len = max_len;
|
||||
ret = USB_RET_BABBLE;
|
||||
}
|
||||
if (len > 0) {
|
||||
/* write the data back */
|
||||
cpu_physical_memory_write(td->buffer, s->usb_buf, len);
|
||||
}
|
||||
} else {
|
||||
len = 0;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
/* invalid pid : frame interrupted */
|
||||
s->status |= UHCI_STS_HCPERR;
|
||||
uhci_update_irq(s);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if (ret == USB_RET_ASYNC) {
|
||||
return 2;
|
||||
}
|
||||
if (td->ctrl & TD_CTRL_IOS)
|
||||
td->ctrl &= ~TD_CTRL_ACTIVE;
|
||||
|
@ -520,6 +550,61 @@ static int uhci_handle_td(UHCIState *s, UHCI_TD *td, int *int_mask)
|
|||
}
|
||||
}
|
||||
|
||||
static void uhci_async_complete_packet(USBPacket * packet, void *opaque)
|
||||
{
|
||||
UHCIState *s = opaque;
|
||||
UHCI_QH qh;
|
||||
UHCI_TD td;
|
||||
uint32_t link;
|
||||
uint32_t old_td_ctrl;
|
||||
uint32_t val;
|
||||
int ret;
|
||||
|
||||
link = s->async_qh;
|
||||
if (!link) {
|
||||
/* This should never happen. It means a TD somehow got removed
|
||||
without cancelling the associated async IO request. */
|
||||
return;
|
||||
}
|
||||
cpu_physical_memory_read(link & ~0xf, (uint8_t *)&qh, sizeof(qh));
|
||||
le32_to_cpus(&qh.link);
|
||||
le32_to_cpus(&qh.el_link);
|
||||
/* Re-process the queue containing the async packet. */
|
||||
while (1) {
|
||||
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);
|
||||
/* 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,
|
||||
sizeof(val));
|
||||
}
|
||||
if (ret < 0)
|
||||
break; /* interrupted frame */
|
||||
if (ret == 2) {
|
||||
s->async_qh = link;
|
||||
break;
|
||||
} else if (ret == 0) {
|
||||
/* 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,
|
||||
sizeof(val));
|
||||
if (!(qh.el_link & 4))
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void uhci_frame_timer(void *opaque)
|
||||
{
|
||||
UHCIState *s = opaque;
|
||||
|
@ -528,6 +613,7 @@ static void uhci_frame_timer(void *opaque)
|
|||
int int_mask, cnt, ret;
|
||||
UHCI_TD td;
|
||||
UHCI_QH qh;
|
||||
uint32_t old_async_qh;
|
||||
|
||||
if (!(s->cmd & UHCI_CMD_RS)) {
|
||||
qemu_del_timer(s->frame_timer);
|
||||
|
@ -535,6 +621,14 @@ static void uhci_frame_timer(void *opaque)
|
|||
s->status |= UHCI_STS_HCHALTED;
|
||||
return;
|
||||
}
|
||||
/* Complete the previous frame. */
|
||||
s->frnum = (s->frnum + 1) & 0x7ff;
|
||||
if (s->pending_int_mask) {
|
||||
s->status2 |= s->pending_int_mask;
|
||||
s->status |= UHCI_STS_USBINT;
|
||||
uhci_update_irq(s);
|
||||
}
|
||||
old_async_qh = s->async_qh;
|
||||
frame_addr = s->fl_base_addr + ((s->frnum & 0x3ff) << 2);
|
||||
cpu_physical_memory_read(frame_addr, (uint8_t *)&link, 4);
|
||||
le32_to_cpus(&link);
|
||||
|
@ -546,6 +640,12 @@ static void uhci_frame_timer(void *opaque)
|
|||
/* valid frame */
|
||||
if (link & 2) {
|
||||
/* QH */
|
||||
if (link == s->async_qh) {
|
||||
/* We've found a previously issues packet.
|
||||
Nothing else to do. */
|
||||
old_async_qh = 0;
|
||||
break;
|
||||
}
|
||||
cpu_physical_memory_read(link & ~0xf, (uint8_t *)&qh, sizeof(qh));
|
||||
le32_to_cpus(&qh.link);
|
||||
le32_to_cpus(&qh.el_link);
|
||||
|
@ -556,6 +656,10 @@ static void uhci_frame_timer(void *opaque)
|
|||
} else if (qh.el_link & 2) {
|
||||
/* QH */
|
||||
link = qh.el_link;
|
||||
} else if (s->async_qh) {
|
||||
/* We can only cope with one pending packet. Keep looking
|
||||
for the previously issued packet. */
|
||||
link = qh.link;
|
||||
} else {
|
||||
/* TD */
|
||||
if (--cnt == 0)
|
||||
|
@ -577,7 +681,9 @@ static void uhci_frame_timer(void *opaque)
|
|||
}
|
||||
if (ret < 0)
|
||||
break; /* interrupted frame */
|
||||
if (ret == 0) {
|
||||
if (ret == 2) {
|
||||
s->async_qh = link;
|
||||
} else if (ret == 0) {
|
||||
/* update qh element link */
|
||||
qh.el_link = td.link;
|
||||
val = cpu_to_le32(qh.el_link);
|
||||
|
@ -599,25 +705,41 @@ static void uhci_frame_timer(void *opaque)
|
|||
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);
|
||||
/* 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));
|
||||
/* 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);
|
||||
}
|
||||
}
|
||||
if (ret < 0)
|
||||
break; /* interrupted frame */
|
||||
link = td.link;
|
||||
}
|
||||
}
|
||||
s->frnum = (s->frnum + 1) & 0x7ff;
|
||||
if (int_mask) {
|
||||
s->status2 |= int_mask;
|
||||
s->status |= UHCI_STS_USBINT;
|
||||
uhci_update_irq(s);
|
||||
s->pending_int_mask = int_mask;
|
||||
if (old_async_qh) {
|
||||
/* A previously started transfer has disappeared from the transfer
|
||||
list. There's nothing useful we can do with it now, so just
|
||||
discard the packet and hope it wasn't too important. */
|
||||
#ifdef DEBUG
|
||||
printf("Discarding USB packet\n");
|
||||
#endif
|
||||
usb_cancel_packet(&s->usb_packet);
|
||||
s->async_qh = 0;
|
||||
}
|
||||
/* prepare the timer for the next frame */
|
||||
expire_time = qemu_get_clock(vm_clock) +
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue