📄 usb-uhci.c
字号:
int len, max_len, err, ret; if (!(td->ctrl & TD_CTRL_ACTIVE)) { ret = 1; goto out; } /* Clear TD's status field explicitly */ td->ctrl = td->ctrl & (~TD_CTRL_MASK); /* TD is active */ max_len = ((td->token >> 21) + 1) & 0x7ff; pid = td->token & 0xff; if (s->async_qh) { ret = s->usb_packet.len; 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; } 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); ret = -1; goto out; } } if (ret == USB_RET_ASYNC) { ret = 2; goto out; } if (td->ctrl & TD_CTRL_IOS) td->ctrl &= ~TD_CTRL_ACTIVE; if (ret >= 0) { td->ctrl = (td->ctrl & ~0x7ff) | ((len - 1) & 0x7ff); td->ctrl &= ~TD_CTRL_ACTIVE; if (pid == USB_TOKEN_IN && (td->ctrl & TD_CTRL_SPD) && len < max_len) { *int_mask |= 0x02; /* short packet: do not update QH */ ret = 1; goto out; } else { /* success */ ret = 0; goto out; } } else { switch(ret) { default: case USB_RET_NODEV: do_timeout: td->ctrl |= TD_CTRL_TIMEOUT; err = (td->ctrl >> TD_CTRL_ERROR_SHIFT) & 3; if (err != 0) { err--; if (err == 0) { td->ctrl &= ~TD_CTRL_ACTIVE; s->status |= UHCI_STS_USBERR; uhci_update_irq(s); } } td->ctrl = (td->ctrl & ~(3 << TD_CTRL_ERROR_SHIFT)) | (err << TD_CTRL_ERROR_SHIFT); ret = 1; goto out; case USB_RET_NAK: td->ctrl |= TD_CTRL_NAK; if (pid == USB_TOKEN_SETUP) goto do_timeout; ret = 1; goto out; case USB_RET_STALL: td->ctrl |= TD_CTRL_STALL; td->ctrl &= ~TD_CTRL_ACTIVE; ret = 1; goto out; case USB_RET_BABBLE: td->ctrl |= TD_CTRL_BABBLE | TD_CTRL_STALL; td->ctrl &= ~TD_CTRL_ACTIVE; /* frame interrupted */ ret = -1; goto out; } } out: /* If TD is inactive and IOC bit set to 1 then update int_mask */ if ((td->ctrl & TD_CTRL_IOC) && (!(td->ctrl & TD_CTRL_ACTIVE))) { *int_mask |= 0x01; } return ret;}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; int64_t expire_time; uint32_t frame_addr, link, old_td_ctrl, val; 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); /* set hchalted bit in status - UHCI11D 2.1.2 */ 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); int_mask = 0; cnt = FRAME_MAX_LOOPS; while ((link & 1) == 0) { if (--cnt == 0) break; /* 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); depth_first: if (qh.el_link & 1) { /* no element : go to next entry */ link = qh.link; } 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) break; 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, &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; } 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) { /* depth first */ goto depth_first; } } /* go to next entry */ link = qh.link; } } else { /* TD */ cpu_physical_memory_read(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); /* 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); } } link = td.link; } } 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) + (ticks_per_sec / FRAME_TIMER_FREQ); qemu_mod_timer(s->frame_timer, expire_time);}static void uhci_map(PCIDevice *pci_dev, int region_num, uint32_t addr, uint32_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);}void uhci_usb_save(QEMUFile *f, void *opaque){ int i; UHCIState *s = (UHCIState*)opaque; pci_device_save(&s->dev, f); qemu_put_be16s(f, &s->cmd); qemu_put_be16s(f, &s->status); qemu_put_be16s(f, &s->intr); qemu_put_be16s(f, &s->frnum); qemu_put_be32s(f, &s->fl_base_addr); qemu_put_8s(f, &s->sof_timing); qemu_put_8s(f, &s->status2); for(i = 0; i < NB_PORTS; i++) { qemu_put_be16s(f, &s->ports[i].ctrl); } qemu_put_timer(f, s->frame_timer);}int uhci_usb_load(QEMUFile *f, void *opaque, int version_id){ int i; UHCIState *s = (UHCIState*)opaque; if (version_id != 1) return -EINVAL; i = pci_device_load(&s->dev, f); if (i < 0) return i; qemu_get_be16s(f, &s->cmd); qemu_get_be16s(f, &s->status); qemu_get_be16s(f, &s->intr); qemu_get_be16s(f, &s->frnum); qemu_get_be32s(f, &s->fl_base_addr); qemu_get_8s(f, &s->sof_timing); qemu_get_8s(f, &s->status2); for(i = 0; i < NB_PORTS; i++) { qemu_get_be16s(f, &s->ports[i].ctrl); } qemu_get_timer(f, s->frame_timer); return 0;}void usb_uhci_init(PCIBus *bus, int devfn){ UHCIState *s; uint8_t *pci_conf; int i; s = (UHCIState *)pci_register_device(bus, "USB-UHCI", sizeof(UHCIState), devfn, NULL, NULL); pci_conf = s->dev.config; pci_conf[0x00] = 0x86; pci_conf[0x01] = 0x80; pci_conf[0x02] = 0x20; pci_conf[0x03] = 0x70; pci_conf[0x08] = 0x01; // revision number pci_conf[0x09] = 0x00; pci_conf[0x0a] = 0x03; pci_conf[0x0b] = 0x0c; pci_conf[0x0e] = 0x00; // header_type pci_conf[0x3d] = 4; // interrupt pin 3 pci_conf[0x60] = 0x10; // release number pci_conf[0x2c] = pci_conf[0x00]; // same as Vendor ID pci_conf[0x2d] = pci_conf[0x01]; for(i = 0; i < NB_PORTS; i++) { qemu_register_usb_port(&s->ports[i].port, s, i, uhci_attach); } s->frame_timer = qemu_new_timer(vm_clock, uhci_frame_timer, s); uhci_reset(s); /* Use region 4 for consistency with real hardware. BSD guests seem to rely on this. */ pci_register_io_region(&s->dev, 4, 0x20, PCI_ADDRESS_SPACE_IO, uhci_map); register_savevm("UHCI usb controller", 0, 1, uhci_usb_save, uhci_usb_load, s);}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -