📄 usb-ohci.c
字号:
ptr = td->cbp; n = 0x1000 - (ptr & 0xfff); if (n > len) n = len; cpu_physical_memory_rw(ptr, buf, n, write); if (n == len) return; ptr = td->be & ~0xfffu; buf += n; cpu_physical_memory_rw(ptr, buf, len - n, write);}static void ohci_process_lists(OHCIState *ohci);static void ohci_async_complete_packet(USBPacket * packet, void *opaque){ OHCIState *ohci = opaque;#ifdef DEBUG_PACKET dprintf("Async packet complete\n");#endif ohci->async_complete = 1; ohci_process_lists(ohci);}/* Service a transport descriptor. Returns nonzero to terminate processing of this endpoint. */static int ohci_service_td(OHCIState *ohci, struct ohci_ed *ed){ int dir; size_t len = 0; char *str = NULL; int pid; int ret; int i; USBDevice *dev; struct ohci_td td; uint32_t addr; int flag_r; int completion; addr = ed->head & OHCI_DPTR_MASK; /* See if this TD has already been submitted to the device. */ completion = (addr == ohci->async_td); if (completion && !ohci->async_complete) {#ifdef DEBUG_PACKET dprintf("Skipping async TD\n");#endif return 1; } if (!ohci_read_td(addr, &td)) { fprintf(stderr, "usb-ohci: TD read error at %x\n", addr); return 0; } dir = OHCI_BM(ed->flags, ED_D); switch (dir) { case OHCI_TD_DIR_OUT: case OHCI_TD_DIR_IN: /* Same value. */ break; default: dir = OHCI_BM(td.flags, TD_DP); break; } switch (dir) { case OHCI_TD_DIR_IN: str = "in"; pid = USB_TOKEN_IN; break; case OHCI_TD_DIR_OUT: str = "out"; pid = USB_TOKEN_OUT; break; case OHCI_TD_DIR_SETUP: str = "setup"; pid = USB_TOKEN_SETUP; break; default: fprintf(stderr, "usb-ohci: Bad direction\n"); return 1; } if (td.cbp && td.be) { if ((td.cbp & 0xfffff000) != (td.be & 0xfffff000)) { len = (td.be & 0xfff) + 0x1001 - (td.cbp & 0xfff); } else { len = (td.be - td.cbp) + 1; } if (len && dir != OHCI_TD_DIR_IN && !completion) { ohci_copy_td(&td, ohci->usb_buf, len, 0); } } flag_r = (td.flags & OHCI_TD_R) != 0;#ifdef DEBUG_PACKET dprintf(" TD @ 0x%.8x %u bytes %s r=%d cbp=0x%.8x be=0x%.8x\n", addr, len, str, flag_r, td.cbp, td.be); if (len >= 0 && dir != OHCI_TD_DIR_IN) { dprintf(" data:"); for (i = 0; i < len; i++) printf(" %.2x", ohci->usb_buf[i]); dprintf("\n"); }#endif if (completion) { ret = ohci->usb_packet.len; ohci->async_td = 0; ohci->async_complete = 0; } else { ret = USB_RET_NODEV; for (i = 0; i < ohci->num_ports; i++) { dev = ohci->rhport[i].port.dev; if ((ohci->rhport[i].ctrl & OHCI_PORT_PES) == 0) continue; if (ohci->async_td) { /* ??? The hardware should allow one active packet per endpoint. We only allow one active packet per controller. This should be sufficient as long as devices respond in a timely manner. */#ifdef DEBUG_PACKET dprintf("Too many pending packets\n");#endif return 1; } ohci->usb_packet.pid = pid; ohci->usb_packet.devaddr = OHCI_BM(ed->flags, ED_FA); ohci->usb_packet.devep = OHCI_BM(ed->flags, ED_EN); ohci->usb_packet.data = ohci->usb_buf; ohci->usb_packet.len = len; ohci->usb_packet.complete_cb = ohci_async_complete_packet; ohci->usb_packet.complete_opaque = ohci; ret = dev->handle_packet(dev, &ohci->usb_packet); if (ret != USB_RET_NODEV) break; }#ifdef DEBUG_PACKET dprintf("ret=%d\n", ret);#endif if (ret == USB_RET_ASYNC) { ohci->async_td = addr; return 1; } } if (ret >= 0) { if (dir == OHCI_TD_DIR_IN) { ohci_copy_td(&td, ohci->usb_buf, ret, 1);#ifdef DEBUG_PACKET dprintf(" data:"); for (i = 0; i < ret; i++) printf(" %.2x", ohci->usb_buf[i]); dprintf("\n");#endif } else { ret = len; } } /* Writeback */ if (ret == len || (dir == OHCI_TD_DIR_IN && ret >= 0 && flag_r)) { /* Transmission succeeded. */ if (ret == len) { td.cbp = 0; } else { td.cbp += ret; if ((td.cbp & 0xfff) + ret > 0xfff) { td.cbp &= 0xfff; td.cbp |= td.be & ~0xfff; } } td.flags |= OHCI_TD_T1; td.flags ^= OHCI_TD_T0; OHCI_SET_BM(td.flags, TD_CC, OHCI_CC_NOERROR); OHCI_SET_BM(td.flags, TD_EC, 0); ed->head &= ~OHCI_ED_C; if (td.flags & OHCI_TD_T0) ed->head |= OHCI_ED_C; } else { if (ret >= 0) { dprintf("usb-ohci: Underrun\n"); OHCI_SET_BM(td.flags, TD_CC, OHCI_CC_DATAUNDERRUN); } else { switch (ret) { case USB_RET_NODEV: OHCI_SET_BM(td.flags, TD_CC, OHCI_CC_DEVICENOTRESPONDING); case USB_RET_NAK: dprintf("usb-ohci: got NAK\n"); return 1; case USB_RET_STALL: dprintf("usb-ohci: got STALL\n"); OHCI_SET_BM(td.flags, TD_CC, OHCI_CC_STALL); break; case USB_RET_BABBLE: dprintf("usb-ohci: got BABBLE\n"); OHCI_SET_BM(td.flags, TD_CC, OHCI_CC_DATAOVERRUN); break; default: fprintf(stderr, "usb-ohci: Bad device response %d\n", ret); OHCI_SET_BM(td.flags, TD_CC, OHCI_CC_UNDEXPETEDPID); OHCI_SET_BM(td.flags, TD_EC, 3); break; } } ed->head |= OHCI_ED_H; } /* Retire this TD */ ed->head &= ~OHCI_DPTR_MASK; ed->head |= td.next & OHCI_DPTR_MASK; td.next = ohci->done; ohci->done = addr; i = OHCI_BM(td.flags, TD_DI); if (i < ohci->done_count) ohci->done_count = i; ohci_put_td(addr, &td); return OHCI_BM(td.flags, TD_CC) != OHCI_CC_NOERROR;}/* Service an endpoint list. Returns nonzero if active TD were found. */static int ohci_service_ed_list(OHCIState *ohci, uint32_t head){ struct ohci_ed ed; uint32_t next_ed; uint32_t cur; int active; active = 0; if (head == 0) return 0; for (cur = head; cur; cur = next_ed) { if (!ohci_read_ed(cur, &ed)) { fprintf(stderr, "usb-ohci: ED read error at %x\n", cur); return 0; } next_ed = ed.next & OHCI_DPTR_MASK; if ((ed.head & OHCI_ED_H) || (ed.flags & OHCI_ED_K)) { uint32_t addr; /* Cancel pending packets for ED that have been paused. */ addr = ed.head & OHCI_DPTR_MASK; if (ohci->async_td && addr == ohci->async_td) { usb_cancel_packet(&ohci->usb_packet); ohci->async_td = 0; } continue; } /* Skip isochronous endpoints. */ if (ed.flags & OHCI_ED_F) continue; while ((ed.head & OHCI_DPTR_MASK) != ed.tail) {#ifdef DEBUG_PACKET dprintf("ED @ 0x%.8x fa=%u en=%u d=%u s=%u k=%u f=%u mps=%u " "h=%u c=%u\n head=0x%.8x tailp=0x%.8x next=0x%.8x\n", cur, OHCI_BM(ed.flags, ED_FA), OHCI_BM(ed.flags, ED_EN), OHCI_BM(ed.flags, ED_D), (ed.flags & OHCI_ED_S)!= 0, (ed.flags & OHCI_ED_K) != 0, (ed.flags & OHCI_ED_F) != 0, OHCI_BM(ed.flags, ED_MPS), (ed.head & OHCI_ED_H) != 0, (ed.head & OHCI_ED_C) != 0, ed.head & OHCI_DPTR_MASK, ed.tail & OHCI_DPTR_MASK, ed.next & OHCI_DPTR_MASK);#endif active = 1; if (ohci_service_td(ohci, &ed)) break; } ohci_put_ed(cur, &ed); } return active;}/* Generate a SOF event, and set a timer for EOF */static void ohci_sof(OHCIState *ohci){ ohci->sof_time = qemu_get_clock(vm_clock); qemu_mod_timer(ohci->eof_timer, ohci->sof_time + usb_frame_time); ohci_set_interrupt(ohci, OHCI_INTR_SF);}/* Process Control and Bulk lists. */static void ohci_process_lists(OHCIState *ohci){ if ((ohci->ctl & OHCI_CTL_CLE) && (ohci->status & OHCI_STATUS_CLF)) { if (ohci->ctrl_cur && ohci->ctrl_cur != ohci->ctrl_head) dprintf("usb-ohci: head %x, cur %x\n", ohci->ctrl_head, ohci->ctrl_cur); if (!ohci_service_ed_list(ohci, ohci->ctrl_head)) { ohci->ctrl_cur = 0; ohci->status &= ~OHCI_STATUS_CLF; } } if ((ohci->ctl & OHCI_CTL_BLE) && (ohci->status & OHCI_STATUS_BLF)) { if (!ohci_service_ed_list(ohci, ohci->bulk_head)) { ohci->bulk_cur = 0; ohci->status &= ~OHCI_STATUS_BLF; } }}/* Do frame processing on frame boundary */static void ohci_frame_boundary(void *opaque){ OHCIState *ohci = opaque; struct ohci_hcca hcca; cpu_physical_memory_rw(ohci->hcca, (uint8_t *)&hcca, sizeof(hcca), 0); /* Process all the lists at the end of the frame */ if (ohci->ctl & OHCI_CTL_PLE) { int n; n = ohci->frame_number & 0x1f; ohci_service_ed_list(ohci, le32_to_cpu(hcca.intr[n])); } /* Cancel all pending packets if either of the lists has been disabled. */ if (ohci->async_td && ohci->old_ctl & (~ohci->ctl) & (OHCI_CTL_BLE | OHCI_CTL_CLE)) { usb_cancel_packet(&ohci->usb_packet); ohci->async_td = 0; } ohci->old_ctl = ohci->ctl; ohci_process_lists(ohci); /* Frame boundary, so do EOF stuf here */ ohci->frt = ohci->fit; /* XXX: endianness */ ohci->frame_number = (ohci->frame_number + 1) & 0xffff; hcca.frame = cpu_to_le32(ohci->frame_number); if (ohci->done_count == 0 && !(ohci->intr_status & OHCI_INTR_WD)) { if (!ohci->done) abort(); if (ohci->intr & ohci->intr_status) ohci->done |= 1; hcca.done = cpu_to_le32(ohci->done); ohci->done = 0; ohci->done_count = 7; ohci_set_interrupt(ohci, OHCI_INTR_WD); } if (ohci->done_count != 7 && ohci->done_count != 0) ohci->done_count--; /* Do SOF stuff here */ ohci_sof(ohci); /* Writeback HCCA */ cpu_physical_memory_rw(ohci->hcca, (uint8_t *)&hcca, sizeof(hcca), 1);}/* Start sending SOF tokens across the USB bus, lists are processed in * next frame */static int ohci_bus_start(OHCIState *ohci){ ohci->eof_timer = qemu_new_timer(vm_clock, ohci_frame_boundary, ohci); if (ohci->eof_timer == NULL) { fprintf(stderr, "usb-ohci: %s: qemu_new_timer failed\n", ohci->pci_dev.name); /* TODO: Signal unrecoverable error */ return 0; } dprintf("usb-ohci: %s: USB Operational\n", ohci->pci_dev.name); ohci_sof(ohci); return 1;}/* Stop sending SOF tokens on the bus */static void ohci_bus_stop(OHCIState *ohci){ if (ohci->eof_timer) qemu_del_timer(ohci->eof_timer);}/* Sets a flag in a port status register but only set it if the port is * connected, if not set ConnectStatusChange flag. If flag is enabled * return 1. */static int ohci_port_set_if_connected(OHCIState *ohci, int i, uint32_t val){ int ret = 1; /* writing a 0 has no effect */ if (val == 0) return 0; /* If CurrentConnectStatus is cleared we set * ConnectStatusChange */ if (!(ohci->rhport[i].ctrl & OHCI_PORT_CCS)) { ohci->rhport[i].ctrl |= OHCI_PORT_CSC; if (ohci->rhstatus & OHCI_RHS_DRWE) { /* TODO: CSC is a wakeup event */ } return 0; } if (ohci->rhport[i].ctrl & val) ret = 0; /* set the bit */ ohci->rhport[i].ctrl |= val; return ret;}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -