Skip to content

Commit

Permalink
Usb compliance (#91)
Browse files Browse the repository at this point in the history
Made all devices pass USB 2.0 Compliance on WinXP (old version of the
compliance test).
USB Hub still needs a little work to be in complete compliance.
Fixed potential bug in EHCI to UHCI hand-off.
Fixed compilation error with Floppy CB/CBI emulation.
Minor syntax fixes (tabulation, old irrelevant comments)
MSD serial numbers must be 12 chars.
Added to CHANGES file
  • Loading branch information
fysnet authored Oct 15, 2023
1 parent ffa6446 commit bcbe5da
Show file tree
Hide file tree
Showing 11 changed files with 411 additions and 111 deletions.
44 changes: 44 additions & 0 deletions bochs/CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,50 @@ We welcome every new contributor !
- Apply standard CPPFLAGS from environment in all makefiles
- Fixed and improved PCI slot config error handling
- Updated Bochs instrumentation examples for new disassembler introduced in Bochs 2.7 release
- USB: SCSI: added the Event Status command (0x4a).
- USB: SCSI: added BX_DEBUG checks to the command sent by the Guest.
- USB: SCSI: added the block_size in the Read Capacity command (0x25).
- USB: SCSI: added some 0x9E/xx commands. ReadCapacity(16).
- USB: UHCI: re-wrote the uhci stack to better support control/bulk reclamation.
- USB: UHCI: check that a reset doesn't clear the CSC bit during a reset, but should after the reset is complete.
- USB: added the ability to change interfaces (ex: BBB to UASP).
- USB: added the Toggle bit check to all controllers/devices. (optional with a #define)
- USB: added the ability to trigger an over-current to all controllers.
- USB: added numberous BX_DEBUG checks to the USB_COMMON emulation to help show bugs in a Guest's device driver.
- USB: added BX_DEBUG if the first requested packet after a reset is not 8 bytes.
- USB: added a BX_DEBUG check to make sure the speed indicator is correct within TDs.
- USB: added BX_DEBUG checks to all request lengths, max packet size, and other length checks.
- USB: return default speed of device/controller combination.
- USB: check that the bochsrc file doesn't specify two different devices for the same port.
- USB: check that the bochsrc file has a super-speed device defined on a correct port number.
- USB: re-wrote how the USB_COMMON passed on packets to allow for zero-length packets to be accepted.
- USB: HID: added multiple mouse modes to have different HID Reports, including a physical report, and an irregular report.
- USB: HID: added the Boot Protocol function.
- USB: HUB: fixed/Added a more accurate DeviceRemovable emulation.
- USB: HUB: Allow USB 1.0 or USB 1.1 emulation (there is a difference).
- USB: HUB: Add bit 0 function to the returned report (change status bit).
- USB: HUB: Some Guests think a NAK on the Interrupt EP is an error. (option to ignore this)
- USB: Floppy: Fixed the CBI/CB configuration descriptor.
- USB: fixed/added USB 2.0 Compliance to all devices. Hub still needs a few additions.
- Floppies (using the CB/CBI protocol) can only be full-speed.
- Fixed the Endpoint Clear Feature request (halted, etc.).
- Added the Endpoint Get Status request to each device.
- Hub power switching emulation.
- Fixed the return/request error on Device Qualifier requests.
- Fixed Device Qualifier and Other Speed requests.
- USB: MSD: fixed USB 2.0 only descriptor emulation. (Device Qualifier, etc.)
- USB: OHCI: fixed the toggle emulation.
- USB: added the ability to catch 0xEE descriptors (Microsoft specific).
- USB: xHCI: added the ability to have more than one model of xHCI hardware. Currently there are two.
- USB: xHCI: added the ability to indicate the port count.
- USB: xHCI: check the USB2 and USB3 port assignments.
- USB: xHCI: added experimental Stream emulation.
- USB: added experimental MSD UASP emulation.
- USB: xHCI: added the (vendor) Dump Controller command (Specific to Bochs).
- USB: xHCI: added checks to the Evaluate Context and Address Device commands.
- USB: xHCI: fixed/updated the Port Status Change Event emulation.
- USB: xHCI: fixed/updated the port bandwidth emulation.
- USB: Many documentation additions.
- Documentation fixes

-------------------------------------------------------------------------
Expand Down
8 changes: 5 additions & 3 deletions bochs/iodev/usb/uhci_core.cc
Original file line number Diff line number Diff line change
Expand Up @@ -550,9 +550,9 @@ void bx_uhci_core_c::write(Bit32u address, Bit32u value, unsigned io_len)
if (value & (1<<11)) hub.usb_port[port].over_current_change = 0;
hub.usb_port[port].reset = (value & (1<<9)) ? 1 : 0;
hub.usb_port[port].resume = (value & (1<<6)) ? 1 : 0;
if (!hub.usb_port[port].enabled && (value & (1<<2)))
if (!hub.usb_port[port].enabled && (value & (1<<2))) {
hub.usb_port[port].enable_changed = 0;
else
} else
if (value & (1<<3)) hub.usb_port[port].enable_changed = 0;
hub.usb_port[port].enabled = (value & (1<<2)) ? 1 : 0;
if (value & (1<<1)) hub.usb_port[port].connect_changed = 0;
Expand Down Expand Up @@ -908,7 +908,7 @@ bool bx_uhci_core_c::DoTransfer(Bit32u address, struct TD *td) {
}

BX_DEBUG(("TD found at address 0x%08X: 0x%08X 0x%08X 0x%08X 0x%08X", address, td->dword0, td->dword1, td->dword2, td->dword3));

// check TD to make sure it is valid
// A max length 0x500 to 0x77E is illegal
if ((maxlen >= 0x500) && (maxlen != 0x7FF)) {
Expand Down Expand Up @@ -1141,6 +1141,8 @@ void bx_uhci_core_c::set_port_device(int port, usb_device_c *dev)
{
usb_device_c *olddev = hub.usb_port[port].device;
if ((dev != NULL) && (olddev == NULL)) {
// make sure we are calling the correct handler for the device
dev->set_event_handler(this, uhci_event_handler, port);
hub.usb_port[port].device = dev;
set_connect_status(port, 1);
} else if ((dev == NULL) && (olddev != NULL)) {
Expand Down
40 changes: 34 additions & 6 deletions bochs/iodev/usb/usb_common.cc
Original file line number Diff line number Diff line change
Expand Up @@ -477,7 +477,7 @@ int usb_device_c::handle_packet(USBPacket *p)
break;

default:
BX_ERROR(("Unknown Data state while finding Control In Packet."));
BX_ERROR(("Unknown Data state while finding Control In Packet. (state = %i)", d.setup_state));
goto fail;
}
break;
Expand Down Expand Up @@ -550,7 +550,7 @@ int usb_device_c::handle_packet(USBPacket *p)
break;

default:
BX_ERROR(("Unknown Data state while finding Control Out Packet."));
BX_ERROR(("Unknown Data state while finding Control Out Packet. (state = %i)", d.setup_state));
goto fail;
}
break;
Expand Down Expand Up @@ -695,6 +695,11 @@ int usb_device_c::handle_control_common(int request, int value, int index, int l
}
d.config = value;
d.state = USB_STATE_CONFIGURED;
#if HANDLE_TOGGLE_CONTROL
// we must also clear all of the EP toggle bits
for (int i=0; i<USB_MAX_ENDPOINTS; i++)
set_toggle(i, 0);
#endif
ret = 0;
break;
case DeviceOutRequest | USB_REQ_CLEAR_FEATURE:
Expand Down Expand Up @@ -752,13 +757,36 @@ int usb_device_c::handle_control_common(int request, int value, int index, int l
}
break;
case EndpointOutRequest | USB_REQ_CLEAR_FEATURE:
BX_DEBUG(("EndpointOutRequest | USB_REQ_CLEAR_FEATURE:"));
BX_DEBUG(("EndpointOutRequest | USB_REQ_CLEAR_FEATURE: ep = %d", index & 0x7F));
// Value == 0 == Endpoint Halt (the Guest wants to reset the endpoint)
if (value == 0) { /* clear ep halt */
if (value == USB_ENDPOINT_HALT) {
if ((index & 0x7F) < USB_MAX_ENDPOINTS) {
#if HANDLE_TOGGLE_CONTROL
set_toggle(index & 0x7F, 0);
set_toggle(index, 0);
#endif
ret = 0;
set_halted(index, 0);
ret = 0;
} else {
BX_ERROR(("EndpointOutRequest | USB_REQ_CLEAR_FEATURE: index > ep count: %d", index));
}
} else {
BX_ERROR(("EndpointOutRequest | USB_REQ_CLEAR_FEATURE: Unknown Clear Feature Request found: %d", value));
}
break;
case EndpointOutRequest | USB_REQ_SET_FEATURE:
// with EndpointRequest, The wLength field must be zero
if (length != 0) {
BX_ERROR(("USB_REQ_SET_FEATURE: This type of request requires the wLength field to be zero."));
}
if (value == USB_ENDPOINT_HALT) {
if ((index & 0x7F) < USB_MAX_ENDPOINTS) {
set_halted(index, 1);
ret = 0;
} else {
BX_ERROR(("EndpointOutRequest | USB_REQ_SET_FEATURE: index > ep count: %d", index));
}
} else {
BX_ERROR(("EndpointOutRequest | USB_REQ_SET_FEATURE: Unknown Set Feature Request found: %d", value));
}
break;
// should not have a default: here, so allowing the device's handle_control() to try to execute the request
Expand Down
15 changes: 12 additions & 3 deletions bochs/iodev/usb/usb_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@
#define USB_REQ_SET_SEL 0x30
#define USB_REQ_SET_ISO_DELAY 0x31

#define USB_ENDPOINT_HALT 0
#define USB_DEVICE_SELF_POWERED 0
#define USB_DEVICE_REMOTE_WAKEUP 1
#define USB_DEVICE_U1_ENABLE 48
Expand Down Expand Up @@ -188,6 +189,7 @@ typedef struct USBEndPoint {
#if HANDLE_TOGGLE_CONTROL
int toggle; // the current toggle for the endpoint (0, 1, or -1 for xHCI)
#endif
bool halted; // is the current ep halted?
} USBEndPoint;

class BOCHSAPI bx_usbdev_ctl_c : public logfunctions {
Expand Down Expand Up @@ -258,13 +260,20 @@ class BOCHSAPI usb_device_c : public logfunctions {

#if HANDLE_TOGGLE_CONTROL
int get_toggle(const int ep) {
return (ep < USB_MAX_ENDPOINTS) ? d.endpoint_info[ep].toggle : 0;
return ((ep & 0x7F) < USB_MAX_ENDPOINTS) ? d.endpoint_info[(ep & 0x7F)].toggle : 0;
}
void set_toggle(const int ep, const int toggle) {
if (ep < USB_MAX_ENDPOINTS)
d.endpoint_info[ep].toggle = toggle;
if ((ep & 0x7F) < USB_MAX_ENDPOINTS)
d.endpoint_info[(ep & 0x7F)].toggle = toggle;
}
#endif
bool get_halted(const int ep) {
return ((ep & 0x7F) < USB_MAX_ENDPOINTS) ? d.endpoint_info[(ep & 0x7F)].halted : 0;
}
void set_halted(const int ep, const bool halted) {
if ((ep & 0x7F) < USB_MAX_ENDPOINTS)
d.endpoint_info[(ep & 0x7F)].halted = halted;
}

Bit8u get_type() {
return d.type;
Expand Down
34 changes: 20 additions & 14 deletions bochs/iodev/usb/usb_ehci.cc
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,6 @@ static inline struct EHCIPacket *ehci_container_of_usb_packet(void *ptr)
reinterpret_cast<size_t>(&(static_cast<struct EHCIPacket*>(0)->packet)));
}

int ehci_event_handler(int event, void *ptr, void *dev, int port);

// builtin configuration handling functions

Bit32s usb_ehci_options_parser(const char *context, int num_params, char *params[])
Expand Down Expand Up @@ -525,6 +523,8 @@ void bx_usb_ehci_c::reset_port(int p)
BX_EHCI_THIS hub.usb_port[p].portsc.csc = 0;
}

int ehci_event_handler(int event, void *ptr, void *dev, int port);

void bx_usb_ehci_c::init_device(Bit8u port, bx_list_c *portconf)
{
char pname[BX_PATHNAME_LEN];
Expand Down Expand Up @@ -603,18 +603,18 @@ bool bx_usb_ehci_c::set_connect_status(Bit8u port, bool connected)
} else { // not connected
BX_INFO(("port #%d: device disconnect", port+1));
if (BX_EHCI_THIS hub.usb_port[port].portsc.po) {
BX_EHCI_THIS uhci[port >> 1]->set_port_device(port & 1, NULL);
if ((!BX_EHCI_THIS hub.usb_port[port].owner_change) &&
(BX_EHCI_THIS hub.op_regs.ConfigFlag & 1)) {
BX_EHCI_THIS hub.usb_port[port].portsc.po = 0;
BX_EHCI_THIS hub.usb_port[port].portsc.csc = 1;
}
BX_EHCI_THIS uhci[port >> 1]->set_port_device(port & 1, NULL);
if ((!BX_EHCI_THIS hub.usb_port[port].owner_change) &&
(BX_EHCI_THIS hub.op_regs.ConfigFlag & 1)) {
BX_EHCI_THIS hub.usb_port[port].portsc.po = 0;
BX_EHCI_THIS hub.usb_port[port].portsc.csc = 1;
}
} else {
BX_EHCI_THIS hub.usb_port[port].portsc.ccs = 0;
BX_EHCI_THIS hub.usb_port[port].portsc.ped = 0;
BX_EHCI_THIS queues_rip_device(device, 0);
BX_EHCI_THIS queues_rip_device(device, 1);
device->set_async_mode(0);
BX_EHCI_THIS hub.usb_port[port].portsc.ccs = 0;
BX_EHCI_THIS hub.usb_port[port].portsc.ped = 0;
BX_EHCI_THIS queues_rip_device(device, 0);
BX_EHCI_THIS queues_rip_device(device, 1);
device->set_async_mode(0);
}
if (!BX_EHCI_THIS hub.usb_port[port].owner_change) {
remove_device(port);
Expand Down Expand Up @@ -652,6 +652,11 @@ void bx_usb_ehci_c::change_port_owner(int port)
set_connect_status(port, 1);
}
}
// make sure we are calling the correct handler for the device
if (device != NULL) {
if (BX_EHCI_THIS hub.usb_port[port].portsc.po == 0)
device->set_event_handler(BX_EHCI_THIS_PTR, ehci_event_handler, port);
}
BX_EHCI_THIS hub.usb_port[port].owner_change = 0;
}
}
Expand Down Expand Up @@ -888,6 +893,7 @@ bool bx_usb_ehci_c::write_handler(bx_phy_address addr, unsigned len, void *data,
if (oldfpr && !BX_EHCI_THIS hub.usb_port[port].portsc.fpr) {
BX_EHCI_THIS hub.usb_port[port].portsc.sus = 0;
}
} else if (port == USB_EHCI_PORTS) {
}
}
} else {
Expand Down Expand Up @@ -2224,7 +2230,7 @@ void bx_usb_ehci_c::ehci_frame_timer(void)
t_now = bx_pc_system.time_usec();
usec_elapsed = t_now - BX_EHCI_THIS hub.last_run_usec;
frames = (int)(usec_elapsed / FRAME_TIMER_USEC);

if (BX_EHCI_THIS periodic_enabled() || (BX_EHCI_THIS hub.pstate != EST_INACTIVE)) {
need_timer++;
BX_EHCI_THIS hub.async_stepdown = 0;
Expand Down
55 changes: 37 additions & 18 deletions bochs/iodev/usb/usb_floppy.cc
Original file line number Diff line number Diff line change
Expand Up @@ -93,20 +93,22 @@ class bx_usb_floppy_locator_c : public usbdev_locator_c {
// to match your changes.

// Full-speed only
// * USB Mass Storage Class Specification, p4, section 3, states that
// * that a Mass Storage Class device using CB/CBI must be full-speed only.
static Bit8u bx_floppy_dev_descriptor[] = {
0x12, /* u8 bLength; */
0x01, /* u8 bDescriptorType; Device */
0x00, 0x02, /* u16 bcdUSB; v2.0 */
0x01, 0x01, /* u16 bcdUSB; v1.1 */

0x00, /* u8 bDeviceClass; */
0x00, /* u8 bDeviceSubClass; */
0x00, /* u8 bDeviceProtocol; [ low/full speeds only ] */
0x00, /* u8 bDeviceProtocol; */
0x40, /* u8 bMaxPacketSize; 64 Bytes */

/* Vendor and product id are arbitrary. */
0x00, 0x00, /* u16 idVendor; */
0x00, 0x00, /* u16 idProduct; */
0x00, 0x00, /* u16 bcdDevice */
0x00, 0x00, /* u16 bcdDevice; */

0x01, /* u8 iManufacturer; */
0x02, /* u8 iProduct; */
Expand All @@ -120,7 +122,11 @@ static const Bit8u bx_floppy_config_descriptor[] = {
/* one configuration */
0x09, /* u8 bLength; */
0x02, /* u8 bDescriptorType; Configuration */
#if USB_FLOPPY_USE_INTERRUPT
0x27, 0x00, /* u16 wTotalLength; */
#else
0x20, 0x00, /* u16 wTotalLength; */
#endif
0x01, /* u8 bNumInterfaces; (1) */
0x01, /* u8 bConfigurationValue; */
0x00, /* u8 iConfiguration; */
Expand Down Expand Up @@ -324,6 +330,7 @@ usb_floppy_device_c::usb_floppy_device_c()
bx_param_bool_c *readonly;
bx_param_enum_c *status, *mode;

// MSC Compliance states that a CB(I) device must be full-speed only
d.speed = d.minspeed = d.maxspeed = USB_SPEED_FULL;
memset((void *) &s, 0, sizeof(s));
strcpy(d.devname, "BOCHS UFI/CBI FLOPPY");
Expand Down Expand Up @@ -456,13 +463,13 @@ bool usb_floppy_device_c::init()
bx_floppy_dev_descriptor[9] = 0x06;
d.vendor_desc = "TEAC ";
d.product_desc = "TEAC FD-05PUW ";
d.serial_num = "3000";
d.serial_num = "3000 ";
} else {
bx_floppy_dev_descriptor[8] = 0x00;
bx_floppy_dev_descriptor[9] = 0x00;
d.vendor_desc = "BOCHS ";
d.product_desc = d.devname;
d.serial_num = "00.10";
d.serial_num = "00.10 ";
}
if (set_inserted(1)) {
sprintf(s.info_txt, "USB floppy: path='%s', mode='%s'", s.fname, s.image_mode);
Expand Down Expand Up @@ -552,6 +559,29 @@ int usb_floppy_device_c::handle_control(int request, int value, int index, int l
}
ret = 0;
break;
case EndpointRequest | USB_REQ_GET_STATUS:
BX_DEBUG(("USB_REQ_GET_STATUS: Endpoint."));
// if the endpoint is currently halted, return bit 0 = 1
if (value == USB_ENDPOINT_HALT) {
int indx = (index & 0x7F);
#if USB_FLOPPY_USE_INTERRUPT
int limit = 3;
#else
int limit = 2;
#endif
if ((indx > 0) && (indx <= limit)) {
data[0] = 0x00 | (get_halted(indx) ? 1 : 0);
data[1] = 0x00;
ret = 2;
} else {
BX_ERROR(("EndpointRequest | USB_REQ_GET_STATUS: index > ep count: %d", index));
goto fail;
}
} else {
BX_ERROR(("EndpointRequest | USB_REQ_SET_FEATURE: Unknown Get Status Request found: %d", value));
goto fail;
}
break;
case DeviceRequest | USB_REQ_GET_DESCRIPTOR:
switch (value >> 8) {
case USB_DT_STRING:
Expand Down Expand Up @@ -579,17 +609,6 @@ int usb_floppy_device_c::handle_control(int request, int value, int index, int l
goto fail;
}
break;
case EndpointOutRequest | USB_REQ_CLEAR_FEATURE:
BX_DEBUG(("USB_REQ_CLEAR_FEATURE:"));
// Value == 0 == Endpoint Halt (the Guest wants to reset the endpoint)
if (value == 0) { /* clear ep halt */
#if HANDLE_TOGGLE_CONTROL
set_toggle(index, 0);
#endif
ret = 0;
} else
goto fail;
break;
case DeviceOutRequest | USB_REQ_SET_SEL:
// Set U1 and U2 System Exit Latency
BX_DEBUG(("SET_SEL (U1 and U2):"));
Expand Down Expand Up @@ -646,13 +665,13 @@ bool usb_floppy_device_c::handle_command(Bit8u *command)
}

#if UFI_DO_INQUIRY_HACK
// to be consistant with real hardware, we need to fail with
// to be consistent with real hardware, we need to fail with
// a STALL twice for any command after the first Inquiry except
// for the inquiry and request_sense commands.
// (I don't know why, and will document further when I know more)
if ((s.fail_count > 0) &&
(s.cur_command != UFI_INQUIRY) && (s.cur_command != UFI_REQUEST_SENSE)) {
BX_DEBUG(("Consistant stall of %d of 2.", 2 - s.fail_count + 1));
BX_INFO(("Consistent stall of %d of 2.", 2 - s.fail_count + 1));
s.fail_count--;
return 0;
}
Expand Down
Loading

0 comments on commit bcbe5da

Please sign in to comment.