struct EHCIQueue {
EHCIState *ehci;
QTAILQ_ENTRY(EHCIQueue) next;
- bool async_schedule;
uint32_t seen;
uint64_t ts;
int usb_status;
};
+typedef QTAILQ_HEAD(EHCIQueueHead, EHCIQueue) EHCIQueueHead;
+
struct EHCIState {
PCIDevice dev;
USBBus bus;
USBPort ports[NB_PORTS];
USBPort *companion_ports[NB_PORTS];
uint32_t usbsts_pending;
- QTAILQ_HEAD(, EHCIQueue) queues;
+ EHCIQueueHead aqueues;
+ EHCIQueueHead pqueues;
uint32_t a_fetch_addr; // which address to look at next
uint32_t p_fetch_addr; // which address to look at next
static EHCIQueue *ehci_alloc_queue(EHCIState *ehci, int async)
{
+ EHCIQueueHead *head = async ? &ehci->aqueues : &ehci->pqueues;
EHCIQueue *q;
q = g_malloc0(sizeof(*q));
q->ehci = ehci;
- q->async_schedule = async;
- QTAILQ_INSERT_HEAD(&ehci->queues, q, next);
+ QTAILQ_INSERT_HEAD(head, q, next);
trace_usb_ehci_queue_action(q, "alloc");
return q;
}
-static void ehci_free_queue(EHCIQueue *q)
+static void ehci_free_queue(EHCIQueue *q, int async)
{
+ EHCIQueueHead *head = async ? &q->ehci->aqueues : &q->ehci->pqueues;
trace_usb_ehci_queue_action(q, "free");
if (q->async == EHCI_ASYNC_INFLIGHT) {
usb_cancel_packet(&q->packet);
}
- QTAILQ_REMOVE(&q->ehci->queues, q, next);
+ QTAILQ_REMOVE(head, q, next);
g_free(q);
}
-static EHCIQueue *ehci_find_queue_by_qh(EHCIState *ehci, uint32_t addr)
+static EHCIQueue *ehci_find_queue_by_qh(EHCIState *ehci, uint32_t addr,
+ int async)
{
+ EHCIQueueHead *head = async ? &ehci->aqueues : &ehci->pqueues;
EHCIQueue *q;
- QTAILQ_FOREACH(q, &ehci->queues, next) {
+ QTAILQ_FOREACH(q, head, next) {
if (addr == q->qhaddr) {
return q;
}
return NULL;
}
-static void ehci_queues_rip_unused(EHCIState *ehci)
+static void ehci_queues_rip_unused(EHCIState *ehci, int async, int flush)
{
+ EHCIQueueHead *head = async ? &ehci->aqueues : &ehci->pqueues;
EHCIQueue *q, *tmp;
- QTAILQ_FOREACH_SAFE(q, &ehci->queues, next, tmp) {
+ QTAILQ_FOREACH_SAFE(q, head, next, tmp) {
if (q->seen) {
q->seen = 0;
q->ts = ehci->last_run_ns;
continue;
}
- if (ehci->last_run_ns < q->ts + 250000000) {
+ if (!flush && ehci->last_run_ns < q->ts + 250000000) {
/* allow 0.25 sec idle */
continue;
}
- ehci_free_queue(q);
+ ehci_free_queue(q, async);
}
}
-static void ehci_queues_rip_device(EHCIState *ehci, USBDevice *dev)
+static void ehci_queues_rip_device(EHCIState *ehci, USBDevice *dev, int async)
{
+ EHCIQueueHead *head = async ? &ehci->aqueues : &ehci->pqueues;
EHCIQueue *q, *tmp;
- QTAILQ_FOREACH_SAFE(q, &ehci->queues, next, tmp) {
+ QTAILQ_FOREACH_SAFE(q, head, next, tmp) {
if (!usb_packet_is_inflight(&q->packet) ||
q->packet.ep->dev != dev) {
continue;
}
- ehci_free_queue(q);
+ ehci_free_queue(q, async);
}
}
-static void ehci_queues_rip_all(EHCIState *ehci)
+static void ehci_queues_rip_all(EHCIState *ehci, int async)
{
+ EHCIQueueHead *head = async ? &ehci->aqueues : &ehci->pqueues;
EHCIQueue *q, *tmp;
- QTAILQ_FOREACH_SAFE(q, &ehci->queues, next, tmp) {
- ehci_free_queue(q);
+ QTAILQ_FOREACH_SAFE(q, head, next, tmp) {
+ ehci_free_queue(q, async);
}
}
return;
}
- ehci_queues_rip_device(s, port->dev);
+ ehci_queues_rip_device(s, port->dev, 0);
+ ehci_queues_rip_device(s, port->dev, 1);
*portsc &= ~(PORTSC_CONNECT|PORTSC_PED);
*portsc |= PORTSC_CSC;
return;
}
- ehci_queues_rip_device(s, child);
+ ehci_queues_rip_device(s, child, 0);
+ ehci_queues_rip_device(s, child, 1);
}
static void ehci_wakeup(USBPort *port)
usb_device_reset(devs[i]);
}
}
- ehci_queues_rip_all(s);
+ ehci_queues_rip_all(s, 0);
+ ehci_queues_rip_all(s, 1);
qemu_del_timer(s->frame_timer);
}
if (!(val & USBCMD_RUNSTOP) && (s->usbcmd & USBCMD_RUNSTOP)) {
qemu_del_timer(s->frame_timer);
- // TODO - should finish out some stuff before setting halt
+ ehci_queues_rip_all(s, 0);
+ ehci_queues_rip_all(s, 1);
ehci_set_usbsts(s, USBSTS_HALT);
}
static void ehci_execute_complete(EHCIQueue *q)
{
- int c_err, reload;
-
assert(q->async != EHCI_ASYNC_INFLIGHT);
q->async = EHCI_ASYNC_NONE;
q->qhaddr, q->qh.next, q->qtdaddr, q->usb_status);
if (q->usb_status < 0) {
-err:
- /* TO-DO: put this is in a function that can be invoked below as well */
- c_err = get_field(q->qh.token, QTD_TOKEN_CERR);
- c_err--;
- set_field(&q->qh.token, c_err, QTD_TOKEN_CERR);
-
switch(q->usb_status) {
+ case USB_RET_IOERROR:
case USB_RET_NODEV:
q->qh.token |= (QTD_TOKEN_HALT | QTD_TOKEN_XACTERR);
+ set_field(&q->qh.token, 0, QTD_TOKEN_CERR);
ehci_record_interrupt(q->ehci, USBSTS_ERRINT);
break;
case USB_RET_STALL:
ehci_record_interrupt(q->ehci, USBSTS_ERRINT);
break;
case USB_RET_NAK:
- /* 4.10.3 */
- reload = get_field(q->qh.epchar, QH_EPCHAR_RL);
- if ((q->pid == USB_TOKEN_IN) && reload) {
- int nakcnt = get_field(q->qh.altnext_qtd, QH_ALTNEXT_NAKCNT);
- nakcnt--;
- set_field(&q->qh.altnext_qtd, nakcnt, QH_ALTNEXT_NAKCNT);
- } else if (!reload) {
- return;
- }
- break;
+ set_field(&q->qh.altnext_qtd, 0, QH_ALTNEXT_NAKCNT);
+ return; /* We're not done yet with this transaction */
case USB_RET_BABBLE:
q->qh.token |= (QTD_TOKEN_HALT | QTD_TOKEN_BABBLE);
ehci_record_interrupt(q->ehci, USBSTS_ERRINT);
assert(0);
break;
}
+ } else if ((q->usb_status > q->tbytes) && (q->pid == USB_TOKEN_IN)) {
+ q->usb_status = USB_RET_BABBLE;
+ q->qh.token |= (QTD_TOKEN_HALT | QTD_TOKEN_BABBLE);
+ ehci_record_interrupt(q->ehci, USBSTS_ERRINT);
} else {
- // DPRINTF("Short packet condition\n");
// TODO check 4.12 for splits
- if ((q->usb_status > q->tbytes) && (q->pid == USB_TOKEN_IN)) {
- q->usb_status = USB_RET_BABBLE;
- goto err;
- }
-
if (q->tbytes && q->pid == USB_TOKEN_IN) {
q->tbytes -= q->usb_status;
} else {
q->qh.token ^= QTD_TOKEN_DTOGGLE;
q->qh.token &= ~QTD_TOKEN_ACTIVE;
- if ((q->usb_status >= 0) && (q->qh.token & QTD_TOKEN_IOC)) {
+ if (q->qh.token & QTD_TOKEN_IOC) {
ehci_record_interrupt(q->ehci, USBSTS_INT);
}
}
}
qemu_sglist_destroy(&ehci->isgl);
- if (ret == USB_RET_NAK) {
- /* no data for us, so do a zero-length transfer */
- ret = 0;
- }
-
- if (ret >= 0) {
- if (!dir) {
- /* OUT */
- set_field(&itd->transact[i], len - ret, ITD_XACT_LENGTH);
- } else {
- /* IN */
- set_field(&itd->transact[i], ret, ITD_XACT_LENGTH);
- }
- } else {
+ if (ret < 0) {
switch (ret) {
default:
fprintf(stderr, "Unexpected iso usb result: %d\n", ret);
/* Fall through */
+ case USB_RET_IOERROR:
case USB_RET_NODEV:
/* 3.3.2: XACTERR is only allowed on IN transactions */
if (dir) {
itd->transact[i] |= ITD_XACT_BABBLE;
ehci_record_interrupt(ehci, USBSTS_ERRINT);
break;
+ case USB_RET_NAK:
+ /* no data for us, so do a zero-length transfer */
+ ret = 0;
+ break;
+ }
+ }
+ if (ret >= 0) {
+ if (!dir) {
+ /* OUT */
+ set_field(&itd->transact[i], len - ret, ITD_XACT_LENGTH);
+ } else {
+ /* IN */
+ set_field(&itd->transact[i], ret, ITD_XACT_LENGTH);
}
}
if (itd->transact[i] & ITD_XACT_IOC) {
ehci_set_usbsts(ehci, USBSTS_REC);
}
- ehci_queues_rip_unused(ehci);
+ ehci_queues_rip_unused(ehci, async, 0);
/* Find the head of the list (4.9.1.1) */
for(i = 0; i < MAX_QH; i++) {
int again = 0;
uint32_t entry = ehci_get_fetch_addr(ehci, async);
- if (entry < 0x1000) {
- DPRINTF("fetchentry: entry invalid (0x%08x)\n", entry);
+ if (NLPTR_TBIT(entry)) {
ehci_set_state(ehci, async, EST_ACTIVE);
goto out;
}
{
uint32_t entry;
EHCIQueue *q;
- int reload;
entry = ehci_get_fetch_addr(ehci, async);
- q = ehci_find_queue_by_qh(ehci, entry);
+ q = ehci_find_queue_by_qh(ehci, entry, async);
if (NULL == q) {
q = ehci_alloc_queue(ehci, async);
}
}
#endif
- reload = get_field(q->qh.epchar, QH_EPCHAR_RL);
- if (reload) {
- set_field(&q->qh.altnext_qtd, reload, QH_ALTNEXT_NAKCNT);
- }
-
if (q->qh.token & QTD_TOKEN_HALT) {
ehci_set_state(ehci, async, EST_HORIZONTALQH);
- } else if ((q->qh.token & QTD_TOKEN_ACTIVE) && (q->qh.current_qtd > 0x1000)) {
+ } else if ((q->qh.token & QTD_TOKEN_ACTIVE) &&
+ (NLPTR_TBIT(q->qh.current_qtd) == 0)) {
q->qtdaddr = q->qh.current_qtd;
ehci_set_state(ehci, async, EST_FETCHQTD);
* want data and alt-next qTD is valid
*/
if (((q->qh.token & QTD_TOKEN_TBYTES_MASK) != 0) &&
- (q->qh.altnext_qtd > 0x1000) &&
(NLPTR_TBIT(q->qh.altnext_qtd) == 0)) {
q->qtdaddr = q->qh.altnext_qtd;
ehci_set_state(q->ehci, async, EST_FETCHQTD);
/*
* next qTD is valid
*/
- } else if ((q->qh.next_qtd > 0x1000) &&
- (NLPTR_TBIT(q->qh.next_qtd) == 0)) {
+ } else if (NLPTR_TBIT(q->qh.next_qtd) == 0) {
q->qtdaddr = q->qh.next_qtd;
ehci_set_state(q->ehci, async, EST_FETCHQTD);
static int ehci_state_execute(EHCIQueue *q, int async)
{
int again = 0;
- int reload, nakcnt;
- int smask;
if (ehci_qh_do_overlay(q) != 0) {
return -1;
}
- smask = get_field(q->qh.epcap, QH_EPCAP_SMASK);
-
- if (!smask) {
- reload = get_field(q->qh.epchar, QH_EPCHAR_RL);
- nakcnt = get_field(q->qh.altnext_qtd, QH_ALTNEXT_NAKCNT);
- if (reload && !nakcnt) {
- ehci_set_state(q->ehci, async, EST_HORIZONTALQH);
- again = 1;
- goto out;
- }
- }
-
// TODO verify enough time remains in the uframe as in 4.4.1.1
// TODO write back ptr to async list when done or out of time
// TODO Windows does not seem to ever set the MULT field
static int ehci_state_executing(EHCIQueue *q, int async)
{
int again = 0;
- int reload, nakcnt;
ehci_execute_complete(q);
if (q->usb_status == USB_RET_ASYNC) {
// counter decrements to 0
}
- reload = get_field(q->qh.epchar, QH_EPCHAR_RL);
- if (reload) {
- nakcnt = get_field(q->qh.altnext_qtd, QH_ALTNEXT_NAKCNT);
- if (q->usb_status == USB_RET_NAK) {
- if (nakcnt) {
- nakcnt--;
- }
- } else {
- nakcnt = reload;
- }
- set_field(&q->qh.altnext_qtd, nakcnt, QH_ALTNEXT_NAKCNT);
- }
-
/* 4.10.5 */
- if ((q->usb_status == USB_RET_NAK) || (q->qh.token & QTD_TOKEN_ACTIVE)) {
+ if (q->usb_status == USB_RET_NAK) {
ehci_set_state(q->ehci, async, EST_HORIZONTALQH);
} else {
ehci_set_state(q->ehci, async, EST_WRITEBACK);
static void ehci_advance_async_state(EHCIState *ehci)
{
- int async = 1;
+ const int async = 1;
switch(ehci_get_state(ehci, async)) {
case EST_INACTIVE:
case EST_ACTIVE:
if ( !(ehci->usbcmd & USBCMD_ASE)) {
+ ehci_queues_rip_all(ehci, async);
ehci_clear_usbsts(ehci, USBSTS_ASS);
ehci_set_state(ehci, async, EST_INACTIVE);
break;
}
- /* If the doorbell is set, the guest wants to make a change to the
- * schedule. The host controller needs to release cached data.
- * (section 4.8.2)
- */
- if (ehci->usbcmd & USBCMD_IAAD) {
- DPRINTF("ASYNC: doorbell request acknowledged\n");
- ehci->usbcmd &= ~USBCMD_IAAD;
- ehci_set_interrupt(ehci, USBSTS_IAA);
- break;
- }
-
- /* make sure guest has acknowledged */
+ /* make sure guest has acknowledged the doorbell interrupt */
/* TO-DO: is this really needed? */
if (ehci->usbsts & USBSTS_IAA) {
DPRINTF("IAA status bit still set.\n");
ehci_set_state(ehci, async, EST_WAITLISTHEAD);
ehci_advance_state(ehci, async);
+
+ /* If the doorbell is set, the guest wants to make a change to the
+ * schedule. The host controller needs to release cached data.
+ * (section 4.8.2)
+ */
+ if (ehci->usbcmd & USBCMD_IAAD) {
+ /* Remove all unseen qhs from the async qhs queue */
+ ehci_queues_rip_unused(ehci, async, 1);
+ DPRINTF("ASYNC: doorbell request acknowledged\n");
+ ehci->usbcmd &= ~USBCMD_IAAD;
+ ehci_set_interrupt(ehci, USBSTS_IAA);
+ }
break;
default:
{
uint32_t entry;
uint32_t list;
- int async = 0;
+ const int async = 0;
// 4.6
case EST_ACTIVE:
if ( !(ehci->frindex & 7) && !(ehci->usbcmd & USBCMD_PSE)) {
+ ehci_queues_rip_all(ehci, async);
ehci_clear_usbsts(ehci, USBSTS_PSS);
ehci_set_state(ehci, async, EST_INACTIVE);
break;
ehci_set_fetch_addr(ehci, async,entry);
ehci_set_state(ehci, async, EST_FETCHENTRY);
ehci_advance_state(ehci, async);
+ ehci_queues_rip_unused(ehci, async, 0);
break;
default:
}
s->frame_timer = qemu_new_timer_ns(vm_clock, ehci_frame_timer, s);
- QTAILQ_INIT(&s->queues);
+ QTAILQ_INIT(&s->aqueues);
+ QTAILQ_INIT(&s->pqueues);
qemu_register_reset(ehci_reset, s);
#define SETUP_STATE_SETUP 1
#define SETUP_STATE_DATA 2
#define SETUP_STATE_ACK 3
+#define SETUP_STATE_PARAM 4
static int do_token_setup(USBDevice *s, USBPacket *p)
{
}
}
+static int do_parameter(USBDevice *s, USBPacket *p)
+{
+ int request, value, index;
+ int i, ret = 0;
+
+ for (i = 0; i < 8; i++) {
+ s->setup_buf[i] = p->parameter >> (i*8);
+ }
+
+ s->setup_state = SETUP_STATE_PARAM;
+ s->setup_len = (s->setup_buf[7] << 8) | s->setup_buf[6];
+ s->setup_index = 0;
+
+ request = (s->setup_buf[0] << 8) | s->setup_buf[1];
+ value = (s->setup_buf[3] << 8) | s->setup_buf[2];
+ index = (s->setup_buf[5] << 8) | s->setup_buf[4];
+
+ if (s->setup_len > sizeof(s->data_buf)) {
+ fprintf(stderr,
+ "usb_generic_handle_packet: ctrl buffer too small (%d > %zu)\n",
+ s->setup_len, sizeof(s->data_buf));
+ return USB_RET_STALL;
+ }
+
+ if (p->pid == USB_TOKEN_OUT) {
+ usb_packet_copy(p, s->data_buf, s->setup_len);
+ }
+
+ ret = usb_device_handle_control(s, p, request, value, index,
+ s->setup_len, s->data_buf);
+ if (ret < 0) {
+ return ret;
+ }
+
+ if (ret < s->setup_len) {
+ s->setup_len = ret;
+ }
+ if (p->pid == USB_TOKEN_IN) {
+ usb_packet_copy(p, s->data_buf, s->setup_len);
+ }
+
+ return ret;
+}
+
/* ctrl complete function for devices which use usb_generic_handle_packet and
may return USB_RET_ASYNC from their handle_control callback. Device code
which does this *must* call this function instead of the normal
p->result = 0;
break;
+ case SETUP_STATE_PARAM:
+ if (p->result < s->setup_len) {
+ s->setup_len = p->result;
+ }
+ if (p->pid == USB_TOKEN_IN) {
+ p->result = 0;
+ usb_packet_copy(p, s->data_buf, s->setup_len);
+ }
+ break;
+
default:
break;
}
if (p->ep->nr == 0) {
/* control pipe */
+ if (p->parameter) {
+ return do_parameter(dev, p);
+ }
switch (p->pid) {
case USB_TOKEN_SETUP:
return do_token_setup(dev, p);
assert(p->state == USB_PACKET_SETUP);
assert(p->ep != NULL);
- if (QTAILQ_EMPTY(&p->ep->queue)) {
+ if (QTAILQ_EMPTY(&p->ep->queue) || p->ep->pipeline) {
ret = usb_process_one(p);
if (ret == USB_RET_ASYNC) {
usb_packet_set_state(p, USB_PACKET_ASYNC);
while (!QTAILQ_EMPTY(&ep->queue)) {
p = QTAILQ_FIRST(&ep->queue);
+ if (p->state == USB_PACKET_ASYNC) {
+ break;
+ }
assert(p->state == USB_PACKET_QUEUED);
ret = usb_process_one(p);
if (ret == USB_RET_ASYNC) {
p->pid = pid;
p->ep = ep;
p->result = 0;
+ p->parameter = 0;
qemu_iovec_reset(&p->iov);
usb_packet_set_state(p, USB_PACKET_SETUP);
}
dev->ep_ctl.type = USB_ENDPOINT_XFER_CONTROL;
dev->ep_ctl.ifnum = 0;
dev->ep_ctl.dev = dev;
+ dev->ep_ctl.pipeline = false;
QTAILQ_INIT(&dev->ep_ctl.queue);
for (ep = 0; ep < USB_MAX_ENDPOINTS; ep++) {
dev->ep_in[ep].nr = ep + 1;
dev->ep_out[ep].ifnum = 0;
dev->ep_in[ep].dev = dev;
dev->ep_out[ep].dev = dev;
+ dev->ep_in[ep].pipeline = false;
+ dev->ep_out[ep].pipeline = false;
QTAILQ_INIT(&dev->ep_in[ep].queue);
QTAILQ_INIT(&dev->ep_out[ep].queue);
}
struct USBEndpoint *uep = usb_ep_get(dev, pid, ep);
return uep->max_packet_size;
}
+
+void usb_ep_set_pipeline(USBDevice *dev, int pid, int ep, bool enabled)
+{
+ struct USBEndpoint *uep = usb_ep_get(dev, pid, ep);
+ uep->pipeline = enabled;
+}