From bb36d4708bc93874af95db54da2a9cee0d30a84d Mon Sep 17 00:00:00 2001 From: bellard Date: Sat, 5 Nov 2005 14:22:28 +0000 Subject: [PATCH] initial USB support git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@1597 c046a42c-6fe2-441c-8c8c-71466251a162 --- Changelog | 1 + Makefile.target | 2 +- hw/pc.c | 22 ++ hw/usb-uhci.c | 672 ++++++++++++++++++++++++++++++++++++++++++++++ hw/usb.c | 694 ++++++++++++++++++++++++++++++++++++++++++++++++ hw/usb.h | 139 ++++++++++ usb-linux.c | 309 +++++++++++++++++++++ vl.c | 6 + vl.h | 3 + 9 files changed, 1847 insertions(+), 1 deletion(-) create mode 100644 hw/usb-uhci.c create mode 100644 hw/usb.c create mode 100644 hw/usb.h create mode 100644 usb-linux.c diff --git a/Changelog b/Changelog index 56efe3cc8d..6aff1395d0 100644 --- a/Changelog +++ b/Changelog @@ -6,6 +6,7 @@ version 0.7.3: - ALSA audio driver (malc) - new audio options: '-soundhw' and '-audio-help' (malc) - ES1370 PCI audio device (malc) + - Initial USB support version 0.7.2: diff --git a/Makefile.target b/Makefile.target index b1773f4ddb..56b7036a5d 100644 --- a/Makefile.target +++ b/Makefile.target @@ -294,7 +294,7 @@ ifeq ($(TARGET_BASE_ARCH), i386) # Hardware support VL_OBJS+= ide.o ne2000.o pckbd.o vga.o $(SOUND_HW) dma.o $(AUDIODRV) VL_OBJS+= fdc.o mc146818rtc.o serial.o i8259.o i8254.o pc.o -VL_OBJS+= cirrus_vga.o mixeng.o apic.o parallel.o +VL_OBJS+= cirrus_vga.o mixeng.o apic.o parallel.o usb.o usb-uhci.o usb-linux.o DEFINES += -DHAS_AUDIO endif ifeq ($(TARGET_BASE_ARCH), ppc) diff --git a/hw/pc.c b/hw/pc.c index ad3d5e032b..cd28998e1e 100644 --- a/hw/pc.c +++ b/hw/pc.c @@ -620,6 +620,28 @@ static void pc_init1(int ram_size, int vga_ram_size, int boot_device, cmos_init(ram_size, boot_device, bs_table); + if (pci_enabled && usb_enabled) { + USBPort *usb_root_ports[2]; + USBDevice *usb_hub; + usb_uhci_init(pci_bus, usb_root_ports); +#if 0 + { + USBPort *usb_hub1_ports[4]; + USBPort *usb_hub2_ports[2]; + /* test: we simulate a USB hub */ + usb_hub = usb_hub_init(usb_hub1_ports, 4); + usb_attach(usb_root_ports[0], usb_hub); + + /* test: we simulate a USB hub */ + usb_hub = usb_hub_init(usb_hub2_ports, 2); + usb_attach(usb_hub1_ports[0], usb_hub); + } +#endif + /* simulated hub with the host USB devices connected to it */ + usb_hub = usb_host_hub_init(); + usb_attach(usb_root_ports[0], usb_hub); + } + /* must be done after all PCI devices are instanciated */ /* XXX: should be done in the Bochs BIOS */ if (pci_enabled) { diff --git a/hw/usb-uhci.c b/hw/usb-uhci.c new file mode 100644 index 0000000000..42417fedae --- /dev/null +++ b/hw/usb-uhci.c @@ -0,0 +1,672 @@ +/* + * USB UHCI controller emulation + * + * Copyright (c) 2005 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "vl.h" + +//#define DEBUG +//#define DEBUG_PACKET + +#define UHCI_CMD_GRESET (1 << 2) +#define UHCI_CMD_HCRESET (1 << 1) +#define UHCI_CMD_RS (1 << 0) + +#define UHCI_STS_HCHALTED (1 << 5) +#define UHCI_STS_HCPERR (1 << 4) +#define UHCI_STS_HSERR (1 << 3) +#define UHCI_STS_RD (1 << 2) +#define UHCI_STS_USBERR (1 << 1) +#define UHCI_STS_USBINT (1 << 0) + +#define TD_CTRL_SPD (1 << 29) +#define TD_CTRL_ERROR_SHIFT 27 +#define TD_CTRL_IOS (1 << 25) +#define TD_CTRL_IOC (1 << 24) +#define TD_CTRL_ACTIVE (1 << 23) +#define TD_CTRL_STALL (1 << 22) +#define TD_CTRL_BABBLE (1 << 20) +#define TD_CTRL_NAK (1 << 19) +#define TD_CTRL_TIMEOUT (1 << 18) + +#define UHCI_PORT_RESET (1 << 9) +#define UHCI_PORT_LSDA (1 << 8) +#define UHCI_PORT_ENC (1 << 3) +#define UHCI_PORT_EN (1 << 2) +#define UHCI_PORT_CSC (1 << 1) +#define UHCI_PORT_CCS (1 << 0) + +#define FRAME_TIMER_FREQ 1000 + +#define FRAME_MAX_LOOPS 100 + +#define NB_PORTS 2 + +typedef struct UHCIPort { + USBPort port; + uint16_t ctrl; + USBDevice *dev; /* connected device */ +} UHCIPort; + +typedef struct UHCIState { + PCIDevice dev; + uint16_t cmd; /* cmd register */ + uint16_t status; + uint16_t intr; /* interrupt enable register */ + uint16_t frnum; /* frame number */ + uint32_t fl_base_addr; /* frame list base address */ + uint8_t sof_timing; + uint8_t status2; /* bit 0 and 1 are used to generate UHCI_STS_USBINT */ + QEMUTimer *frame_timer; + UHCIPort ports[NB_PORTS]; +} UHCIState; + +typedef struct UHCI_TD { + uint32_t link; + uint32_t ctrl; /* see TD_CTRL_xxx */ + uint32_t token; + uint32_t buffer; +} UHCI_TD; + +typedef struct UHCI_QH { + uint32_t link; + uint32_t el_link; +} UHCI_QH; + +static void uhci_attach(USBPort *port1, USBDevice *dev); + +static void uhci_update_irq(UHCIState *s) +{ + int level; + if (((s->status2 & 1) && (s->intr & (1 << 2))) || + ((s->status2 & 2) && (s->intr & (1 << 3))) || + ((s->status & UHCI_STS_USBERR) && (s->intr & (1 << 0))) || + ((s->status & UHCI_STS_RD) && (s->intr & (1 << 1))) || + (s->status & UHCI_STS_HSERR) || + (s->status & UHCI_STS_HCPERR)) { + level = 1; + } else { + level = 0; + } + pci_set_irq(&s->dev, 0, level); +} + +static void uhci_reset(UHCIState *s) +{ + uint8_t *pci_conf; + int i; + UHCIPort *port; + + pci_conf = s->dev.config; + + pci_conf[0x6a] = 0x01; /* usb clock */ + pci_conf[0x6b] = 0x00; + s->cmd = 0; + s->status = 0; + s->status2 = 0; + s->intr = 0; + s->fl_base_addr = 0; + s->sof_timing = 64; + for(i = 0; i < NB_PORTS; i++) { + port = &s->ports[i]; + port->ctrl = 0x0080; + if (port->dev) + uhci_attach(&port->port, port->dev); + } +} + +static void uhci_ioport_writeb(void *opaque, uint32_t addr, uint32_t val) +{ + UHCIState *s = opaque; + + addr &= 0x1f; + switch(addr) { + case 0x0c: + s->sof_timing = val; + break; + } +} + +static uint32_t uhci_ioport_readb(void *opaque, uint32_t addr) +{ + UHCIState *s = opaque; + uint32_t val; + + addr &= 0x1f; + switch(addr) { + case 0x0c: + val = s->sof_timing; + default: + val = 0xff; + break; + } + return val; +} + +static void uhci_ioport_writew(void *opaque, uint32_t addr, uint32_t val) +{ + UHCIState *s = opaque; + + addr &= 0x1f; +#ifdef DEBUG + printf("uhci writew port=0x%04x val=0x%04x\n", addr, val); +#endif + switch(addr) { + case 0x00: + if ((val & UHCI_CMD_RS) && !(s->cmd & UHCI_CMD_RS)) { + /* start frame processing */ + qemu_mod_timer(s->frame_timer, qemu_get_clock(vm_clock)); + } + if (val & UHCI_CMD_GRESET) { + UHCIPort *port; + USBDevice *dev; + int i; + + /* send reset on the USB bus */ + for(i = 0; i < NB_PORTS; i++) { + port = &s->ports[i]; + dev = port->dev; + if (dev) { + dev->handle_packet(dev, + USB_MSG_RESET, 0, 0, NULL, 0); + } + } + uhci_reset(s); + return; + } + if (val & UHCI_CMD_GRESET) { + uhci_reset(s); + return; + } + s->cmd = val; + break; + case 0x02: + s->status &= ~val; + /* XXX: the chip spec is not coherent, so we add a hidden + register to distinguish between IOC and SPD */ + if (val & UHCI_STS_USBINT) + s->status2 = 0; + uhci_update_irq(s); + break; + case 0x04: + s->intr = val; + uhci_update_irq(s); + break; + case 0x06: + if (s->status & UHCI_STS_HCHALTED) + s->frnum = val & 0x7ff; + break; + case 0x10 ... 0x1f: + { + UHCIPort *port; + USBDevice *dev; + int n; + + n = (addr >> 1) & 7; + if (n >= NB_PORTS) + return; + port = &s->ports[n]; + dev = port->dev; + if (dev) { + /* port reset */ + if ( (val & UHCI_PORT_RESET) && + !(port->ctrl & UHCI_PORT_RESET) ) { + dev->handle_packet(dev, + USB_MSG_RESET, 0, 0, NULL, 0); + } + } + port->ctrl = (port->ctrl & 0x01fb) | (val & ~0x01fb); + /* some bits are reset when a '1' is written to them */ + port->ctrl &= ~(val & 0x000a); + } + break; + } +} + +static uint32_t uhci_ioport_readw(void *opaque, uint32_t addr) +{ + UHCIState *s = opaque; + uint32_t val; + + addr &= 0x1f; + switch(addr) { + case 0x00: + val = s->cmd; + break; + case 0x02: + val = s->status; + break; + case 0x04: + val = s->intr; + break; + case 0x06: + val = s->frnum; + break; + case 0x10 ... 0x1f: + { + UHCIPort *port; + int n; + n = (addr >> 1) & 7; + if (n >= NB_PORTS) + goto read_default; + port = &s->ports[n]; + val = port->ctrl; + } + break; + default: + read_default: + val = 0xff7f; /* disabled port */ + break; + } +#ifdef DEBUG + printf("uhci readw port=0x%04x val=0x%04x\n", addr, val); +#endif + return val; +} + +static void uhci_ioport_writel(void *opaque, uint32_t addr, uint32_t val) +{ + UHCIState *s = opaque; + + addr &= 0x1f; +#ifdef DEBUG + printf("uhci writel port=0x%04x val=0x%08x\n", addr, val); +#endif + switch(addr) { + case 0x08: + s->fl_base_addr = val & ~0xfff; + break; + } +} + +static uint32_t uhci_ioport_readl(void *opaque, uint32_t addr) +{ + UHCIState *s = opaque; + uint32_t val; + + addr &= 0x1f; + switch(addr) { + case 0x08: + val = s->fl_base_addr; + break; + default: + val = 0xffffffff; + break; + } + return val; +} + +static void uhci_attach(USBPort *port1, USBDevice *dev) +{ + UHCIState *s = port1->opaque; + UHCIPort *port = &s->ports[port1->index]; + + if (dev) { + if (port->dev) { + usb_attach(port1, NULL); + } + /* set connect status */ + if (!(port->ctrl & UHCI_PORT_CCS)) { + port->ctrl |= UHCI_PORT_CCS | UHCI_PORT_CSC; + } + /* update speed */ + if (dev->speed == USB_SPEED_LOW) + port->ctrl |= UHCI_PORT_LSDA; + else + port->ctrl &= ~UHCI_PORT_LSDA; + port->dev = dev; + /* send the attach message */ + dev->handle_packet(dev, + USB_MSG_ATTACH, 0, 0, NULL, 0); + } else { + /* set connect status */ + if (!(port->ctrl & UHCI_PORT_CCS)) { + port->ctrl |= UHCI_PORT_CCS | UHCI_PORT_CSC; + } + /* disable port */ + if (port->ctrl & UHCI_PORT_EN) { + port->ctrl &= ~UHCI_PORT_EN; + port->ctrl |= UHCI_PORT_ENC; + } + dev = port->dev; + if (dev) { + /* send the detach message */ + dev->handle_packet(dev, + USB_MSG_DETACH, 0, 0, NULL, 0); + } + port->dev = NULL; + } +} + +static int uhci_broadcast_packet(UHCIState *s, uint8_t pid, + uint8_t devaddr, uint8_t devep, + uint8_t *data, int len) +{ + UHCIPort *port; + USBDevice *dev; + int i, ret; + +#ifdef DEBUG_PACKET + { + const char *pidstr; + switch(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) { + printf(" data_out="); + for(i = 0; i < len; i++) { + printf(" %02x", data[i]); + } + printf("\n"); + } + } +#endif + for(i = 0; i < NB_PORTS; i++) { + port = &s->ports[i]; + dev = port->dev; + if (dev && (port->ctrl & UHCI_PORT_EN)) { + ret = dev->handle_packet(dev, pid, + devaddr, devep, + data, len); + if (ret != USB_RET_NODEV) { +#ifdef DEBUG_PACKET + { + printf(" ret=%d ", ret); + if (pid == USB_TOKEN_IN && ret > 0) { + printf("data_in="); + for(i = 0; i < ret; i++) { + printf(" %02x", data[i]); + } + } + printf("\n"); + } +#endif + return ret; + } + } + } + return USB_RET_NODEV; +} + +/* return -1 if fatal error (frame must be stopped) + 0 if TD successful + 1 if TD unsuccessful or inactive +*/ +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; + + if (td->ctrl & TD_CTRL_IOC) { + *int_mask |= 0x01; + } + + if (!(td->ctrl & TD_CTRL_ACTIVE)) + return 1; + + /* 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 (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, buf, len); + } + } else { + len = 0; + } + break; + default: + /* invalid pid : frame interrupted */ + s->status |= UHCI_STS_HCPERR; + uhci_update_irq(s); + return -1; + } + 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 */ + return 1; + } else { + /* success */ + return 0; + } + } 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); + return 1; + case USB_RET_NAK: + td->ctrl |= TD_CTRL_NAK; + if (pid == USB_TOKEN_SETUP) + goto do_timeout; + return 1; + case USB_RET_STALL: + td->ctrl |= TD_CTRL_STALL; + td->ctrl &= ~TD_CTRL_ACTIVE; + return 1; + case USB_RET_BABBLE: + td->ctrl |= TD_CTRL_BABBLE | TD_CTRL_STALL; + td->ctrl &= ~TD_CTRL_ACTIVE; + /* frame interrupted */ + return -1; + } + } +} + +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; + + if (!(s->cmd & UHCI_CMD_RS)) { + qemu_del_timer(s->frame_timer); + return; + } + 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 */ + 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 { + /* 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 == 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); + 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 */ + 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); + } + /* 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 usb_uhci_init(PCIBus *bus, USBPort **usb_ports) +{ + UHCIState *s; + uint8_t *pci_conf; + UHCIPort *port; + int i; + + s = (UHCIState *)pci_register_device(bus, + "USB-UHCI", sizeof(UHCIState), + -1, + 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] = 1; // interrupt pin 0 + + for(i = 0; i < NB_PORTS; i++) { + port = &s->ports[i]; + port->port.opaque = s; + port->port.index = i; + port->port.attach = uhci_attach; + usb_ports[i] = &port->port; + } + s->frame_timer = qemu_new_timer(vm_clock, uhci_frame_timer, s); + + uhci_reset(s); + + pci_register_io_region(&s->dev, 0, 0x20, + PCI_ADDRESS_SPACE_IO, uhci_map); +} diff --git a/hw/usb.c b/hw/usb.c new file mode 100644 index 0000000000..cfbdb15d11 --- /dev/null +++ b/hw/usb.c @@ -0,0 +1,694 @@ +/* + * QEMU USB emulation + * + * Copyright (c) 2005 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "vl.h" + +void usb_attach(USBPort *port, USBDevice *dev) +{ + port->attach(port, dev); +} + +/**********************/ +/* generic USB device helpers (you are not forced to use them when + writing your USB device driver, but they help handling the + protocol) +*/ + +#define SETUP_STATE_IDLE 0 +#define SETUP_STATE_DATA 1 +#define SETUP_STATE_ACK 2 + +int usb_generic_handle_packet(USBDevice *s, int pid, + uint8_t devaddr, uint8_t devep, + uint8_t *data, int len) +{ + int l, ret = 0; + + switch(pid) { + case USB_MSG_ATTACH: + s->state = USB_STATE_ATTACHED; + break; + case USB_MSG_DETACH: + s->state = USB_STATE_NOTATTACHED; + break; + case USB_MSG_RESET: + s->remote_wakeup = 0; + s->addr = 0; + s->state = USB_STATE_DEFAULT; + s->handle_reset(s); + break; + case USB_TOKEN_SETUP: + if (s->state < USB_STATE_DEFAULT || devaddr != s->addr) + return USB_RET_NODEV; + if (len != 8) + goto fail; + memcpy(s->setup_buf, data, 8); + s->setup_len = (s->setup_buf[7] << 8) | s->setup_buf[6]; + s->setup_index = 0; + if (s->setup_buf[0] & USB_DIR_IN) { + ret = s->handle_control(s, + (s->setup_buf[0] << 8) | s->setup_buf[1], + (s->setup_buf[3] << 8) | s->setup_buf[2], + (s->setup_buf[5] << 8) | s->setup_buf[4], + s->setup_len, + s->data_buf); + if (ret < 0) + return ret; + if (ret < s->setup_len) + s->setup_len = ret; + s->setup_state = SETUP_STATE_DATA; + } else { + if (s->setup_len == 0) + s->setup_state = SETUP_STATE_ACK; + else + s->setup_state = SETUP_STATE_DATA; + } + break; + case USB_TOKEN_IN: + if (s->state < USB_STATE_DEFAULT || devaddr != s->addr) + return USB_RET_NODEV; + switch(devep) { + case 0: + switch(s->setup_state) { + case SETUP_STATE_ACK: + s->setup_state = SETUP_STATE_IDLE; + if (!(s->setup_buf[0] & USB_DIR_IN)) { + ret = s->handle_control(s, + (s->setup_buf[0] << 8) | s->setup_buf[1], + (s->setup_buf[3] << 8) | s->setup_buf[2], + (s->setup_buf[5] << 8) | s->setup_buf[4], + s->setup_len, + s->data_buf); + if (ret > 0) + ret = 0; + } else { + goto fail; + } + break; + case SETUP_STATE_DATA: + if (s->setup_buf[0] & USB_DIR_IN) { + l = s->setup_len - s->setup_index; + if (l > len) + l = len; + memcpy(data, s->data_buf + s->setup_index, l); + s->setup_index += l; + if (s->setup_index >= s->setup_len) + s->setup_state = SETUP_STATE_ACK; + ret = l; + } else { + s->setup_state = SETUP_STATE_IDLE; + goto fail; + } + break; + default: + goto fail; + } + break; + default: + ret = s->handle_data(s, pid, devep, data, len); + break; + } + break; + case USB_TOKEN_OUT: + if (s->state < USB_STATE_DEFAULT || devaddr != s->addr) + return USB_RET_NODEV; + switch(devep) { + case 0: + switch(s->setup_state) { + case SETUP_STATE_ACK: + s->setup_state = SETUP_STATE_IDLE; + if (s->setup_buf[0] & USB_DIR_IN) { + /* transfer OK */ + } else { + goto fail; + } + break; + case SETUP_STATE_DATA: + if (!(s->setup_buf[0] & USB_DIR_IN)) { + l = s->setup_len - s->setup_index; + if (l > len) + l = len; + memcpy(s->data_buf + s->setup_index, data, l); + s->setup_index += l; + if (s->setup_index >= s->setup_len) + s->setup_state = SETUP_STATE_ACK; + ret = l; + } else { + s->setup_state = SETUP_STATE_IDLE; + goto fail; + } + break; + default: + goto fail; + } + break; + default: + ret = s->handle_data(s, pid, devep, data, len); + break; + } + break; + default: + fail: + ret = USB_RET_STALL; + break; + } + return ret; +} + +/* XXX: fix overflow */ +int set_usb_string(uint8_t *buf, const char *str) +{ + int len, i; + uint8_t *q; + + q = buf; + len = strlen(str); + *q++ = 2 * len + 1; + *q++ = 3; + for(i = 0; i < len; i++) { + *q++ = str[i]; + *q++ = 0; + } + return q - buf; +} + +/**********************/ +/* USB hub emulation */ + +//#define DEBUG + +#define MAX_PORTS 8 + +#define DS_IDLE 0 +#define DS_CONTROL_IN 1 +#define DS_CONTROL_OUT 2 + +typedef struct USBHubPort { + USBPort port; + USBDevice *dev; + uint16_t wPortStatus; + uint16_t wPortChange; +} USBHubPort; + +typedef struct USBHubState { + USBDevice dev; + int nb_ports; + USBHubPort ports[MAX_PORTS]; +} USBHubState; + +#define ClearHubFeature (0x2000 | USB_REQ_CLEAR_FEATURE) +#define ClearPortFeature (0x2300 | USB_REQ_CLEAR_FEATURE) +#define GetHubDescriptor (0xa000 | USB_REQ_GET_DESCRIPTOR) +#define GetHubStatus (0xa000 | USB_REQ_GET_STATUS) +#define GetPortStatus (0xa300 | USB_REQ_GET_STATUS) +#define SetHubFeature (0x2000 | USB_REQ_SET_FEATURE) +#define SetPortFeature (0x2300 | USB_REQ_SET_FEATURE) + +#define PORT_STAT_CONNECTION 0x0001 +#define PORT_STAT_ENABLE 0x0002 +#define PORT_STAT_SUSPEND 0x0004 +#define PORT_STAT_OVERCURRENT 0x0008 +#define PORT_STAT_RESET 0x0010 +#define PORT_STAT_POWER 0x0100 +#define PORT_STAT_LOW_SPEED 0x0200 +#define PORT_STAT_HIGH_SPEED 0x0400 +#define PORT_STAT_TEST 0x0800 +#define PORT_STAT_INDICATOR 0x1000 + +#define PORT_STAT_C_CONNECTION 0x0001 +#define PORT_STAT_C_ENABLE 0x0002 +#define PORT_STAT_C_SUSPEND 0x0004 +#define PORT_STAT_C_OVERCURRENT 0x0008 +#define PORT_STAT_C_RESET 0x0010 + +#define PORT_CONNECTION 0 +#define PORT_ENABLE 1 +#define PORT_SUSPEND 2 +#define PORT_OVERCURRENT 3 +#define PORT_RESET 4 +#define PORT_POWER 8 +#define PORT_LOWSPEED 9 +#define PORT_HIGHSPEED 10 +#define PORT_C_CONNECTION 16 +#define PORT_C_ENABLE 17 +#define PORT_C_SUSPEND 18 +#define PORT_C_OVERCURRENT 19 +#define PORT_C_RESET 20 +#define PORT_TEST 21 +#define PORT_INDICATOR 22 + +/* same as Linux kernel root hubs */ + +static const uint8_t qemu_hub_dev_descriptor[] = { + 0x12, /* __u8 bLength; */ + 0x01, /* __u8 bDescriptorType; Device */ + 0x10, 0x01, /* __u16 bcdUSB; v1.1 */ + + 0x09, /* __u8 bDeviceClass; HUB_CLASSCODE */ + 0x00, /* __u8 bDeviceSubClass; */ + 0x00, /* __u8 bDeviceProtocol; [ low/full speeds only ] */ + 0x08, /* __u8 bMaxPacketSize0; 8 Bytes */ + + 0x00, 0x00, /* __u16 idVendor; */ + 0x00, 0x00, /* __u16 idProduct; */ + 0x01, 0x01, /* __u16 bcdDevice */ + + 0x03, /* __u8 iManufacturer; */ + 0x02, /* __u8 iProduct; */ + 0x01, /* __u8 iSerialNumber; */ + 0x01 /* __u8 bNumConfigurations; */ +}; + +/* XXX: patch interrupt size */ +static const uint8_t qemu_hub_config_descriptor[] = { + + /* one configuration */ + 0x09, /* __u8 bLength; */ + 0x02, /* __u8 bDescriptorType; Configuration */ + 0x19, 0x00, /* __u16 wTotalLength; */ + 0x01, /* __u8 bNumInterfaces; (1) */ + 0x01, /* __u8 bConfigurationValue; */ + 0x00, /* __u8 iConfiguration; */ + 0xc0, /* __u8 bmAttributes; + Bit 7: must be set, + 6: Self-powered, + 5: Remote wakeup, + 4..0: resvd */ + 0x00, /* __u8 MaxPower; */ + + /* USB 1.1: + * USB 2.0, single TT organization (mandatory): + * one interface, protocol 0 + * + * USB 2.0, multiple TT organization (optional): + * two interfaces, protocols 1 (like single TT) + * and 2 (multiple TT mode) ... config is + * sometimes settable + * NOT IMPLEMENTED + */ + + /* one interface */ + 0x09, /* __u8 if_bLength; */ + 0x04, /* __u8 if_bDescriptorType; Interface */ + 0x00, /* __u8 if_bInterfaceNumber; */ + 0x00, /* __u8 if_bAlternateSetting; */ + 0x01, /* __u8 if_bNumEndpoints; */ + 0x09, /* __u8 if_bInterfaceClass; HUB_CLASSCODE */ + 0x00, /* __u8 if_bInterfaceSubClass; */ + 0x00, /* __u8 if_bInterfaceProtocol; [usb1.1 or single tt] */ + 0x00, /* __u8 if_iInterface; */ + + /* one endpoint (status change endpoint) */ + 0x07, /* __u8 ep_bLength; */ + 0x05, /* __u8 ep_bDescriptorType; Endpoint */ + 0x81, /* __u8 ep_bEndpointAddress; IN Endpoint 1 */ + 0x03, /* __u8 ep_bmAttributes; Interrupt */ + 0x02, 0x00, /* __u16 ep_wMaxPacketSize; 1 + (MAX_ROOT_PORTS / 8) */ + 0xff /* __u8 ep_bInterval; (255ms -- usb 2.0 spec) */ +}; + +static const uint8_t qemu_hub_hub_descriptor[] = +{ + 0x09, /* __u8 bLength; */ + 0x29, /* __u8 bDescriptorType; Hub-descriptor */ + 0x00, /* __u8 bNbrPorts; (patched later) */ + 0x0a, /* __u16 wHubCharacteristics; */ + 0x00, /* (per-port OC, no power switching) */ + 0x01, /* __u8 bPwrOn2pwrGood; 2ms */ + 0x00, /* __u8 bHubContrCurrent; 0 mA */ + 0x00, /* __u8 DeviceRemovable; *** 7 Ports max *** */ + 0xff /* __u8 PortPwrCtrlMask; *** 7 ports max *** */ +}; + +static void usb_hub_attach(USBPort *port1, USBDevice *dev) +{ + USBHubState *s = port1->opaque; + USBHubPort *port = &s->ports[port1->index]; + + if (dev) { + if (port->dev) + usb_attach(port1, NULL); + + port->wPortStatus |= PORT_STAT_CONNECTION; + port->wPortChange |= PORT_STAT_C_CONNECTION; + if (dev->speed == USB_SPEED_LOW) + port->wPortStatus |= PORT_STAT_LOW_SPEED; + else + port->wPortStatus &= ~PORT_STAT_LOW_SPEED; + port->dev = dev; + } else { + dev = port->dev; + if (dev) { + port->wPortStatus &= ~PORT_STAT_CONNECTION; + port->wPortChange |= PORT_STAT_C_CONNECTION; + if (port->wPortStatus & PORT_STAT_ENABLE) { + port->wPortStatus &= ~PORT_STAT_ENABLE; + port->wPortChange |= PORT_STAT_C_ENABLE; + } + port->dev = NULL; + } + } +} + +static void usb_hub_handle_reset(USBDevice *dev) +{ + /* XXX: do it */ +} + +static int usb_hub_handle_control(USBDevice *dev, int request, int value, + int index, int length, uint8_t *data) +{ + USBHubState *s = (USBHubState *)dev; + int ret; + + switch(request) { + case DeviceRequest | USB_REQ_GET_STATUS: + data[0] = (1 << USB_DEVICE_SELF_POWERED) | + (dev->remote_wakeup << USB_DEVICE_REMOTE_WAKEUP); + data[1] = 0x00; + ret = 2; + break; + case DeviceOutRequest | USB_REQ_CLEAR_FEATURE: + if (value == USB_DEVICE_REMOTE_WAKEUP) { + dev->remote_wakeup = 0; + } else { + goto fail; + } + ret = 0; + break; + case DeviceOutRequest | USB_REQ_SET_FEATURE: + if (value == USB_DEVICE_REMOTE_WAKEUP) { + dev->remote_wakeup = 1; + } else { + goto fail; + } + ret = 0; + break; + case DeviceOutRequest | USB_REQ_SET_ADDRESS: + dev->addr = value; + ret = 0; + break; + case DeviceRequest | USB_REQ_GET_DESCRIPTOR: + switch(value >> 8) { + case USB_DT_DEVICE: + memcpy(data, qemu_hub_dev_descriptor, + sizeof(qemu_hub_dev_descriptor)); + ret = sizeof(qemu_hub_dev_descriptor); + break; + case USB_DT_CONFIG: + memcpy(data, qemu_hub_config_descriptor, + sizeof(qemu_hub_config_descriptor)); + ret = sizeof(qemu_hub_config_descriptor); + break; + case USB_DT_STRING: + switch(value & 0xff) { + case 0: + /* language ids */ + data[0] = 4; + data[1] = 3; + data[2] = 0x09; + data[3] = 0x04; + ret = 4; + break; + case 1: + /* serial number */ + ret = set_usb_string(data, "314159"); + break; + case 2: + /* product description */ + ret = set_usb_string(data, "QEMU USB Hub"); + break; + case 3: + /* vendor description */ + ret = set_usb_string(data, "QEMU " QEMU_VERSION); + break; + default: + goto fail; + } + break; + default: + goto fail; + } + break; + case DeviceRequest | USB_REQ_GET_CONFIGURATION: + data[0] = 1; + ret = 1; + break; + case DeviceOutRequest | USB_REQ_SET_CONFIGURATION: + ret = 0; + break; + case DeviceRequest | USB_REQ_GET_INTERFACE: + data[0] = 0; + ret = 1; + break; + case DeviceOutRequest | USB_REQ_SET_INTERFACE: + ret = 0; + break; + /* usb specific requests */ + case GetHubStatus: + data[0] = 0; + data[1] = 0; + data[2] = 0; + data[3] = 0; + ret = 4; + break; + case GetPortStatus: + { + unsigned int n = index - 1; + USBHubPort *port; + if (n >= s->nb_ports) + goto fail; + port = &s->ports[n]; + data[0] = port->wPortStatus; + data[1] = port->wPortStatus >> 8; + data[2] = port->wPortChange; + data[3] = port->wPortChange >> 8; + ret = 4; + } + break; + case SetHubFeature: + case ClearHubFeature: + if (value == 0 || value == 1) { + } else { + goto fail; + } + ret = 0; + break; + case SetPortFeature: + { + unsigned int n = index - 1; + USBHubPort *port; + USBDevice *dev; + if (n >= s->nb_ports) + goto fail; + port = &s->ports[n]; + dev = port->dev; + switch(value) { + case PORT_SUSPEND: + port->wPortStatus |= PORT_STAT_SUSPEND; + break; + case PORT_RESET: + if (dev) { + dev->handle_packet(dev, + USB_MSG_RESET, 0, 0, NULL, 0); + port->wPortChange |= PORT_STAT_C_RESET; + /* set enable bit */ + port->wPortChange |= PORT_STAT_C_ENABLE; + port->wPortStatus |= PORT_STAT_ENABLE; + } + break; + case PORT_POWER: + break; + default: + goto fail; + } + ret = 0; + } + break; + case ClearPortFeature: + { + unsigned int n = index - 1; + USBHubPort *port; + USBDevice *dev; + if (n >= s->nb_ports) + goto fail; + port = &s->ports[n]; + dev = port->dev; + switch(value) { + case PORT_ENABLE: + port->wPortStatus &= ~PORT_STAT_ENABLE; + break; + case PORT_C_ENABLE: + port->wPortChange &= ~PORT_STAT_C_ENABLE; + break; + case PORT_SUSPEND: + port->wPortStatus &= ~PORT_STAT_SUSPEND; + break; + case PORT_C_SUSPEND: + port->wPortChange &= ~PORT_STAT_C_SUSPEND; + break; + case PORT_C_CONNECTION: + port->wPortChange &= ~PORT_STAT_C_CONNECTION; + break; + case PORT_C_OVERCURRENT: + port->wPortChange &= ~PORT_STAT_C_OVERCURRENT; + break; + case PORT_C_RESET: + port->wPortChange &= ~PORT_STAT_C_RESET; + break; + default: + goto fail; + } + ret = 0; + } + break; + case GetHubDescriptor: + memcpy(data, qemu_hub_hub_descriptor, + sizeof(qemu_hub_hub_descriptor)); + data[2] = s->nb_ports; + ret = sizeof(qemu_hub_hub_descriptor); + break; + default: + fail: + ret = USB_RET_STALL; + } + return ret; +} + +static int usb_hub_handle_data(USBDevice *dev, int pid, + uint8_t devep, uint8_t *data, int len) +{ + USBHubState *s = (USBHubState *)dev; + int ret; + + switch(pid) { + case USB_TOKEN_IN: + if (devep == 1) { + USBHubPort *port; + unsigned int status; + int i, n; + n = (s->nb_ports + 1 + 7) / 8; + if (n > len) + return USB_RET_BABBLE; + status = 0; + for(i = 0; i < s->nb_ports; i++) { + port = &s->ports[i]; + if (port->wPortChange) + status |= (1 << (i + 1)); + } + if (status != 0) { + for(i = 0; i < n; i++) { + data[i] = status >> (8 * i); + } + ret = n; + } else { + ret = 0; + } + } else { + goto fail; + } + break; + case USB_TOKEN_OUT: + default: + fail: + ret = USB_RET_STALL; + break; + } + return ret; +} + +static int usb_hub_broadcast_packet(USBHubState *s, int pid, + uint8_t devaddr, uint8_t devep, + uint8_t *data, int len) +{ + USBHubPort *port; + USBDevice *dev; + int i, ret; + + for(i = 0; i < s->nb_ports; i++) { + port = &s->ports[i]; + dev = port->dev; + if (dev && (port->wPortStatus & PORT_STAT_ENABLE)) { + ret = dev->handle_packet(dev, pid, + devaddr, devep, + data, len); + if (ret != USB_RET_NODEV) { + return ret; + } + } + } + return USB_RET_NODEV; +} + +static int usb_hub_handle_packet(USBDevice *dev, int pid, + uint8_t devaddr, uint8_t devep, + uint8_t *data, int len) +{ + USBHubState *s = (USBHubState *)dev; + +#if defined(DEBUG) && 0 + printf("usb_hub: pid=0x%x\n", pid); +#endif + if (dev->state == USB_STATE_DEFAULT && + dev->addr != 0 && + devaddr != dev->addr && + (pid == USB_TOKEN_SETUP || + pid == USB_TOKEN_OUT || + pid == USB_TOKEN_IN)) { + /* broadcast the packet to the devices */ + return usb_hub_broadcast_packet(s, pid, devaddr, devep, data, len); + } + return usb_generic_handle_packet(dev, pid, devaddr, devep, data, len); +} + +USBDevice *usb_hub_init(USBPort **usb_ports, int nb_ports) +{ + USBHubState *s; + USBHubPort *port; + int i; + + if (nb_ports > MAX_PORTS) + return NULL; + s = qemu_mallocz(sizeof(USBHubState)); + if (!s) + return NULL; + s->dev.speed = USB_SPEED_FULL; + s->dev.handle_packet = usb_hub_handle_packet; + + /* generic USB device init */ + s->dev.handle_reset = usb_hub_handle_reset; + s->dev.handle_control = usb_hub_handle_control; + s->dev.handle_data = usb_hub_handle_data; + + s->nb_ports = nb_ports; + for(i = 0; i < s->nb_ports; i++) { + port = &s->ports[i]; + port->wPortStatus = PORT_STAT_POWER; + port->wPortChange = 0; + port->port.attach = usb_hub_attach; + port->port.opaque = s; + port->port.index = i; + usb_ports[i] = &port->port; + } + return (USBDevice *)s; +} diff --git a/hw/usb.h b/hw/usb.h new file mode 100644 index 0000000000..1bfce3b516 --- /dev/null +++ b/hw/usb.h @@ -0,0 +1,139 @@ +/* + * QEMU USB API + * + * Copyright (c) 2005 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#define USB_TOKEN_SETUP 0x2d +#define USB_TOKEN_IN 0x69 /* device -> host */ +#define USB_TOKEN_OUT 0xe1 /* host -> device */ + +/* specific usb messages, also sent in the 'pid' parameter */ +#define USB_MSG_ATTACH 0x100 +#define USB_MSG_DETACH 0x101 +#define USB_MSG_RESET 0x102 + +#define USB_RET_NODEV (-1) +#define USB_RET_NAK (-2) +#define USB_RET_STALL (-3) +#define USB_RET_BABBLE (-4) + +#define USB_SPEED_LOW 0 +#define USB_SPEED_FULL 1 +#define USB_SPEED_HIGH 2 + +#define USB_STATE_NOTATTACHED 0 +#define USB_STATE_ATTACHED 1 +//#define USB_STATE_POWERED 2 +#define USB_STATE_DEFAULT 3 +//#define USB_STATE_ADDRESS 4 +//#define USB_STATE_CONFIGURED 5 +#define USB_STATE_SUSPENDED 6 + +#define USB_DIR_OUT 0 +#define USB_DIR_IN 0x80 + +#define USB_TYPE_MASK (0x03 << 5) +#define USB_TYPE_STANDARD (0x00 << 5) +#define USB_TYPE_CLASS (0x01 << 5) +#define USB_TYPE_VENDOR (0x02 << 5) +#define USB_TYPE_RESERVED (0x03 << 5) + +#define USB_RECIP_MASK 0x1f +#define USB_RECIP_DEVICE 0x00 +#define USB_RECIP_INTERFACE 0x01 +#define USB_RECIP_ENDPOINT 0x02 +#define USB_RECIP_OTHER 0x03 + +#define DeviceRequest ((USB_DIR_IN|USB_TYPE_STANDARD|USB_RECIP_DEVICE)<<8) +#define DeviceOutRequest ((USB_DIR_OUT|USB_TYPE_STANDARD|USB_RECIP_DEVICE)<<8) + +#define USB_REQ_GET_STATUS 0x00 +#define USB_REQ_CLEAR_FEATURE 0x01 +#define USB_REQ_SET_FEATURE 0x03 +#define USB_REQ_SET_ADDRESS 0x05 +#define USB_REQ_GET_DESCRIPTOR 0x06 +#define USB_REQ_SET_DESCRIPTOR 0x07 +#define USB_REQ_GET_CONFIGURATION 0x08 +#define USB_REQ_SET_CONFIGURATION 0x09 +#define USB_REQ_GET_INTERFACE 0x0A +#define USB_REQ_SET_INTERFACE 0x0B +#define USB_REQ_SYNCH_FRAME 0x0C + +#define USB_DEVICE_SELF_POWERED 0 +#define USB_DEVICE_REMOTE_WAKEUP 1 + +#define USB_DT_DEVICE 0x01 +#define USB_DT_CONFIG 0x02 +#define USB_DT_STRING 0x03 +#define USB_DT_INTERFACE 0x04 +#define USB_DT_ENDPOINT 0x05 + +typedef struct USBPort USBPort; +typedef struct USBDevice USBDevice; + +/* definition of a USB device */ +struct USBDevice { + void *opaque; + int (*handle_packet)(USBDevice *dev, int pid, + uint8_t devaddr, uint8_t devep, + uint8_t *data, int len); + int speed; + + /* The following fields are used by the generic USB device + layer. They are here just to avoid creating a new structure for + them. */ + void (*handle_reset)(USBDevice *dev); + int (*handle_control)(USBDevice *dev, int request, int value, + int index, int length, uint8_t *data); + int (*handle_data)(USBDevice *dev, int pid, uint8_t devep, + uint8_t *data, int len); + uint8_t addr; + + int state; + uint8_t setup_buf[8]; + uint8_t data_buf[1024]; + int remote_wakeup; + int setup_state; + int setup_len; + int setup_index; +}; + +/* USB port on which a device can be connected */ +struct USBPort { + void (*attach)(USBPort *port, USBDevice *dev); + void *opaque; + int index; /* internal port index, may be used with the opaque */ +}; + +void usb_attach(USBPort *port, USBDevice *dev); +int usb_generic_handle_packet(USBDevice *s, int pid, + uint8_t devaddr, uint8_t devep, + uint8_t *data, int len); +int set_usb_string(uint8_t *buf, const char *str); + +/* usb hub */ +USBDevice *usb_hub_init(USBPort **usb_ports, int nb_ports); + +/* usb-uhci.c */ +void usb_uhci_init(PCIBus *bus, USBPort **usb_ports); + +/* usb-linux.c */ +USBDevice *usb_host_hub_init(void); diff --git a/usb-linux.c b/usb-linux.c new file mode 100644 index 0000000000..87f3d120b9 --- /dev/null +++ b/usb-linux.c @@ -0,0 +1,309 @@ +/* + * Linux host USB redirector + * + * Copyright (c) 2005 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "vl.h" + +#if defined(__linux__) +#include +#include +#include +#include + +/* We redefine it to avoid version problems */ +struct usb_ctrltransfer { + uint8_t bRequestType; + uint8_t bRequest; + uint16_t wValue; + uint16_t wIndex; + uint16_t wLength; + uint32_t timeout; + void *data; +}; + +//#define DEBUG + +#define MAX_DEVICES 8 + +#define USBDEVFS_PATH "/proc/bus/usb" + +typedef struct USBHostDevice { + USBDevice dev; + int fd; +} USBHostDevice; + +typedef struct USBHostHubState { + USBDevice *hub_dev; + USBPort *hub_ports[MAX_DEVICES]; + USBDevice *hub_devices[MAX_DEVICES]; +} USBHostHubState; + +static void usb_host_handle_reset(USBDevice *dev) +{ +#if 0 + USBHostDevice *s = (USBHostDevice *)dev; + /* USBDEVFS_RESET, but not the first time as it has already be + done by the host OS */ + ioctl(s->fd, USBDEVFS_RESET); +#endif +} + +static int usb_host_handle_control(USBDevice *dev, + int request, + int value, + int index, + int length, + uint8_t *data) +{ + USBHostDevice *s = (USBHostDevice *)dev; + struct usb_ctrltransfer ct; + int ret; + + if (request == (DeviceOutRequest | USB_REQ_SET_ADDRESS)) { + /* specific SET_ADDRESS support */ + dev->addr = value; + return 0; + } else { + ct.bRequestType = request >> 8; + ct.bRequest = request; + ct.wValue = value; + ct.wIndex = index; + ct.wLength = length; + ct.timeout = 50; + ct.data = data; + ret = ioctl(s->fd, USBDEVFS_CONTROL, &ct); + if (ret < 0) { + switch(errno) { + case ETIMEDOUT: + return USB_RET_NAK; + default: + return USB_RET_STALL; + } + } else { + return ret; + } + } +} + +static int usb_host_handle_data(USBDevice *dev, int pid, + uint8_t devep, + uint8_t *data, int len) +{ + USBHostDevice *s = (USBHostDevice *)dev; + struct usbdevfs_bulktransfer bt; + int ret; + + /* XXX: optimize and handle all data types by looking at the + config descriptor */ + if (pid == USB_TOKEN_IN) + devep |= 0x80; + bt.ep = devep; + bt.len = len; + bt.timeout = 50; + bt.data = data; + ret = ioctl(s->fd, USBDEVFS_BULK, &bt); + if (ret < 0) { + switch(errno) { + case ETIMEDOUT: + return USB_RET_NAK; + case EPIPE: + default: +#ifdef DEBUG + printf("handle_data: errno=%d\n", errno); +#endif + return USB_RET_STALL; + } + } else { + return ret; + } +} + +static int usb_host_handle_packet(USBDevice *dev, int pid, + uint8_t devaddr, uint8_t devep, + uint8_t *data, int len) +{ + return usb_generic_handle_packet(dev, pid, devaddr, devep, data, len); +} + +/* XXX: exclude high speed devices or implement EHCI */ +static void scan_host_device(USBHostHubState *s, const char *filename) +{ + int fd, interface, ret, i; + USBHostDevice *dev; + struct usbdevfs_connectinfo ci; + uint8_t descr[1024]; + int descr_len, dev_descr_len, config_descr_len, nb_interfaces; + +#ifdef DEBUG + printf("scanning %s\n", filename); +#endif + fd = open(filename, O_RDWR); + if (fd < 0) { + perror(filename); + return; + } + + /* read the config description */ + descr_len = read(fd, descr, sizeof(descr)); + if (descr_len <= 0) { + perror("read descr"); + goto fail; + } + + i = 0; + dev_descr_len = descr[0]; + if (dev_descr_len > descr_len) + goto fail; + i += dev_descr_len; + config_descr_len = descr[i]; + if (i + config_descr_len > descr_len) + goto fail; + nb_interfaces = descr[i + 4]; + if (nb_interfaces != 1) { + /* NOTE: currently we grab only one interface */ + goto fail; + } + /* XXX: only grab if all interfaces are free */ + interface = 0; + ret = ioctl(fd, USBDEVFS_CLAIMINTERFACE, &interface); + if (ret < 0) { + if (errno == EBUSY) { +#ifdef DEBUG + printf("%s already grabbed\n", filename); +#endif + } else { + perror("USBDEVFS_CLAIMINTERFACE"); + } + fail: + close(fd); + return; + } + + ret = ioctl(fd, USBDEVFS_CONNECTINFO, &ci); + if (ret < 0) { + perror("USBDEVFS_CONNECTINFO"); + goto fail; + } + +#ifdef DEBUG + printf("%s grabbed\n", filename); +#endif + + /* find a free slot */ + for(i = 0; i < MAX_DEVICES; i++) { + if (!s->hub_devices[i]) + break; + } + if (i == MAX_DEVICES) { +#ifdef DEBUG + printf("too many host devices\n"); + goto fail; +#endif + } + + dev = qemu_mallocz(sizeof(USBHostDevice)); + if (!dev) + goto fail; + dev->fd = fd; + if (ci.slow) + dev->dev.speed = USB_SPEED_LOW; + else + dev->dev.speed = USB_SPEED_HIGH; + dev->dev.handle_packet = usb_host_handle_packet; + + dev->dev.handle_reset = usb_host_handle_reset; + dev->dev.handle_control = usb_host_handle_control; + dev->dev.handle_data = usb_host_handle_data; + + s->hub_devices[i] = (USBDevice *)dev; + + /* activate device on hub */ + usb_attach(s->hub_ports[i], s->hub_devices[i]); +} + +static void scan_host_devices(USBHostHubState *s, const char *bus_path) +{ + DIR *d; + struct dirent *de; + char buf[1024]; + + d = opendir(bus_path); + if (!d) + return; + for(;;) { + de = readdir(d); + if (!de) + break; + if (de->d_name[0] != '.') { + snprintf(buf, sizeof(buf), "%s/%s", bus_path, de->d_name); + scan_host_device(s, buf); + } + } + closedir(d); +} + +static void scan_host_buses(USBHostHubState *s) +{ + DIR *d; + struct dirent *de; + char buf[1024]; + + d = opendir(USBDEVFS_PATH); + if (!d) + return; + for(;;) { + de = readdir(d); + if (!de) + break; + if (isdigit(de->d_name[0])) { + snprintf(buf, sizeof(buf), "%s/%s", USBDEVFS_PATH, de->d_name); + scan_host_devices(s, buf); + } + } + closedir(d); +} + +/* virtual hub containing the USB devices of the host */ +USBDevice *usb_host_hub_init(void) +{ + USBHostHubState *s; + s = qemu_mallocz(sizeof(USBHostHubState)); + if (!s) + return NULL; + s->hub_dev = usb_hub_init(s->hub_ports, MAX_DEVICES); + if (!s->hub_dev) { + free(s); + return NULL; + } + scan_host_buses(s); + return s->hub_dev; +} + +#else + +/* XXX: modify configure to compile the right host driver */ +USBDevice *usb_host_hub_init(void) +{ + return NULL; +} + +#endif diff --git a/vl.c b/vl.c index 1ca0c347a9..d551a4e55c 100644 --- a/vl.c +++ b/vl.c @@ -153,6 +153,7 @@ CharDriverState *parallel_hds[MAX_PARALLEL_PORTS]; #ifdef TARGET_I386 int win2k_install_hack = 0; #endif +int usb_enabled = 0; /***********************************************************/ /* x86 ISA bus support */ @@ -2986,6 +2987,7 @@ enum { QEMU_OPTION_pidfile, QEMU_OPTION_no_kqemu, QEMU_OPTION_win2k_hack, + QEMU_OPTION_usb, }; typedef struct QEMUOption { @@ -3060,6 +3062,7 @@ const QEMUOption qemu_options[] = { { "full-screen", 0, QEMU_OPTION_full_screen }, { "pidfile", HAS_ARG, QEMU_OPTION_pidfile }, { "win2k-hack", 0, QEMU_OPTION_win2k_hack }, + { "usb", 0, QEMU_OPTION_usb }, /* temporary options */ { "pci", 0, QEMU_OPTION_pci }, @@ -3646,6 +3649,9 @@ int main(int argc, char **argv) kqemu_allowed = 0; break; #endif + case QEMU_OPTION_usb: + usb_enabled = 1; + break; } } } diff --git a/vl.h b/vl.h index 543d57972a..e1a2ec3f10 100644 --- a/vl.h +++ b/vl.h @@ -135,6 +135,7 @@ extern int graphic_depth; extern const char *keyboard_layout; extern int kqemu_allowed; extern int win2k_install_hack; +extern int usb_enabled; /* XXX: make it dynamic */ #if defined (TARGET_PPC) @@ -859,6 +860,8 @@ void adb_mouse_init(ADBBusState *bus); extern ADBBusState adb_bus; int cuda_init(SetIRQFunc *set_irq, void *irq_opaque, int irq); +#include "hw/usb.h" + #endif /* defined(QEMU_TOOL) */ /* monitor.c */