///////////////////////////////////////////////////////////////////////// // $Id$ ///////////////////////////////////////////////////////////////////////// // // Generic USB emulation code // // Copyright (c) 2005 Fabrice Bellard // Copyright (C) 2009-2015 Benjamin D Lunt (fys at fysnet net) // 2009-2017 The Bochs Project // // 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 "iodev.h" #if BX_SUPPORT_PCI && BX_SUPPORT_PCIUSB #include "usb_common.h" #define LOG_THIS bx_usbdev_ctl. bx_usbdev_ctl_c bx_usbdev_ctl; bx_usbdev_ctl_c::bx_usbdev_ctl_c() { put("usbdevctl", "USBCTL"); } void bx_usbdev_ctl_c::init(void) { // Nothing here yet } void bx_usbdev_ctl_c::exit(void) { usbdev_locator_c::cleanup(); } const char *usbmod_names[] = { "none", "usb_cbi", "usb_hid", "usb_hub", "usb_msd", "usb_printer" }; const char *usbdev_names[] = { "none", "mouse", "tablet", "keypad", "disk", "cdrom", "hub", "printer", "floppy" }; int bx_usbdev_ctl_c::init_device(bx_list_c *portconf, logfunctions *hub, void **dev, bx_list_c *sr_list) { usbmod_type modtype = USB_MOD_TYPE_NONE; usbdev_type devtype = USB_DEV_TYPE_NONE; usb_device_c **device = (usb_device_c**)dev; const char *raw_devname = NULL; const char *args; char *devname, *ptr; size_t len; raw_devname = ((bx_param_string_c*)portconf->get_by_name("device"))->getptr(); len = strlen(raw_devname); devname = new char[len + 1]; strcpy(devname, raw_devname); ptr = strtok(devname, ":"); ptr = strtok(NULL, "\n"); if (ptr == NULL) { args = raw_devname+strlen(devname); } else { args = raw_devname+(ptr-devname); } if (!strcmp(devname, "mouse")) { modtype = USB_MOD_TYPE_HID; devtype = USB_DEV_TYPE_MOUSE; } else if (!strcmp(devname, "tablet")) { modtype = USB_MOD_TYPE_HID; devtype = USB_DEV_TYPE_TABLET; } else if (!strcmp(devname, "keypad")) { modtype = USB_MOD_TYPE_HID; devtype = USB_DEV_TYPE_KEYPAD; } else if (!strcmp(devname, "disk")) { if (ptr != NULL) { modtype = USB_MOD_TYPE_MSD; devtype = USB_DEV_TYPE_DISK; } else { delete [] devname; hub->panic("USB device 'disk' needs a filename separated with a colon"); return devtype; } } else if (!strcmp(devname, "cdrom")) { modtype = USB_MOD_TYPE_MSD; devtype = USB_DEV_TYPE_CDROM; } else if (!strcmp(devname, "hub")) { modtype = USB_MOD_TYPE_HUB; devtype = USB_DEV_TYPE_HUB; } else if (!strcmp(devname, "printer")) { if (ptr != NULL) { modtype = USB_MOD_TYPE_PRINTER; devtype = USB_DEV_TYPE_PRINTER; } else { hub->panic("USB device 'printer' needs a filename separated with a colon"); delete [] devname; return devtype; } } else if (!strncmp(devname, "floppy", 6)) { modtype = USB_MOD_TYPE_CBI; devtype = USB_DEV_TYPE_FLOPPY; } else { hub->panic("unknown USB device: %s", devname); delete [] devname; return devtype; } if (!usbdev_locator_c::module_present(usbmod_names[modtype])) { #if BX_PLUGINS PLUG_load_usb_plugin(usbmod_names[modtype]); #else BX_PANIC(("could not find USB device '%s'", usbmod_names[modtype])); #endif } *device = usbdev_locator_c::create(usbmod_names[modtype], devtype, args); if (*device != NULL) { (*device)->register_state(sr_list); parse_port_options(*device, portconf); } delete [] devname; return devtype; } void bx_usbdev_ctl_c::parse_port_options(usb_device_c *device, bx_list_c *portconf) { const char *raw_options; char *options; unsigned i, string_i; int optc, speed = USB_SPEED_LOW; // assume LOW speed device if parameter not given. char *opts[16]; char *ptr; char string[512]; size_t len; memset(opts, 0, sizeof(opts)); optc = 0; raw_options = ((bx_param_string_c*)portconf->get_by_name("options"))->getptr(); len = strlen(raw_options); if ((len > 0) && (strcmp(raw_options, "none"))) { options = new char[len + 1]; strcpy(options, raw_options); ptr = strtok(options, ","); while (ptr) { string_i = 0; for (i=0; iset_speed(speed)) { BX_PANIC(("USB device '%s' doesn't support '%s' speed", usbdev_names[device->get_type()], opts[i]+6)); } } else if (!strcmp(opts[i], "debug")) { device->set_debug_mode(); } else if (!device->set_option(opts[i])) { BX_ERROR(("ignoring unknown USB device option: '%s'", opts[i])); } } for (i = 1; i < (unsigned)optc; i++) { if (opts[i] != NULL) { free(opts[i]); opts[i] = NULL; } } } usbdev_locator_c *usbdev_locator_c::all; // // Each USB device module has a static locator class that registers // here // usbdev_locator_c::usbdev_locator_c(const char *type) { next = all; all = this; this->type = type; } usbdev_locator_c::~usbdev_locator_c() { usbdev_locator_c *ptr = 0; if (this == all) { all = all->next; } else { ptr = all; while (ptr != NULL) { if (ptr->next != this) { ptr = ptr->next; } else { break; } } } if (ptr) { ptr->next = this->next; } } bx_bool usbdev_locator_c::module_present(const char *type) { usbdev_locator_c *ptr = 0; for (ptr = all; ptr != NULL; ptr = ptr->next) { if (strcmp(type, ptr->type) == 0) return 1; } return 0; } void usbdev_locator_c::cleanup() { #if BX_PLUGINS while (all != NULL) { PLUG_unload_usb_plugin(all->type); } #endif } // // Called by USB HC emulations to locate and create a usb_device_c // object // usb_device_c* usbdev_locator_c::create(const char *type, usbdev_type devtype, const char *args) { usbdev_locator_c *ptr = 0; for (ptr = all; ptr != NULL; ptr = ptr->next) { if (strcmp(type, ptr->type) == 0) return (ptr->allocate(devtype, args)); } return NULL; } // Base class for USB devices usb_device_c::usb_device_c(void) { memset((void*)&d, 0, sizeof(d)); d.async_mode = 1; } // Find device with given address usb_device_c* usb_device_c::find_device(Bit8u addr) { if (addr == d.addr) { return this; } else { return NULL; } } // Generic USB packet handler #define SETUP_STATE_IDLE 0 #define SETUP_STATE_DATA 1 #define SETUP_STATE_ACK 2 int usb_device_c::handle_packet(USBPacket *p) { int l, ret = 0; int len = p->len; Bit8u *data = p->data; switch(p->pid) { case USB_MSG_ATTACH: d.state = USB_STATE_ATTACHED; break; case USB_MSG_DETACH: d.state = USB_STATE_NOTATTACHED; break; case USB_MSG_RESET: d.remote_wakeup = 0; d.addr = 0; d.state = USB_STATE_DEFAULT; handle_reset(); break; case USB_TOKEN_SETUP: if (d.state < USB_STATE_DEFAULT || p->devaddr != d.addr) return USB_RET_NODEV; if (len != 8) goto fail; d.stall = 0; memcpy(d.setup_buf, data, 8); d.setup_len = (d.setup_buf[7] << 8) | d.setup_buf[6]; d.setup_index = 0; if (d.setup_buf[0] & USB_DIR_IN) { ret = handle_control((d.setup_buf[0] << 8) | d.setup_buf[1], (d.setup_buf[3] << 8) | d.setup_buf[2], (d.setup_buf[5] << 8) | d.setup_buf[4], d.setup_len, d.data_buf); if (ret < 0) return ret; if (ret < d.setup_len) d.setup_len = ret; d.setup_state = SETUP_STATE_DATA; } else { if (d.setup_len == 0) d.setup_state = SETUP_STATE_ACK; else d.setup_state = SETUP_STATE_DATA; } break; case USB_TOKEN_IN: if (d.state < USB_STATE_DEFAULT || p->devaddr != d.addr) return USB_RET_NODEV; if (d.stall) goto fail; switch(p->devep) { case 0: switch(d.setup_state) { case SETUP_STATE_ACK: if (!(d.setup_buf[0] & USB_DIR_IN)) { d.setup_state = SETUP_STATE_IDLE; ret = handle_control((d.setup_buf[0] << 8) | d.setup_buf[1], (d.setup_buf[3] << 8) | d.setup_buf[2], (d.setup_buf[5] << 8) | d.setup_buf[4], d.setup_len, d.data_buf); if (ret > 0) ret = 0; } else { // return 0 byte } break; case SETUP_STATE_DATA: if (d.setup_buf[0] & USB_DIR_IN) { l = d.setup_len - d.setup_index; if (l > len) l = len; memcpy(data, d.data_buf + d.setup_index, l); d.setup_index += l; if (d.setup_index >= d.setup_len) d.setup_state = SETUP_STATE_ACK; ret = l; } else { d.setup_state = SETUP_STATE_IDLE; goto fail; } break; default: goto fail; } break; default: ret = handle_data(p); break; } break; case USB_TOKEN_OUT: if (d.state < USB_STATE_DEFAULT || p->devaddr != d.addr) return USB_RET_NODEV; if (d.stall) goto fail; switch(p->devep) { case 0: switch(d.setup_state) { case SETUP_STATE_ACK: if (d.setup_buf[0] & USB_DIR_IN) { d.setup_state = SETUP_STATE_IDLE; // transfer OK } else { // ignore additionnal output } break; case SETUP_STATE_DATA: if (!(d.setup_buf[0] & USB_DIR_IN)) { l = d.setup_len - d.setup_index; if (l > len) l = len; memcpy(d.data_buf + d.setup_index, data, l); d.setup_index += l; if (d.setup_index >= d.setup_len) d.setup_state = SETUP_STATE_ACK; ret = l; } else { // it is okay for a host to send an OUT before it reads // all of the expected IN. It is telling the controller // that it doesn't want any more from that particular call. ret = 0; d.setup_state = SETUP_STATE_IDLE; } break; default: goto fail; } break; default: ret = handle_data(p); break; } break; default: fail: d.stall = 1; ret = USB_RET_STALL; break; } return ret; } int usb_device_c::handle_control_common(int request, int value, int index, int length, Bit8u *data) { int ret = -1; switch (request) { case DeviceOutRequest | USB_REQ_SET_ADDRESS: BX_DEBUG(("USB_REQ_SET_ADDRESS:")); d.state = USB_STATE_ADDRESS; d.addr = value; ret = 0; break; case DeviceRequest | USB_REQ_GET_DESCRIPTOR: switch (value >> 8) { case USB_DT_DEVICE: BX_DEBUG(("USB_REQ_GET_DESCRIPTOR: Device")); memcpy(data, d.dev_descriptor, d.device_desc_size); ret = d.device_desc_size; break; case USB_DT_CONFIG: BX_DEBUG(("USB_REQ_GET_DESCRIPTOR: Config")); memcpy(data, d.config_descriptor, d.config_desc_size); ret = d.config_desc_size; break; case USB_DT_STRING: BX_DEBUG(("USB_REQ_GET_DESCRIPTOR: 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: // vendor description ret = set_usb_string(data, d.vendor_desc); break; case 2: // product description ret = set_usb_string(data, d.product_desc); break; case 3: // serial number ret = set_usb_string(data, d.serial_num); break; } break; } break; case DeviceRequest | USB_REQ_GET_STATUS: BX_DEBUG(("USB_REQ_GET_STATUS:")); data[0] = 0x00; if (d.config_descriptor[7] & 0x40) { data[0] |= (1 << USB_DEVICE_SELF_POWERED); } if (d.remote_wakeup) { data[0] |= (1 << USB_DEVICE_REMOTE_WAKEUP); } data[1] = 0x00; ret = 2; break; case DeviceRequest | USB_REQ_GET_CONFIGURATION: BX_DEBUG(("USB_REQ_GET_CONFIGURATION:")); data[0] = d.config; ret = 1; break; case DeviceOutRequest | USB_REQ_SET_CONFIGURATION: BX_DEBUG(("USB_REQ_SET_CONFIGURATION: value=%d", value)); d.config = value; d.state = USB_STATE_CONFIGURED; ret = 0; break; case DeviceOutRequest | USB_REQ_CLEAR_FEATURE: if (value == USB_DEVICE_REMOTE_WAKEUP) { d.remote_wakeup = 0; ret = 0; } break; case DeviceOutRequest | USB_REQ_SET_FEATURE: if (value == USB_DEVICE_REMOTE_WAKEUP) { d.remote_wakeup = 1; ret = 0; } break; case InterfaceRequest | USB_REQ_GET_INTERFACE: BX_DEBUG(("USB_REQ_GET_INTERFACE:")); data[0] = d.interface; ret = 1; break; case InterfaceOutRequest | USB_REQ_SET_INTERFACE: BX_DEBUG(("USB_REQ_SET_INTERFACE: value=%d", value)); d.interface = value; ret = 0; break; } return ret; } void usb_device_c::register_state(bx_list_c *parent) { d.sr = parent; bx_list_c *list = new bx_list_c(parent, "d", "Common USB Device State"); BXRS_DEC_PARAM_FIELD(list, addr, d.addr); BXRS_DEC_PARAM_FIELD(list, config, d.config); BXRS_DEC_PARAM_FIELD(list, interface, d.interface); BXRS_DEC_PARAM_FIELD(list, state, d.state); BXRS_DEC_PARAM_FIELD(list, remote_wakeup, d.remote_wakeup); register_state_specific(parent); } // Turn on BX_DEBUG messages at connection time void usb_device_c::set_debug_mode() { setonoff(LOGLEV_DEBUG, ACT_REPORT); } // Send an internal message to a USB device void usb_device_c::usb_send_msg(int msg) { USBPacket p; memset(&p, 0, sizeof(p)); p.pid = msg; handle_packet(&p); } // Dumps the contents of a buffer to the log file void usb_device_c::usb_dump_packet(Bit8u *data, unsigned size) { char buf_str[1025], temp_str[17]; if (getonoff(LOGLEV_DEBUG) == ACT_REPORT) { BX_DEBUG(("packet hexdump (%i bytes)", size)); buf_str[0] = 0; for (unsigned i = 0; i < size; i++) { sprintf(temp_str, "%02X ", data[i]); strcat(buf_str, temp_str); if ((i % 16) == 15) { BX_DEBUG(("%s", buf_str)); buf_str[0] = 0; } } if (strlen(buf_str) > 0) BX_DEBUG(("%s", buf_str)); } } int usb_device_c::set_usb_string(Bit8u *buf, const char *str) { size_t len, i; Bit8u *q; q = buf; len = strlen(str); if (len > 32) { *q = 0; return 0; } *q++ = 2 * len + 2; *q++ = 3; for(i = 0; i < len; i++) { *q++ = str[i]; *q++ = 0; } return q - buf; } #endif // BX_SUPPORT_PCI && BX_SUPPORT_PCIUSB