diff --git a/bochs/iodev/usb/scsi_device.cc b/bochs/iodev/usb/scsi_device.cc index 90134d30b..bb59b79a6 100644 --- a/bochs/iodev/usb/scsi_device.cc +++ b/bochs/iodev/usb/scsi_device.cc @@ -546,7 +546,7 @@ Bit32s scsi_device_t::scsi_send_command(Bit32u tag, Bit8u *buf, Bit8u cmd_len, i goto fail; } switch (command) { - case 0x0: + case 0x00: BX_DEBUG(("Test Unit Ready")); if (!inserted) goto notready; @@ -974,11 +974,11 @@ Bit32s scsi_device_t::scsi_send_command(Bit32u tag, Bit8u *buf, Bit8u cmd_len, i outbuf[0] = (Bit8u)((nb_sectors >> 24) & 0xff); outbuf[1] = (Bit8u)((nb_sectors >> 16) & 0xff); outbuf[2] = (Bit8u)((nb_sectors >> 8) & 0xff); - outbuf[3] = (Bit8u)(nb_sectors & 0xff); - outbuf[4] = 0; - outbuf[5] = 0; - outbuf[6] = (block_size >> 8); - outbuf[7] = 0; + outbuf[3] = (Bit8u) (nb_sectors & 0xff); + outbuf[4] = (Bit8u) ((block_size >> 24) & 0xff); + outbuf[5] = (Bit8u) ((block_size >> 16) & 0xff); + outbuf[6] = (Bit8u) ((block_size >> 8) & 0xff); + outbuf[7] = (Bit8u) (block_size & 0xff); r->buf_len = 8; } else { notready: @@ -1016,6 +1016,7 @@ Bit32s scsi_device_t::scsi_send_command(Bit32u tag, Bit8u *buf, Bit8u cmd_len, i r->async_mode = async; break; case 0x35: + case 0x91: BX_DEBUG(("Synchronise cache (sector " FMT_LL "d, count %d)", lba, len)); // TODO: flush cache break; @@ -1112,6 +1113,52 @@ Bit32s scsi_device_t::scsi_send_command(Bit32u tag, Bit8u *buf, Bit8u cmd_len, i } break; + // The 0x9E command uses a service action code (ex: 0x9E/0x10) + case 0x9E: + switch (buf[1] & 0x1F) { // service action code + case 0x10: // Read Capacity(16) + BX_DEBUG(("Read Capacity 16")); + memset(outbuf, 0, 32); + if (type == SCSIDEV_TYPE_CDROM) { + nb_sectors = max_lba; + } else { + nb_sectors = hdimage->hd_size / block_size; + nb_sectors--; + } + /* Returned value is the address of the last sector. */ + if (nb_sectors) { + // 64-bit lba + outbuf[ 0] = (Bit8u)((nb_sectors >> 56) & 0xff); + outbuf[ 1] = (Bit8u)((nb_sectors >> 48) & 0xff); + outbuf[ 2] = (Bit8u)((nb_sectors >> 40) & 0xff); + outbuf[ 3] = (Bit8u)((nb_sectors >> 32) & 0xff); + outbuf[ 4] = (Bit8u)((nb_sectors >> 24) & 0xff); + outbuf[ 5] = (Bit8u)((nb_sectors >> 16) & 0xff); + outbuf[ 6] = (Bit8u)((nb_sectors >> 8) & 0xff); + outbuf[ 7] = (Bit8u) (nb_sectors & 0xff); + // 32-bit block size + outbuf[ 8] = (Bit8u) ((block_size >> 24) & 0xff); + outbuf[ 9] = (Bit8u) ((block_size >> 16) & 0xff); + outbuf[10] = (Bit8u) ((block_size >> 8) & 0xff); + outbuf[11] = (Bit8u) (block_size & 0xff); + // protection + outbuf[12] = 0; + // exponent/one or more physical blocks per logical block + outbuf[13] = 0; + // lowest aligned logical block address + outbuf[14] = 0; + outbuf[15] = 0; + // bytes 16 through 31 are reserved (zero'd above) + r->buf_len = (len < 32) ? len : 32; + } else { + scsi_command_complete(r, STATUS_CHECK_CONDITION, SENSE_NOT_READY); + return 0; + } + break; + default: + BX_ERROR(("Unknown SCSI command (0x9E/%02X)", buf[1] & 0x1F)); + } + break; default: BX_ERROR(("Unknown SCSI command (0x%02X)", buf[0])); fail: diff --git a/bochs/iodev/usb/usb_common.cc b/bochs/iodev/usb/usb_common.cc index ce190136d..39d3817f6 100644 --- a/bochs/iodev/usb/usb_common.cc +++ b/bochs/iodev/usb/usb_common.cc @@ -448,7 +448,7 @@ int usb_device_c::handle_packet(USBPacket *p) #if HANDLE_TOGGLE_CONTROL // manage our toggle bit if ((p->toggle > -1) && (p->toggle != get_toggle(p->devep))) { - BX_ERROR(("DATA IN: Packet Toggle indicator doesn't match Device Toggle indicator. %d != %d", p->toggle, get_toggle(p->devep))); + BX_ERROR(("DATA IN EP%d: Packet Toggle indicator doesn't match Device Toggle indicator. %d != %d", p->devep, p->toggle, get_toggle(p->devep))); goto fail; } set_toggle(p->devep, get_toggle(p->devep) ^ 1); // toggle the bit @@ -525,7 +525,7 @@ int usb_device_c::handle_packet(USBPacket *p) #if HANDLE_TOGGLE_CONTROL // manage our toggle bit if ((p->toggle > -1) && (p->toggle != get_toggle(p->devep))) { - BX_ERROR(("DATA OUT: Packet Toggle indicator doesn't match Device Toggle indicator. %d != %d", p->toggle, get_toggle(p->devep))); + BX_ERROR(("DATA OUT EP%d: Packet Toggle indicator doesn't match Device Toggle indicator. %d != %d", p->devep, p->toggle, get_toggle(p->devep))); goto fail; } set_toggle(p->devep, get_toggle(p->devep) ^ 1); // toggle the bit @@ -687,22 +687,33 @@ int usb_device_c::handle_control_common(int request, int value, int index, int l } break; case InterfaceRequest | USB_REQ_GET_INTERFACE: + // Ben TODO: If the device is not in the configured state, this request should stall BX_DEBUG(("USB_REQ_GET_INTERFACE:")); // with InterfaceRequest, the wValue field must be zero and wLength field must be 1 if ((value != 0) || (length != 1)) { BX_ERROR(("USB_REQ_GET_INTERFACE: This type of request requires the wValue field to be zero and wLength field to be one.")); } - data[0] = d.iface; - ret = 1; + // all our devices only have one interface, and that value must be zero + // if we ever add a device that has more than one interface (a video cam ?), we will need to modify this + if (index == 0) { + data[0] = d.alt_iface; + ret = 1; + } break; case InterfaceOutRequest | USB_REQ_SET_INTERFACE: + // Ben TODO: If the device is not in the configured state, this request should stall BX_DEBUG(("USB_REQ_SET_INTERFACE: value=%d", value)); // with InterfaceRequest, the wIndex and wLength fields must be zero if ((index != 0) || (length != 0)) { BX_ERROR(("USB_REQ_SET_INTERFACE: This type of request requires the wIndex and wLength fields to be zero.")); } - d.iface = value; - ret = 0; + // all our devices only have one interface, and that value must be zero + // if we ever add a device that has more than one interface (a video cam ?), we will need to modify this + if ((index == 0) && (value <= d.alt_iface_max)) { + d.alt_iface = value; // alternate interface + handle_iface_change(value); // let the device know we changed the interface number + ret = 0; + } break; // should not have a default: here, so allowing the device's handle_control() to try to execute the request } @@ -717,7 +728,7 @@ void usb_device_c::register_state(bx_list_c *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.iface); + BXRS_DEC_PARAM_FIELD(list, interface, d.alt_iface); BXRS_DEC_PARAM_FIELD(list, state, d.state); BXRS_DEC_PARAM_FIELD(list, remote_wakeup, d.remote_wakeup); register_state_specific(parent); @@ -755,13 +766,13 @@ void usb_device_c::usb_dump_packet(Bit8u *data, int size, int bus, int dev_addr, } // safety catch - if (size > 4096) { - BX_DEBUG(("packet hexdump with irregular size: %u (truncating to 64 bytes)", size)); + if (size > 8192) { + BX_DEBUG(("packet hexdump with irregular size: %u (truncating to 8192 bytes)", size)); } - // safety catch (only dump up to 512 bytes per packet so to not fill the log file) - if (size > 512) - size = 512; + // safety catch (only dump up to 8192 bytes per packet so to not fill the log file) + if (size > 8192) + size = 8192; if (getonoff(LOGLEV_DEBUG) == ACT_REPORT) { BX_DEBUG(("packet hexdump (%d bytes)", size)); diff --git a/bochs/iodev/usb/usb_common.h b/bochs/iodev/usb/usb_common.h index 3d0c45da4..677378d14 100644 --- a/bochs/iodev/usb/usb_common.h +++ b/bochs/iodev/usb/usb_common.h @@ -115,6 +115,7 @@ #define USB_REQ_SET_INTERFACE 0x0B #define USB_REQ_SYNCH_FRAME 0x0C #define USB_REQ_SET_SEL 0x30 +#define USB_REQ_SET_ISO_DELAY 0x31 #define USB_DEVICE_SELF_POWERED 0 #define USB_DEVICE_REMOTE_WAKEUP 1 @@ -162,6 +163,7 @@ struct USBPacket { USBCallback *complete_cb; void *complete_dev; usb_device_c *dev; + int strm_pid; // stream primary id }; typedef struct USBAsync { @@ -211,6 +213,7 @@ public: virtual void handle_reset() {} virtual int handle_control(int request, int value, int index, int length, Bit8u *data) { return -1; } virtual int handle_data(USBPacket *p) { return 0; } + virtual void handle_iface_change(int iface) {} void register_state(bx_list_c *parent); virtual void register_state_specific(bx_list_c *parent) {} virtual void after_restore_state() {} @@ -231,7 +234,7 @@ public: } // return information for the specified ep of the current device -#define USB_MAX_ENDPOINTS 4 // we currently don't use more than 4 endpoints (ep0, ep1, ep2, and ep3) +#define USB_MAX_ENDPOINTS 5 // we currently don't use more than 5 endpoints (ep0, ep1, ep2, ep3, and ep4) int get_mps(const int ep) { return (ep < USB_MAX_ENDPOINTS) ? d.endpoint_info[ep].max_packet_size : 0; } @@ -252,6 +255,9 @@ public: Bit8u get_type() { return d.type; } + Bit8u get_aIface() { + return d.alt_iface; + } Bit8u get_address() {return d.addr;} void set_async_mode(bool async) { d.async_mode = async; } @@ -275,7 +281,8 @@ protected: int speed; Bit8u addr; Bit8u config; - Bit8u iface; + Bit8u alt_iface; + Bit8u alt_iface_max; char devname[32]; USBEndPoint endpoint_info[USB_MAX_ENDPOINTS]; diff --git a/bochs/iodev/usb/usb_ehci.cc b/bochs/iodev/usb/usb_ehci.cc index ccd9cf156..5a89bf790 100644 --- a/bochs/iodev/usb/usb_ehci.cc +++ b/bochs/iodev/usb/usb_ehci.cc @@ -1432,12 +1432,13 @@ int bx_usb_ehci_c::execute(EHCIPacket *p) #endif p->packet.complete_cb = ehci_event_handler; p->packet.complete_dev = BX_EHCI_THIS_PTR; + p->packet.strm_pid = 0; // if UASP is used, this must be 0 p->async = EHCI_ASYNC_INITIALIZED; } ret = p->queue->dev->handle_packet(&p->packet); - BX_DEBUG(("submit: qh %x next %x qtd %x pid %x len %d (total %d) endp %x ret %d\n", + BX_DEBUG(("submit: qh %x next %x qtd %x pid %x len %d (total %d) endp %x ret %d", p->queue->qhaddr, p->queue->qh.next, p->queue->qtdaddr, p->pid, p->packet.len, p->tbytes, endp, ret)); @@ -2079,7 +2080,10 @@ void bx_usb_ehci_c::advance_async_state(void) default: /* this should only be due to a developer mistake */ - BX_PANIC(("Bad asynchronous state %d. Resetting to active", BX_EHCI_THIS hub.astate)); + // Ben: I commented this line due to the fact that after a USB_RET_ASYNC return, + // then a usb_packet_complete(p), the event handler is setting the astate to + // EST_EXECUTE instead of EST_ACTIVE ??? + //BX_PANIC(("Bad asynchronous state %d. Resetting to active", BX_EHCI_THIS hub.astate)); BX_EHCI_THIS set_state(async, EST_ACTIVE); } } diff --git a/bochs/iodev/usb/usb_floppy.cc b/bochs/iodev/usb/usb_floppy.cc index eebdf073f..c02553e4e 100644 --- a/bochs/iodev/usb/usb_floppy.cc +++ b/bochs/iodev/usb/usb_floppy.cc @@ -470,6 +470,7 @@ bool usb_floppy_device_c::init() sprintf(s.info_txt, "USB floppy: media not present"); } d.connected = 1; + d.alt_iface_max = 0; s.did_inquiry_fail = 0; s.fail_count = 0; diff --git a/bochs/iodev/usb/usb_hid.cc b/bochs/iodev/usb/usb_hid.cc index d58448344..4141352b1 100644 --- a/bochs/iodev/usb/usb_hid.cc +++ b/bochs/iodev/usb/usb_hid.cc @@ -792,7 +792,7 @@ bool usb_hid_device_c::init() * in the configuration, simply uncomment this line. I use * it when I am working on this emulation. */ - LOG_THIS setonoff(LOGLEV_DEBUG, ACT_REPORT); + //LOG_THIS setonoff(LOGLEV_DEBUG, ACT_REPORT); if ((d.type == USB_HID_TYPE_MOUSE) || (d.type == USB_HID_TYPE_TABLET)) { @@ -850,6 +850,7 @@ bool usb_hid_device_c::init() } } d.connected = 1; + d.alt_iface_max = 0; return 1; } diff --git a/bochs/iodev/usb/usb_hub.cc b/bochs/iodev/usb/usb_hub.cc index ee958c8c7..2e75489f9 100644 --- a/bochs/iodev/usb/usb_hub.cc +++ b/bochs/iodev/usb/usb_hub.cc @@ -283,6 +283,7 @@ bool usb_hub_device_c::init() } sprintf(hub.info_txt, "%d-port USB hub", hub.n_ports); d.connected = 1; + d.alt_iface_max = 0; return 1; } @@ -740,8 +741,10 @@ Bit64s usb_hub_device_c::hub_param_handler(bx_param_c *param, bool set, Bit64s v } else { BX_PANIC(("usb_param_handler called with unexpected parameter '%s'", param->get_name())); } - } else { - BX_PANIC(("hub_param_handler: external hub not found")); + // hub == NULL. We shouldn't call BX_PANIC with a NULL pointer. + // #define BX_PANIC(x) (LOG_THIS /* = hub-> */ panic) x + //} else { + // BX_PANIC(("hub_param_handler: external hub not found")); } } return val; diff --git a/bochs/iodev/usb/usb_msd.cc b/bochs/iodev/usb/usb_msd.cc index d2e169d98..e310e2bcb 100644 --- a/bochs/iodev/usb/usb_msd.cc +++ b/bochs/iodev/usb/usb_msd.cc @@ -140,7 +140,7 @@ static const Bit8u bx_msd_config_descriptor[] = { 0x09, /* u8 if_bLength; */ 0x04, /* u8 if_bDescriptorType; Interface */ 0x00, /* u8 if_bInterfaceNumber; */ - 0x00, /* u8 if_bAlternateSetting; */ + MSD_PROTO_BBB, /* u8 if_bAlternateSetting; */ 0x02, /* u8 if_bNumEndpoints; */ 0x08, /* u8 if_bInterfaceClass; MASS STORAGE */ 0x06, /* u8 if_bInterfaceSubClass; SCSI */ @@ -150,7 +150,7 @@ static const Bit8u bx_msd_config_descriptor[] = { /* Bulk-In endpoint */ 0x07, /* u8 ep_bLength; */ 0x05, /* u8 ep_bDescriptorType; Endpoint */ - 0x81, /* u8 ep_bEndpointAddress; IN Endpoint 1 */ + 0x80 | MSD_BBB_DATAIN_EP, /* u8 ep_bEndpointAddress; IN Endpoint */ 0x02, /* u8 ep_bmAttributes; Bulk */ 0x40, 0x00, /* u16 ep_wMaxPacketSize; 64 */ 0x00, /* u8 ep_bInterval; */ @@ -158,7 +158,7 @@ static const Bit8u bx_msd_config_descriptor[] = { /* Bulk-Out endpoint */ 0x07, /* u8 ep_bLength; */ 0x05, /* u8 ep_bDescriptorType; Endpoint */ - 0x02, /* u8 ep_bEndpointAddress; OUT Endpoint 2 */ + 0x00 | MSD_BBB_DATAOUT_EP, /* u8 ep_bEndpointAddress; OUT Endpoint */ 0x02, /* u8 ep_bmAttributes; Bulk */ 0x40, 0x00, /* u16 ep_wMaxPacketSize; 64 */ 0x00 /* u8 ep_bInterval; */ @@ -179,7 +179,7 @@ static const Bit8u bx_msd_dev_descriptor2[] = { 0x00, 0x00, /* u16 idVendor; */ 0x00, 0x00, /* u16 idProduct; */ 0x00, 0x01, /* u16 bcdDevice */ - + 0x01, /* u8 iManufacturer; */ 0x02, /* u8 iProduct; */ 0x03, /* u8 iSerialNumber; */ @@ -187,7 +187,7 @@ static const Bit8u bx_msd_dev_descriptor2[] = { }; // High-speed -static const Bit8u bx_msd_config_descriptor2[] = { +static Bit8u bx_msd_config_descriptor2[] = { /* one configuration */ 0x09, /* u8 bLength; */ @@ -196,18 +196,18 @@ static const Bit8u bx_msd_config_descriptor2[] = { 0x01, /* u8 bNumInterfaces; (1) */ 0x01, /* u8 bConfigurationValue; */ 0x00, /* u8 iConfiguration; */ - 0xc0, /* u8 bmAttributes; + 0x80, /* u8 bmAttributes; Bit 7: must be set, 6: Self-powered, 5: Remote wakeup, 4..0: resvd */ 0x00, /* u8 MaxPower; */ - + /* one interface */ 0x09, /* u8 if_bLength; */ 0x04, /* u8 if_bDescriptorType; Interface */ 0x00, /* u8 if_bInterfaceNumber; */ - 0x00, /* u8 if_bAlternateSetting; */ + MSD_PROTO_BBB, /* u8 if_bAlternateSetting; */ 0x02, /* u8 if_bNumEndpoints; */ 0x08, /* u8 if_bInterfaceClass; MASS STORAGE */ 0x06, /* u8 if_bInterfaceSubClass; SCSI */ @@ -217,7 +217,7 @@ static const Bit8u bx_msd_config_descriptor2[] = { /* Bulk-In endpoint */ 0x07, /* u8 ep_bLength; */ 0x05, /* u8 ep_bDescriptorType; Endpoint */ - 0x81, /* u8 ep_bEndpointAddress; IN Endpoint 1 */ + 0x80 | MSD_BBB_DATAIN_EP, /* u8 ep_bEndpointAddress; IN Endpoint */ 0x02, /* u8 ep_bmAttributes; Bulk */ 0x00, 0x02, /* u16 ep_wMaxPacketSize; 512 */ 0x00, /* u8 ep_bInterval; */ @@ -225,10 +225,83 @@ static const Bit8u bx_msd_config_descriptor2[] = { /* Bulk-Out endpoint */ 0x07, /* u8 ep_bLength; */ 0x05, /* u8 ep_bDescriptorType; Endpoint */ - 0x02, /* u8 ep_bEndpointAddress; OUT Endpoint 2 */ + 0x00 | MSD_BBB_DATAOUT_EP, /* u8 ep_bEndpointAddress; OUT Endpoint */ 0x02, /* u8 ep_bmAttributes; Bulk */ 0x00, 0x02, /* u16 ep_wMaxPacketSize; 512 */ - 0x00 /* u8 ep_bInterval; */ + 0x00, /* u8 ep_bInterval; */ + + /***** If UASP is requested, the rest of this descriptor + * will be returned, else, the descriptor stops here *****/ + /* alt interface 1 */ + 0x09, /* u8 if_bLength; */ + 0x04, /* u8 if_bDescriptorType; Interface */ + 0x00, /* u8 if_bInterfaceNumber; */ + MSD_PROTO_UASP, /* u8 if_bAlternateSetting; */ + 0x04, /* u8 if_bNumEndpoints; */ + 0x08, /* u8 if_bInterfaceClass; MASS STORAGE */ + 0x06, /* u8 if_bInterfaceSubClass; SCSI */ + 0x62, /* u8 if_bInterfaceProtocol; UASP */ + 0x00, /* u8 if_iInterface; */ + + /***** Command Out Pipe *****/ + /* Alt Int 1: Bulk-Out endpoint */ + 0x07, /* u8 ep_bLength; */ + 0x05, /* u8 ep_bDescriptorType; Endpoint */ + 0x00 | MSD_UASP_COMMAND, /* u8 ep_bEndpointAddress; Out Endpoint */ + 0x02, /* u8 ep_bmAttributes; Bulk */ + 0x00, 0x02, /* u16 ep_wMaxPacketSize; 512 */ + 0x00, /* u8 ep_bInterval; */ + + /* Alt Int 1: Bulk-In Pipe usage */ + 0x04, /* u8 bLength; */ + 0x24, /* u8 bDescriptorType; Function */ + MSD_UASP_COMMAND, /* u8 bPipeUsage; Command Out pipe */ + 0x00, /* u8 breserved; */ + + /***** Status In Pipe *****/ + /* Alt Int 1: Bulk-In endpoint */ + 0x07, /* u8 ep_bLength; */ + 0x05, /* u8 ep_bDescriptorType; Endpoint */ + 0x80 | MSD_UASP_STATUS, /* u8 ep_bEndpointAddress; IN Endpoint */ + 0x02, /* u8 ep_bmAttributes; Bulk */ + 0x00, 0x02, /* u16 ep_wMaxPacketSize; 512 */ + 0x00, /* u8 ep_bInterval; */ + + /* Alt Int 1: Bulk-In Pipe usage */ + 0x04, /* u8 bLength; */ + 0x24, /* u8 bDescriptorType; Function */ + MSD_UASP_STATUS, /* u8 bPipeUsage; Status In pipe */ + 0x00, /* u8 breserved; */ + + /***** Data In Pipe *****/ + /* Alt Int 1: Bulk-In endpoint */ + 0x07, /* u8 ep_bLength; */ + 0x05, /* u8 ep_bDescriptorType; Endpoint */ + 0x80 | MSD_UASP_DATAIN, /* u8 ep_bEndpointAddress; IN Endpoint */ + 0x02, /* u8 ep_bmAttributes; Bulk */ + 0x00, 0x02, /* u16 ep_wMaxPacketSize; 512 */ + 0x00, /* u8 ep_bInterval; */ + + /* Alt Int 1: Bulk-In Pipe usage */ + 0x04, /* u8 bLength; */ + 0x24, /* u8 bDescriptorType; Function */ + MSD_UASP_DATAIN, /* u8 bPipeUsage; Data In pipe */ + 0x00, /* u8 breserved; */ + + /***** Data Out Pipe *****/ + /* Alt Int 1: Bulk-Out endpoint */ + 0x07, /* u8 ep_bLength; */ + 0x05, /* u8 ep_bDescriptorType; Endpoint */ + 0x00 | MSD_UASP_DATAOUT, /* u8 ep_bEndpointAddress; OUT Endpoint */ + 0x02, /* u8 ep_bmAttributes; Bulk */ + 0x00, 0x02, /* u16 ep_wMaxPacketSize; 512 */ + 0x00, /* u8 ep_bInterval; */ + + /* Alt Int 1: Bulk-Out Pipe usage */ + 0x04, /* u8 bLength; */ + 0x24, /* u8 bDescriptorType; Function */ + MSD_UASP_DATAOUT, /* u8 bPipeUsage; Data Out pipe */ + 0x00, /* u8 breserved; */ }; // Super-speed @@ -253,11 +326,14 @@ static const Bit8u bx_msd_dev_descriptor3[] = { 0x01 /* u8 bNumConfigurations; */ }; -static const Bit8u bx_msd_config_descriptor3[] = { +static Bit8u bx_msd_config_descriptor3[] = { /* one configuration */ 0x09, /* u8 bLength; */ 0x02, /* u8 bDescriptorType; Configuration */ - 0x2C, 0x00, /* u16 wTotalLength; */ + + /* this length will be modified depending if UASP is requested (0x002C or 0x0079) */ + 0x2C, 0x00, /* u16 wTotalLength; */ + 0x01, /* u8 bNumInterfaces; (1) */ 0x01, /* u8 bConfigurationValue; */ 0x00, /* u8 iConfiguration; */ @@ -266,48 +342,149 @@ static const Bit8u bx_msd_config_descriptor3[] = { 6: Self-powered, 5: Remote wakeup, 4..0: resvd */ - 0x3F, /* u8 MaxPower; */ + 0x12, /* u8 MaxPower; */ - /* one interface */ + /* alt interface 0 */ 0x09, /* u8 if_bLength; */ 0x04, /* u8 if_bDescriptorType; Interface */ 0x00, /* u8 if_bInterfaceNumber; */ - 0x00, /* u8 if_bAlternateSetting; */ + MSD_PROTO_BBB, /* u8 if_bAlternateSetting; */ 0x02, /* u8 if_bNumEndpoints; */ 0x08, /* u8 if_bInterfaceClass; MASS STORAGE */ 0x06, /* u8 if_bInterfaceSubClass; SCSI */ 0x50, /* u8 if_bInterfaceProtocol; Bulk Only */ 0x00, /* u8 if_iInterface; */ - /* Bulk-In endpoint */ + /* Alt Int 0: Bulk-In endpoint */ 0x07, /* u8 ep_bLength; */ 0x05, /* u8 ep_bDescriptorType; Endpoint */ - 0x81, /* u8 ep_bEndpointAddress; IN Endpoint 1 */ + 0x80 | MSD_BBB_DATAIN_EP, /* u8 ep_bEndpointAddress; IN Endpoint */ 0x02, /* u8 ep_bmAttributes; Bulk */ 0x00, 0x04, /* u16 ep_wMaxPacketSize; 1024 */ 0x00, /* u8 ep_bInterval; */ - /* Bulk-In companion descriptor */ + /* Alt Int 0: Bulk-In companion descriptor */ 0x06, /* u8 epc_bLength; */ 0x30, /* u8 epc_bDescriptorType; Endpoint Companion */ 0x0F, /* u8 epc_bMaxPerBurst; */ 0x00, /* u8 epc_bmAttributes; */ 0x00, 0x00, /* u16 epc_reserved; */ - /* Bulk-Out endpoint */ + /* Alt Int 0: Bulk-Out endpoint */ 0x07, /* u8 ep_bLength; */ 0x05, /* u8 ep_bDescriptorType; Endpoint */ - 0x02, /* u8 ep_bEndpointAddress; OUT Endpoint 2 */ + 0x00 | MSD_BBB_DATAOUT_EP, /* u8 ep_bEndpointAddress; OUT Endpoint */ 0x02, /* u8 ep_bmAttributes; Bulk */ 0x00, 0x04, /* u16 ep_wMaxPacketSize; 1024 */ 0x00, /* u8 ep_bInterval; */ - /* Bulk-Out companion descriptor */ + /* Alt Int 0: Bulk-Out companion descriptor */ 0x06, /* u8 epc_bLength; */ 0x30, /* u8 epc_bDescriptorType; Endpoint Companion */ 0x0F, /* u8 epc_bMaxPerBurst; */ 0x00, /* u8 epc_bmAttributes; */ - 0x00, 0x00 /* u16 epc_reserved; */ + 0x00, 0x00, /* u16 epc_reserved; */ + + /***** If UASP is requested, the rest of this descriptor + * will be returned, else, the descriptor stops here *****/ + /* alt interface 1 */ + 0x09, /* u8 if_bLength; */ + 0x04, /* u8 if_bDescriptorType; Interface */ + 0x00, /* u8 if_bInterfaceNumber; */ + MSD_PROTO_UASP, /* u8 if_bAlternateSetting; */ + 0x04, /* u8 if_bNumEndpoints; */ + 0x08, /* u8 if_bInterfaceClass; MASS STORAGE */ + 0x06, /* u8 if_bInterfaceSubClass; SCSI */ + 0x62, /* u8 if_bInterfaceProtocol; UASP */ + 0x00, /* u8 if_iInterface; */ + + /***** Command Out Pipe *****/ + /* Alt Int 1: Bulk-Out endpoint */ + 0x07, /* u8 ep_bLength; */ + 0x05, /* u8 ep_bDescriptorType; Endpoint */ + 0x00 | MSD_UASP_COMMAND, /* u8 ep_bEndpointAddress; Out Endpoint */ + 0x02, /* u8 ep_bmAttributes; Bulk */ + 0x00, 0x04, /* u16 ep_wMaxPacketSize; 1024 */ + 0x00, /* u8 ep_bInterval; */ + + /* Alt Int 1: Bulk-Out companion descriptor */ + 0x06, /* u8 epc_bLength; */ + 0x30, /* u8 epc_bDescriptorType; Endpoint Companion */ + 0x00, /* u8 epc_bMaxPerBurst; */ + 0x00, /* u8 epc_bmAttributes; no streams */ + 0x00, 0x00, /* u16 epc_reserved; */ + + /* Alt Int 1: Bulk-In Pipe usage */ + 0x04, /* u8 bLength; */ + 0x24, /* u8 bDescriptorType; Function */ + MSD_UASP_COMMAND, /* u8 bPipeUsage; Command Out pipe */ + 0x00, /* u8 breserved; */ + + /***** Status In Pipe *****/ + /* Alt Int 1: Bulk-In endpoint */ + 0x07, /* u8 ep_bLength; */ + 0x05, /* u8 ep_bDescriptorType; Endpoint */ + 0x80 | MSD_UASP_STATUS, /* u8 ep_bEndpointAddress; IN Endpoint */ + 0x02, /* u8 ep_bmAttributes; Bulk */ + 0x00, 0x04, /* u16 ep_wMaxPacketSize; 1024 */ + 0x00, /* u8 ep_bInterval; */ + + /* Alt Int 1: Bulk-In companion descriptor */ + 0x06, /* u8 epc_bLength; */ + 0x30, /* u8 epc_bDescriptorType; Endpoint Companion */ + 0x0F, /* u8 epc_bMaxPerBurst; */ + UASP_MAX_STREAMS, /* u8 epc_bmAttributes; 2^x max streams */ + 0x00, 0x00, /* u16 epc_reserved; */ + + /* Alt Int 1: Bulk-In Pipe usage */ + 0x04, /* u8 bLength; */ + 0x24, /* u8 bDescriptorType; Function */ + MSD_UASP_STATUS, /* u8 bPipeUsage; Status In pipe */ + 0x00, /* u8 breserved; */ + + /***** Data In Pipe *****/ + /* Alt Int 1: Bulk-In endpoint */ + 0x07, /* u8 ep_bLength; */ + 0x05, /* u8 ep_bDescriptorType; Endpoint */ + 0x80 | MSD_UASP_DATAIN, /* u8 ep_bEndpointAddress; IN Endpoint */ + 0x02, /* u8 ep_bmAttributes; Bulk */ + 0x00, 0x04, /* u16 ep_wMaxPacketSize; 1024 */ + 0x00, /* u8 ep_bInterval; */ + + /* Alt Int 1: Bulk-In companion descriptor */ + 0x06, /* u8 epc_bLength; */ + 0x30, /* u8 epc_bDescriptorType; Endpoint Companion */ + 0x0F, /* u8 epc_bMaxPerBurst; */ + UASP_MAX_STREAMS, /* u8 epc_bmAttributes; 2^x max streams */ + 0x00, 0x00, /* u16 epc_reserved; */ + + /* Alt Int 1: Bulk-In Pipe usage */ + 0x04, /* u8 bLength; */ + 0x24, /* u8 bDescriptorType; Function */ + MSD_UASP_DATAIN, /* u8 bPipeUsage; Data In pipe */ + 0x00, /* u8 breserved; */ + + /***** Data Out Pipe *****/ + /* Alt Int 1: Bulk-Out endpoint */ + 0x07, /* u8 ep_bLength; */ + 0x05, /* u8 ep_bDescriptorType; Endpoint */ + 0x00 | MSD_UASP_DATAOUT, /* u8 ep_bEndpointAddress; OUT Endpoint */ + 0x02, /* u8 ep_bmAttributes; Bulk */ + 0x00, 0x04, /* u16 ep_wMaxPacketSize; 1024 */ + 0x00, /* u8 ep_bInterval; */ + + /* Alt Int 1: Bulk-Out companion descriptor */ + 0x06, /* u8 epc_bLength; */ + 0x30, /* u8 epc_bDescriptorType; Endpoint Companion */ + 0x0F, /* u8 epc_bMaxPerBurst; */ + UASP_MAX_STREAMS, /* u8 epc_bmAttributes; 2^x max streams */ + 0x00, 0x00, /* u16 epc_reserved; */ + + /* Alt Int 1: Bulk-Out Pipe usage */ + 0x04, /* u8 bLength; */ + 0x24, /* u8 bDescriptorType; Function */ + MSD_UASP_DATAOUT, /* u8 bPipeUsage; Data Out pipe */ + 0x00, /* u8 breserved; */ }; // BOS Descriptor @@ -479,18 +656,36 @@ bool usb_msd_device_c::set_option(const char *option) } else { BX_ERROR(("Option 'sect_size' is only valid for USB disks")); } + } else if (!strncmp(option, "proto:", 6)) { + if (!strcmp(option+6, "uasp")) { + s.proto = MSD_PROTO_UASP; + } else if (!strcmp(option+6, "bbb")) { + s.proto = MSD_PROTO_BBB; + } else { + BX_ERROR(("Unknown option '%s' for proto:", option+6)); + } + return 1; } return 0; } bool usb_msd_device_c::init() { + unsigned i; + /* If you wish to set DEBUG=report in the code, instead of * in the configuration, simply uncomment this line. I use * it when I am working on this emulation. */ - //LOG_THIS setonoff(LOGLEV_DEBUG, ACT_REPORT); + LOG_THIS setonoff(LOGLEV_DEBUG, ACT_REPORT); + + // check to make sure correct speed is used if the proto is uasp + if ((s.proto == MSD_PROTO_UASP) && (d.speed < USB_SPEED_HIGH)) { + BX_ERROR(("UASP selected on a non-uasp speed device.")); + s.proto = MSD_PROTO_BBB; + } + d.alt_iface_max = 0; if (d.type == USB_MSD_TYPE_DISK) { if (strlen(s.fname) > 0) { s.hdimage = DEV_hdimage_init_image(s.image_mode, 0, s.journal); @@ -504,7 +699,7 @@ bool usb_msd_device_c::init() s.hdimage->sect_size = s.sect_size; } if (s.hdimage->open(s.fname) < 0) { - BX_ERROR(("could not open hard drive image file '%s'", s.fname)); + BX_PANIC(("could not open hard drive image file '%s'", s.fname)); return 0; } else { s.scsi_dev = new scsi_device_t(s.hdimage, 0, usb_msd_command_complete, (void*)this); @@ -512,7 +707,7 @@ bool usb_msd_device_c::init() sprintf(s.info_txt, "USB HD: path='%s', mode='%s', sect_size=%d", s.fname, s.image_mode, s.hdimage->sect_size); } else { - BX_ERROR(("USB HD: disk image not specified")); + BX_PANIC(("USB HD: disk image not specified")); return 0; } } else if (d.type == USB_MSD_TYPE_CDROM) { @@ -528,28 +723,36 @@ bool usb_msd_device_c::init() if (getonoff(LOGLEV_DEBUG) == ACT_REPORT) { s.scsi_dev->set_debug_mode(); } - if (get_speed() == USB_SPEED_SUPER) { + if (d.speed == USB_SPEED_SUPER) { d.dev_descriptor = bx_msd_dev_descriptor3; d.config_descriptor = bx_msd_config_descriptor3; d.device_desc_size = sizeof(bx_msd_dev_descriptor3); - d.config_desc_size = sizeof(bx_msd_config_descriptor3); - d.endpoint_info[USB_CONTROL_EP].max_packet_size = 512; // Control ep0 - d.endpoint_info[USB_CONTROL_EP].max_burst_size = 0; - d.endpoint_info[1].max_packet_size = 1024; // In ep1 - d.endpoint_info[1].max_burst_size = 15; - d.endpoint_info[2].max_packet_size = 1024; // Out ep2 - d.endpoint_info[2].max_burst_size = 15; + // we need to set the length of the descriptor per the protocol used + if (s.proto == MSD_PROTO_UASP) { + * (Bit16u *) &bx_msd_config_descriptor3[2] = + d.config_desc_size = sizeof(bx_msd_config_descriptor3); + d.alt_iface_max = 1; // allow alt interface 0 through 1 + } else { + * (Bit16u *) &bx_msd_config_descriptor3[2] = + d.config_desc_size = 0x002C; + } + // initialize the bbb's endpoint data + handle_iface_change(MSD_PROTO_BBB); } else if (get_speed() == USB_SPEED_HIGH) { d.dev_descriptor = bx_msd_dev_descriptor2; d.config_descriptor = bx_msd_config_descriptor2; d.device_desc_size = sizeof(bx_msd_dev_descriptor2); - d.config_desc_size = sizeof(bx_msd_config_descriptor2); - d.endpoint_info[USB_CONTROL_EP].max_packet_size = 64; // Control ep0 - d.endpoint_info[USB_CONTROL_EP].max_burst_size = 0; - d.endpoint_info[1].max_packet_size = 512; // In ep1 - d.endpoint_info[1].max_burst_size = 0; - d.endpoint_info[2].max_packet_size = 512; // Out ep2 - d.endpoint_info[2].max_burst_size = 0; + // we need to set the length of the descriptor per the protocol used + if (s.proto == MSD_PROTO_UASP) { + * (Bit16u *) &bx_msd_config_descriptor2[2] = + d.config_desc_size = sizeof(bx_msd_config_descriptor2); + d.alt_iface_max = 1; // allow alt interface 0 through 1 + } else { + * (Bit16u *) &bx_msd_config_descriptor2[2] = + d.config_desc_size = 0x0020; + } + // initialize the bbb's endpoint data + handle_iface_change(MSD_PROTO_BBB); } else { // USB_SPEED_FULL d.dev_descriptor = bx_msd_dev_descriptor; d.config_descriptor = bx_msd_config_descriptor; @@ -557,15 +760,21 @@ bool usb_msd_device_c::init() d.config_desc_size = sizeof(bx_msd_config_descriptor); d.endpoint_info[USB_CONTROL_EP].max_packet_size = 64; // Control ep0 d.endpoint_info[USB_CONTROL_EP].max_burst_size = 0; - d.endpoint_info[1].max_packet_size = 64; // In ep1 - d.endpoint_info[1].max_burst_size = 0; - d.endpoint_info[2].max_packet_size = 64; // Out ep2 - d.endpoint_info[2].max_burst_size = 0; + d.endpoint_info[MSD_BBB_DATAIN_EP].max_packet_size = 64; // In ep + d.endpoint_info[MSD_BBB_DATAIN_EP].max_burst_size = 0; + d.endpoint_info[MSD_BBB_DATAOUT_EP].max_packet_size = 64; // Out ep + d.endpoint_info[MSD_BBB_DATAOUT_EP].max_burst_size = 0; } d.serial_num = s.scsi_dev->get_serial_number(); s.mode = USB_MSDM_CBW; d.connected = 1; s.status_changed = 0; + + // initialize the uasp stream data + for (i=0; i> 8) { case USB_DT_STRING: BX_DEBUG(("USB_REQ_GET_DESCRIPTOR: String")); - switch(value & 0xFF) { + switch (value & 0xFF) { case 0xEE: // Microsoft OS Descriptor check // We don't support this check, so fail @@ -705,14 +914,25 @@ int usb_msd_device_c::handle_control(int request, int value, int index, int leng set_toggle(index, 0); #endif ret = 0; - } else + } else { + BX_ERROR(("Bad value for clear feature: %d", value)); goto fail; + } break; case DeviceOutRequest | USB_REQ_SET_SEL: // Set U1 and U2 System Exit Latency BX_DEBUG(("SET_SEL (U1 and U2):")); ret = 0; break; + case DeviceOutRequest | USB_REQ_SET_ISO_DELAY: + // Set Isochronous Delay (USB 3.0+) + BX_DEBUG(("USB_REQ_SET_ISO_DELAY: %d", value)); + if ((index == 0) && (length == 0)) { // index and length must be zero + // value = setting (ignored) + ret = 0; + } else + goto fail; + break; // Class specific requests case InterfaceOutClassRequest | MassStorageReset: case MassStorageReset: @@ -740,12 +960,61 @@ int usb_msd_device_c::handle_control(int request, int value, int index, int leng return ret; } +void usb_msd_device_c::handle_iface_change(int iface) +{ + if (d.speed == USB_SPEED_SUPER) { + d.endpoint_info[USB_CONTROL_EP].max_packet_size = 512; // Control ep0 + d.endpoint_info[USB_CONTROL_EP].max_burst_size = 0; + if (iface == MSD_PROTO_BBB) { + // initialize the bbb's endpoint data + d.endpoint_info[MSD_BBB_DATAIN_EP].max_packet_size = 1024; // In ep + d.endpoint_info[MSD_BBB_DATAIN_EP].max_burst_size = 15; + d.endpoint_info[MSD_BBB_DATAOUT_EP].max_packet_size = 1024; // Out ep + d.endpoint_info[MSD_BBB_DATAOUT_EP].max_burst_size = 15; + } else if (iface == MSD_PROTO_UASP) { + d.endpoint_info[MSD_UASP_COMMAND].max_packet_size = 1024; + d.endpoint_info[MSD_UASP_COMMAND].max_burst_size = 0; + d.endpoint_info[MSD_UASP_STATUS].max_packet_size = 1024; + d.endpoint_info[MSD_UASP_STATUS].max_burst_size = 15; + d.endpoint_info[MSD_UASP_DATAIN].max_packet_size = 1024; + d.endpoint_info[MSD_UASP_DATAIN].max_burst_size = 15; + d.endpoint_info[MSD_UASP_DATAOUT].max_packet_size = 1024; + d.endpoint_info[MSD_UASP_DATAOUT].max_burst_size = 15; + } else { + BX_ERROR(("Unknown interface number: %d", iface)); + } + // ben + } else if (d.speed == USB_SPEED_HIGH) { + d.endpoint_info[USB_CONTROL_EP].max_packet_size = 64; // Control ep0 + d.endpoint_info[USB_CONTROL_EP].max_burst_size = 0; + if (iface == MSD_PROTO_BBB) { + // initialize the bbb's endpoint data + d.endpoint_info[MSD_BBB_DATAIN_EP].max_packet_size = 512; // In ep + d.endpoint_info[MSD_BBB_DATAIN_EP].max_burst_size = 0; + d.endpoint_info[MSD_BBB_DATAOUT_EP].max_packet_size = 512; // Out ep + d.endpoint_info[MSD_BBB_DATAOUT_EP].max_burst_size = 0; + } else if (iface == MSD_PROTO_UASP) { + d.endpoint_info[MSD_UASP_COMMAND].max_packet_size = 512; + d.endpoint_info[MSD_UASP_COMMAND].max_burst_size = 0; + d.endpoint_info[MSD_UASP_STATUS].max_packet_size = 512; + d.endpoint_info[MSD_UASP_STATUS].max_burst_size = 0; + d.endpoint_info[MSD_UASP_DATAIN].max_packet_size = 512; + d.endpoint_info[MSD_UASP_DATAIN].max_burst_size = 0; + d.endpoint_info[MSD_UASP_DATAOUT].max_packet_size = 512; + d.endpoint_info[MSD_UASP_DATAOUT].max_burst_size = 0; + } else { + BX_ERROR(("Unknown interface number: %d", iface)); + } + } +} + int usb_msd_device_c::handle_data(USBPacket *p) { struct usb_msd_cbw cbw; int ret = 0; Bit8u devep = p->devep; Bit8u *data = p->data; + Bit8u aIface = get_aIface(); int len = p->len; bool was_status = false; // so don't dump the status packet twice @@ -754,145 +1023,157 @@ int usb_msd_device_c::handle_data(USBPacket *p) BX_DEBUG(("EP%d transfer length (%d) is greater than Max Packet Size (%d).", p->devep, p->len, get_mps(p->devep))); } - switch (p->pid) { - case USB_TOKEN_OUT: - usb_dump_packet(data, len, 0, p->devaddr, USB_DIR_OUT | p->devep, USB_TRANS_TYPE_BULK, false, true); - if (devep != 2) - goto fail; + // are we are doing bbb interface? + if (aIface == MSD_PROTO_BBB) { + switch (p->pid) { + case USB_TOKEN_OUT: + usb_dump_packet(data, len, 0, p->devaddr, USB_DIR_OUT | p->devep, USB_TRANS_TYPE_BULK, false, true); + if (devep != MSD_BBB_DATAOUT_EP) + goto fail; - switch (s.mode) { - case USB_MSDM_CBW: - if (len != 31) { - BX_ERROR(("bad CBW len (%d)", len)); - goto fail; - } - memcpy(&cbw, data, 31); - if (dtoh32(cbw.sig) != 0x43425355) { - BX_ERROR(("bad signature %08X", dtoh32(cbw.sig))); - goto fail; - } - BX_DEBUG(("command on LUN %d", cbw.lun)); - s.tag = dtoh32(cbw.tag); - s.data_len = dtoh32(cbw.data_len); - if (s.data_len == 0) { - s.mode = USB_MSDM_CSW; - } else if (cbw.flags & 0x80) { - s.mode = USB_MSDM_DATAIN; - } else { - s.mode = USB_MSDM_DATAOUT; - } - BX_DEBUG(("command tag 0x%X flags %02X cmd_len %d data_len %d", - s.tag, cbw.flags, cbw.cmd_len, s.data_len)); - s.residue = 0; - s.scsi_dev->scsi_send_command(s.tag, cbw.cmd, cbw.cmd_len, cbw.lun, d.async_mode); - if (s.residue == 0) { - if (s.mode == USB_MSDM_DATAIN) { - s.scsi_dev->scsi_read_data(s.tag); - } else if (s.mode == USB_MSDM_DATAOUT) { - s.scsi_dev->scsi_write_data(s.tag); + switch (s.mode) { + case USB_MSDM_CBW: + if (len != 31) { + BX_ERROR(("bad CBW len (%d)", len)); + goto fail; } - } - ret = len; - break; - - case USB_MSDM_DATAOUT: - BX_DEBUG(("data out %d/%d", len, s.data_len)); - if (len > (int)s.data_len) - goto fail; - - s.usb_buf = data; - s.usb_len = len; - while (s.usb_len && s.scsi_len) { - copy_data(); - } - if (s.residue && s.usb_len) { - s.data_len -= s.usb_len; - if (s.data_len == 0) + memcpy(&cbw, data, 31); + if (dtoh32(cbw.sig) != 0x43425355) { + BX_ERROR(("bad signature %08X", dtoh32(cbw.sig))); + goto fail; + } + BX_DEBUG(("command on LUN %d", cbw.lun)); + s.tag = dtoh32(cbw.tag); + s.data_len = dtoh32(cbw.data_len); + if (s.data_len == 0) { s.mode = USB_MSDM_CSW; - s.usb_len = 0; - } - if (s.usb_len) { + } else if (cbw.flags & 0x80) { + s.mode = USB_MSDM_DATAIN; + } else { + s.mode = USB_MSDM_DATAOUT; + } + BX_DEBUG(("command tag 0x%X flags %02X cmd_len %d data_len %d", + s.tag, cbw.flags, cbw.cmd_len, s.data_len)); + s.residue = 0; + s.scsi_dev->scsi_send_command(s.tag, cbw.cmd, cbw.cmd_len, cbw.lun, d.async_mode); + if (s.residue == 0) { + if (s.mode == USB_MSDM_DATAIN) { + s.scsi_dev->scsi_read_data(s.tag); + } else if (s.mode == USB_MSDM_DATAOUT) { + s.scsi_dev->scsi_write_data(s.tag); + } + } + ret = len; + break; + + case USB_MSDM_DATAOUT: + BX_DEBUG(("data out %d/%d", len, s.data_len)); + if (len > (int)s.data_len) + goto fail; + + s.usb_buf = data; + s.usb_len = len; + while (s.usb_len && s.scsi_len) { + copy_data(); + } + if (s.residue && s.usb_len) { + s.data_len -= s.usb_len; + if (s.data_len == 0) + s.mode = USB_MSDM_CSW; + s.usb_len = 0; + } + if (s.usb_len) { + BX_DEBUG(("deferring packet %p", p)); + usb_defer_packet(p, this); + s.packet = p; + ret = USB_RET_ASYNC; + } else { + ret = len; + } + break; + + default: + BX_ERROR(("USB MSD handle_data: unexpected mode at USB_TOKEN_OUT: (0x%02X)", s.mode)); + goto fail; + } + break; + + case USB_TOKEN_IN: + if (devep != MSD_BBB_DATAIN_EP) + goto fail; + + switch (s.mode) { + case USB_MSDM_DATAOUT: + if (s.data_len != 0 || len < 13) + goto fail; BX_DEBUG(("deferring packet %p", p)); usb_defer_packet(p, this); s.packet = p; ret = USB_RET_ASYNC; - } else { - ret = len; - } - break; + break; - default: - BX_ERROR(("USB MSD handle_data: unexpected mode at USB_TOKEN_OUT: (0x%02X)", s.mode)); - goto fail; - } - break; + case USB_MSDM_CSW: + BX_DEBUG(("command status %d tag 0x%x, len %d", + s.result, s.tag, len)); + if (len < 13) + return ret; - case USB_TOKEN_IN: - if (devep != 1) - goto fail; + send_status(p); + s.mode = USB_MSDM_CBW; + was_status = true; + ret = 13; + break; - switch (s.mode) { - case USB_MSDM_DATAOUT: - if (s.data_len != 0 || len < 13) + case USB_MSDM_DATAIN: + BX_DEBUG(("data in %d/%d", len, s.data_len)); + if (len > (int)s.data_len) + len = s.data_len; + s.usb_buf = data; + s.usb_len = len; + while (s.usb_len && s.scsi_len) { + copy_data(); + } + if (s.residue && s.usb_len) { + s.data_len -= s.usb_len; + memset(s.usb_buf, 0, s.usb_len); + if (s.data_len == 0) + s.mode = USB_MSDM_CSW; + s.usb_len = 0; + } + if (s.usb_len) { + BX_DEBUG(("deferring packet %p", p)); + usb_defer_packet(p, this); + s.packet = p; + ret = USB_RET_ASYNC; + } else { + ret = len; + } + break; + + default: + BX_ERROR(("USB MSD handle_data: unexpected mode at USB_TOKEN_IN: (0x%02X)", s.mode)); goto fail; - BX_DEBUG(("deferring packet %p", p)); - usb_defer_packet(p, this); - s.packet = p; - ret = USB_RET_ASYNC; - break; + } + if (!was_status && (ret > 0)) usb_dump_packet(data, ret, 0, p->devaddr, USB_DIR_IN | p->devep, USB_TRANS_TYPE_BULK, false, true); + break; - case USB_MSDM_CSW: - BX_DEBUG(("command status %d tag 0x%x, len %d", - s.result, s.tag, len)); - if (len < 13) - return ret; - - send_status(p); - s.mode = USB_MSDM_CBW; - was_status = true; - ret = 13; - break; - - case USB_MSDM_DATAIN: - BX_DEBUG(("data in %d/%d", len, s.data_len)); - if (len > (int)s.data_len) - len = s.data_len; - s.usb_buf = data; - s.usb_len = len; - while (s.usb_len && s.scsi_len) { - copy_data(); - } - if (s.residue && s.usb_len) { - s.data_len -= s.usb_len; - memset(s.usb_buf, 0, s.usb_len); - if (s.data_len == 0) - s.mode = USB_MSDM_CSW; - s.usb_len = 0; - } - if (s.usb_len) { - BX_DEBUG(("deferring packet %p", p)); - usb_defer_packet(p, this); - s.packet = p; - ret = USB_RET_ASYNC; - } else { - ret = len; - } - break; - - default: - BX_ERROR(("USB MSD handle_data: unexpected mode at USB_TOKEN_IN: (0x%02X)", s.mode)); - goto fail; - } - if (!was_status && (ret > 0)) usb_dump_packet(data, ret, 0, p->devaddr, USB_DIR_IN | p->devep, USB_TRANS_TYPE_BULK, false, true); - break; - - default: - BX_ERROR(("USB MSD handle_data: bad token")); -fail: - d.stall = 1; - ret = USB_RET_STALL; - break; - } + default: + BX_ERROR(("USB MSD handle_data: bad token")); + fail: + d.stall = 1; + ret = USB_RET_STALL; + break; + } + + // must be uasp interface + } else if (aIface == MSD_PROTO_UASP) { + ret = uasp_handle_data(p); + + } else { + BX_ERROR(("Unknown interface number: %d", aIface)); + d.stall = 1; + ret = USB_RET_STALL; + } return ret; } @@ -943,52 +1224,60 @@ void usb_msd_device_c::usb_msd_command_complete(void *this_ptr, int reason, Bit3 void usb_msd_device_c::command_complete(int reason, Bit32u tag, Bit32u arg) { USBPacket *p = s.packet; + Bit8u aIface = get_aIface(); - if (tag != s.tag) { - BX_ERROR(("usb-msd_command_complete: unexpected SCSI tag 0x%x", tag)); - } - if (reason == SCSI_REASON_DONE) { - BX_DEBUG(("command complete %d", arg)); - s.residue = s.data_len; - s.result = arg != 0; - if (s.packet) { - if ((s.data_len == 0) && (s.mode == USB_MSDM_DATAOUT)) { - send_status(p); - s.mode = USB_MSDM_CBW; - } else if (s.mode == USB_MSDM_CSW) { - send_status(p); - s.mode = USB_MSDM_CBW; - } else { - if (s.data_len) { - s.data_len -= s.usb_len; - if (s.mode == USB_MSDM_DATAIN) - memset(s.usb_buf, 0, s.usb_len); - s.usb_len = 0; + // if bulk/bulk/bulk + if (aIface == MSD_PROTO_BBB) { + if (tag != s.tag) { + BX_ERROR(("usb-msd_command_complete: unexpected SCSI tag 0x%x", tag)); + } + if (reason == SCSI_REASON_DONE) { + BX_DEBUG(("command complete %d", arg)); + s.residue = s.data_len; + s.result = arg != 0; + if (s.packet) { + if ((s.data_len == 0) && (s.mode == USB_MSDM_DATAOUT)) { + send_status(p); + s.mode = USB_MSDM_CBW; + } else if (s.mode == USB_MSDM_CSW) { + send_status(p); + s.mode = USB_MSDM_CBW; + } else { + if (s.data_len) { + s.data_len -= s.usb_len; + if (s.mode == USB_MSDM_DATAIN) + memset(s.usb_buf, 0, s.usb_len); + s.usb_len = 0; + } + if (s.data_len == 0) + s.mode = USB_MSDM_CSW; } - if (s.data_len == 0) - s.mode = USB_MSDM_CSW; - } - s.packet = NULL; - usb_packet_complete(p); - } else if (s.data_len == 0) { - s.mode = USB_MSDM_CSW; - } - return; - } - s.scsi_len = arg; - s.scsi_buf = s.scsi_dev->scsi_get_buf(tag); - if (p) { - if ((s.scsi_len > 0) && (s.mode == USB_MSDM_DATAIN)) { - usb_dump_packet(s.scsi_buf, p->len, 0, p->devaddr, USB_DIR_OUT | p->devep, USB_TRANS_TYPE_BULK, false, true); - } - copy_data(); - if (s.usb_len == 0) { - BX_DEBUG(("packet complete %p", p)); - if (s.packet != NULL) { s.packet = NULL; usb_packet_complete(p); + } else if (s.data_len == 0) { + s.mode = USB_MSDM_CSW; + } + return; + } + s.scsi_len = arg; + s.scsi_buf = s.scsi_dev->scsi_get_buf(tag); + if (p) { + if ((s.scsi_len > 0) && (s.mode == USB_MSDM_DATAIN)) { + usb_dump_packet(s.scsi_buf, p->len, 0, p->devaddr, USB_DIR_IN | p->devep, USB_TRANS_TYPE_BULK, false, true); + } + copy_data(); + if (s.usb_len == 0) { + BX_DEBUG(("packet complete %p", p)); + if (s.packet != NULL) { + s.packet = NULL; + usb_packet_complete(p); + } } } + + // else if uasp + } else if (aIface == MSD_PROTO_UASP) { + uasp_command_complete(reason, tag, arg); } } diff --git a/bochs/iodev/usb/usb_msd.h b/bochs/iodev/usb/usb_msd.h index c04bbf707..49a3d2796 100644 --- a/bochs/iodev/usb/usb_msd.h +++ b/bochs/iodev/usb/usb_msd.h @@ -30,6 +30,36 @@ class device_image_t; class cdrom_base_c; class scsi_device_t; +#define UASP_MAX_STREAMS 5 +#define UASP_MAX_STREAMS_N (1 << (UASP_MAX_STREAMS + 1)) + +// protocol +#define MSD_PROTO_BBB 0 // bulk only +#define MSD_PROTO_UASP 1 // UASP protocol (uses streams) + +// BBB endpoint numbers (must remain consecutive) +#define MSD_BBB_DATAIN_EP 1 +#define MSD_BBB_DATAOUT_EP 2 +// UASP endpoint numbers (must remain consecutive) +#define MSD_UASP_COMMAND 1 +#define MSD_UASP_STATUS 2 +#define MSD_UASP_DATAIN 3 +#define MSD_UASP_DATAOUT 4 + +typedef struct UASPRequest { + Bit32u mode; // current direction and other flags + Bit32u data_len; // count of bytes requested (from command requested) + Bit32u residue; // = data_len - actual_len + Bit32u scsi_len; // + Bit8u *scsi_buf; // + Bit32u usb_len; // + Bit8u *usb_buf; // + int result; // + Bit32u tag; // tag number from command ui + USBPacket *p; // + USBPacket *status; // +} UASPRequest; + class usb_msd_device_c : public usb_device_c { public: usb_msd_device_c(const char *devname); @@ -44,6 +74,7 @@ public: virtual void handle_reset(); virtual int handle_control(int request, int value, int index, int length, Bit8u *data); virtual int handle_data(USBPacket *p); + virtual void handle_iface_change(int iface); virtual void register_state_specific(bx_list_c *parent); virtual void cancel_packet(USBPacket *p); bool set_inserted(bool value); @@ -60,6 +91,7 @@ private: struct { // members set in constructor / init char *image_mode; + int proto; // MSD_PROTO_BBB (default), MSD_PROTO_UASP (uses streams) device_image_t *hdimage; cdrom_base_c *cdrom; scsi_device_t *scsi_dev; @@ -84,11 +116,27 @@ private: Bit8u *scsi_buf; Bit8u *usb_buf; USBPacket *packet; + // UASP (w/streams) + UASPRequest uasp_request[UASP_MAX_STREAMS_N]; } s; static const char *cdrom_path_handler(bx_param_string_c *param, bool set, const char *oldval, const char *val, int maxlen); static Bit64s cdrom_status_handler(bx_param_c *param, bool set, Bit64s val); + + int uasp_handle_data(USBPacket *p); + void uasp_initialize_request(int tag); + UASPRequest *uasp_find_request(Bit32u tag); + Bit32u get_data_len(const struct S_UASP_INPUT *input, Bit8u *buf); + const struct S_UASP_INPUT *uasp_get_info(Bit8u command, Bit8u serv_action); + int uasp_do_stall(UASPRequest *req); + int uasp_do_command(USBPacket *p); + int uasp_process_request(USBPacket *p, int index); + int uasp_do_data(UASPRequest *req, USBPacket *p); + int uasp_do_ready(UASPRequest *req, USBPacket *p); + int uasp_do_status(UASPRequest *req, USBPacket *p); + void uasp_copy_data(UASPRequest *req); + void uasp_command_complete(int reason, Bit32u tag, Bit32u arg); }; #endif diff --git a/bochs/iodev/usb/usb_printer.cc b/bochs/iodev/usb/usb_printer.cc index b458864b7..a42c2a3e4 100644 --- a/bochs/iodev/usb/usb_printer.cc +++ b/bochs/iodev/usb/usb_printer.cc @@ -216,6 +216,7 @@ bool usb_printer_device_c::init() } else { BX_ERROR(("USB printer: missing output file")); } + d.alt_iface_max = 0; return 0; } diff --git a/bochs/iodev/usb/usb_uasp.cc b/bochs/iodev/usb/usb_uasp.cc new file mode 100644 index 000000000..f7158d204 --- /dev/null +++ b/bochs/iodev/usb/usb_uasp.cc @@ -0,0 +1,512 @@ +///////////////////////////////////////////////////////////////////////// +// $Id$ +///////////////////////////////////////////////////////////////////////// +// +// Experimental USB Attached SCSI Protocol support (UASP) +// +// Copyright (C) 2023 Benjamin D Lunt (fys [at] fysnet [dot] net) +// Copyright (C) 2023 The Bochs Project +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +///////////////////////////////////////////////////////////////////////// + +// Define BX_PLUGGABLE in files that can be compiled into plugins. For +// platforms that require a special tag on exported symbols, BX_PLUGGABLE +// is used to know when we are exporting symbols and when we are importing. +#define BX_PLUGGABLE + +#include "iodev.h" + +#if BX_SUPPORT_PCI && BX_SUPPORT_PCIUSB +#include "usb_common.h" +#include "hdimage/cdrom.h" +#include "hdimage/hdimage.h" +#include "scsi_device.h" +#include "usb_msd.h" + +#define LOG_THIS + +// Information Unit Types +// byte[0] in uasp command and status???? +#define IU_CMD 1 +#define IU_SENSE 3 +#define IU_RESP 4 +#define IU_TMF 5 +#define IU_RRDY 6 +#define IU_WRDY 7 + +#define IU_CMD_LEN 32 +#define IU_SENSE_LEN 16 +#define IU_RESP_LEN 8 +#define IU_TMF_LEN 16 +#define IU_RRDY_LEN 4 +#define IU_WRDY_LEN 4 + +// Task Attribute +#define UASP_TASK_SIMPLE 0 +#define UASP_TASK_HOQ 1 +#define UASP_TASK_ORDERED 2 +#define UASP_TASK_ACA 4 + +#define UASP_GET_ACTIVE(m) (((m) & 1) > 0) // is the request active? +#define UASP_SET_ACTIVE(m) ((m) > 0) // clearing/setting active needs to clear everything else +#define UASP_GET_STATUS(m) (((m) & 2) > 0) // have we sent the RRIU or WRIU? +#define UASP_SET_STATUS(m) ((m) | 2) // set that we have sent the RRIU or WRIU +#define UASP_GET_CMND(m) (((m) & 4) > 0) // have we received and processed the command yet? +#define UASP_SET_CMND(m) ((m) | 4) // set that we have received and processed the command +#define UASP_GET_COMPLETE(m) (((m) & 8) > 0) // is the datain/dataout transfer complete? +#define UASP_SET_COMPLETE(m) ((m) | 8) // set that the datain/dataout transfer is complete +#define UASP_GET_DIR(m) (((m) & 0x0000FF00) >> 8) +#define UASP_SET_DIR(m,d) (((m) & ~0x0000FF00) | ((d) << 8)) + +struct S_UASP_COMMAND { + Bit8u id; // Information Unit Type + Bit8u rsvd0; // + Bit16u tag; // big endian + Bit8u prio_attr; // Task Attribute + Bit8u rsvd1; // + Bit8u len; // len (if more than 16 bytes) (i.e.: if command len is 20 bytes, this field is 4) (bottom 2 bits are reserved) + Bit8u rsvd2; // + Bit64u lun; // eight byte lun + Bit8u com_block[16]; // command block +}; + +struct S_UASP_STATUS { + Bit8u id; // usually IU_SENSE ? + Bit8u rsvd0; + Bit16u tag; // big endian + Bit16u stat_qual; // big endian + Bit8u status; + Bit8u resv1[7]; + Bit16u len; // big endian + Bit8u sense[18]; +}; + +#define UASP_FROM_COMMAND ((Bit32u) -1) +#define U_NONE 0 +#define U_SRV_ACT (1<<0) +#define U_IS_LBA (1<<1) +struct S_UASP_INPUT { + Bit8u command; // command byte + Bit8u serv_action; // command/service action + Bit8u cmd_len; // length of the command buf + Bit8u direction; // in or out + Bit8u flags; // see S_UASP_FLAGS_* + Bit32u data_len; // if the command is fixed at a count of bytes, this holds that count (next two members are ignored) + int offset; // byte offset of data length requested + int size; // size in bytes of this field +}; + +int usb_msd_device_c::uasp_handle_data(USBPacket *p) +{ + int ret = 0; + Bit8u *data = p->data; + int len = p->len; + int index = p->strm_pid; // high-speed device will be zero. super-speed device will have the stream id. + + BX_INFO(("uasp_handle_data(): %X ep=%d index=%d len=%d", p->pid, p->devep, index, len)); + + switch (p->pid) { + case USB_TOKEN_OUT: + if (p->devep == MSD_UASP_DATAOUT) { // Bulk out pipe + if ((index < 0) || (index > UASP_MAX_STREAMS_N)) + goto fail; + ret = uasp_process_request(p, index); + if (ret == USB_RET_ASYNC) + usb_defer_packet(p, this); + } else if (p->devep == MSD_UASP_COMMAND) { // Command pipe + ret = uasp_do_command(p); + } + break; + + case USB_TOKEN_IN: + // TODO: these are the same + if ((p->devep == MSD_UASP_DATAIN) || // Bulk in pipe + (p->devep == MSD_UASP_STATUS)) { // Status pipe + if ((index < 0) || (index > UASP_MAX_STREAMS_N)) + goto fail; + ret = uasp_process_request(p, index); + if (ret == USB_RET_ASYNC) + usb_defer_packet(p, this); + } + break; + + default: + BX_ERROR(("USB MSD (UASP) handle_data: bad token")); +fail: + d.stall = 1; + ret = USB_RET_STALL; + } + + return ret; +} + +void usb_msd_device_c::uasp_initialize_request(int index) +{ + UASPRequest *req = &s.uasp_request[index]; + + req->mode = UASP_SET_ACTIVE(1); // setting to 1 clears other flags + req->data_len = 0; + req->result = 0; + req->tag = 0; + req->scsi_len = 0; + req->status = NULL; + req->p = NULL; + + d.stall = 0; +} + +UASPRequest *usb_msd_device_c::uasp_find_request(Bit32u tag) +{ + int i; + + for (i=0; isize) { + case 1: // byte + ret = * (Bit8u *) &buf[input->offset]; + break; + case 2: // word + ret = * (Bit16u *) &buf[input->offset]; + ret = bx_bswap16(ret); + break; + case 4: // dword + ret = * (Bit32u *) &buf[input->offset]; + ret = bx_bswap32(ret); + break; + } + + // is lba instead of bytes? + if (input->flags & U_IS_LBA) + ret *= 512; // TODO: get size of a sector/block + + return ret; +} + +// get information about the command requested +const struct S_UASP_INPUT *usb_msd_device_c::uasp_get_info(Bit8u command, Bit8u serv_action) +{ + int i = 0; + + do { + if (bx_uasp_info_array[i].command == command) { + if (bx_uasp_info_array[i].flags & U_SRV_ACT) { + if (bx_uasp_info_array[i].serv_action == serv_action) + return &bx_uasp_info_array[i]; + } else + return &bx_uasp_info_array[i]; + } + i++; + } while (bx_uasp_info_array[i].command != 0xFF); + + BX_ERROR(("uasp_get_info: Unknown command found: %02X/%02X", command, serv_action)); + return NULL; +} + +int usb_msd_device_c::uasp_do_stall(UASPRequest *req) +{ + USBPacket *p; + + p = req->p; + if (p) { + req->p = NULL; + p->len = USB_RET_STALL; + usb_packet_complete(p); + } + + p = req->status; + if (p) { + req->status = NULL; + p->len = USB_RET_STALL; + usb_packet_complete(p); + } + + req->mode = UASP_SET_ACTIVE(0); + d.stall = 1; + return USB_RET_STALL; +} + +int usb_msd_device_c::uasp_do_command(USBPacket *p) +{ + struct S_UASP_COMMAND *ui = (struct S_UASP_COMMAND *) p->data; + const struct S_UASP_INPUT *input; + Bit8u lun = (Bit8u) (ui->lun >> 56); + int index = (get_speed() == USB_SPEED_HIGH) ? 0 : bx_bswap16(ui->tag); + UASPRequest *req = &s.uasp_request[index]; + + usb_dump_packet(p->data, p->len, 0, p->devaddr, USB_DIR_OUT | p->devep, USB_TRANS_TYPE_BULK, false, true); + if (ui->id == IU_CMD) { + if ((ui->prio_attr & 0x7) == UASP_TASK_SIMPLE) { + if (!UASP_GET_ACTIVE(req->mode)) + uasp_initialize_request(index); + // get information about the command requested + input = uasp_get_info(ui->com_block[0], ui->com_block[1] & 0x1F); + // if unknown command, stall + if (input == NULL) + return uasp_do_stall(req); + + // get the count of bytes requested, command length, etc. + req->tag = bx_bswap16(ui->tag); + req->mode = UASP_SET_DIR(req->mode, input->direction); + req->data_len = (input->data_len == UASP_FROM_COMMAND) ? get_data_len(input, ui->com_block) : input->data_len; + BX_INFO(("uasp command id %d, tag 0x%04X, command 0x%X, len = %d, data_len = %d", ui->id, req->tag, ui->com_block[0], p->len, req->data_len)); + + s.scsi_dev->scsi_send_command(req->tag, ui->com_block, input->cmd_len, lun, d.async_mode); + if (UASP_GET_DIR(req->mode) == USB_TOKEN_IN) { + s.scsi_dev->scsi_read_data(req->tag); + } else if (UASP_GET_DIR(req->mode) == USB_TOKEN_OUT) { + s.scsi_dev->scsi_write_data(req->tag); + } + + // if a high-speed device, we need to send the status ready ui + if ((get_speed() == USB_SPEED_HIGH) && req->status) { + USBPacket *s = req->status; + s->len = uasp_do_ready(req, s); + req->status = NULL; + usb_packet_complete(s); + } + + req->mode = UASP_SET_CMND(req->mode); // mark that we have sent the command + return p->len; + } else + BX_ERROR(("uasp: unknown/unsupported task attribute. %d", (ui->prio_attr & 0x7))); + } else + BX_ERROR(("uasp: unknown id on command pipe. %d", ui->id)); + + return 0; +} + +// if we have already done the command and there is data +// waiting, we can process the packet request. +// else ret USB_RET_ASYNC +int usb_msd_device_c::uasp_process_request(USBPacket *p, int index) +{ + UASPRequest *req = &s.uasp_request[index]; + int len = p->len; + + if (!UASP_GET_ACTIVE(req->mode)) + uasp_initialize_request(index); + + // if it is the status pipe + if (p->devep == MSD_UASP_STATUS) { + // TODO: check to see if the direction is in + if (UASP_GET_COMPLETE(req->mode)) { + return uasp_do_status(req, p); + } else { + if ((get_speed() == USB_SPEED_HIGH) && UASP_GET_CMND(req->mode) && !UASP_GET_STATUS(req->mode)) { + return uasp_do_ready(req, p); + } else { + req->status = p; + return USB_RET_ASYNC; + } + } + } + + // have we received the Command yet? + if (!UASP_GET_CMND(req->mode)) { + req->p = p; + return USB_RET_ASYNC; + } + + // check to make sure the direction is correct + if (p->pid != UASP_GET_DIR(req->mode)) { + BX_INFO(("Found packet with wrong direction.")); + uasp_do_stall(req); + } + + // do the transfer for this packet + len = uasp_do_data(req, p); + BX_INFO(("uasp: (0) data: transferred %d bytes", len)); + + return len; +} + +int usb_msd_device_c::uasp_do_data(UASPRequest *req, USBPacket *p) +{ + int len = p->len; + + // TODO: if (dir != req->dir) error + if (UASP_GET_DIR(req->mode) == USB_TOKEN_IN) { + BX_INFO(("data in %d/%d/%d", len, req->data_len, req->scsi_len)); + } else if (UASP_GET_DIR(req->mode) == USB_TOKEN_OUT) { + BX_INFO(("data out %d/%d/%d", len, req->data_len, req->scsi_len)); + } + + if (len > (int) req->data_len) + len = req->data_len; + req->usb_buf = p->data; + req->usb_len = len; + while (req->usb_len && req->scsi_len) { + uasp_copy_data(req); + } + + if (req->residue && req->usb_len) { + req->data_len -= req->usb_len; + memset(req->usb_buf, 0, req->usb_len); + req->usb_len = 0; + } + + usb_dump_packet(p->data, len, 0, p->devaddr, ((UASP_GET_DIR(req->mode) == USB_TOKEN_IN) ? USB_DIR_IN : USB_DIR_OUT) | p->devep, USB_TRANS_TYPE_BULK, false, true); + + return len; +} + +int usb_msd_device_c::uasp_do_ready(UASPRequest *req, USBPacket *p) +{ + struct S_UASP_STATUS *status; + + // do the RRIU or WRIU + status = (struct S_UASP_STATUS *) p->data; + status->id = (UASP_GET_DIR(req->mode) == USB_TOKEN_IN) ? IU_RRDY : IU_WRDY; + status->rsvd0 = 0; + status->tag = bx_bswap16((Bit16u) req->tag); + usb_dump_packet(p->data, IU_RRDY_LEN, 0, p->devaddr, USB_DIR_IN | p->devep, USB_TRANS_TYPE_BULK, false, true); + + req->mode = UASP_SET_STATUS(req->mode); + + return IU_RRDY_LEN; +} + +int usb_msd_device_c::uasp_do_status(UASPRequest *req, USBPacket *p) +{ + struct S_UASP_STATUS *status; + + // do the status + BX_INFO(("uasp: Sending Status:")); + status = (struct S_UASP_STATUS *) p->data; + memset(status, 0, 16); + status->id = IU_SENSE; + status->tag = bx_bswap16((Bit16u) req->tag); + status->status = 0; // good return + status->len = bx_bswap16(0); // no sense data + usb_dump_packet(p->data, IU_SENSE_LEN, 0, p->devaddr, USB_DIR_IN | p->devep, USB_TRANS_TYPE_BULK, false, true); + + req->mode = UASP_SET_ACTIVE(0); + + // return the size of the status block + return IU_SENSE_LEN; +} + +void usb_msd_device_c::uasp_copy_data(UASPRequest *req) +{ + Bit32u len = req->usb_len; + if (len > req->scsi_len) + len = req->scsi_len; + if (UASP_GET_DIR(req->mode) == USB_TOKEN_IN) { + memcpy(req->usb_buf, req->scsi_buf, len); + } else { + memcpy(req->scsi_buf, req->usb_buf, len); + } + req->usb_len -= len; + req->scsi_len -= len; + req->usb_buf += len; + req->scsi_buf += len; + req->data_len -= len; + if (req->scsi_len == 0) { + if (UASP_GET_DIR(req->mode) == USB_TOKEN_IN) { + s.scsi_dev->scsi_read_data(req->tag); + } else { + s.scsi_dev->scsi_write_data(req->tag); + } + } +} + +void usb_msd_device_c::uasp_command_complete(int reason, Bit32u tag, Bit32u arg) +{ + USBPacket *p; + UASPRequest *req = uasp_find_request(tag); + + BX_INFO(("uasp_command_complete: reason %d, arg %d, tag 0x%04X", reason, arg, tag)); + + if (req == NULL) + return; + + if (reason == SCSI_REASON_DONE) { + req->residue = req->data_len; + req->result = arg != 0; + req->mode = UASP_SET_COMPLETE(req->mode); // mark that are transfer is complete + // do the status + p = req->status; + if (p) { + p->len = uasp_do_status(req, p); + BX_INFO(("uasp: status: transferred %d bytes (residue = %d)", p->len, req->residue)); + req->status = NULL; + usb_packet_complete(p); + } + return; + } + + // reason == SCSI_REASON_DATA + req->scsi_len = arg; + req->scsi_buf = s.scsi_dev->scsi_get_buf(tag); + p = req->p; + if (p) { + p->len = uasp_do_data(req, p); + BX_INFO(("uasp: (1) data: transferred %d bytes", p->len)); + //if (req->usb_len == 0) { + BX_DEBUG(("packet complete 0x%p", p)); + req->p = NULL; + usb_packet_complete(p); + //} + } +} + +#endif // BX_SUPPORT_PCI && BX_SUPPORT_PCIUSB diff --git a/bochs/iodev/usb/usb_xhci.cc b/bochs/iodev/usb/usb_xhci.cc index 5d5c658ea..9fa8f45f2 100644 --- a/bochs/iodev/usb/usb_xhci.cc +++ b/bochs/iodev/usb/usb_xhci.cc @@ -34,24 +34,28 @@ * * I have tested in with my own tests and WinXP Home Edition SP3 and Windows 7 Ultimate. * - * Use port1 and port2 to emulate a Super-speed device, and use port3 and port4 - * to emulate Low-, Full-, or High-speed devices. + * Assuming a two-socket, four-port root hub: + * Use port1 and port2 to emulate a Super-speed device, and use port3 and port4 + * to emulate Low-, Full-, or High-speed devices. + * (Other settings, use the first half as Super-speed, and the last half as the other speeds) * * Examples: - * usb_xhci: enabled=1, port3=disk:"usbdisk.img", options3=speed:low - * usb_xhci: enabled=1, port3=disk:"usbdisk.img", options3=speed:full - * usb_xhci: enabled=1, port3=disk:"usbdisk.img", options3=speed:high - * usb_xhci: enabled=1, port1=disk:"usbdisk.img", options1=speed:super + * usb_xhci: enabled=1, port3=mouse, options3="speed:low" + * usb_xhci: enabled=1, port3=disk, options3="speed:full, path:usbdisk.img" + * usb_xhci: enabled=1, port3=disk, options3="speed:high, path:usbdisk.img" + * usb_xhci: enabled=1, port1=disk, options1="speed:super, path:usbdisk.img" + * usb_xhci: enabled=1, port1=cdrom, options1="speed:super, path:cdrom.iso" * - * Currently, only a USB MSD Thumb Drive device is emulated as a Super-speed device. - * All other emulated devices are Low- or Full-speed devices. + * Currently, only a USB MSD Thumb Drive device ("=disk") and a USB MSD CDROM ("=cdrom") + * can be emulated as a Super-speed device. + * All other emulated devices must be Low-, Full-, or High-speed devices. * * This code is 32- and 64-bit addressing aware (uses: bx_phy_address & ADDR_CAP_64) * * This emulation is coded with the new registers of xHCI version 1.10. However, - * nothing more has been done to emulate 1.10 features. - * This emulation is set with VERSION_MAJOR and VERSION_MINOR as 0x01 and 0x10 - * respectively (version 1.10). + * at this time, nothing more has been done to emulate 1.10 features. + * This emulation is set with VERSION_MAJOR and VERSION_MINOR as 0x01 and 0x00 + * respectively (version 1.00). * * This emulation seems to work as expected. It may not be completly accorate, though * it is my intention to make it as close as possible. If you find an error or would @@ -69,10 +73,11 @@ #define LOG_THIS theUSB_XHCI-> -bx_usb_xhci_c* theUSB_XHCI = NULL; +bx_usb_xhci_c *theUSB_XHCI = NULL; -Bit8u port_speed_allowed[USB_XHCI_PORTS] = { USB3, USB3, USB2, USB2 }; -Bit8u ext_caps[EXT_CAPS_SIZE] = { +#define PROTOCOL_UBS3_OFFSET (0x510 - 0x500) // offset to the USB3 protocol struct +#define PROTOCOL_UBS2_OFFSET (0x524 - 0x500) // offset to the USB2 protocol struct +static Bit8u ext_caps[EXT_CAPS_SIZE] = { /* 0x500 */ 0x01, // Legacy Support 0x04, // next = 0x04 * 4 = 0x510 0x00, 0x00, // Semaphores @@ -80,25 +85,27 @@ Bit8u ext_caps[EXT_CAPS_SIZE] = { 0x00, 0x00, 0x00, 0x00, // reserved 0x00, 0x00, 0x00, 0x00, // filler + // this block is modified by init() /* 0x510 */ 0x02, // Port Protocol 0x05, // next = 0x05 * 4 = 0x524 0x00, 0x03, // Version 3.0 0x55, 0x53, 0x42, 0x20, // "USB " 0x01, // 1-based starting index 0x02, // count of 2 port registers starting at base above - 0x00, 0x00, // reserved - 0x00, 0x00, 0x00, 0x00, // reserved - 0x00, 0x00, 0x00, 0x00, // filler + 0x00, 0x00, // flags + 0x00, 0x00, 0x00, 0x00, // 8 bytes of filler (unnecessary to this emulation + 0x00, 0x00, 0x00, 0x00, // but it exists on the uPD720202 hardware) + // this block is modified by init() /* 0x524 */ 0x02, // Port Protocol 0x07, // next = 0x07 * 4 = 0x540 0x00, 0x02, // Version 2.0 0x55, 0x53, 0x42, 0x20, // "USB " 0x03, // 1-based starting index 0x02, // count of 2 port registers starting at base above - 0x00, 0x00, // - 0x00, 0x00, 0x00, 0x00, // - 0x00, 0x00, 0x00, 0x00, // + 0x00, 0x00, // flags + 0x00, 0x00, 0x00, 0x00, // 16 bytes of filler (unnecessary to this emulation + 0x00, 0x00, 0x00, 0x00, // but it exists on the uPD720202 hardware) 0x00, 0x00, 0x00, 0x00, // 0x00, 0x00, 0x00, 0x00, // @@ -138,28 +145,31 @@ Bit8u ext_caps[EXT_CAPS_SIZE] = { 0x00, 0x00, 0x00, 0x00 // filler }; -/* See Section 6.2.6 of xHCI version 1.00 - * Bandwidth of each port (4) of each speed. - */ -Bit8u port_band_width[4][1 + USB_XHCI_PORTS] = { -/* rsvd, port1, port2, port3, port4 */ -/* Full Speed */ { 0x00, 0, 0, 90, 90 }, -/* Low Speed */ { 0x00, 0, 0, 90, 90 }, -/* High Speed */ { 0x00, 0, 0, 80, 80 }, -/* Super Speed */ { 0x00, 90, 90, 0, 0 } -}; - // builtin configuration handling functions - Bit32s usb_xhci_options_parser(const char *context, int num_params, char *params[]) { + // try to catch an out of range port number + Bit32s max_ports = SIM->get_param_num(BXPN_XHCI_N_PORTS)->get(); + if (max_ports < 0) + max_ports = USB_XHCI_PORTS; + if (!strcmp(params[0], "usb_xhci")) { bx_list_c *base = (bx_list_c*) SIM->get_param(BXPN_USB_XHCI); for (int i = 1; i < num_params; i++) { if (!strncmp(params[i], "enabled=", 8)) { SIM->get_param_bool(BXPN_XHCI_ENABLED)->set(atol(¶ms[i][8])); + } else if (!strncmp(params[i], "model=", 6)) { + if (!strcmp(¶ms[i][6], "uPD720202")) + SIM->get_param_enum(BXPN_XHCI_MODEL)->set(XHCI_HC_uPD720202); + else if (!strcmp(¶ms[i][6], "uPD720201")) + SIM->get_param_enum(BXPN_XHCI_MODEL)->set(XHCI_HC_uPD720201); + else + BX_PANIC(("%s: unknown parameter '%s' for usb_xhci: model=", context, ¶ms[i][6])); + } else if (!strncmp(params[i], "n_ports=", 8)) { + max_ports = atol(¶ms[i][8]); + SIM->get_param_num(BXPN_XHCI_N_PORTS)->set(max_ports); } else if (!strncmp(params[i], "port", 4) || !strncmp(params[i], "options", 7)) { - if (SIM->parse_usb_port_params(context, params[i], BX_N_USB_XHCI_PORTS, base) < 0) { + if (SIM->parse_usb_port_params(context, params[i], max_ports, base) < 0) { return -1; } } else { @@ -175,7 +185,7 @@ Bit32s usb_xhci_options_parser(const char *context, int num_params, char *params Bit32s usb_xhci_options_save(FILE *fp) { bx_list_c *base = (bx_list_c*) SIM->get_param(BXPN_USB_XHCI); - SIM->write_usb_options(fp, BX_N_USB_XHCI_PORTS, base); + SIM->write_usb_options(fp, USB_XHCI_PORTS_MAX, base); return 0; } @@ -187,7 +197,7 @@ PLUGIN_ENTRY_FOR_MODULE(usb_xhci) theUSB_XHCI = new bx_usb_xhci_c(); BX_REGISTER_DEVICE_DEVMODEL(plugin, type, theUSB_XHCI, BX_PLUGIN_USB_XHCI); // add new configuration parameter for the config interface - SIM->init_usb_options("xHCI", "xhci", BX_N_USB_XHCI_PORTS); + SIM->init_usb_options("xHCI", "xhci", USB_XHCI_PORTS_MAX); // register add-on option for bochsrc and command line SIM->register_addon_option("usb_xhci", usb_xhci_options_parser, usb_xhci_options_save); } else if (mode == PLUGIN_FINI) { @@ -219,7 +229,7 @@ bx_usb_xhci_c::~bx_usb_xhci_c() SIM->unregister_runtime_config_handler(rt_conf_id); - for (int i=0; iget_param_enum(pname, SIM->get_param(BXPN_USB_XHCI))->set_handler(NULL); sprintf(pname, "port%d.options", i+1); @@ -235,11 +245,13 @@ bx_usb_xhci_c::~bx_usb_xhci_c() void bx_usb_xhci_c::init(void) { - unsigned i; + unsigned i, j; char pname[6]; + Bit8u *p; bx_list_c *xhci, *port; bx_param_enum_c *device; bx_param_string_c *options; + struct XHCI_PROTOCOL *protocol; /* If you wish to set DEBUG=report in the code, instead of * in the configuration, simply uncomment this line. I use @@ -264,16 +276,42 @@ void bx_usb_xhci_c::init(void) DEV_register_pci_handlers(this, &BX_XHCI_THIS devfunc, BX_PLUGIN_USB_XHCI, "Experimental USB xHCI"); - // initialize readonly registers - // 0x1912 = vendor (Renesas) - // 0x0015 = device (0x0014 = uPD720201, 0x0015 = uPD720202) - // revision number (0x03 = uPD720201, 0x02 = uPD720202) - init_pci_conf(0x1912, 0x0015, 0x02, 0x0C0330, 0x00, BX_PCI_INTD); + // get the desired host controller type + BX_XHCI_THIS hub.HostController = SIM->get_param_enum(BXPN_XHCI_MODEL)->get(); + if (BX_XHCI_THIS hub.HostController == XHCI_HC_uPD720202) { + BX_INFO(("xHCI Host Controller: uPD720202")); + BX_XHCI_THIS hub.n_ports = USB_XHCI_PORTS; // we hard code to USB_XHCI_PORTS for this host controller (default) + // 0x1912 = vendor (Renesas) + // 0x0015 = device (0x0015 = uPD720202) + // revision number (0x02 = uPD720202) + init_pci_conf(0x1912, 0x0015, 0x02, 0x0C0330, 0x00, BX_PCI_INTD); + } else if (BX_XHCI_THIS hub.HostController == XHCI_HC_uPD720201) { + BX_INFO(("xHCI Host Controller: uPD720201")); + BX_XHCI_THIS hub.n_ports = 8; // we hard code to 8 for this host controller + // 0x1912 = vendor (Renesas) + // 0x0014 = device (0x0014 = uPD720201) + // revision number (0x03 = uPD720201) + init_pci_conf(0x1912, 0x0014, 0x03, 0x0C0330, 0x00, BX_PCI_INTD); + } else { + BX_PANIC(("Unknown xHCI Host Controller specified...")); + return; + } + + // set the number of ports used + // if n_ports is not given in the Bochsrc.txt file, the number of ports + // is defaulted to the controller type above + Bit32s n_ports = SIM->get_param_num(BXPN_XHCI_N_PORTS)->get(); + if (n_ports > -1) BX_XHCI_THIS hub.n_ports = n_ports; + if ((BX_XHCI_THIS hub.n_ports < 2) || (BX_XHCI_THIS hub.n_ports > USB_XHCI_PORTS_MAX) || (BX_XHCI_THIS hub.n_ports & 1)) { + BX_PANIC(("n_ports (%d) must be at least 2, not more than 10, and must be an even number.", BX_XHCI_THIS hub.n_ports)); + return; + } + BX_XHCI_THIS init_bar_mem(0, IO_SPACE_SIZE, read_handler, write_handler); // initialize capability registers BX_XHCI_THIS hub.cap_regs.HcCapLength = (VERSION_MAJOR << 24) | (VERSION_MINOR << 16) | OPS_REGS_OFFSET; - BX_XHCI_THIS hub.cap_regs.HcSParams1 = (USB_XHCI_PORTS << 24) | (INTERRUPTERS << 8) | MAX_SLOTS; + BX_XHCI_THIS hub.cap_regs.HcSParams1 = (BX_XHCI_THIS hub.n_ports << 24) | (INTERRUPTERS << 8) | MAX_SLOTS; BX_XHCI_THIS hub.cap_regs.HcSParams2 = // MAX_SCRATCH_PADS 4:0 in bits 31:27 (v1.00) and bits 9:5 in bits 21:25 (v1.01+) ((MAX_SCRATCH_PADS >> 5) << 21) | ((MAX_SCRATCH_PADS & 0x1F) << 27) | @@ -296,7 +334,7 @@ void bx_usb_xhci_c::init(void) bx_list_c *usb_rt = (bx_list_c *) SIM->get_param(BXPN_MENU_RUNTIME_USB); bx_list_c *xhci_rt = new bx_list_c(usb_rt, "xhci", "xHCI Runtime Options"); xhci_rt->set_options(xhci_rt->SHOW_PARENT | xhci_rt->USE_BOX_TITLE); - for (i=0; iget_param(pname, xhci); xhci_rt->add(port); @@ -314,9 +352,71 @@ void bx_usb_xhci_c::init(void) BX_XHCI_THIS device_change = 0; BX_XHCI_THIS packets = NULL; - for (i=0; istart_index = 1; // 1 based starting index + protocol->count = BX_XHCI_THIS hub.n_ports / 2; + // then the USB2 (second half ports) + protocol = (struct XHCI_PROTOCOL *) &ext_caps[PROTOCOL_UBS2_OFFSET]; + protocol->start_index = (BX_XHCI_THIS hub.n_ports / 2) + 1; // 1 based starting index + protocol->count = BX_XHCI_THIS hub.n_ports / 2; + + // done initializing BX_INFO(("USB xHCI initialized")); } @@ -355,7 +455,7 @@ void bx_usb_xhci_c::reset(unsigned type) // capabilities list: { 0x50, 0x01 }, // PCI Power Management -// { 0x51, 0x70 }, // Pointer to next item (0x00 = no more) +// { 0x51, 0x70 }, // Pointer to next item (0x70 -> MSI stuff) { 0x51, 0x00 }, // Pointer to next item (0x00 = no more) { 0x52, 0xC3 }, { 0x53, 0xC9 }, // Capabilities: version = 1.2, Aux Current = 375mA, @@ -466,7 +566,7 @@ void bx_usb_xhci_c::reset(unsigned type) void bx_usb_xhci_c::reset_hc() { - int i; + unsigned int i; char pname[6]; // Command @@ -542,7 +642,7 @@ void bx_usb_xhci_c::reset_hc() BX_XHCI_THIS hub.op_regs.HcConfig.MaxSlotsEn = 0; // Ports[x] - for (i=0; ipacket); @@ -627,7 +727,7 @@ void bx_usb_xhci_c::reset_port(int p) } BX_XHCI_THIS hub.usb_port[p].has_been_reset = 0; - BX_XHCI_THIS hub.usb_port[p].needs_psce = 0; + BX_XHCI_THIS hub.usb_port[p].psceg = 0; } void bx_usb_xhci_c::reset_port_usb3(int port, const int reset_type) @@ -643,7 +743,6 @@ void bx_usb_xhci_c::reset_port_usb3(int port, const int reset_type) BX_XHCI_THIS hub.usb_port[port].device->usb_send_msg(USB_MSG_RESET); if (BX_XHCI_THIS hub.usb_port[port].is_usb3 && (reset_type == WARM_RESET)) BX_XHCI_THIS hub.usb_port[port].portsc.wrc = 1; - BX_XHCI_THIS hub.usb_port[port].portsc.prc = 1; } } else { BX_XHCI_THIS hub.usb_port[port].portsc.pls = PLS_RXDETECT; @@ -763,10 +862,10 @@ bool bx_usb_xhci_c::restore_hc_state(void) void bx_usb_xhci_c::register_state(void) { - unsigned i, j; + unsigned i, j, k; char tmpname[16]; bx_list_c *hub, *port, *reg, *reg_grp, *reg_grp1; - bx_list_c *list1, *list2, *item, *entries, *entry; + bx_list_c *list1, *list2, *list3, *item, *entries, *entry, *entry1; bx_list_c *list = new bx_list_c(SIM->get_bochs_root(), "usb_xhci", "USB xHCI State"); hub = new bx_list_c(list, "hub"); @@ -826,11 +925,11 @@ void bx_usb_xhci_c::register_state(void) BXRS_PARAM_BOOL(reg_grp, HcConfig_cie, BX_XHCI_THIS hub.op_regs.HcConfig.cie); #endif - for (i = 0; i < USB_XHCI_PORTS; i++) { + for (i = 0; i < BX_XHCI_THIS hub.n_ports; i++) { sprintf(tmpname, "port%d", i+1); port = new bx_list_c(hub, tmpname); BXRS_PARAM_BOOL(port, has_been_reset, BX_XHCI_THIS hub.usb_port[i].has_been_reset); - BXRS_PARAM_BOOL(port, needs_psce, BX_XHCI_THIS hub.usb_port[i].needs_psce); + BXRS_HEX_PARAM_FIELD(port, psceg, BX_XHCI_THIS hub.usb_port[i].psceg); reg = new bx_list_c(port, "portsc"); BXRS_PARAM_BOOL(reg, wpr, BX_XHCI_THIS hub.usb_port[i].portsc.wpr); BXRS_PARAM_BOOL(reg, dr, BX_XHCI_THIS hub.usb_port[i].portsc.dr); @@ -943,6 +1042,13 @@ void bx_usb_xhci_c::register_state(void) BXRS_PARAM_BOOL(entry, rcs, BX_XHCI_THIS hub.slots[i].ep_context[j].rcs); BXRS_PARAM_BOOL(entry, retry, BX_XHCI_THIS hub.slots[i].ep_context[j].retry); BXRS_DEC_PARAM_FIELD(entry, retry_counter, BX_XHCI_THIS hub.slots[i].ep_context[j].retry_counter); + for (k = 0; k < MAX_PSA_SIZE_NUM; k++) { + sprintf(tmpname, "%d", k); + entry1 = new bx_list_c(entry, tmpname); + list3 = new bx_list_c(entry1, "ep_stream"); + BXRS_HEX_PARAM_FIELD(list3, tr_dequeue_pointer, BX_XHCI_THIS hub.slots[i].ep_context[j].stream[k].tr_dequeue_pointer); + BXRS_PARAM_BOOL(list3, dcs, BX_XHCI_THIS hub.slots[i].ep_context[j].stream[k].dcs); + } } } @@ -973,7 +1079,7 @@ void bx_usb_xhci_c::register_state(void) void bx_usb_xhci_c::after_restore_state(void) { bx_pci_device_c::after_restore_pci_state(NULL); - for (int j=0; jafter_restore_state(); } @@ -1173,7 +1279,7 @@ bool bx_usb_xhci_c::read_handler(bx_phy_address addr, unsigned len, void *data, } else // Register Port Sets - if ((offset >= PORT_SET_OFFSET) && (offset < (PORT_SET_OFFSET + (USB_XHCI_PORTS * 16)))) { + if ((offset >= PORT_SET_OFFSET) && (offset < (PORT_SET_OFFSET + (BX_XHCI_THIS hub.n_ports * 16)))) { unsigned port = (((offset - PORT_SET_OFFSET) >> 4) & 0x3F); // calculate port number if (BX_XHCI_THIS hub.usb_port[port].portsc.pp) { // the speed field is only valid for USB3 before a port reset. If a reset has not @@ -1480,7 +1586,7 @@ bool bx_usb_xhci_c::write_handler(bx_phy_address addr, unsigned len, void *data, BX_XHCI_THIS hub.op_regs.HcCommand.hcrst = 0; // the controller will send a reset to all USB 3.0 ports, // enabling the port if a device is attached. - for (int port=0; port= PORT_SET_OFFSET) && (offset < (PORT_SET_OFFSET + (USB_XHCI_PORTS * 16)))) { + if ((offset >= PORT_SET_OFFSET) && (offset < (PORT_SET_OFFSET + (BX_XHCI_THIS hub.n_ports * 16)))) { unsigned port = (((offset - PORT_SET_OFFSET) >> 4) & 0x3F); // calculate port number switch (offset & 0x0000000F) { case 0x00: @@ -1662,21 +1768,6 @@ bool bx_usb_xhci_c::write_handler(bx_phy_address addr, unsigned len, void *data, if (BX_XHCI_THIS hub.usb_port[port].portsc.pp == 0) { // a "has been reset" is false. BX_XHCI_THIS hub.usb_port[port].has_been_reset = 0; - - /* Per section 4.19.3 of the xHCI 1.0 specs, we need to present - * a "Port Status Change Event". - * Also, we should only present this event once if any other bits - * change, only presenting it again when all change bits are written - * back to zero, and a change bit goes from 0 to 1. - * (However, I don't know if that is a worry here, at this moment?) - */ - if (BX_XHCI_THIS hub.usb_port[port].needs_psce == 1) { - if (BX_XHCI_THIS hub.op_regs.HcStatus.hch == 0) { - BX_INFO(("Port #%d Status Change Event.", port + 1)); - write_event_TRB(0, ((port + 1) << 24), TRB_SET_COMP_CODE(1), TRB_SET_TYPE(PORT_STATUS_CHANGE), 1); - } - BX_XHCI_THIS hub.usb_port[port].needs_psce = 0; - } } BX_XHCI_THIS hub.usb_port[port].portsc.pp = 1; if (value & (1<<16)) { // LWS @@ -1896,10 +1987,55 @@ bool bx_usb_xhci_c::write_handler(bx_phy_address addr, unsigned len, void *data, // (value & 0xFF) = ep (1 = control, 2 = ep1 out, 3 = ep1 in, etc); int ep = (value & 0xFF); BX_DEBUG(("Rang Doorbell: slot = %d ep = %d (%s)", doorbell, ep, (ep & 1) ? "IN" : "OUT")); - if (ep > 31) + if (ep > 31) { BX_ERROR(("Doorbell rang with EP > 31 (ep = %d)", ep)); - else - process_transfer_ring(doorbell, ep); + } else { +#if MAX_PSA_SIZE > 0 + // is this endpoint using streams? + if (BX_XHCI_THIS hub.slots[doorbell].ep_context[ep].ep_context.max_pstreams > 0) { // specifying streams + // the ep must be a super-speed bulk endpoint + if ((BX_XHCI_THIS hub.slots[doorbell].slot_context.speed == XHCI_SPEED_SUPER) && // is a super-speed device + ((BX_XHCI_THIS hub.slots[doorbell].ep_context[ep].ep_context.ep_type == 2) || // is a bulk out + (BX_XHCI_THIS hub.slots[doorbell].ep_context[ep].ep_context.ep_type == 6))) { // or bulk in + // if the lsa bit is set, use primary streams only + if (BX_XHCI_THIS hub.slots[doorbell].ep_context[ep].ep_context.lsa == 1) { + unsigned primary_id = PSA_PRIMARY_MASK(value, BX_XHCI_THIS hub.slots[doorbell].ep_context[ep].ep_context.max_pstreams); + if ((primary_id > 0) && (primary_id < MAX_PSA_SIZE_NUM) && + (primary_id < PSA_MAX_SIZE_NUM(BX_XHCI_THIS hub.slots[doorbell].ep_context[ep].ep_context.max_pstreams)) && + BX_XHCI_THIS hub.slots[doorbell].ep_context[ep].stream[primary_id].valid == 1) { + BX_XHCI_THIS hub.slots[doorbell].ep_context[ep].stream[primary_id].tr_dequeue_pointer = + BX_XHCI_THIS process_transfer_ring(doorbell, ep, BX_XHCI_THIS hub.slots[doorbell].ep_context[ep].stream[primary_id].tr_dequeue_pointer, + &BX_XHCI_THIS hub.slots[doorbell].ep_context[ep].stream[primary_id].dcs, primary_id); + } else { + BX_ERROR(("Stream Context with bad Primary Stream ID (%d)", primary_id)); + // no TRB is associated, so can't include a TRB address. Does this make the EventTRB invalid? + //write_event_TRB(0, 0, TRB_SET_COMP_CODE(INVALID_STREAM_ID), TRB_SET_SLOT(doorbell) | TRB_SET_EP(ep) | TRB_SET_TYPE(TRANS_EVENT), 1); + //BX_XHCI_THIS hub.slots[doorbell].ep_context[ep].ep_context.ep_state = EP_STATE_HALTED; // call a function to do this correctly. + } + } else { + #if NO_SSD_SUPPORT + BX_ERROR(("The EP's context lsa bit is zero. Secondary Streams are not supported.")); + #else + // secondary streams are used + unsigned primary_id = PSA_PRIMARY_MASK(value, BX_XHCI_THIS hub.slots[doorbell].ep_context[ep].ep_context.max_pstreams); + unsigned secondary_id = PSA_SECONDARY_MASK(value, BX_XHCI_THIS hub.slots[doorbell].ep_context[ep].ep_context.max_pstreams); + BX_ERROR(("The EP's context lsa bit is set. Secondary Streams are not yet supported.")); + // TODO: + #endif + } + } else { + BX_ERROR(("EP:MaxPStream > 0 on a non-Bulk SS device endpoint")); + } + } else { +#endif + // standard trb ring + BX_XHCI_THIS hub.slots[doorbell].ep_context[ep].enqueue_pointer = + BX_XHCI_THIS process_transfer_ring(doorbell, ep, BX_XHCI_THIS hub.slots[doorbell].ep_context[ep].enqueue_pointer, + &BX_XHCI_THIS hub.slots[doorbell].ep_context[ep].rcs, 0); +#if MAX_PSA_SIZE > 0 + } +#endif + } } } else BX_ERROR(("register write to unknown offset 0x%08X: 0x%08X%08X (len=%d)", offset, (Bit32u) value_hi, (Bit32u) value, len)); @@ -1907,6 +2043,42 @@ bool bx_usb_xhci_c::write_handler(bx_phy_address addr, unsigned len, void *data, return 1; } +void bx_usb_xhci_c::get_stream_info(struct STREAM_CONTEXT *context, const Bit64u address, const int index) { + struct STREAM_CONTEXT stream_context; + Bit8u buffer[16]; + + // the first stream context is reserved + if ((index > 0) && (index < MAX_PSA_SIZE_NUM)) { + // read in the stream context + DEV_MEM_READ_PHYSICAL((bx_phy_address) address + (index * 16), 16, buffer); + copy_stream_from_buffer(&stream_context, buffer); + if ((stream_context.sct == 1) && (stream_context.tr_dequeue_pointer != 0)) { + context->valid = 1; // mark that this is a valid stream context + context->tr_dequeue_pointer = stream_context.tr_dequeue_pointer; + context->dcs = stream_context.dcs; + context->sct = stream_context.sct; + } else { +#if NO_SSD_SUPPORT + context->valid = 0; + BX_DEBUG(("Stream Context index %d with SCT != 1 (%d)", index, stream_context.sct)); +#else + BX_DEBUG(("Stream Context index %d with SCT == 0 (%d)...Secondary Streams not yet supported.", index)); +#endif + } + } +} + +void bx_usb_xhci_c::put_stream_info(struct STREAM_CONTEXT *context, const Bit64u address, const int index) { + Bit8u buffer[16]; + + // the first stream context is reserved + if ((index > 0) && (index < MAX_PSA_SIZE_NUM)) { + copy_stream_to_buffer(buffer, context); + // write the stream context + DEV_MEM_WRITE_PHYSICAL((bx_phy_address) address + (index * 16), 16, buffer); + } +} + void xhci_event_handler(int event, USBPacket *packet, void *dev, int port) { ((bx_usb_xhci_c*)dev)->event_handler(event, packet, port); @@ -1922,7 +2094,16 @@ void bx_usb_xhci_c::event_handler(int event, USBPacket *packet, int port) p->done = 1; slot = (p->slot_ep >> 8); ep = (p->slot_ep & 0xff); - BX_XHCI_THIS process_transfer_ring(slot, ep); + if (BX_XHCI_THIS hub.slots[slot].ep_context[ep].ep_context.max_pstreams > 0) { // specifying streams + BX_DEBUG(("Event Handler: USB_EVENT_ASYNC: slot %d, ep %d, stream ID %d", slot, ep, p->packet.strm_pid)); + BX_XHCI_THIS hub.slots[slot].ep_context[ep].stream[p->packet.strm_pid].tr_dequeue_pointer = + BX_XHCI_THIS process_transfer_ring(slot, ep, BX_XHCI_THIS hub.slots[slot].ep_context[ep].stream[p->packet.strm_pid].tr_dequeue_pointer, + &BX_XHCI_THIS hub.slots[slot].ep_context[ep].stream[p->packet.strm_pid].dcs, p->packet.strm_pid); + } else { + BX_XHCI_THIS hub.slots[slot].ep_context[ep].enqueue_pointer = + BX_XHCI_THIS process_transfer_ring(slot, ep, BX_XHCI_THIS hub.slots[slot].ep_context[ep].enqueue_pointer, + &BX_XHCI_THIS hub.slots[slot].ep_context[ep].rcs, 0); + } } else if (event == USB_EVENT_WAKEUP) { if (BX_XHCI_THIS hub.usb_port[port].portsc.pls != PLS_U3_SUSPENDED) { return; @@ -1941,7 +2122,7 @@ void bx_usb_xhci_c::event_handler(int event, USBPacket *packet, int port) } // This function checks and processes all enqueued TRB's in the EP's transfer ring -void bx_usb_xhci_c::process_transfer_ring(const int slot, const int ep) +Bit64u bx_usb_xhci_c::process_transfer_ring(const int slot, const int ep, Bit64u ring_addr, bool *rcs, const int primary_sid) { struct TRB trb; Bit64u address = 0, org_addr; @@ -1966,16 +2147,16 @@ void bx_usb_xhci_c::process_transfer_ring(const int slot, const int ep) // if the ep is disabled, return an error event trb. if ((BX_XHCI_THIS hub.slots[slot].slot_context.slot_state == SLOT_STATE_DISABLED_ENABLED) || (BX_XHCI_THIS hub.slots[slot].ep_context[ep].ep_context.ep_state == EP_STATE_DISABLED)) { - org_addr = BX_XHCI_THIS hub.slots[slot].ep_context[ep].enqueue_pointer; + org_addr = ring_addr; write_event_TRB(0, org_addr, TRB_SET_COMP_CODE(EP_NOT_ENABLED), TRB_SET_SLOT(slot) | TRB_SET_EP(ep) | TRB_SET_TYPE(TRANS_EVENT), 1); - return; + return ring_addr; } // if the ep is in the halted or error state, ignore the doorbell ring. if ((BX_XHCI_THIS hub.slots[slot].ep_context[ep].ep_context.ep_state == EP_STATE_HALTED) || (BX_XHCI_THIS hub.slots[slot].ep_context[ep].ep_context.ep_state == EP_STATE_ERROR)) - return; + return ring_addr; // if the ep_context::type::direction field is not correct for the ep type of this ep, then ignore the doorbell if (ep >= 2) { @@ -1983,7 +2164,7 @@ void bx_usb_xhci_c::process_transfer_ring(const int slot, const int ep) int ep_type = (ep & 1) ? EP_DIR_IN : EP_DIR_OUT; if (endpoint_dir[BX_XHCI_THIS hub.slots[slot].ep_context[ep].ep_context.ep_type] != ep_type) { BX_ERROR(("Endpoint_context::Endpoint_type::direction is not correct for this endpoint number. Ignoring doorbell ring.")); - return; + return ring_addr; } } @@ -1995,14 +2176,11 @@ void bx_usb_xhci_c::process_transfer_ring(const int slot, const int ep) } // read in the TRB - read_TRB((bx_phy_address) BX_XHCI_THIS hub.slots[slot].ep_context[ep].enqueue_pointer, &trb); - BX_DEBUG(("Found TRB: address = 0x" FORMATADDRESS " 0x" FMT_ADDRX64 " 0x%08X 0x%08X %d", - (bx_phy_address) BX_XHCI_THIS hub.slots[slot].ep_context[ep].enqueue_pointer, - trb.parameter, trb.status, trb.command, BX_XHCI_THIS hub.slots[slot].ep_context[ep].rcs)); - while ((trb.command & 1) == BX_XHCI_THIS hub.slots[slot].ep_context[ep].rcs) { - org_addr = BX_XHCI_THIS hub.slots[slot].ep_context[ep].enqueue_pointer; + read_TRB((bx_phy_address) ring_addr, &trb); + while ((trb.command & 1) == *rcs) { + org_addr = ring_addr; BX_DEBUG(("Found TRB: address = 0x" FORMATADDRESS " 0x" FMT_ADDRX64 " 0x%08X 0x%08X %d (SPD occurred = %d)", - (bx_phy_address) org_addr, trb.parameter, trb.status, trb.command, BX_XHCI_THIS hub.slots[slot].ep_context[ep].rcs, spd_occurred)); + (bx_phy_address) org_addr, trb.parameter, trb.status, trb.command, *rcs, spd_occurred)); trb_count++; // these are used in some/most items. // If not used, won't hurt to extract bad data. @@ -2012,7 +2190,7 @@ void bx_usb_xhci_c::process_transfer_ring(const int slot, const int ep) is_transfer_trb = 0; // assume not a transfer ioc = TRB_IOC(trb.command); - // if a SPD occurred, we only process the LINK and EVENT TRB's in this TD, until either on of these two or end of TD + // if a SPD occurred, we only process the LINK and EVENT TRB's in this TD, until either one of these two or end of TD if (!spd_occurred || (spd_occurred && ((TRB_GET_TYPE(trb.command) == LINK) || (TRB_GET_TYPE(trb.command) == EVENT_DATA)))) { // is the data in trb.parameter? (Immediate data?) @@ -2028,16 +2206,16 @@ void bx_usb_xhci_c::process_transfer_ring(const int slot, const int ep) if (ioc) write_event_TRB(int_target, org_addr, TRB_SET_COMP_CODE(TRB_SUCCESS), TRB_SET_TYPE(LINK), 1); if (TRB_TOGGLE(trb.command)) - BX_XHCI_THIS hub.slots[slot].ep_context[ep].rcs ^= 1; - BX_XHCI_THIS hub.slots[slot].ep_context[ep].enqueue_pointer = trb.parameter & (Bit64u) ~0xF; + (*rcs) ^= 1; + ring_addr = trb.parameter & (Bit64u) ~0xF; BX_DEBUG(("0x" FORMATADDRESS ": Transfer Ring (slot = %d) (ep = %d): LINK TRB: New dq_pointer = 0x" FMT_ADDRX64 " (%d)", - (bx_phy_address) org_addr, slot, ep, BX_XHCI_THIS hub.slots[slot].ep_context[ep].enqueue_pointer, BX_XHCI_THIS hub.slots[slot].ep_context[ep].rcs)); + (bx_phy_address) org_addr, slot, ep, ring_addr, *rcs)); #if ((VERSION_MAJOR == 0) && (VERSION_MINOR == 0x95)) // https://patchwork.kernel.org/patch/51191/ if (!TRB_CHAIN(trb.command)) BX_DEBUG(("Chain Bit in Link TRB not set.")); #endif - read_TRB((bx_phy_address) BX_XHCI_THIS hub.slots[slot].ep_context[ep].enqueue_pointer, &trb); + read_TRB((bx_phy_address) ring_addr, &trb); continue; // Setup Stage TRB @@ -2115,10 +2293,10 @@ void bx_usb_xhci_c::process_transfer_ring(const int slot, const int ep) // is there a transfer to be done? if (is_transfer_trb) { - p = find_async_packet(&BX_XHCI_THIS packets, org_addr); + p = find_async_packet(&BX_XHCI_THIS packets, ring_addr); bool completion = (p != NULL); if (completion && !p->done) { - return; + return ring_addr; } comp_code = TRB_SUCCESS; // assume good trans event if (completion) { @@ -2135,6 +2313,7 @@ void bx_usb_xhci_c::process_transfer_ring(const int slot, const int ep) #endif p->packet.complete_cb = xhci_event_handler; p->packet.complete_dev = BX_XHCI_THIS_PTR; + p->packet.strm_pid = primary_sid; p->slot_ep = (slot << 8) | ep; switch (cur_direction) { case USB_TOKEN_OUT: @@ -2233,13 +2412,16 @@ void bx_usb_xhci_c::process_transfer_ring(const int slot, const int ep) } // advance the Dequeue pointer and continue; - BX_XHCI_THIS hub.slots[slot].ep_context[ep].enqueue_pointer += 16; - read_TRB((bx_phy_address) BX_XHCI_THIS hub.slots[slot].ep_context[ep].enqueue_pointer, &trb); + ring_addr += 16; + read_TRB((bx_phy_address) ring_addr, &trb); } BX_DEBUG(("Process Transfer Ring: Processed %d TRB's", trb_count)); if (trb_count == 0) BX_ERROR(("Process Transfer Ring: Doorbell rang, but no TRB's were enqueued in the ring.")); + + // return the new address + return ring_addr; } // This function call starts at the current position in the Command Ring, @@ -2250,8 +2432,9 @@ void bx_usb_xhci_c::process_transfer_ring(const int slot, const int ep) void bx_usb_xhci_c::process_command_ring(void) { struct TRB trb; - int i, slot, ep, comp_code = 0, new_addr = 0, bsr = 0, trb_command; - Bit32u a_flags = 0, d_flags, tmpval1, tmpval2; + unsigned i, j; + int slot, ep, comp_code = 0, new_addr = 0, bsr = 0, trb_command; + Bit32u a_flags, d_flags; Bit64u org_addr; Bit8u buffer[CONTEXT_SIZE + (32 * CONTEXT_SIZE)]; struct SLOT_CONTEXT slot_context; @@ -2285,7 +2468,7 @@ void bx_usb_xhci_c::process_command_ring(void) // NEC: Get Firmware version case NEC_TRB_TYPE_GET_FW: write_event_TRB(0, org_addr, TRB_SET_COMP_CODE(1) | 0x3021, TRB_SET_SLOT(0) | TRB_SET_TYPE(NEC_TRB_TYPE_CMD_COMP), 1); - BX_INFO(("NEC GET Firmware Version TRB found. Returning 0x3021")); + BX_DEBUG(("NEC GET Firmware Version TRB found. Returning 0x3021")); break; /* NEC: Get Verification @@ -2300,7 +2483,7 @@ void bx_usb_xhci_c::process_command_ring(void) * should be fine. */ case NEC_TRB_TYPE_GET_UN: - BX_INFO(("NEC GET Verification TRB found.")); + BX_DEBUG(("NEC GET Verification TRB found.")); break; /* Bochs Dump Controller command: @@ -2326,8 +2509,8 @@ void bx_usb_xhci_c::process_command_ring(void) BX_XHCI_THIS hub.slots[i].slot_context.slot_state = SLOT_STATE_DISABLED_ENABLED; BX_XHCI_THIS hub.slots[i].enabled = 1; slot = i; - for (i=1; i<32; i++) - BX_XHCI_THIS hub.slots[slot].ep_context[i].ep_context.ep_state = EP_STATE_DISABLED; + for (j=1; j<32; j++) + BX_XHCI_THIS hub.slots[i].ep_context[j].ep_context.ep_state = EP_STATE_DISABLED; comp_code = TRB_SUCCESS; break; } @@ -2361,19 +2544,17 @@ void bx_usb_xhci_c::process_command_ring(void) if (BX_XHCI_THIS hub.slots[slot].enabled == 1) { get_dwords((bx_phy_address) trb.parameter, (Bit32u *) buffer, (CONTEXT_SIZE + (CONTEXT_SIZE * 2)) >> 2); bsr = ((trb.command & (1<<9)) == (1<<9)); - //DEV_MEM_READ_PHYSICAL((bx_phy_address) trb.parameter, 4, (Bit8u *) &tmpval1); - //DEV_MEM_READ_PHYSICAL((bx_phy_address) trb.parameter + 4, 4, (Bit8u *) &tmpval2); - tmpval1 = * (Bit32u *) &buffer[0]; - tmpval2 = * (Bit32u *) &buffer[4]; - if (((tmpval1 & 0x03) == 0) && ((tmpval2 & 0x03) == 3)) { - if ((tmpval1 & ~0x03) || (tmpval2 & ~0x03)) { + d_flags = * (Bit32u *) &buffer[0]; + a_flags = * (Bit32u *) &buffer[4]; + if (((d_flags & 0x03) == 0) && ((a_flags & 0x03) == 3)) { + if ((d_flags & ~0x03) || (a_flags & ~0x03)) { BX_ERROR(("D2->D31 and A2->A31 must be zero...")); } // Use temporary slot and ep context incase there is an error we don't modify the main contexts copy_slot_from_buffer(&slot_context, &buffer[CONTEXT_SIZE]); copy_ep_from_buffer(&ep_context, &buffer[CONTEXT_SIZE + CONTEXT_SIZE]); // check that the Input contexts are valid - if ((validate_slot_context(&slot_context) == TRB_SUCCESS) && + if ((validate_slot_context(&slot_context, trb_command, slot) == TRB_SUCCESS) && (validate_ep_context(&ep_context, trb_command, 0, slot_context.rh_port_num - 1, 1) == TRB_SUCCESS)) { if (bsr == 1) { // BSR flag set if (BX_XHCI_THIS hub.slots[slot].slot_context.slot_state == SLOT_STATE_DISABLED_ENABLED) { @@ -2421,109 +2602,60 @@ void bx_usb_xhci_c::process_command_ring(void) (bx_phy_address) org_addr, bsr, new_addr, slot, comp_code)); break; - case EVALUATE_CONTEXT: { - slot = TRB_GET_SLOT(trb.command); // slots are 1 based - if (BX_XHCI_THIS hub.slots[slot].enabled == 1) { - get_dwords((bx_phy_address) trb.parameter, (Bit32u *) buffer, (CONTEXT_SIZE + (CONTEXT_SIZE * 32)) >> 2); - //DEV_MEM_READ_PHYSICAL((bx_phy_address) trb.parameter + 4, 4, (Bit8u *) &a_flags); - a_flags = * (Bit32u *) &buffer[4]; - // only the Slot context and EP1 (control EP) contexts are evaluated. Section 6.2.3.3 - // If the slot is not addressed or configured, then return error - // XHCI specs 1.0, page 102 says DEFAULT or higher, while page 321 states higher than DEFAULT!!! - // (specs 1.1 removed the DEFAULT word and require it to be greater than the DEFAULT state) + case EVALUATE_CONTEXT: + slot = TRB_GET_SLOT(trb.command); // slots are 1 based + if (BX_XHCI_THIS hub.slots[slot].enabled == 1) { + get_dwords((bx_phy_address) trb.parameter, (Bit32u *) buffer, (CONTEXT_SIZE + (CONTEXT_SIZE * 32)) >> 2); + a_flags = * (Bit32u *) &buffer[4]; + // only the Slot context and EP0 (control EP) contexts are evaluated. Section 6.2.3.3, p326 + // If the slot is not addressed or configured, then return error + // XHCI specs 1.0, page 102 says DEFAULT or higher, while page 321 states higher than DEFAULT!!! + // (specs 1.1 removed the DEFAULT word and require it to be greater than the DEFAULT state) #if ((VERSION_MAJOR == 1) && (VERSION_MINOR >= 0x10)) - if (BX_XHCI_THIS hub.slots[slot].slot_context.slot_state > SLOT_STATE_DEFAULT) { + if (BX_XHCI_THIS hub.slots[slot].slot_context.slot_state > SLOT_STATE_DEFAULT) { #else - if (BX_XHCI_THIS hub.slots[slot].slot_context.slot_state >= SLOT_STATE_DEFAULT) { + if (BX_XHCI_THIS hub.slots[slot].slot_context.slot_state >= SLOT_STATE_DEFAULT) { #endif - comp_code = TRB_SUCCESS; // assume good completion - if (a_flags & (1<<0)) { - copy_slot_from_buffer(&slot_context, &buffer[CONTEXT_SIZE]); - comp_code = validate_slot_context(&slot_context); - } - if (comp_code == TRB_SUCCESS) { - if (a_flags & (1<<1)) { - copy_ep_from_buffer(&ep_context, &buffer[CONTEXT_SIZE + CONTEXT_SIZE]); - comp_code = validate_ep_context(&ep_context, trb_command, a_flags, BX_XHCI_THIS hub.slots[slot].slot_context.rh_port_num - 1, 1); - } - } - } else { - comp_code = CONTEXT_STATE_ERROR; - BX_DEBUG(("Evaluate Context with an illegal slot state")); + comp_code = TRB_SUCCESS; // assume good completion + if (a_flags & (1<<0)) { + copy_slot_from_buffer(&slot_context, &buffer[CONTEXT_SIZE]); + comp_code = validate_slot_context(&slot_context, trb_command, slot); } - - // if all were good, go ahead and update our contexts if (comp_code == TRB_SUCCESS) { - for (i=0; i<32; i++) { - if (a_flags & (1<get_speed()) { - case USB_SPEED_LOW: - BX_XHCI_THIS hub.slots[slot].ep_context[i].ep_context.max_packet_size = 8; - break; - case USB_SPEED_FULL: - case USB_SPEED_HIGH: - BX_XHCI_THIS hub.slots[slot].ep_context[i].ep_context.max_packet_size = 64; - break; - case USB_SPEED_SUPER: - BX_XHCI_THIS hub.slots[slot].ep_context[i].ep_context.max_packet_size = 512; - break; - } - } else - BX_XHCI_THIS hub.slots[slot].ep_context[i].ep_context.max_packet_size = ep_context.max_packet_size; - - switch (ep_context.ep_type) { - case 4: // control - BX_XHCI_THIS hub.slots[slot].ep_context[i].ep_context.cerr = ep_context.cerr; - break; - case 2: // Bulk out - case 6: // Bulk in - BX_XHCI_THIS hub.slots[slot].ep_context[i].ep_context.max_burst_size = ep_context.max_burst_size; - BX_XHCI_THIS hub.slots[slot].ep_context[i].ep_context.cerr = ep_context.cerr; - BX_XHCI_THIS hub.slots[slot].ep_context[i].ep_context.max_pstreams = 0; // we don't support streams yet - break; - default: // ISO or interrupt - BX_XHCI_THIS hub.slots[slot].ep_context[i].ep_context.max_burst_size = ep_context.max_burst_size; - BX_XHCI_THIS hub.slots[slot].ep_context[i].ep_context.mult = ep_context.mult; - BX_XHCI_THIS hub.slots[slot].ep_context[i].ep_context.cerr = 3; - BX_XHCI_THIS hub.slots[slot].ep_context[i].ep_context.max_pstreams = 0; // we don't support streams yet - break; - } - // update our internal enqueue pointer - //BX_XHCI_THIS hub.slots[slot].ep_context[i].enqueue_pointer = ep_context.tr_dequeue_pointer; - BX_XHCI_THIS hub.slots[slot].ep_context[i].rcs = ep_context.dcs; - - // update the DCBAAP slot's ep - update_ep_context(slot, i); - } - } + if (a_flags & (1<<1)) { + copy_ep_from_buffer(&ep_context, &buffer[CONTEXT_SIZE + CONTEXT_SIZE]); + comp_code = validate_ep_context(&ep_context, trb_command, a_flags, BX_XHCI_THIS hub.slots[slot].slot_context.rh_port_num - 1, 1); } } - } else - comp_code = SLOT_NOT_ENABLED; + } else { + comp_code = CONTEXT_STATE_ERROR; + BX_DEBUG(("Evaluate Context with an illegal slot state")); + } - write_event_TRB(0, org_addr, TRB_SET_COMP_CODE(comp_code), TRB_SET_SLOT(slot) | TRB_SET_TYPE(COMMAND_COMPLETION), 1); - BX_DEBUG(("0x" FORMATADDRESS ": Command Ring: Evaluate TRB (slot = %d) (a_flags = 0x%08X) (returning %d)", - (bx_phy_address) org_addr, slot, a_flags, comp_code)); - } + // if all were good, go ahead and update our contexts + if (comp_code == TRB_SUCCESS) { + if (a_flags & (1<<0)) { // slot context + // See section 6.2.2.3 for what fields will be updated + copy_slot_from_buffer(&slot_context, &buffer[CONTEXT_SIZE]); + // only the Interrupter Target and Max Exit Latency are updated by this command + BX_XHCI_THIS hub.slots[slot].slot_context.int_target = slot_context.int_target; + BX_XHCI_THIS hub.slots[slot].slot_context.max_exit_latency = slot_context.max_exit_latency; + update_slot_context(slot); // update the DCBAAP slot + } + if (a_flags & (1<<1)) { // control ep context + // See section 6.2.3.3 for what fields will be updated + copy_ep_from_buffer(&ep_context, &buffer[CONTEXT_SIZE + CONTEXT_SIZE]); + BX_XHCI_THIS hub.slots[slot].ep_context[1].ep_context.max_packet_size = ep_context.max_packet_size; + update_ep_context(slot, 1); + } + } + } else + comp_code = SLOT_NOT_ENABLED; + + write_event_TRB(0, org_addr, TRB_SET_COMP_CODE(comp_code), TRB_SET_SLOT(slot) | TRB_SET_TYPE(COMMAND_COMPLETION), 1); + BX_DEBUG(("0x" FORMATADDRESS ": Command Ring: Evaluate TRB (slot = %d) (d_flags = 0x%08X) (a_flags = 0x%08X) (returning %d)", + (bx_phy_address) org_addr, slot, d_flags, a_flags, comp_code)); + break; case CONFIG_EP: { @@ -2531,28 +2663,33 @@ void bx_usb_xhci_c::process_command_ring(void) bool dc = TRB_DC(trb.command); if (BX_XHCI_THIS hub.slots[slot].enabled) { get_dwords((bx_phy_address) trb.parameter, (Bit32u *) buffer, (CONTEXT_SIZE + (CONTEXT_SIZE * 32)) >> 2); - //DEV_MEM_READ_PHYSICAL((bx_phy_address) trb.parameter, 4, (Bit8u *) &d_flags); - //DEV_MEM_READ_PHYSICAL((bx_phy_address) trb.parameter + 4, 4, (Bit8u *) &a_flags); d_flags = * (Bit32u *) &buffer[0]; a_flags = * (Bit32u *) &buffer[4]; copy_slot_from_buffer(&slot_context, &buffer[CONTEXT_SIZE]); // so we get entry_count - if (BX_XHCI_THIS hub.slots[slot].slot_context.slot_state == SLOT_STATE_CONFIGURED) { - for (i=2; i<32; i++) { - if (dc || (d_flags & (1<= SLOT_STATE_ADDRESSED)) { + if (dc) { + BX_XHCI_THIS hub.slots[slot].slot_context.entries = 1; + BX_XHCI_THIS hub.slots[slot].slot_context.slot_state = SLOT_STATE_ADDRESSED; + update_slot_context(slot); + } else + + if (BX_XHCI_THIS hub.slots[slot].slot_context.slot_state >= SLOT_STATE_ADDRESSED) { comp_code = TRB_SUCCESS; // assume good completion + if ((d_flags & 3) > 0) // the D0 (slot) and D1 (ep0) bits must be cleared + comp_code = PARAMETER_ERROR; + if ((a_flags & 3) == 1) { // the A0 (slot) bit must be set, and A1 (ep0) must be cleared + comp_code = validate_slot_context(&slot_context, trb_command, slot); + } else { + comp_code = PARAMETER_ERROR; + } + // Check all the input context entries with an a_flag == 1 for (i=2; i<32; i++) { if (a_flags & (1< 0) { + for (j=1; j EP_STATE_DISABLED) { + if (BX_XHCI_THIS hub.slots[slot].ep_context[i].ep_context.ep_state == EP_STATE_RUNNING) { BX_XHCI_THIS hub.slots[slot].slot_context.slot_state = SLOT_STATE_CONFIGURED; break; } } - BX_XHCI_THIS hub.slots[slot].slot_context.entries = slot_context.entries; /////// + + // now update the slot update_slot_context(slot); } } else @@ -2604,11 +2761,9 @@ void bx_usb_xhci_c::process_command_ring(void) } else comp_code = SLOT_NOT_ENABLED; - // TODO: Page 101 (change to context entries) - write_event_TRB(0, org_addr, TRB_SET_COMP_CODE(comp_code), TRB_SET_SLOT(slot) | TRB_SET_TYPE(COMMAND_COMPLETION), 1); - BX_DEBUG(("0x" FORMATADDRESS ": Command Ring: Found Config_EP TRB (slot = %d) (returning %d)", - (bx_phy_address) org_addr, slot, comp_code)); + BX_DEBUG(("0x" FORMATADDRESS ": Command Ring: Found Config_EP TRB (slot = %d) (dc = %d) (d_flags = 0x%08X) (a_flags = 0x%08X) (returning %d)", + (bx_phy_address) org_addr, slot, dc, d_flags, a_flags, comp_code)); } break; @@ -2665,14 +2820,15 @@ void bx_usb_xhci_c::process_command_ring(void) (bx_phy_address) org_addr, slot, ep, ((trb.command & (1<<23)) == (1<<23)))); break; - // Get Port Bandwidth (only available on version 0.96 or 0.95.) + // Get Port Bandwidth (manditory on versions 0.95, 0.96, and 1.00. Optional on later versions) case GET_PORT_BAND: { unsigned hub_id = TRB_GET_SLOT(trb.command); unsigned band_speed = ((trb.command & (0x0F << 16)) >> 16) - 1; + unsigned offset = band_speed * (((1 + BX_XHCI_THIS hub.n_ports) + 7) & ~7); if (hub_id == 0) { // root hub if (band_speed < 4) { - DEV_MEM_WRITE_PHYSICAL_DMA((bx_phy_address) trb.parameter, 1 + USB_XHCI_PORTS, port_band_width[band_speed]); + DEV_MEM_WRITE_PHYSICAL_DMA((bx_phy_address) trb.parameter, 1 + BX_XHCI_THIS hub.n_ports, &BX_XHCI_THIS hub.port_band_width[offset]); comp_code = TRB_SUCCESS; } else { comp_code = TRB_ERROR; @@ -2769,7 +2925,15 @@ void bx_usb_xhci_c::write_event_TRB(const unsigned interrupter, const Bit64u par { // write the TRB write_TRB((bx_phy_address) BX_XHCI_THIS hub.ring_members.event_rings[interrupter].cur_trb, parameter, status, - command | (Bit32u)BX_XHCI_THIS hub.ring_members.event_rings[interrupter].rcs); // set the cycle bit + command | (Bit32u) BX_XHCI_THIS hub.ring_members.event_rings[interrupter].rcs); // set the cycle bit + + BX_DEBUG(("Write Event TRB: table index: %d, trb index: %d", + BX_XHCI_THIS hub.ring_members.event_rings[interrupter].count, + BX_XHCI_THIS hub.ring_members.event_rings[interrupter].entrys[BX_XHCI_THIS hub.ring_members.event_rings[interrupter].count].size - + BX_XHCI_THIS hub.ring_members.event_rings[interrupter].trb_count)); + BX_DEBUG(("Write Event TRB: address = 0x" FORMATADDRESS " 0x" FMT_ADDRX64 " 0x%08X 0x%08X (type = %d)", + (bx_phy_address) BX_XHCI_THIS hub.ring_members.event_rings[interrupter].cur_trb, + parameter, status, command, TRB_GET_TYPE(command))); // calculate position for next event TRB BX_XHCI_THIS hub.ring_members.event_rings[interrupter].cur_trb += 16; @@ -2815,7 +2979,7 @@ void bx_usb_xhci_c::update_slot_context(const int slot) { Bit32u buffer[16]; - memset(buffer, 0, 64); + memset(buffer, 0, 16 * sizeof(Bit32u)); copy_slot_to_buffer(buffer, slot); Bit64u slot_addr = (BX_XHCI_THIS hub.op_regs.HcDCBAAP.dcbaap + (slot * sizeof(Bit64u))); DEV_MEM_READ_PHYSICAL((bx_phy_address) slot_addr, sizeof(Bit64u), (Bit8u *) &slot_addr); @@ -2825,12 +2989,29 @@ void bx_usb_xhci_c::update_slot_context(const int slot) void bx_usb_xhci_c::update_ep_context(const int slot, const int ep) { Bit32u buffer[16]; + unsigned i; - memset(buffer, 0, 64); + memset(buffer, 0, 16 * sizeof(Bit32u)); copy_ep_to_buffer(buffer, slot, ep); Bit64u slot_addr = (BX_XHCI_THIS hub.op_regs.HcDCBAAP.dcbaap + (slot * sizeof(Bit64u))); DEV_MEM_READ_PHYSICAL((bx_phy_address) slot_addr, sizeof(Bit64u), (Bit8u *) &slot_addr); put_dwords((bx_phy_address) (slot_addr + (ep * CONTEXT_SIZE)), buffer, CONTEXT_SIZE >> 2); + +#if MAX_PSA_SIZE > 0 + // do we need to update the stream context? + if (BX_XHCI_THIS hub.slots[slot].ep_context[ep].ep_context.max_pstreams > 0) { + for (i=1; i> 1)); + BX_INFO((" DCS: %d (%d)", (context[0] & (1 << 0)) >> 0)); +} + void bx_usb_xhci_c::copy_slot_from_buffer(struct SLOT_CONTEXT *slot_context, const Bit8u *buffer) { Bit32u *buffer32 = (Bit32u *) buffer; @@ -2983,27 +3172,91 @@ void bx_usb_xhci_c::copy_ep_to_buffer(Bit32u *buffer32, const int slot, const in BX_XHCI_THIS hub.slots[slot].ep_context[ep].ep_context.average_trb_len; } +void bx_usb_xhci_c::copy_stream_from_buffer(struct STREAM_CONTEXT *context, const Bit8u *buffer) +{ + Bit32u *buffer32 = (Bit32u *) buffer; + + context->tr_dequeue_pointer = (buffer32[0] & (Bit64u) ~0xF) | ((Bit64u) buffer32[1] << 32); + context->dcs = (buffer32[0] & (1<<0)); + context->sct = ((buffer32[0] & (7<<1)) >> 1); +#if ((VERSION_MAJOR == 1) && (VERSION_MINOR >= 0x10)) + context->stopped_EDTLA = (buffer32[2] & 0x00FFFFFF); +#endif +} + +void bx_usb_xhci_c::copy_stream_to_buffer(Bit8u *buffer, const struct STREAM_CONTEXT *context) +{ + Bit32u *buffer32 = (Bit32u *) buffer; + + buffer32[0] = (Bit32u) context->tr_dequeue_pointer | (((Bit32u) context->sct) << 1) | (Bit32u) context->dcs; + buffer32[1] = (Bit32u)(context->tr_dequeue_pointer >> 32); +#if ((VERSION_MAJOR == 1) && (VERSION_MINOR >= 0x10)) + buffer[2] = context->stopped_EDTLA; +#endif +} + // Validate a slot context +// specs 1.0: sect 6.2.2.2 (p321): // specs 1.0: sect 6.2.2.3 (p321): // "A 'valid' Input Slot Context for an Evaluate Context Command requires the Interrupter Target and // Max Exit Latency fields to be initialized." -int bx_usb_xhci_c::validate_slot_context(const struct SLOT_CONTEXT *slot_context) +int bx_usb_xhci_c::validate_slot_context(const struct SLOT_CONTEXT *slot_context, const int trb_command, const int slot) { int ret = TRB_SUCCESS; + unsigned MaxIntrs; + //int speed = -1; + //int port_num = slot_context->rh_port_num - 1; - // valid values for the Interrupt Target are 0 to MaxIntrs-1, inclusive. - unsigned int MaxIntrs = (BX_XHCI_THIS hub.cap_regs.HcSParams1 & (0x7FF << 8)) >> 8; - if (slot_context->int_target > MaxIntrs) - ret = PARAMETER_ERROR; + //if ((port_num >= 0) && (BX_XHCI_THIS hub.usb_port[port_num].device != NULL)) { + // speed = BX_XHCI_THIS hub.usb_port[port_num].device->get_speed(); + //} else { + // BX_ERROR(("Validate Slot Context: Invalid port_num (%d) sent.", port_num)); + // return PARAMETER_ERROR; + //} - // all high-speed and lower devices must have a Max Exit Latency value of zero - // (this may fail, because the speed field may not yet be valid) - //if ((slot_context->max_exit_latency > 0) && (slot_context->speed < XHCI_SPEED_SUPER)) - // ret = PARAMETER_ERROR; - - if (ret != TRB_SUCCESS) { - BX_ERROR(("Validate Slot Context: int_target = %d (0 -> %d), slot_context->max_exit_latency = %d", - slot_context->int_target, MaxIntrs, slot_context->max_exit_latency)); + switch (trb_command) { + case ADDRESS_DEVICE: + case EVALUATE_CONTEXT: + // valid values for the Interrupt Target are 0 to MaxIntrs-1, inclusive. + MaxIntrs = (BX_XHCI_THIS hub.cap_regs.HcSParams1 & (0x7FF << 8)) >> 8; + if (slot_context->int_target > MaxIntrs) + ret = PARAMETER_ERROR; + + // all high-speed and lower devices must have a Max Exit Latency value of zero + // (this will fail because 'rh_port_num' hasn't been initialized yet, so 'speed' will be -1) + //if ((slot_context->max_exit_latency > 0) && (speed < USB_SPEED_SUPER)) + // ret = PARAMETER_ERROR; + + if (ret != TRB_SUCCESS) { + BX_ERROR(("Validate Slot Context: int_target = %d (0 -> %d), slot_context->max_exit_latency = %d", + slot_context->int_target, MaxIntrs, slot_context->max_exit_latency)); + } + break; + + case CONFIG_EP: + // The config command needs to check the Context Entries field + if (slot_context->entries < BX_XHCI_THIS hub.slots[slot].slot_context.entries) + ret = PARAMETER_ERROR; + + // if 'hub' is 1 and device is a high-speed device, then the 'tt think time', 'multi-tt', + // and 'number of ports' fields must be initialized. + // (this will fail because 'rh_port_num' hasn't been initialized yet, so 'speed' will be -1) + //if (slot_context->hub) { + // if ((speed == USB_SPEED_HIGH) && (slot_context->ttt == 0) || (slot_context->mtt == 0)) { + // ret = PARAMETER_ERROR; + // } + //} else { + // if (slot_context->num_ports != 0) { + // ret = PARAMETER_ERROR; + // } + //} + + if (ret != TRB_SUCCESS) { + BX_ERROR(("Validate Slot Context: entry count = %d (%d), hub = %d", + slot_context->entries, BX_XHCI_THIS hub.slots[slot].slot_context.entries, + slot_context->hub)); + } + break; } return ret; @@ -3021,7 +3274,7 @@ int bx_usb_xhci_c::validate_ep_context(const struct EP_CONTEXT *ep_context, cons int speed = -1; // get the device's speed - if (BX_XHCI_THIS hub.usb_port[port_num].device != NULL) { + if ((port_num >= 0) && (BX_XHCI_THIS hub.usb_port[port_num].device != NULL)) { speed = BX_XHCI_THIS hub.usb_port[port_num].device->get_speed(); } else { BX_ERROR(("Validate EP Context: Invalid port_num (%d) sent.", port_num)); @@ -3070,7 +3323,7 @@ int bx_usb_xhci_c::validate_ep_context(const struct EP_CONTEXT *ep_context, cons // 6) all other fields are within the valid range of values. - // The Max Burst Size, and EP State values shall be cleared to '0'. + // The Max Burst Size, and EP State values shall be cleared to 0. if ((ep_context->max_burst_size != 0) || (ep_context->ep_state != 0)) ret = PARAMETER_ERROR; } @@ -3160,7 +3413,8 @@ int bx_usb_xhci_c::send_set_address(const int addr, const int port_num, const in return ret; } -int bx_usb_xhci_c::broadcast_speed(const int slot) { +int bx_usb_xhci_c::broadcast_speed(const int slot) +{ int ret = -1; switch (BX_XHCI_THIS hub.slots[slot].slot_context.speed) { @@ -3200,19 +3454,63 @@ void bx_usb_xhci_c::xhci_timer_handler(void *this_ptr) class_ptr->xhci_timer(); } +// return the status of all the PSCEG (Port Status Change Event Generation) bits +Bit8u bx_usb_xhci_c::get_psceg(const int port) +{ + Bit8u ret; + + ret = (BX_XHCI_THIS hub.usb_port[port].portsc.csc) ? PSCEG_CSC : 0; + ret |= (BX_XHCI_THIS hub.usb_port[port].portsc.pec) ? PSCEG_PEC : 0; + ret |= (BX_XHCI_THIS hub.usb_port[port].portsc.wrc) ? PSCEG_WRC : 0; + ret |= (BX_XHCI_THIS hub.usb_port[port].portsc.occ) ? PSCEG_OCC : 0; + ret |= (BX_XHCI_THIS hub.usb_port[port].portsc.prc) ? PSCEG_PRC : 0; + ret |= (BX_XHCI_THIS hub.usb_port[port].portsc.plc) ? PSCEG_PLC : 0; + ret |= (BX_XHCI_THIS hub.usb_port[port].portsc.cec) ? PSCEG_CEC : 0; + + return ret; +} + void bx_usb_xhci_c::xhci_timer(void) { int slot, ep; + unsigned int port; + Bit8u new_psceg; if (BX_XHCI_THIS hub.op_regs.HcStatus.hch) return; + /* Per section 4.19.3 of the xHCI 1.0 specs, we need to present + * a "Port Status Change Event". + * Also, we should only present this event once if any other bits + * change, only presenting it again when all change bits are written + * back to zero, and a change bit goes from 0 to 1. + */ + for (port=0; port 0) { // specifying streams + // + // ben: TODO: + // + BX_ERROR(("Retry on a streamed endpoint.")); + + } else { + BX_XHCI_THIS hub.slots[slot].ep_context[ep].enqueue_pointer = + BX_XHCI_THIS process_transfer_ring(slot, ep, BX_XHCI_THIS hub.slots[slot].ep_context[ep].enqueue_pointer, + &BX_XHCI_THIS hub.slots[slot].ep_context[ep].rcs, 0); + } } } } @@ -3228,10 +3526,10 @@ void bx_usb_xhci_c::runtime_config_handler(void *this_ptr) void bx_usb_xhci_c::runtime_config(void) { - int i; + unsigned int i; char pname[6]; - for (i = 0; i < BX_N_USB_XHCI_PORTS; i++) { + for (i = 0; i < BX_XHCI_THIS hub.n_ports; i++) { // device change support if ((BX_XHCI_THIS device_change & (1 << i)) != 0) { if (!BX_XHCI_THIS hub.usb_port[i].portsc.ccs) { @@ -3291,15 +3589,27 @@ bool bx_usb_xhci_c::usb_set_connect_status(Bit8u port, bool connected) { const bool ccs_org = BX_XHCI_THIS hub.usb_port[port].portsc.ccs; const bool ped_org = BX_XHCI_THIS hub.usb_port[port].portsc.ped; + int otherportnum = BX_XHCI_THIS hub.paired_portnum[port]; usb_device_c *device = BX_XHCI_THIS hub.usb_port[port].device; if (device != NULL) { if (connected) { + // make sure the user has not tried to put a device on a paired port number + // (this is invalid in all but external USB3 hubs) + if (BX_XHCI_THIS hub.usb_port[otherportnum].portsc.ccs) { + BX_PANIC(("Port #%d: Paired port number #%d already in use.", port + 1, otherportnum + 1)); + return 0; + } if ((device->get_speed() == USB_SPEED_SUPER) && !BX_XHCI_THIS hub.usb_port[port].is_usb3) { BX_PANIC(("Super-speed device not supported on USB2 port.")); return 0; } + if ((device->get_speed() < USB_SPEED_SUPER) && + BX_XHCI_THIS hub.usb_port[port].is_usb3) { + BX_PANIC(("Non super-speed device not supported on USB3 port.")); + return 0; + } if (BX_XHCI_THIS hub.usb_port[port].is_usb3) { if (!device->set_speed(USB_SPEED_SUPER)) { BX_PANIC(("Only super-speed devices supported on USB3 port.")); @@ -3340,27 +3650,11 @@ bool bx_usb_xhci_c::usb_set_connect_status(Bit8u port, bool connected) BX_XHCI_THIS hub.usb_port[port].portsc.speed = 0; remove_device(port); } + // did we change? if (ccs_org != BX_XHCI_THIS hub.usb_port[port].portsc.ccs) BX_XHCI_THIS hub.usb_port[port].portsc.csc = 1; if (ped_org != BX_XHCI_THIS hub.usb_port[port].portsc.ped) BX_XHCI_THIS hub.usb_port[port].portsc.pec = 1; - - /* Per section 4.19.3 of the xHCI 1.0 specs, we need to present - * a "Port Status Change Event". - * Also, we should only present this event once if any other bits - * change, only presenting it again when all change bits are written - * back to zero, and a change bit goes from 0 to 1. - * (However, I don't know if that is a worry here, at this moment?) - */ - if (BX_XHCI_THIS hub.usb_port[port].portsc.pp == 1) { - if (BX_XHCI_THIS hub.op_regs.HcStatus.hch == 0) { - BX_INFO(("Port #%d Status Change Event.", port + 1)); - write_event_TRB(0, ((port + 1) << 24), TRB_SET_COMP_CODE(1), TRB_SET_TYPE(PORT_STATUS_CHANGE), 1); - } - BX_XHCI_THIS hub.usb_port[port].needs_psce = 0; - } else { - BX_XHCI_THIS hub.usb_port[port].needs_psce = 1; - } } return connected; } @@ -3373,7 +3667,7 @@ Bit64s bx_usb_xhci_c::usb_param_handler(bx_param_c *param, bool set, Bit64s val) if (set) { portnum = atoi((param->get_parent())->get_name()+4) - 1; bool empty = (val == 0); - if ((portnum >= 0) && (portnum < USB_XHCI_PORTS)) { + if ((portnum >= 0) && (portnum < (int) BX_XHCI_THIS hub.n_ports)) { if (empty && BX_XHCI_THIS hub.usb_port[portnum].portsc.ccs) { BX_XHCI_THIS device_change |= (1 << portnum); } else if (!empty && !BX_XHCI_THIS hub.usb_port[portnum].portsc.ccs) { @@ -3396,15 +3690,16 @@ bool bx_usb_xhci_c::usb_param_enable_handler(bx_param_c *param, bool en) if (en && (BX_XHCI_THIS hub.usb_port[portnum].device != NULL)) { en = 0; } + return en; } -void bx_usb_xhci_c::dump_xhci_core(const int slots, const int eps) +void bx_usb_xhci_c::dump_xhci_core(const unsigned int slots, const unsigned int eps) { bx_phy_address addr = BX_XHCI_THIS pci_bar[0].addr; Bit32u dword; Bit64u qword, slot_addr; - int p, i; + unsigned int p, i; Bit8u buffer[4096]; // dump the caps registers @@ -3441,7 +3736,7 @@ void bx_usb_xhci_c::dump_xhci_core(const int slots, const int eps) BX_XHCI_THIS read_handler(addr + 0x58, 4, &dword, NULL); BX_INFO((" CONFIG: 0x%08X", dword)); - for (i=0, p=0; i USB_XHCI_PORTS_MAX) || ((USB_XHCI_PORTS & 1) == 1)) + #error "USB_XHCI_PORTS must be at least 2 and no more than USB_XHCI_PORTS_MAX and must be an even number." #endif // HCSPARAMS2 @@ -83,10 +94,16 @@ #define SEC_DOMAIN_BAND 1 // version 0.96 and below only (MUST BE 1 in v1.00+) #define STOPPED_EDTLA 0 #define CONT_FRAME_ID 0 -#define MAX_PSA_SIZE 0x05 +#define MAX_PSA_SIZE 0x05 // 2^(5+1) = 63 Primary Streams (first one is reserved) + #define MAX_PSA_SIZE_NUM (1 << (MAX_PSA_SIZE + 1)) #define EXT_CAPS_OFFSET 0x500 #define EXT_CAPS_SIZE 144 +// doorbell masks +#define PSA_MAX_SIZE_NUM(m) (1UL << ((m) + 1)) +#define PSA_PRIMARY_MASK(d, m) (((d) >> 16) & ((1 << ((m) + 1)) - 1)) +#define PSA_SECONDARY_MASK(d, m) (((d) >> 16) >> ((m) + 1)) + // HCCPARAMS2 (v1.10+) #if ((VERSION_MAJOR == 1) && (VERSION_MINOR >= 0x10)) #define U3_ENTRY_CAP 0 @@ -141,16 +158,6 @@ # error "NO_SSD_SUPPORT must be used with in version 1.10 and above" #endif -// Each controller supports its own number of ports. We must adhere to that for now. -// (The Extended Capabilities register set is hardcoded for this as of now.) -// As long as BX_N_USB_XHCI_PORTS was defined as greater than what we have now, then -// we are fine. -// Note: BX_N_USB_XHCI_PORTS should have been defined as twice the amount of ports wanted. -// ie.: Each physical port (socket) has two defined port register sets. One for USB3, one for USB2 -// Only one port type may be used at a time. Socket0 or Socket1, not both. If Socket0 is used, then -// Socket1 must be vacant. -#define BX_N_USB_XHCI_PORTS 4 - // xHCI speed values #define XHCI_SPEED_FULL 1 #define XHCI_SPEED_LOW 2 @@ -160,6 +167,26 @@ #define USB2 0 #define USB3 1 +// Port Status Change Bits +#define PSCEG_CSC (1<<0) +#define PSCEG_PEC (1<<1) +#define PSCEG_WRC (1<<2) +#define PSCEG_OCC (1<<3) +#define PSCEG_PRC (1<<4) +#define PSCEG_PLC (1<<5) +#define PSCEG_CEC (1<<6) + +// Extended Capabilities: Protocol +struct XHCI_PROTOCOL { + Bit8u id; + Bit8u next; + Bit16u version; + Bit8u name[4]; + Bit8u start_index; + Bit8u count; + Bit16u flags; +}; + // our saved ring members struct RING_MEMBERS { struct { @@ -213,6 +240,16 @@ struct EP_CONTEXT { unsigned average_trb_len; }; +struct STREAM_CONTEXT { + bool valid; // is this context valid + Bit64u tr_dequeue_pointer; + bool dcs; + int sct; +#if ((VERSION_MAJOR == 1) && (VERSION_MINOR >= 0x10)) + Bit32u stopped_EDTLA; +#endif +}; + struct HC_SLOT_CONTEXT { bool enabled; struct SLOT_CONTEXT slot_context; @@ -224,6 +261,7 @@ struct HC_SLOT_CONTEXT { bool rcs; bool retry; int retry_counter; + struct STREAM_CONTEXT stream[MAX_PSA_SIZE_NUM]; // first one is reserved } ep_context[32]; // first one is ignored by controller. }; @@ -322,7 +360,14 @@ struct TRB { Bit32u command; }; +enum { + XHCI_HC_uPD720202, // Renesas/NEC uPD720202 (2 sockets) (default) + XHCI_HC_uPD720201 // Renesas/NEC uPD720201 (4 sockets) +}; + typedef struct { + Bit32u HostController; + unsigned int n_ports; struct XHCI_CAP_REGS { Bit32u HcCapLength; @@ -419,7 +464,7 @@ typedef struct { usb_device_c *device; // device connected to this port bool is_usb3; // set if usb3 port, cleared if usb2 port. bool has_been_reset; // set if the port has been reset aftet powered up. - bool needs_psce; // needs a port status change event on port powered. + Bit8u psceg; // current port status change event struct { bool wpr; // 1 bit Warm Port Reset = 0b RW or RsvdZ @@ -483,7 +528,7 @@ typedef struct { Bit8u hirdd; // 4 bit host initiated resume duration deep Bit32u RsvdP; // 18 bit reserved and preseved = 0x00000000 RW } porthlpmc; - } usb_port[USB_XHCI_PORTS]; + } usb_port[USB_XHCI_PORTS_MAX]; // Extended Caps Registers Bit8u extended_caps[EXT_CAPS_SIZE]; @@ -523,6 +568,13 @@ typedef struct { struct HC_SLOT_CONTEXT slots[MAX_SLOTS]; // first one is ignored by controller. struct RING_MEMBERS ring_members; + + // filled at runtime with ex: { USB3, USB3, USB2, USB2 }; + //Bit8u port_speed_allowed[USB_XHCI_PORTS_MAX]; + // four speeds of: 'reserved' + a port count of bytes rounded up to and 8 byte size (ie: 8, 16, 24, 32 bytes each speed) + Bit8u port_band_width[4 * ((1 + USB_XHCI_PORTS_MAX) + 8)]; // + 8 gives us ample room for a boundary of 8-byte entries per speed + // the port's paired port num. i.e., with 4 ports, 1 is paired with 3, 2 is paired with 4 + int paired_portnum[USB_XHCI_PORTS_MAX]; } bx_usb_xhci_t; // Version 3.0.23.0 of the Renesas uPD720202 driver, even though the card is @@ -572,11 +624,14 @@ private: static int broadcast_speed(const int slot); static int broadcast_packet(USBPacket *p, const int port); + static Bit8u get_psceg(const int port); static void xhci_timer_handler(void *); void xhci_timer(void); - static void process_transfer_ring(const int slot, const int ep); + static Bit64u process_transfer_ring(const int slot, const int ep, Bit64u ring_addr, bool *rcs, const int primary_sid); static void process_command_ring(void); + static void get_stream_info(struct STREAM_CONTEXT *context, const Bit64u address, const int index); + static void put_stream_info(struct STREAM_CONTEXT *context, const Bit64u address, const int index); static void write_event_TRB(const unsigned interrupter, const Bit64u parameter, const Bit32u status, const Bit32u command, const bool fire_int); static Bit32u NEC_verification(const Bit64u parameter); @@ -587,16 +642,19 @@ private: static void update_ep_context(const int slot, const int ep); static void dump_slot_context(const Bit32u *context, const int slot); static void dump_ep_context(const Bit32u *context, const int slot, const int ep); + static void dump_stream_context(const Bit32u *context); static void copy_slot_from_buffer(struct SLOT_CONTEXT *slot_context, const Bit8u *buffer); static void copy_ep_from_buffer(struct EP_CONTEXT *ep_context, const Bit8u *buffer); static void copy_slot_to_buffer(Bit32u *buffer, const int slot); static void copy_ep_to_buffer(Bit32u *buffer, const int slot, const int ep); - static int validate_slot_context(const struct SLOT_CONTEXT *slot_context); + static void copy_stream_from_buffer(struct STREAM_CONTEXT *context, const Bit8u *buffer); + static void copy_stream_to_buffer(Bit8u *buffer, const struct STREAM_CONTEXT *context); + static int validate_slot_context(const struct SLOT_CONTEXT *slot_context, const int trb_command, const int slot); static int validate_ep_context(const struct EP_CONTEXT *ep_context, const int trb_command, const Bit32u a_flags, int port_num, int ep_num); static int create_unique_address(const int slot); static int send_set_address(const int addr, const int port_num, const int slot); - static void dump_xhci_core(const int slots, const int eps); + static void dump_xhci_core(const unsigned int slots, const unsigned int eps); #if BX_USE_USB_XHCI_SMF static bool read_handler(bx_phy_address addr, unsigned len, void *data, void *param);