diff --git a/tools/ioemu/hw/pc.c b/tools/ioemu/hw/pc.c index 247375f544..b4ac188cca 100644 --- a/tools/ioemu/hw/pc.c +++ b/tools/ioemu/hw/pc.c @@ -40,6 +40,7 @@ int speaker_data_on; int dummy_refresh_clock; static fdctrl_t *floppy_controller; static RTCState *rtc_state; +static USBPort *usb_root_ports[2]; static void ioport80_write(void *opaque, uint32_t addr, uint32_t data) { @@ -584,6 +585,11 @@ void pc_init(uint64_t ram_size, int vga_ram_size, int boot_device, cmos_init(ram_size, boot_device, bs_table, timeoffset); acpi_init(0x8000); + if (pci_enabled && usb_enabled) { + usb_uhci_init(pci_bus, usb_root_ports); + usb_attach(usb_root_ports[0], vm_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/tools/ioemu/hw/pckbd.c b/tools/ioemu/hw/pckbd.c index c9b0c5e00e..b14ff046ff 100644 --- a/tools/ioemu/hw/pckbd.c +++ b/tools/ioemu/hw/pckbd.c @@ -118,6 +118,9 @@ #define SUMMA_MAXX (16000 - 1) #define SUMMA_MAXY (16000 - 1) +#define MAX_ABSX 0x7fff +#define MAX_ABSY 0x7fff + typedef struct { uint8_t aux[KBD_QUEUE_SIZE]; uint8_t data[KBD_QUEUE_SIZE]; @@ -149,8 +152,6 @@ typedef struct KBDState { uint8_t mouse_wrap; uint8_t mouse_type; /* 0 = PS2, 3 = IMPS/2, 4 = IMEX */ uint8_t mouse_detect_state; - int mouse_x; /* absolute coordinates (for mousepad) */ - int mouse_y; int mouse_dx; /* current values, needed for 'poll' mode */ int mouse_dy; int mouse_dz; @@ -422,7 +423,7 @@ static void kbd_write_keyboard(KBDState *s, int val) int mouse_maxx, mouse_maxy; -static int kbd_mouse_send_packet(KBDState *s) +static void kbd_mouse_send_packet(KBDState *s) { unsigned int b; int dx1, dy1, dz1; @@ -430,100 +431,63 @@ static int kbd_mouse_send_packet(KBDState *s) dx1 = s->mouse_dx; dy1 = s->mouse_dy; dz1 = s->mouse_dz; + /* XXX: increase range to 8 bits ? */ + if (dx1 > 127) + dx1 = 127; + else if (dx1 < -127) + dx1 = -127; + if (dy1 > 127) + dy1 = 127; + else if (dy1 < -127) + dy1 = -127; + b = 0x08 | ((dx1 < 0) << 4) | ((dy1 < 0) << 5) | (s->mouse_buttons & 0x07); + kbd_queue(s, b, 1); + kbd_queue(s, dx1 & 0xff, 1); + kbd_queue(s, dy1 & 0xff, 1); + /* extra byte for IMPS/2 or IMEX */ switch(s->mouse_type) { - - case TABLET: /* Summagraphics pen tablet */ - if (SummaState.report_mode == MODE_STREAM) { - dx1 = s->mouse_x; - dy1 = s->mouse_y; - if (SummaState.origin == ORIGIN_LOWER_LEFT) - dy1 = mouse_maxy - dy1; - dx1 = ((dx1 * SUMMA_MAXX) / mouse_maxx) + SUMMA_BORDER; - dy1 = ((dy1 * SUMMA_MAXY) / mouse_maxy) + SUMMA_BORDER; - ser_queue(s->serial, 0x80 | (s->mouse_buttons & 7)); - ser_queue(s->serial, dx1 & 0x7f); - ser_queue(s->serial, dx1 >> 7); - ser_queue(s->serial, dy1 & 0x7f); - ser_queue(s->serial, dy1 >> 7); - } - s->mouse_dx = 0; - s->mouse_dy = 0; - s->mouse_dz = 0; - return 0; - - default: /* PS/2 style mice */ - /* XXX: increase range to 8 bits ? */ - if (dx1 > 127) - dx1 = 127; - else if (dx1 < -127) - dx1 = -127; - if (dy1 > 127) - dy1 = 127; - else if (dy1 < -127) - dy1 = -127; - b = 0x08 | ((dx1 < 0) << 4) | ((dy1 < 0) << 5) | (s->mouse_buttons & 0x07); - kbd_queue(s, b, 1); - kbd_queue(s, dx1 & 0xff, 1); - kbd_queue(s, dy1 & 0xff, 1); - /* extra byte for IMPS/2 or IMEX */ - switch(s->mouse_type) { - - default: - break; - - case IMPS2: - if (dz1 > 127) - dz1 = 127; - else if (dz1 < -127) - dz1 = -127; - kbd_queue(s, dz1 & 0xff, 1); - break; - - case IMEX: - if (dz1 > 7) - dz1 = 7; - else if (dz1 < -7) - dz1 = -7; - b = (dz1 & 0x0f) | ((s->mouse_buttons & 0x18) << 1); - kbd_queue(s, b, 1); - break; - } - - /* update deltas */ - s->mouse_dx -= dx1; - s->mouse_dy -= dy1; - s->mouse_dz -= dz1; - return s->mouse_dx || s->mouse_dy || s->mouse_dz; - + default: + break; + case IMPS2: + if (dz1 > 127) + dz1 = 127; + else if (dz1 < -127) + dz1 = -127; + kbd_queue(s, dz1 & 0xff, 1); + break; + case IMEX: + if (dz1 > 7) + dz1 = 7; + else if (dz1 < -7) + dz1 = -7; + b = (dz1 & 0x0f) | ((s->mouse_buttons & 0x18) << 1); + kbd_queue(s, b, 1); + break; } + + /* update deltas */ + s->mouse_dx -= dx1; + s->mouse_dy -= dy1; + s->mouse_dz -= dz1; } -static void pc_kbd_mouse_event(void *opaque, - int dx, int dy, int dz, int buttons_state, - int x, int y) +static void summa_mouse_event(void *opaque, int x, int y, int z, int buttons_state) { KBDState *s = opaque; - /* check if deltas are recorded when disabled */ - if (!(s->mouse_status & MOUSE_STATUS_ENABLED)) - return; - - s->mouse_x = x; - s->mouse_y = y; - s->mouse_dx += dx; - s->mouse_dy -= dy; - s->mouse_dz += dz; - /* XXX: SDL sometimes generates nul events: we delete them */ - if (s->mouse_dx == 0 && s->mouse_dy == 0 && s->mouse_dz == 0 && - s->mouse_buttons == buttons_state) - return; - s->mouse_buttons = buttons_state; - - if (!(s->mouse_status & MOUSE_STATUS_REMOTE) && - (s->queue.count < (KBD_QUEUE_SIZE - 16))) { - while (kbd_mouse_send_packet(s)) - ; + if (SummaState.report_mode == MODE_STREAM) { + if (SummaState.origin == ORIGIN_LOWER_LEFT) + y = mouse_maxy - y; + x = ((x * SUMMA_MAXX) / MAX_ABSX) + SUMMA_BORDER; + y = ((y * SUMMA_MAXY) / MAX_ABSY) + SUMMA_BORDER; +fprintf(stderr, "summa_mouse_event: x, y - %d, %d\n", x, y); + ser_queue(s->serial, 0x80 | (buttons_state & 7)); + ser_queue(s->serial, x & 0x7f); + ser_queue(s->serial, x >> 7); + ser_queue(s->serial, y & 0x7f); + ser_queue(s->serial, y >> 7); } + return; } static void summa(KBDState *s, uint8_t val) @@ -564,6 +528,7 @@ static void summa(KBDState *s, uint8_t val) s->mouse_status |= MOUSE_STATUS_ENABLED; SummaState.origin = ORIGIN_LOWER_LEFT; SummaState.report_mode = (val == 'B') ? MODE_POINT : MODE_STREAM_SWITCH; + qemu_add_mouse_event_handler(summa_mouse_event, s, 1); break; case 'z': /* start of 2 byte command */ @@ -647,6 +612,36 @@ void summa_init(SerialState *serial, CharDriverState *chr) return; } +static void pc_kbd_mouse_event(void *opaque, + int dx, int dy, int dz, int buttons_state) +{ + KBDState *s = opaque; + + /* check if deltas are recorded when disabled */ + if (!(s->mouse_status & MOUSE_STATUS_ENABLED)) + return; + + s->mouse_dx += dx; + s->mouse_dy -= dy; + s->mouse_dz += dz; + /* XXX: SDL sometimes generates nul events: we delete them */ + if (s->mouse_dx == 0 && s->mouse_dy == 0 && s->mouse_dz == 0 && + s->mouse_buttons == buttons_state) + return; + s->mouse_buttons = buttons_state; + + if (!(s->mouse_status & MOUSE_STATUS_REMOTE) && + (s->queue.count < (KBD_QUEUE_SIZE - 16))) { + for(;;) { + /* if not remote, send event. Multiple events are sent if + too big deltas */ + kbd_mouse_send_packet(s); + if (s->mouse_dx == 0 && s->mouse_dy == 0 && s->mouse_dz == 0) + break; + } + } +} + static void kbd_write_mouse(KBDState *s, int val) { #ifdef DEBUG_MOUSE @@ -890,6 +885,6 @@ void kbd_init(void) register_ioport_write(0x64, 1, 1, kbd_write_command, s); qemu_add_kbd_event_handler(pc_kbd_put_keycode, s); - qemu_add_mouse_event_handler(pc_kbd_mouse_event, s); + qemu_add_mouse_event_handler(pc_kbd_mouse_event, s, 0); qemu_register_reset(kbd_reset, s); } diff --git a/tools/ioemu/hw/usb-hid.c b/tools/ioemu/hw/usb-hid.c new file mode 100644 index 0000000000..17160ebe32 --- /dev/null +++ b/tools/ioemu/hw/usb-hid.c @@ -0,0 +1,537 @@ +/* + * QEMU USB HID devices + * + * 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" + +/* HID interface requests */ +#define GET_REPORT 0xa101 +#define GET_IDLE 0xa102 +#define GET_PROTOCOL 0xa103 +#define SET_IDLE 0x210a +#define SET_PROTOCOL 0x210b + +#define USB_MOUSE 1 +#define USB_TABLET 2 + +typedef struct USBMouseState { + USBDevice dev; + int dx, dy, dz, buttons_state; + int x, y; + int kind; + int mouse_grabbed; +} USBMouseState; + +/* mostly the same values as the Bochs USB Mouse device */ +static const uint8_t qemu_mouse_dev_descriptor[] = { + 0x12, /* u8 bLength; */ + 0x01, /* u8 bDescriptorType; Device */ + 0x10, 0x00, /* u16 bcdUSB; v1.0 */ + + 0x00, /* u8 bDeviceClass; */ + 0x00, /* u8 bDeviceSubClass; */ + 0x00, /* u8 bDeviceProtocol; [ low/full speeds only ] */ + 0x08, /* u8 bMaxPacketSize0; 8 Bytes */ + + 0x27, 0x06, /* u16 idVendor; */ + 0x01, 0x00, /* u16 idProduct; */ + 0x00, 0x00, /* u16 bcdDevice */ + + 0x03, /* u8 iManufacturer; */ + 0x02, /* u8 iProduct; */ + 0x01, /* u8 iSerialNumber; */ + 0x01 /* u8 bNumConfigurations; */ +}; + +static const uint8_t qemu_mouse_config_descriptor[] = { + /* one configuration */ + 0x09, /* u8 bLength; */ + 0x02, /* u8 bDescriptorType; Configuration */ + 0x22, 0x00, /* u16 wTotalLength; */ + 0x01, /* u8 bNumInterfaces; (1) */ + 0x01, /* u8 bConfigurationValue; */ + 0x04, /* u8 iConfiguration; */ + 0xa0, /* u8 bmAttributes; + Bit 7: must be set, + 6: Self-powered, + 5: Remote wakeup, + 4..0: resvd */ + 50, /* 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; */ + 0x03, /* u8 if_bInterfaceClass; */ + 0x01, /* u8 if_bInterfaceSubClass; */ + 0x02, /* u8 if_bInterfaceProtocol; [usb1.1 or single tt] */ + 0x05, /* u8 if_iInterface; */ + + /* HID descriptor */ + 0x09, /* u8 bLength; */ + 0x21, /* u8 bDescriptorType; */ + 0x01, 0x00, /* u16 HID_class */ + 0x00, /* u8 country_code */ + 0x01, /* u8 num_descriptors */ + 0x22, /* u8 type; Report */ + 50, 0, /* u16 len */ + + /* 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 */ + 0x03, 0x00, /* u16 ep_wMaxPacketSize; */ + 0x0a, /* u8 ep_bInterval; (255ms -- usb 2.0 spec) */ +}; + +static const uint8_t qemu_tablet_config_descriptor[] = { + /* one configuration */ + 0x09, /* u8 bLength; */ + 0x02, /* u8 bDescriptorType; Configuration */ + 0x22, 0x00, /* u16 wTotalLength; */ + 0x01, /* u8 bNumInterfaces; (1) */ + 0x01, /* u8 bConfigurationValue; */ + 0x04, /* u8 iConfiguration; */ + 0xa0, /* u8 bmAttributes; + Bit 7: must be set, + 6: Self-powered, + 5: Remote wakeup, + 4..0: resvd */ + 50, /* 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; */ + 0x03, /* u8 if_bInterfaceClass; */ + 0x01, /* u8 if_bInterfaceSubClass; */ + 0x02, /* u8 if_bInterfaceProtocol; [usb1.1 or single tt] */ + 0x05, /* u8 if_iInterface; */ + + /* HID descriptor */ + 0x09, /* u8 bLength; */ + 0x21, /* u8 bDescriptorType; */ + 0x01, 0x00, /* u16 HID_class */ + 0x00, /* u8 country_code */ + 0x01, /* u8 num_descriptors */ + 0x22, /* u8 type; Report */ + 74, 0, /* u16 len */ + + /* 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 */ + 0x08, 0x00, /* u16 ep_wMaxPacketSize; */ + 0x03, /* u8 ep_bInterval; (255ms -- usb 2.0 spec) */ +}; + +static const uint8_t qemu_mouse_hid_report_descriptor[] = { + 0x05, 0x01, 0x09, 0x02, 0xA1, 0x01, 0x09, 0x01, + 0xA1, 0x00, 0x05, 0x09, 0x19, 0x01, 0x29, 0x03, + 0x15, 0x00, 0x25, 0x01, 0x95, 0x03, 0x75, 0x01, + 0x81, 0x02, 0x95, 0x01, 0x75, 0x05, 0x81, 0x01, + 0x05, 0x01, 0x09, 0x30, 0x09, 0x31, 0x15, 0x81, + 0x25, 0x7F, 0x75, 0x08, 0x95, 0x02, 0x81, 0x06, + 0xC0, 0xC0, +}; + +static const uint8_t qemu_tablet_hid_report_descriptor[] = { + 0x05, 0x01, /* Usage Page Generic Desktop */ + 0x09, 0x01, /* Usage Mouse */ + 0xA1, 0x01, /* Collection Application */ + 0x09, 0x01, /* Usage Pointer */ + 0xA1, 0x00, /* Collection Physical */ + 0x05, 0x09, /* Usage Page Button */ + 0x19, 0x01, /* Usage Minimum Button 1 */ + 0x29, 0x03, /* Usage Maximum Button 3 */ + 0x15, 0x00, /* Logical Minimum 0 */ + 0x25, 0x01, /* Logical Maximum 1 */ + 0x95, 0x03, /* Report Count 3 */ + 0x75, 0x01, /* Report Size 1 */ + 0x81, 0x02, /* Input (Data, Var, Abs) */ + 0x95, 0x01, /* Report Count 1 */ + 0x75, 0x05, /* Report Size 5 */ + 0x81, 0x01, /* Input (Cnst, Var, Abs) */ + 0x05, 0x01, /* Usage Page Generic Desktop */ + 0x09, 0x30, /* Usage X */ + 0x09, 0x31, /* Usage Y */ + 0x15, 0x00, /* Logical Minimum 0 */ + 0x26, 0xFF, 0x7F, /* Logical Maximum 0x7fff */ + 0x35, 0x00, /* Physical Minimum 0 */ + 0x46, 0xFE, 0x7F, /* Physical Maximum 0x7fff */ + 0x75, 0x10, /* Report Size 16 */ + 0x95, 0x02, /* Report Count 2 */ + 0x81, 0x02, /* Input (Data, Var, Abs) */ + 0x05, 0x01, /* Usage Page Generic Desktop */ + 0x09, 0x38, /* Usage Wheel */ + 0x15, 0x81, /* Logical Minimum -127 */ + 0x25, 0x7F, /* Logical Maximum 127 */ + 0x35, 0x00, /* Physical Minimum 0 (same as logical) */ + 0x45, 0x00, /* Physical Maximum 0 (same as logical) */ + 0x75, 0x08, /* Report Size 8 */ + 0x95, 0x01, /* Report Count 1 */ + 0x81, 0x02, /* Input (Data, Var, Rel) */ + 0xC0, /* End Collection */ + 0xC0, /* End Collection */ +}; + +static void usb_mouse_event(void *opaque, + int dx1, int dy1, int dz1, int buttons_state) +{ + USBMouseState *s = opaque; + + s->dx += dx1; + s->dy += dy1; + s->dz += dz1; + s->buttons_state = buttons_state; +} + +static void usb_tablet_event(void *opaque, + int x, int y, int dz, int buttons_state) +{ + USBMouseState *s = opaque; + + s->x = x; + s->y = y; + s->dz += dz; + s->buttons_state = buttons_state; +} + +static inline int int_clamp(int val, int vmin, int vmax) +{ + if (val < vmin) + return vmin; + else if (val > vmax) + return vmax; + else + return val; +} + +static int usb_mouse_poll(USBMouseState *s, uint8_t *buf, int len) +{ + int dx, dy, dz, b, l; + + if (!s->mouse_grabbed) { + qemu_add_mouse_event_handler(usb_mouse_event, s, 0); + s->mouse_grabbed = 1; + } + + dx = int_clamp(s->dx, -128, 127); + dy = int_clamp(s->dy, -128, 127); + dz = int_clamp(s->dz, -128, 127); + + s->dx -= dx; + s->dy -= dy; + s->dz -= dz; + + b = 0; + if (s->buttons_state & MOUSE_EVENT_LBUTTON) + b |= 0x01; + if (s->buttons_state & MOUSE_EVENT_RBUTTON) + b |= 0x02; + if (s->buttons_state & MOUSE_EVENT_MBUTTON) + b |= 0x04; + + buf[0] = b; + buf[1] = dx; + buf[2] = dy; + l = 3; + if (len >= 4) { + buf[3] = dz; + l = 4; + } + return l; +} + +static int usb_tablet_poll(USBMouseState *s, uint8_t *buf, int len) +{ + int dz, b, l; + + if (!s->mouse_grabbed) { + qemu_add_mouse_event_handler(usb_tablet_event, s, 1); + s->mouse_grabbed = 1; + } + + dz = int_clamp(s->dz, -128, 127); + s->dz -= dz; + + /* Appears we have to invert the wheel direction */ + dz = 0 - dz; + b = 0; + if (s->buttons_state & MOUSE_EVENT_LBUTTON) + b |= 0x01; + if (s->buttons_state & MOUSE_EVENT_RBUTTON) + b |= 0x02; + if (s->buttons_state & MOUSE_EVENT_MBUTTON) + b |= 0x04; + + buf[0] = b; + buf[1] = s->x & 0xff; + buf[2] = s->x >> 8; + buf[3] = s->y & 0xff; + buf[4] = s->y >> 8; + buf[5] = dz; + l = 6; + + return l; +} + +static void usb_mouse_handle_reset(USBDevice *dev) +{ + USBMouseState *s = (USBMouseState *)dev; + + s->dx = 0; + s->dy = 0; + s->dz = 0; + s->x = 0; + s->y = 0; + s->buttons_state = 0; +} + +static int usb_mouse_handle_control(USBDevice *dev, int request, int value, + int index, int length, uint8_t *data) +{ + USBMouseState *s = (USBMouseState *)dev; + int ret = 0; + + 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_mouse_dev_descriptor, + sizeof(qemu_mouse_dev_descriptor)); + ret = sizeof(qemu_mouse_dev_descriptor); + break; + case USB_DT_CONFIG: + if (s->kind == USB_MOUSE) { + memcpy(data, qemu_mouse_config_descriptor, + sizeof(qemu_mouse_config_descriptor)); + ret = sizeof(qemu_mouse_config_descriptor); + } else if (s->kind == USB_TABLET) { + memcpy(data, qemu_tablet_config_descriptor, + sizeof(qemu_tablet_config_descriptor)); + ret = sizeof(qemu_tablet_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, "1"); + break; + case 2: + /* product description */ + if (s->kind == USB_MOUSE) + ret = set_usb_string(data, "QEMU USB Mouse"); + else if (s->kind == USB_TABLET) + ret = set_usb_string(data, "QEMU USB Tablet"); + break; + case 3: + /* vendor description */ + ret = set_usb_string(data, "QEMU " QEMU_VERSION); + break; + case 4: + ret = set_usb_string(data, "HID Mouse"); + break; + case 5: + ret = set_usb_string(data, "Endpoint1 Interrupt Pipe"); + 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; + /* hid specific requests */ + case InterfaceRequest | USB_REQ_GET_DESCRIPTOR: + switch(value >> 8) { + case 0x22: + if (s->kind == USB_MOUSE) { + memcpy(data, qemu_mouse_hid_report_descriptor, + sizeof(qemu_mouse_hid_report_descriptor)); + ret = sizeof(qemu_mouse_hid_report_descriptor); + } else if (s->kind == USB_TABLET) { + memcpy(data, qemu_tablet_hid_report_descriptor, + sizeof(qemu_tablet_hid_report_descriptor)); + ret = sizeof(qemu_tablet_hid_report_descriptor); + } + break; + default: + goto fail; + } + break; + case GET_REPORT: + if (s->kind == USB_MOUSE) + ret = usb_mouse_poll(s, data, length); + else if (s->kind == USB_TABLET) + ret = usb_tablet_poll(s, data, length); + break; + case SET_IDLE: + ret = 0; + break; + default: + fail: + ret = USB_RET_STALL; + break; + } + return ret; +} + +static int usb_mouse_handle_data(USBDevice *dev, int pid, + uint8_t devep, uint8_t *data, int len) +{ + USBMouseState *s = (USBMouseState *)dev; + int ret = 0; + + switch(pid) { + case USB_TOKEN_IN: + if (devep == 1) { + if (s->kind == USB_MOUSE) + ret = usb_mouse_poll(s, data, len); + else if (s->kind == USB_TABLET) + ret = usb_tablet_poll(s, data, len); + } else { + goto fail; + } + break; + case USB_TOKEN_OUT: + default: + fail: + ret = USB_RET_STALL; + break; + } + return ret; +} + +USBDevice *usb_tablet_init(void) +{ + USBMouseState *s; + + s = qemu_mallocz(sizeof(USBMouseState)); + if (!s) + return NULL; + s->dev.speed = USB_SPEED_FULL; + s->dev.handle_packet = usb_generic_handle_packet; + + s->dev.handle_reset = usb_mouse_handle_reset; + s->dev.handle_control = usb_mouse_handle_control; + s->dev.handle_data = usb_mouse_handle_data; + s->kind = USB_TABLET; + + return (USBDevice *)s; +} + +USBDevice *usb_mouse_init(void) +{ + USBMouseState *s; + + s = qemu_mallocz(sizeof(USBMouseState)); + if (!s) + return NULL; + s->dev.speed = USB_SPEED_FULL; + s->dev.handle_packet = usb_generic_handle_packet; + + s->dev.handle_reset = usb_mouse_handle_reset; + s->dev.handle_control = usb_mouse_handle_control; + s->dev.handle_data = usb_mouse_handle_data; + s->kind = USB_MOUSE; + + return (USBDevice *)s; +} diff --git a/tools/ioemu/hw/usb-hub.c b/tools/ioemu/hw/usb-hub.c new file mode 100644 index 0000000000..fd916128cf --- /dev/null +++ b/tools/ioemu/hw/usb-hub.c @@ -0,0 +1,549 @@ +/* + * QEMU USB HUB 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 MAX_PORTS 8 + +typedef struct USBHubPort { + USBPort port; + 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 */ + + /* DeviceRemovable and PortPwrCtrlMask patched in later */ +}; + +static void usb_hub_attach(USBPort *port1, USBDevice *dev) +{ + USBHubState *s = port1->opaque; + USBHubPort *port = &s->ports[port1->index]; + + if (dev) { + if (port->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->port.dev = dev; + } else { + dev = port->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->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 EndpointOutRequest | USB_REQ_CLEAR_FEATURE: + if (value == 0 && index != 0x81) { /* clear ep halt */ + 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)); + + /* status change endpoint size based on number + * of ports */ + data[22] = (s->nb_ports + 1 + 7) / 8; + + 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->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->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->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: + { + unsigned int n, limit, var_hub_size = 0; + memcpy(data, qemu_hub_hub_descriptor, + sizeof(qemu_hub_hub_descriptor)); + data[2] = s->nb_ports; + + /* fill DeviceRemovable bits */ + limit = ((s->nb_ports + 1 + 7) / 8) + 7; + for (n = 7; n < limit; n++) { + data[n] = 0x00; + var_hub_size++; + } + + /* fill PortPwrCtrlMask bits */ + limit = limit + ((s->nb_ports + 7) / 8); + for (;n < limit; n++) { + data[n] = 0xff; + var_hub_size++; + } + + ret = sizeof(qemu_hub_hub_descriptor) + var_hub_size; + break; + } + default: + fail: + ret = USB_RET_STALL; + break; + } + 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 (len == 1) { /* FreeBSD workaround */ + n = 1; + } else 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 = USB_RET_NAK; /* usb11 11.13.1 */ + } + } 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->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/tools/ioemu/hw/usb-uhci.c b/tools/ioemu/hw/usb-uhci.c new file mode 100644 index 0000000000..23964f36a5 --- /dev/null +++ b/tools/ioemu/hw/usb-uhci.c @@ -0,0 +1,680 @@ +/* + * 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; +} 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, 3, 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->port.dev) + uhci_attach(&port->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; + break; + 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)); + s->status &= ~UHCI_STS_HCHALTED; + } else if (!(val & UHCI_CMD_RS)) { + s->status |= UHCI_STS_HCHALTED; + } + 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->port.dev; + if (dev) { + dev->handle_packet(dev, + USB_MSG_RESET, 0, 0, NULL, 0); + } + } + uhci_reset(s); + return; + } + if (val & UHCI_CMD_HCRESET) { + 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->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->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->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->port.dev; + if (dev) { + /* send the detach message */ + dev->handle_packet(dev, + USB_MSG_DETACH, 0, 0, NULL, 0); + } + port->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->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); + /* set hchalted bit in status - UHCI11D 2.1.2 */ + s->status |= UHCI_STS_HCHALTED; + 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), + ((PCIDevice *)piix3_state)->devfn + 2, + 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 + + 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); + + /* 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); +} diff --git a/tools/ioemu/hw/usb.c b/tools/ioemu/hw/usb.c new file mode 100644 index 0000000000..34aac5fa9b --- /dev/null +++ b/tools/ioemu/hw/usb.c @@ -0,0 +1,193 @@ +/* + * 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: + if (!(s->setup_buf[0] & USB_DIR_IN)) { + s->setup_state = SETUP_STATE_IDLE; + 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 { + /* return 0 byte */ + } + 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: + if (s->setup_buf[0] & USB_DIR_IN) { + s->setup_state = SETUP_STATE_IDLE; + /* transfer OK */ + } else { + /* ignore additionnal output */ + } + 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 + 2; + *q++ = 3; + for(i = 0; i < len; i++) { + *q++ = str[i]; + *q++ = 0; + } + return q - buf; +} diff --git a/tools/ioemu/hw/usb.h b/tools/ioemu/hw/usb.h new file mode 100644 index 0000000000..05502e04db --- /dev/null +++ b/tools/ioemu/hw/usb.h @@ -0,0 +1,166 @@ +/* + * 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_CLASS_AUDIO 1 +#define USB_CLASS_COMM 2 +#define USB_CLASS_HID 3 +#define USB_CLASS_PHYSICAL 5 +#define USB_CLASS_STILL_IMAGE 6 +#define USB_CLASS_PRINTER 7 +#define USB_CLASS_MASS_STORAGE 8 +#define USB_CLASS_HUB 9 +#define USB_CLASS_CDC_DATA 0x0a +#define USB_CLASS_CSCID 0x0b +#define USB_CLASS_CONTENT_SEC 0x0d +#define USB_CLASS_APP_SPEC 0xfe +#define USB_CLASS_VENDOR_SPEC 0xff + +#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 InterfaceRequest \ + ((USB_DIR_IN|USB_TYPE_STANDARD|USB_RECIP_INTERFACE)<<8) +#define InterfaceOutRequest \ + ((USB_DIR_OUT|USB_TYPE_STANDARD|USB_RECIP_INTERFACE)<<8) +#define EndpointRequest ((USB_DIR_IN|USB_TYPE_STANDARD|USB_RECIP_ENDPOINT)<<8) +#define EndpointOutRequest \ + ((USB_DIR_OUT|USB_TYPE_STANDARD|USB_RECIP_ENDPOINT)<<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 { + USBDevice *dev; + 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_device_open(const char *devname); +void usb_host_info(void); + +/* usb-hid.c */ +USBDevice *usb_mouse_init(void); +USBDevice *usb_tablet_init(void); diff --git a/tools/ioemu/monitor.c b/tools/ioemu/monitor.c index 1effb6d962..4676fe2a42 100644 --- a/tools/ioemu/monitor.c +++ b/tools/ioemu/monitor.c @@ -492,6 +492,10 @@ static term_cmd_t term_cmds[] = { "", "quit the emulator" }, { "sendkey", "s", do_send_key, "keys", "send keys to the VM (e.g. 'sendkey ctrl-alt-f1')" }, + { "usb_add", "s", do_usb_add, + "device", "add USB device (e.g. 'host:bus.addr' or 'host:vendor_id:product_id')" }, + { "usb_del", "s", do_usb_del, + "device", "remove USB device 'bus.addr'" }, { NULL, NULL, }, }; @@ -510,6 +514,10 @@ static term_cmd_t info_cmds[] = { "", "show i8259 (PIC) state", }, { "pci", "", pci_info, "", "show PCI info", }, + { "usb", "", usb_info, + "", "show guest USB devices", }, + { "usbhost", "", usb_host_info, + "", "show host USB devices", }, { "hvmiopage", "", sp_info, "", "show HVM device model shared page info", }, { NULL, NULL, }, diff --git a/tools/ioemu/sdl.c b/tools/ioemu/sdl.c index 2294268a58..3bcec0187c 100644 --- a/tools/ioemu/sdl.c +++ b/tools/ioemu/sdl.c @@ -49,8 +49,12 @@ static void* kbd_layout=0; static int gui_fullscreen_initial_grab; static int gui_grab_code = KMOD_LALT | KMOD_LCTRL; static uint8_t modifiers_state[256]; +static int width, height; +static SDL_Cursor *sdl_cursor_normal; +static SDL_Cursor *sdl_cursor_hidden; +static int absolute_enabled = 0; -SDL_PixelFormat* sdl_get_format() { +SDL_PixelFormat* sdl_get_format(void) { return screen->format; } @@ -69,6 +73,8 @@ static void sdl_resize(DisplayState *ds, int w, int h) flags |= SDL_RESIZABLE; if (gui_fullscreen) flags |= SDL_FULLSCREEN; + width = w; + height = h; screen = SDL_SetVideoMode(w, h, 0, flags); if (!screen) { fprintf(stderr, "Could not open SDL display\n"); @@ -368,9 +374,21 @@ static void sdl_update_caption(void) SDL_WM_SetCaption(buf, domain_name); } +static void sdl_hide_cursor(void) +{ + SDL_SetCursor(sdl_cursor_hidden); +} + +static void sdl_show_cursor(void) +{ + if (!kbd_mouse_is_absolute()) { + SDL_SetCursor(sdl_cursor_normal); + } +} + static void sdl_grab_start(void) { - SDL_ShowCursor(0); + sdl_hide_cursor(); SDL_WM_GrabInput(SDL_GRAB_ON); /* dummy read to avoid moving the mouse */ SDL_GetRelativeMouseState(NULL, NULL); @@ -381,6 +399,7 @@ static void sdl_grab_start(void) static void sdl_grab_end(void) { SDL_WM_GrabInput(SDL_GRAB_OFF); + sdl_show_cursor(); SDL_ShowCursor(1); gui_grab = 0; sdl_update_caption(); @@ -397,6 +416,21 @@ static void sdl_send_mouse_event(void) buttons |= MOUSE_EVENT_RBUTTON; if (state & SDL_BUTTON(SDL_BUTTON_MIDDLE)) buttons |= MOUSE_EVENT_MBUTTON; + + if (kbd_mouse_is_absolute()) { + if (!absolute_enabled) { + sdl_hide_cursor(); + if (gui_grab) { + sdl_grab_end(); + } + absolute_enabled = 1; + } + + SDL_GetMouseState(&dx, &dy); + dx = dx * 0x7FFF / width; + dy = dy * 0x7FFF / height; + } + /* XXX: test wheel */ dz = 0; #ifdef SDL_BUTTON_WHEELUP @@ -405,7 +439,7 @@ static void sdl_send_mouse_event(void) if (state & SDL_BUTTON(SDL_BUTTON_WHEELDOWN)) dz++; #endif - kbd_mouse_event(dx, dy, dz, buttons, 0, 0); + kbd_mouse_event(dx, dy, dz, buttons); } static void toggle_full_screen(DisplayState *ds) @@ -571,6 +605,7 @@ static void sdl_cleanup(void) void sdl_display_init(DisplayState *ds, int full_screen) { int flags; + uint8_t data = 0; if(keyboard_layout) kbd_layout=init_keyboard_layout(keyboard_layout); @@ -597,6 +632,9 @@ void sdl_display_init(DisplayState *ds, int full_screen) SDL_EnableUNICODE(1); gui_grab = 0; + sdl_cursor_hidden = SDL_CreateCursor(&data, &data, 8, 1, 0, 0); + sdl_cursor_normal = SDL_GetCursor(); + atexit(sdl_cleanup); if (full_screen) { gui_fullscreen = 1; diff --git a/tools/ioemu/target-i386-dm/Makefile b/tools/ioemu/target-i386-dm/Makefile index d8bcafc43b..f575e86039 100644 --- a/tools/ioemu/target-i386-dm/Makefile +++ b/tools/ioemu/target-i386-dm/Makefile @@ -275,6 +275,9 @@ audio.o fmodaudio.o: DEFINES := -I$(CONFIG_FMOD_INC) $(DEFINES) LIBS += $(CONFIG_FMOD_LIB) endif +# USB layer +VL_OBJS+= usb.o usb-hub.o usb-uhci.o usb-linux.o usb-hid.o + # Hardware support VL_OBJS+= ide.o ne2000.o pckbd.o vga.o dma.o VL_OBJS+= fdc.o mc146818rtc.o serial.o i8259_stub.o pc.o port-e9.o diff --git a/tools/ioemu/usb-linux.c b/tools/ioemu/usb-linux.c new file mode 100644 index 0000000000..9cd543d714 --- /dev/null +++ b/tools/ioemu/usb-linux.c @@ -0,0 +1,487 @@ +/* + * 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; +}; + +typedef int USBScanFunc(void *opaque, int bus_num, int addr, int class_id, + int vendor_id, int product_id, + const char *product_name, int speed); +static int usb_host_find_device(int *pbus_num, int *paddr, + const char *devname); + +//#define DEBUG + +#define USBDEVFS_PATH "/proc/bus/usb" + +typedef struct USBHostDevice { + USBDevice dev; + int fd; +} USBHostDevice; + +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; + } +} + +/* XXX: exclude high speed devices or implement EHCI */ +USBDevice *usb_host_device_open(const char *devname) +{ + int fd, interface, ret, i; + USBHostDevice *dev; + struct usbdevfs_connectinfo ci; + uint8_t descr[1024]; + char buf[1024]; + int descr_len, dev_descr_len, config_descr_len, nb_interfaces; + int bus_num, addr; + + if (usb_host_find_device(&bus_num, &addr, devname) < 0) + return NULL; + + snprintf(buf, sizeof(buf), USBDEVFS_PATH "/%03d/%03d", + bus_num, addr); + fd = open(buf, O_RDWR); + if (fd < 0) { + perror(buf); + return NULL; + } + + /* 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 */ + fprintf(stderr, "usb_host: only one interface supported\n"); + goto fail; + } + +#ifdef USBDEVFS_DISCONNECT + /* earlier Linux 2.4 do not support that */ + { + struct usbdevfs_ioctl ctrl; + ctrl.ioctl_code = USBDEVFS_DISCONNECT; + ctrl.ifno = 0; + ret = ioctl(fd, USBDEVFS_IOCTL, &ctrl); + if (ret < 0 && errno != ENODATA) { + perror("USBDEVFS_DISCONNECT"); + goto fail; + } + } +#endif + + /* XXX: only grab if all interfaces are free */ + interface = 0; + ret = ioctl(fd, USBDEVFS_CLAIMINTERFACE, &interface); + if (ret < 0) { + if (errno == EBUSY) { + fprintf(stderr, "usb_host: device already grabbed\n"); + } else { + perror("USBDEVFS_CLAIMINTERFACE"); + } + fail: + close(fd); + return NULL; + } + + ret = ioctl(fd, USBDEVFS_CONNECTINFO, &ci); + if (ret < 0) { + perror("USBDEVFS_CONNECTINFO"); + goto fail; + } + +#ifdef DEBUG + printf("host USB device %d.%d grabbed\n", bus_num, addr); +#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_generic_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; + return (USBDevice *)dev; +} + +static int get_tag_value(char *buf, int buf_size, + const char *str, const char *tag, + const char *stopchars) +{ + const char *p; + char *q; + p = strstr(str, tag); + if (!p) + return -1; + p += strlen(tag); + while (isspace(*p)) + p++; + q = buf; + while (*p != '\0' && !strchr(stopchars, *p)) { + if ((q - buf) < (buf_size - 1)) + *q++ = *p; + p++; + } + *q = '\0'; + return q - buf; +} + +static int usb_host_scan(void *opaque, USBScanFunc *func) +{ + FILE *f; + char line[1024]; + char buf[1024]; + int bus_num, addr, speed, device_count, class_id, product_id, vendor_id; + int ret; + char product_name[512]; + + f = fopen(USBDEVFS_PATH "/devices", "r"); + if (!f) { + term_printf("Could not open %s\n", USBDEVFS_PATH "/devices"); + return 0; + } + device_count = 0; + bus_num = addr = speed = class_id = product_id = vendor_id = 0; + ret = 0; + for(;;) { + if (fgets(line, sizeof(line), f) == NULL) + break; + if (strlen(line) > 0) + line[strlen(line) - 1] = '\0'; + if (line[0] == 'T' && line[1] == ':') { + if (device_count && (vendor_id || product_id)) { + /* New device. Add the previously discovered device. */ + ret = func(opaque, bus_num, addr, class_id, vendor_id, + product_id, product_name, speed); + if (ret) + goto the_end; + } + if (get_tag_value(buf, sizeof(buf), line, "Bus=", " ") < 0) + goto fail; + bus_num = atoi(buf); + if (get_tag_value(buf, sizeof(buf), line, "Dev#=", " ") < 0) + goto fail; + addr = atoi(buf); + if (get_tag_value(buf, sizeof(buf), line, "Spd=", " ") < 0) + goto fail; + if (!strcmp(buf, "480")) + speed = USB_SPEED_HIGH; + else if (!strcmp(buf, "1.5")) + speed = USB_SPEED_LOW; + else + speed = USB_SPEED_FULL; + product_name[0] = '\0'; + class_id = 0xff; + device_count++; + product_id = 0; + vendor_id = 0; + } else if (line[0] == 'P' && line[1] == ':') { + if (get_tag_value(buf, sizeof(buf), line, "Vendor=", " ") < 0) + goto fail; + vendor_id = strtoul(buf, NULL, 16); + if (get_tag_value(buf, sizeof(buf), line, "ProdID=", " ") < 0) + goto fail; + product_id = strtoul(buf, NULL, 16); + } else if (line[0] == 'S' && line[1] == ':') { + if (get_tag_value(buf, sizeof(buf), line, "Product=", "") < 0) + goto fail; + pstrcpy(product_name, sizeof(product_name), buf); + } else if (line[0] == 'D' && line[1] == ':') { + if (get_tag_value(buf, sizeof(buf), line, "Cls=", " (") < 0) + goto fail; + class_id = strtoul(buf, NULL, 16); + } + fail: ; + } + if (device_count && (vendor_id || product_id)) { + /* Add the last device. */ + ret = func(opaque, bus_num, addr, class_id, vendor_id, + product_id, product_name, speed); + } + the_end: + fclose(f); + return ret; +} + +typedef struct FindDeviceState { + int vendor_id; + int product_id; + int bus_num; + int addr; +} FindDeviceState; + +static int usb_host_find_device_scan(void *opaque, int bus_num, int addr, + int class_id, + int vendor_id, int product_id, + const char *product_name, int speed) +{ + FindDeviceState *s = opaque; + if (vendor_id == s->vendor_id && + product_id == s->product_id) { + s->bus_num = bus_num; + s->addr = addr; + return 1; + } else { + return 0; + } +} + +/* the syntax is : + 'bus.addr' (decimal numbers) or + 'vendor_id:product_id' (hexa numbers) */ +static int usb_host_find_device(int *pbus_num, int *paddr, + const char *devname) +{ + const char *p; + int ret; + FindDeviceState fs; + + p = strchr(devname, '.'); + if (p) { + *pbus_num = strtoul(devname, NULL, 0); + *paddr = strtoul(p + 1, NULL, 0); + return 0; + } + p = strchr(devname, ':'); + if (p) { + fs.vendor_id = strtoul(devname, NULL, 16); + fs.product_id = strtoul(p + 1, NULL, 16); + ret = usb_host_scan(&fs, usb_host_find_device_scan); + if (ret) { + *pbus_num = fs.bus_num; + *paddr = fs.addr; + return 0; + } + } + return -1; +} + +/**********************/ +/* USB host device info */ + +struct usb_class_info { + int class; + const char *class_name; +}; + +static const struct usb_class_info usb_class_info[] = { + { USB_CLASS_AUDIO, "Audio"}, + { USB_CLASS_COMM, "Communication"}, + { USB_CLASS_HID, "HID"}, + { USB_CLASS_HUB, "Hub" }, + { USB_CLASS_PHYSICAL, "Physical" }, + { USB_CLASS_PRINTER, "Printer" }, + { USB_CLASS_MASS_STORAGE, "Storage" }, + { USB_CLASS_CDC_DATA, "Data" }, + { USB_CLASS_APP_SPEC, "Application Specific" }, + { USB_CLASS_VENDOR_SPEC, "Vendor Specific" }, + { USB_CLASS_STILL_IMAGE, "Still Image" }, + { USB_CLASS_CSCID, "Smart Card" }, + { USB_CLASS_CONTENT_SEC, "Content Security" }, + { -1, NULL } +}; + +static const char *usb_class_str(uint8_t class) +{ + const struct usb_class_info *p; + for(p = usb_class_info; p->class != -1; p++) { + if (p->class == class) + break; + } + return p->class_name; +} + +void usb_info_device(int bus_num, int addr, int class_id, + int vendor_id, int product_id, + const char *product_name, + int speed) +{ + const char *class_str, *speed_str; + + switch(speed) { + case USB_SPEED_LOW: + speed_str = "1.5"; + break; + case USB_SPEED_FULL: + speed_str = "12"; + break; + case USB_SPEED_HIGH: + speed_str = "480"; + break; + default: + speed_str = "?"; + break; + } + + term_printf(" Device %d.%d, speed %s Mb/s\n", + bus_num, addr, speed_str); + class_str = usb_class_str(class_id); + if (class_str) + term_printf(" %s:", class_str); + else + term_printf(" Class %02x:", class_id); + term_printf(" USB device %04x:%04x", vendor_id, product_id); + if (product_name[0] != '\0') + term_printf(", %s", product_name); + term_printf("\n"); +} + +static int usb_host_info_device(void *opaque, int bus_num, int addr, + int class_id, + int vendor_id, int product_id, + const char *product_name, + int speed) +{ + usb_info_device(bus_num, addr, class_id, vendor_id, product_id, + product_name, speed); + return 0; +} + +void usb_host_info(void) +{ + usb_host_scan(NULL, usb_host_info_device); +} + +#else + +void usb_host_info(void) +{ + term_printf("USB host devices not supported\n"); +} + +/* XXX: modify configure to compile the right host driver */ +USBDevice *usb_host_device_open(const char *devname) +{ + return NULL; +} + +#endif diff --git a/tools/ioemu/vl.c b/tools/ioemu/vl.c index 0de9a4b18d..2d981d54af 100644 --- a/tools/ioemu/vl.c +++ b/tools/ioemu/vl.c @@ -144,6 +144,9 @@ int graphic_height = 600; int graphic_depth = 15; int full_screen = 0; int repeat_key = 1; +int usb_enabled = 0; +USBPort *vm_usb_ports[MAX_VM_USB_PORTS]; +USBDevice *vm_usb_hub; TextConsole *vga_console; CharDriverState *serial_hds[MAX_SERIAL_PORTS]; int serial_summa_port = -1; @@ -438,6 +441,7 @@ static QEMUPutKBDEvent *qemu_put_kbd_event; static void *qemu_put_kbd_event_opaque; static QEMUPutMouseEvent *qemu_put_mouse_event; static void *qemu_put_mouse_event_opaque; +static int qemu_put_mouse_event_absolute; void qemu_add_kbd_event_handler(QEMUPutKBDEvent *func, void *opaque) { @@ -445,10 +449,11 @@ void qemu_add_kbd_event_handler(QEMUPutKBDEvent *func, void *opaque) qemu_put_kbd_event = func; } -void qemu_add_mouse_event_handler(QEMUPutMouseEvent *func, void *opaque) +void qemu_add_mouse_event_handler(QEMUPutMouseEvent *func, void *opaque, int absolute) { qemu_put_mouse_event_opaque = opaque; qemu_put_mouse_event = func; + qemu_put_mouse_event_absolute = absolute; } void kbd_put_keycode(int keycode) @@ -458,14 +463,19 @@ void kbd_put_keycode(int keycode) } } -void kbd_mouse_event(int dx, int dy, int dz, int buttons_state, int x, int y) +void kbd_mouse_event(int dx, int dy, int dz, int buttons_state) { if (qemu_put_mouse_event) { qemu_put_mouse_event(qemu_put_mouse_event_opaque, - dx, dy, dz, buttons_state, x, y); + dx, dy, dz, buttons_state); } } +int kbd_mouse_is_absolute(void) +{ + return qemu_put_mouse_event_absolute; +} + /***********************************************************/ /* timers */ @@ -1644,6 +1654,121 @@ static int net_fd_init(NetDriverState *nd, int fd) } #endif /* !_WIN32 */ + +/***********************************************************/ +/* USB devices */ + +static int usb_device_add(const char *devname) +{ + const char *p; + USBDevice *dev; + int i; + + if (!vm_usb_hub) + return -1; + for(i = 0;i < MAX_VM_USB_PORTS; i++) { + if (!vm_usb_ports[i]->dev) + break; + } + if (i == MAX_VM_USB_PORTS) + return -1; + + if (strstart(devname, "host:", &p)) { + dev = usb_host_device_open(p); + if (!dev) + return -1; + } else if (!strcmp(devname, "mouse")) { + dev = usb_mouse_init(); + if (!dev) + return -1; + } else if (!strcmp(devname, "tablet")) { + dev = usb_tablet_init(); + if (!dev) + return -1; + } else { + return -1; + } + usb_attach(vm_usb_ports[i], dev); + return 0; +} + +static int usb_device_del(const char *devname) +{ + USBDevice *dev; + int bus_num, addr, i; + const char *p; + + if (!vm_usb_hub) + return -1; + + p = strchr(devname, '.'); + if (!p) + return -1; + bus_num = strtoul(devname, NULL, 0); + addr = strtoul(p + 1, NULL, 0); + if (bus_num != 0) + return -1; + for(i = 0;i < MAX_VM_USB_PORTS; i++) { + dev = vm_usb_ports[i]->dev; + if (dev && dev->addr == addr) + break; + } + if (i == MAX_VM_USB_PORTS) + return -1; + usb_attach(vm_usb_ports[i], NULL); + return 0; +} + +void do_usb_add(const char *devname) +{ + int ret; + ret = usb_device_add(devname); + if (ret < 0) + term_printf("Could not add USB device '%s'\n", devname); +} + +void do_usb_del(const char *devname) +{ + int ret; + ret = usb_device_del(devname); + if (ret < 0) + term_printf("Could not remove USB device '%s'\n", devname); +} + +void usb_info(void) +{ + USBDevice *dev; + int i; + const char *speed_str; + + if (!vm_usb_hub) { + term_printf("USB support not enabled\n"); + return; + } + + for(i = 0; i < MAX_VM_USB_PORTS; i++) { + dev = vm_usb_ports[i]->dev; + if (dev) { + term_printf("Hub port %d:\n", i); + switch(dev->speed) { + case USB_SPEED_LOW: + speed_str = "1.5"; + break; + case USB_SPEED_FULL: + speed_str = "12"; + break; + case USB_SPEED_HIGH: + speed_str = "480"; + break; + default: + speed_str = "?"; + break; + } + term_printf(" Device %d.%d, speed %s Mb/s\n", + 0, dev->addr, speed_str); + } + } +} /***********************************************************/ /* dumb display */ @@ -2214,6 +2339,8 @@ void help(void) "-enable-audio enable audio support\n" "-localtime set the real time clock to local time [default=utc]\n" "-full-screen start in full screen\n" + "-usb enable the USB driver (will be the default soon)\n" + "-usbdevice name add the host or guest USB device 'name'\n" #ifdef TARGET_PPC "-prep Simulate a PREP system (default is PowerMAC)\n" "-g WxH[xDEPTH] Set the initial VGA graphic mode\n" @@ -2355,6 +2482,8 @@ enum { QEMU_OPTION_full_screen, QEMU_OPTION_vgaacc, QEMU_OPTION_repeatkey, + QEMU_OPTION_usb, + QEMU_OPTION_usbdevice, }; typedef struct QEMUOption { @@ -2428,8 +2557,10 @@ const QEMUOption qemu_options[] = { { "serial", 1, QEMU_OPTION_serial }, { "loadvm", HAS_ARG, QEMU_OPTION_loadvm }, { "full-screen", 0, QEMU_OPTION_full_screen }, + { "usbdevice", HAS_ARG, QEMU_OPTION_usbdevice }, /* temporary options */ + { "usb", 0, QEMU_OPTION_usb }, { "pci", 0, QEMU_OPTION_pci }, { "nic-ne2000", 0, QEMU_OPTION_nic_ne2000 }, { "cirrusvga", 0, QEMU_OPTION_cirrusvga }, @@ -2555,6 +2686,8 @@ int main(int argc, char **argv) char monitor_device[128]; char serial_devices[MAX_SERIAL_PORTS][128]; int serial_device_index; + char usb_devices[MAX_VM_USB_PORTS][128]; + int usb_devices_index; char qemu_dm_logfilename[64]; const char *loadvm = NULL; unsigned long nr_pages; @@ -2595,6 +2728,7 @@ int main(int argc, char **argv) serial_devices[i][0] = '\0'; serial_device_index = 0; + usb_devices_index = 0; nb_tun_fds = 0; net_if_type = -1; nb_nics = 1; @@ -2939,6 +3073,20 @@ int main(int argc, char **argv) case QEMU_OPTION_full_screen: full_screen = 1; break; + case QEMU_OPTION_usb: + usb_enabled = 1; + break; + case QEMU_OPTION_usbdevice: + usb_enabled = 1; + if (usb_devices_index >= MAX_VM_USB_PORTS) { + fprintf(stderr, "Too many USB devices\n"); + exit(1); + } + pstrcpy(usb_devices[usb_devices_index], + sizeof(usb_devices[usb_devices_index]), + optarg); + usb_devices_index++; + break; case QEMU_OPTION_domainname: strncat(domain_name, optarg, sizeof(domain_name) - 20); break; @@ -3139,6 +3287,17 @@ int main(int argc, char **argv) } } + /* init USB devices */ + if (usb_enabled) { + vm_usb_hub = usb_hub_init(vm_usb_ports, MAX_VM_USB_PORTS); + for(i = 0; i < usb_devices_index; i++) { + if (usb_device_add(usb_devices[i]) < 0) { + fprintf(stderr, "Warning: could not add USB device %s\n", + usb_devices[i]); + } + } + } + /* init CPU state */ env = cpu_init(); global_env = env; diff --git a/tools/ioemu/vl.h b/tools/ioemu/vl.h index bb55749753..9457d6f305 100644 --- a/tools/ioemu/vl.h +++ b/tools/ioemu/vl.h @@ -154,13 +154,14 @@ extern int graphic_depth; #define MOUSE_EVENT_MBUTTON 0x04 typedef void QEMUPutKBDEvent(void *opaque, int keycode); -typedef void QEMUPutMouseEvent(void *opaque, int dx, int dy, int dz, int buttons_state, int x, int y); +typedef void QEMUPutMouseEvent(void *opaque, int dx, int dy, int dz, int buttons_state); void qemu_add_kbd_event_handler(QEMUPutKBDEvent *func, void *opaque); -void qemu_add_mouse_event_handler(QEMUPutMouseEvent *func, void *opaque); +void qemu_add_mouse_event_handler(QEMUPutMouseEvent *func, void *opaque, int absolute); void kbd_put_keycode(int keycode); -void kbd_mouse_event(int dx, int dy, int dz, int buttons_state, int x, int y); +void kbd_mouse_event(int dx, int dy, int dz, int buttons_state); +int kbd_mouse_is_absolute(void); /* keysym is a unicode code except for special keys (see QEMU_KEY_xxx constants) */ @@ -633,6 +634,7 @@ void pci_pcnet_init(PCIBus *bus, NetDriverState *nd); void kbd_init(void); extern const char* keyboard_layout; extern int repeat_key; +extern int usb_enabled; /* mc146818rtc.c */ @@ -792,6 +794,19 @@ void adb_mouse_init(ADBBusState *bus); /* cuda.c */ +#include "hw/usb.h" + +/* usb ports of the VM */ + +#define MAX_VM_USB_PORTS 8 + +extern USBPort *vm_usb_ports[MAX_VM_USB_PORTS]; +extern USBDevice *vm_usb_hub; + +void do_usb_add(const char *devname); +void do_usb_del(const char *devname); +void usb_info(void); + extern ADBBusState adb_bus; int cuda_init(openpic_t *openpic, int irq); diff --git a/tools/ioemu/vnc.c b/tools/ioemu/vnc.c index 3c3175cad5..6db658e65c 100644 --- a/tools/ioemu/vnc.c +++ b/tools/ioemu/vnc.c @@ -138,9 +138,16 @@ static void init_mouse(int max_x,int max_y) { } static void mouse_refresh() { + static int last_x = -1; + static int last_y = -1; + static int last_z = -1; + static int last_b = -1; int dx=0,dy=0,dz=new_mouse_z; static int counter=1; + if (new_mouse_x == last_x && new_mouse_y == last_y && + new_mouse_z == last_z && new_mouse_buttons == last_b) + return; /* * Simulate lifting the mouse by pressing left together * e.g. don't send mouse events. @@ -148,27 +155,40 @@ static void mouse_refresh() { if (ctl_keys == 3) { mouse_x = new_mouse_x; mouse_y = new_mouse_y; + last_x = new_mouse_x; + last_y = new_mouse_y; + last_z = new_mouse_z; + last_b = new_mouse_buttons; return; } counter++; - if(!mouse_magic->calibration && counter>=2) { counter=0; return; } - - dx=new_mouse_x-mouse_x; - dy=new_mouse_y-mouse_y; - - if(mouse_magic->sonic_wall_is_orthogonal) { - if(abs(dx)>=mouse_magic->sonic_wall_x) { dx/=2; mouse_x+=dx; } - if(abs(dy)>=mouse_magic->sonic_wall_y) { dy/=2; mouse_y+=dy; } - } else { - if(abs(dx)>=mouse_magic->sonic_wall_x || abs(dy)>=mouse_magic->sonic_wall_y) { - dx/=2; mouse_x+=dx; - dy/=2; mouse_y+=dy; - } - } //fprintf(stderr,"sending mouse event %d,%d\n",dx,dy); - kbd_mouse_event(dx,dy,dz,new_mouse_buttons,new_mouse_x,new_mouse_y); - mouse_x+=dx; - mouse_y+=dy; + if (kbd_mouse_is_absolute()) { + kbd_mouse_event(new_mouse_x * 0x7FFF / screen->width, + new_mouse_y * 0x7FFF / screen->height, dz, new_mouse_buttons); + } else { + if(!mouse_magic->calibration && counter>=2) { counter=0; return; } + + dx=new_mouse_x-last_x; + dy=new_mouse_y-last_y; + + if(mouse_magic->sonic_wall_is_orthogonal) { + if(abs(dx)>=mouse_magic->sonic_wall_x) { dx/=2; mouse_x+=dx; } + if(abs(dy)>=mouse_magic->sonic_wall_y) { dy/=2; mouse_y+=dy; } + } else { + if(abs(dx)>=mouse_magic->sonic_wall_x || abs(dy)>=mouse_magic->sonic_wall_y) { + dx/=2; mouse_x+=dx; + dy/=2; mouse_y+=dy; + } + } + if (last_x != -1) + kbd_mouse_event(dx,dy,dz,new_mouse_buttons); + + } + last_x = new_mouse_x; + last_y = new_mouse_y; + last_z = new_mouse_z; + last_b = new_mouse_buttons; updates_since_mouse=0; } @@ -250,7 +270,7 @@ static void mouse_calibration_refresh() { if(calibration_step==0) { x=0; y=1; - kbd_mouse_event(0,-1,0,0,x,y); + kbd_mouse_event(0,-1,0,0); calibration_step++; } else if(calibration_step==1) { // find out the initial position of the cursor @@ -282,7 +302,7 @@ static void mouse_calibration_refresh() { } else { y++; move_calibrate: - kbd_mouse_event(-x,-y,0,0,x,y); + kbd_mouse_event(-x,-y,0,0); before_update=last_update; } } else if(calibration_step==3) { diff --git a/tools/python/xen/xend/image.py b/tools/python/xen/xend/image.py index 98c5ec3779..82f6488a85 100644 --- a/tools/python/xen/xend/image.py +++ b/tools/python/xen/xend/image.py @@ -249,7 +249,8 @@ class HVMImageHandler(ImageHandler): # xm config file def parseDeviceModelArgs(self, imageConfig, deviceConfig): dmargs = [ 'cdrom', 'boot', 'fda', 'fdb', 'ne2000', 'audio', - 'localtime', 'serial', 'stdvga', 'isa', 'vcpus'] + 'localtime', 'serial', 'stdvga', 'isa', 'vcpus', + 'usb', 'usbdevice'] ret = [] for a in dmargs: v = sxp.child_value(imageConfig, a) @@ -260,7 +261,7 @@ class HVMImageHandler(ImageHandler): if a == 'audio': a = 'enable-audio' # Handle booleans gracefully - if a in ['localtime', 'std-vga', 'isa', 'nic-ne2000', 'enable-audio']: + if a in ['localtime', 'std-vga', 'isa', 'nic-ne2000', 'enable-audio', 'usb']: if v != None: v = int(v) if v: ret.append("-%s" % a) else: diff --git a/tools/python/xen/xm/create.py b/tools/python/xen/xm/create.py index 2a6a398738..f2d91c2c0d 100644 --- a/tools/python/xen/xm/create.py +++ b/tools/python/xen/xm/create.py @@ -264,7 +264,7 @@ gopts.var('irq', val='IRQ', For example 'irq=7'. This option may be repeated to add more than one IRQ.""") -gopts.var('usb', val='PATH', +gopts.var('usbport', val='PATH', fn=append_value, default=[], use="""Add a physical USB port to a domain, as specified by the path to that port. This option may be repeated to add more than one port.""") @@ -372,6 +372,14 @@ gopts.var('localtime', val='no|yes', fn=set_bool, default=0, use="Is RTC set to localtime?") +gopts.var('usb', val='no|yes', + fn=set_bool, default=0, + use="Emulate USB devices?") + +gopts.var('usbdevice', val='NAME', + fn=set_value, default='', + use="Name of USB device to add?") + gopts.var('stdvga', val='no|yes', fn=set_bool, default=0, use="Use std vga or cirrhus logic graphics") @@ -508,8 +516,8 @@ def configure_irq(config_devs, vals): config_devs.append(['device', config_irq]) def configure_usb(config_devs, vals): - for path in vals.usb: - config_usb = ['usb', ['path', path]] + for path in vals.usbport: + config_usb = ['usbport', ['path', path]] config_devs.append(['device', config_usb]) @@ -614,7 +622,7 @@ def configure_hvm(config_image, vals): args = [ 'device_model', 'pae', 'vcpus', 'cdrom', 'boot', 'fda', 'fdb', 'localtime', 'serial', 'stdvga', 'isa', 'nographic', 'audio', 'vnc', 'vncviewer', 'sdl', 'display', 'ne2000', 'acpi', 'apic', - 'xauthority' ] + 'xauthority', 'usb', 'usbdevice' ] for a in args: if (vals.__dict__[a]): config_image.append([a, vals.__dict__[a]])