static int ehci_process_itd(EHCIState *ehci, EHCIitd *itd) { USBPort *port; USBDevice *dev; int ret; uint32_t i, j, len, pid, dir, devaddr, endp; uint32_t pg, off, ptr1, ptr2, max, mult; dir =(itd->bufptr[1] & ITD_BUFPTR_DIRECTION); devaddr = get_field(itd->bufptr[0], ITD_BUFPTR_DEVADDR); endp = get_field(itd->bufptr[0], ITD_BUFPTR_EP); max = get_field(itd->bufptr[1], ITD_BUFPTR_MAXPKT); mult = get_field(itd->bufptr[2], ITD_BUFPTR_MULT); for(i = 0; i < 8; i++) { if (itd->transact[i] & ITD_XACT_ACTIVE) { pg = get_field(itd->transact[i], ITD_XACT_PGSEL); off = itd->transact[i] & ITD_XACT_OFFSET_MASK; ptr1 = (itd->bufptr[pg] & ITD_BUFPTR_MASK); ptr2 = (itd->bufptr[pg+1] & ITD_BUFPTR_MASK); len = get_field(itd->transact[i], ITD_XACT_LENGTH); if (len > max * mult) { len = max * mult; } if (len > BUFF_SIZE) { return USB_RET_PROCERR; } qemu_sglist_init(&ehci->isgl, 2); if (off + len > 4096) { /* transfer crosses page border */ uint32_t len2 = off + len - 4096; uint32_t len1 = len - len2; qemu_sglist_add(&ehci->isgl, ptr1 + off, len1); qemu_sglist_add(&ehci->isgl, ptr2, len2); } else { qemu_sglist_add(&ehci->isgl, ptr1 + off, len); } pid = dir ? USB_TOKEN_IN : USB_TOKEN_OUT; usb_packet_setup(&ehci->ipacket, pid, devaddr, endp); usb_packet_map(&ehci->ipacket, &ehci->isgl); ret = USB_RET_NODEV; for (j = 0; j < NB_PORTS; j++) { port = &ehci->ports[j]; dev = port->dev; if (!(ehci->portsc[j] &(PORTSC_CONNECT))) { continue; } ret = usb_handle_packet(dev, &ehci->ipacket); if (ret != USB_RET_NODEV) { break; } } usb_packet_unmap(&ehci->ipacket); qemu_sglist_destroy(&ehci->isgl); #if 0 /* In isoch, there is no facility to indicate a NAK so let's * instead just complete a zero-byte transaction. Setting * DBERR seems too draconian. */ if (ret == USB_RET_NAK) { if (ehci->isoch_pause > 0) { DPRINTF("ISOCH: received a NAK but paused so returning\n"); ehci->isoch_pause--; return 0; } else if (ehci->isoch_pause == -1) { DPRINTF("ISOCH: recv NAK & isoch pause inactive, setting\n"); // Pause frindex for up to 50 msec waiting for data from // remote ehci->isoch_pause = 50; return 0; } else { DPRINTF("ISOCH: isoch pause timeout! return 0\n"); ret = 0; } } else { DPRINTF("ISOCH: received ACK, clearing pause\n"); ehci->isoch_pause = -1; } #else if (ret == USB_RET_NAK) { ret = 0; } #endif 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_record_interrupt(ehci, USBSTS_INT); } } itd->transact[i] &= ~ITD_XACT_ACTIVE; } } return 0; }