Usb compliance (#91)

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
This commit is contained in:
Benjamin David Lunt 2023-10-14 21:23:18 -07:00 committed by GitHub
parent ffa64461ab
commit bcbe5da030
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 411 additions and 111 deletions

View File

@ -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
-------------------------------------------------------------------------

View File

@ -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;
@ -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)) {
@ -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)) {

View File

@ -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;
@ -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;
@ -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:
@ -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

View File

@ -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
@ -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 {
@ -258,13 +260,20 @@ public:
#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;

View File

@ -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[])
@ -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];
@ -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);
@ -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;
}
}
@ -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 {
@ -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;

View File

@ -93,20 +93,22 @@ protected:
// 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; */
@ -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; */
@ -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");
@ -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);
@ -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:
@ -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):"));
@ -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;
}

View File

@ -96,11 +96,11 @@ protected:
static const Bit8u bx_mouse_dev_descriptor[] = {
0x12, /* u8 bLength; */
0x01, /* u8 bDescriptorType; Device */
0x00, 0x01, /* u16 bcdUSB; v1.0 */
0x01, 0x01, /* u16 bcdUSB; v1.1 */
0x00, /* u8 bDeviceClass; */
0x00, /* u8 bDeviceSubClass; */
0x00, /* u8 bDeviceProtocol; [ low/full speeds only ] */
0x00, /* u8 bDeviceProtocol; */
0x08, /* u8 bMaxPacketSize; 8 Bytes */
0x27, 0x06, /* u16 idVendor; */
@ -120,7 +120,7 @@ static const Bit8u bx_mouse_dev_descriptor2[] = {
0x00, /* u8 bDeviceClass; */
0x00, /* u8 bDeviceSubClass; */
0x00, /* u8 bDeviceProtocol; [ low/full speeds only ] */
0x00, /* u8 bDeviceProtocol; */
0x40, /* u8 bMaxPacketSize; 64 Bytes */
0x27, 0x06, /* u16 idVendor; */
@ -461,7 +461,8 @@ static Bit8u bx_mouse_config_descriptor0[] = {
0x81, /* u8 ep_bEndpointAddress; IN Endpoint 1 */
0x03, /* u8 ep_bmAttributes; Interrupt */
0x08, 0x00, /* u16 ep_wMaxPacketSize; */
0x0a, /* u8 ep_bInterval; (0 - 255ms -- usb 2.0 spec) */
0x0A, /* u8 ep_bInterval; (10 - 255ms -- usb 2.0 spec, 5.7.4) */
/* however: (11 - 255ms -- usb 2.0 compliance 1.2.84, pg 11 */
};
#define HID_PHYS_DESC_SET_LEN 7
@ -797,11 +798,11 @@ static const Bit8u bx_keypad_hid_report_descriptor[] = {
static const Bit8u bx_keypad_dev_descriptor[] = {
0x12, /* u8 bLength; */
0x01, /* u8 bDescriptorType; Device */
0x10, 0x01, /* u16 bcdUSB; v1.1 */
0x01, 0x01, /* u16 bcdUSB; v1.1 */
0x00, /* u8 bDeviceClass; */
0x00, /* u8 bDeviceSubClass; */
0x00, /* u8 bDeviceProtocol; [ low/full speeds only ] */
0x00, /* u8 bDeviceProtocol; */
0x08, /* u8 bMaxPacketSize; 8 Bytes */
0xB4, 0x04, /* u16 idVendor; */
@ -821,7 +822,7 @@ static const Bit8u bx_keypad_dev_descriptor2[] = {
0x00, /* u8 bDeviceClass; */
0x00, /* u8 bDeviceSubClass; */
0x00, /* u8 bDeviceProtocol; [ low/full speeds only ] */
0x00, /* u8 bDeviceProtocol; */
0x40, /* u8 bMaxPacketSize; 64 Bytes */
0xB4, 0x04, /* u16 idVendor; */
@ -1346,6 +1347,23 @@ int usb_hid_device_c::handle_control(int request, int value, int index, int leng
BX_DEBUG(("HID: DeviceRequest | SET_FEATURE:"));
goto fail;
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) {
if (index == 0x81) {
data[0] = 0x00 | (get_halted(index) ? 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:
BX_DEBUG(("HID: DeviceRequest | USB_REQ_GET_DESCRIPTOR:"));
switch(value >> 8) {
@ -1439,12 +1457,6 @@ int usb_hid_device_c::handle_control(int request, int value, int index, int leng
goto fail;
}
break;
case EndpointOutRequest | USB_REQ_CLEAR_FEATURE:
BX_DEBUG(("HID: CLEAR_FEATURE:"));
if ((value == 0) && (index != 0x81)) { /* clear EP halt */
goto fail;
}
break;
case InterfaceInClassRequest | GET_REPORT:
BX_DEBUG(("HID: GET_REPORT:"));
if ((value >> 8) == 1) { // Input report

View File

@ -97,6 +97,31 @@ protected:
static Bit32u serial_number = 1234;
// Set this to a version of the USB you wish to emulate.
// 0x0100 = v1.0 or 0x0101 = v1.1
#define USB_HUB_VERSION 0x0100
// Set USB_HUB_POWER_SWITCHING to 1 if power switching is supported
// (If not supported power is always on)
// (It must be set if USB_HUB_VERSION == 1.1+)
#define USB_HUB_POWER_SWITCHING 1
#if (USB_HUB_VERSION > 0x100) && (USB_HUB_POWER_SWITCHING == 0)
#error USB_HUB_POWER_SWITCHING must be set for USB_HUB_VERSION 1.1+
#endif
// Set USB_HUB_POWER_PER_PORT to 1 if per-port power switching is on,
// else set to 0 for ganged power switching (all ports powered at same time)
// (is ignored if USB_HUB_POWER_SWITCHING == 0)
#define USB_HUB_POWER_PER_PORT 1
// The USB specification (usb1.0 11.8, usb1.1 11.13.1, usb2.0 11.12.1)
// states that if there is nothing to report from the IN interrupt
// that the device should respond with a USB_RET_NAK. However, Win95
// considers that an error and stops the IN pipe, finding no further
// connection status. Set this to 1 if you are emulating Win95 or
// a similar Guest.
#define USB_HUB_ALWAYS_REPORT 1
// If you change any of the Max Packet Size, or other fields within these
// descriptors, you must also change the d.endpoint_info[] array
// to match your changes.
@ -104,11 +129,16 @@ static Bit32u serial_number = 1234;
static const Bit8u bx_hub_dev_descriptor[] = {
0x12, /* u8 bLength; */
0x01, /* u8 bDescriptorType; Device */
0x10, 0x01, /* u16 bcdUSB; v1.1 */
#if USB_HUB_VERSION == 0x0100
0x00, 0x01, /* u16 bcdUSB; v1.0 */
#elif USB_HUB_VERSION == 0x0101
0x01, 0x01, /* u16 bcdUSB; v1.1 */
#else
#error Unknown USB_HUB_VERSION value found
#endif
0x09, /* u8 bDeviceClass; HUB_CLASSCODE */
0x00, /* u8 bDeviceSubClass; */
0x00, /* u8 bDeviceProtocol; [ low/full speeds only ] */
0x00, /* u8 bDeviceProtocol; */
0x40, /* u8 bMaxPacketSize; 64 Bytes */
0x09, 0x04, /* u16 idVendor; */
@ -122,6 +152,7 @@ static const Bit8u bx_hub_dev_descriptor[] = {
};
/* XXX: patch interrupt size */
#define BX_Hub_Config_Descriptor_pos 22
static Bit8u bx_hub_config_descriptor[] = {
/* one configuration */
@ -174,10 +205,17 @@ static const Bit8u bx_hub_hub_descriptor[] =
0x00, /* u8 bLength; patched in later */
0x29, /* u8 bDescriptorType; Hub-descriptor */
0x00, /* u8 bNbrPorts; (patched later) */
0x0a, /* u16 wHubCharacteristics; */
0x00, /* (per-port OC, no power switching) */
#if USB_HUB_POWER_SWITCHING
#if USB_HUB_POWER_PER_PORT
0x09, 0x00, /* u16 wHubCharacteristics; (per-port OC, per-port power switching) */
#else
0x08, 0x00, /* u16 wHubCharacteristics; (per-port OC, ganged power switching) */
#endif
#else
0x0a, 0x00, /* u16 wHubCharacteristics; (per-port OC, no power switching) */
#endif
0x01, /* u8 bPwrOn2pwrGood; 2ms */
0x00 /* u8 bHubContrCurrent; 0 mA */
0x40 /* u8 bHubContrCurrent; 64 mA */
/* DeviceRemovable and PortPwrCtrlMask patched in later */
};
@ -197,9 +235,9 @@ usb_hub_device_c::usb_hub_device_c()
d.device_desc_size = sizeof(bx_hub_dev_descriptor);
d.config_descriptor = bx_hub_config_descriptor;
d.config_desc_size = sizeof(bx_hub_config_descriptor);
d.endpoint_info[USB_CONTROL_EP].max_packet_size = 8; // Control ep0
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 = 2; // In ep1
d.endpoint_info[1].max_packet_size = (USB_HUB_MAX_PORTS + 1 + 7) / 8; // In ep1
d.endpoint_info[1].max_burst_size = 0;
d.vendor_desc = "BOCHS";
d.product_desc = "BOCHS USB HUB";
@ -257,11 +295,12 @@ bool usb_hub_device_c::init()
bx_param_bool_c *overcurrent;
// set up config descriptor, status and runtime config for hub.n_ports
bx_hub_config_descriptor[22] = (hub.n_ports + 1 + 7) / 8;
bx_hub_config_descriptor[BX_Hub_Config_Descriptor_pos] = (hub.n_ports + 1 + 7) / 8;
for(i = 0; i < hub.n_ports; i++) {
hub.usb_port[i].PortStatus = PORT_STAT_POWER;
hub.usb_port[i].PortChange = 0;
}
hub.PortStatusC = 0;
for(i = 0; i < hub.n_ports; i++) {
sprintf(pname, "port%d", i+1);
sprintf(label, "Port #%d Configuration", i+1);
@ -319,6 +358,7 @@ void usb_hub_device_c::register_state_specific(bx_list_c *parent)
// empty list for USB device state
new bx_list_c(port, "device");
}
BXRS_HEX_PARAM_FIELD(hub.state, PortStatusC, hub.PortStatusC);
}
void usb_hub_device_c::after_restore_state()
@ -363,15 +403,26 @@ int usb_hub_device_c::handle_control(int request, int value, int index, int leng
case DeviceOutRequest | USB_REQ_CLEAR_FEATURE:
goto fail;
break;
case EndpointOutRequest | USB_REQ_CLEAR_FEATURE:
if (value == 0 && index != 0x81) { /* clear ep halt */
goto fail;
}
ret = 0;
break;
case DeviceOutRequest | USB_REQ_SET_FEATURE:
goto fail;
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) {
if (index == 0x81) {
data[0] = 0x00 | (get_halted(index) ? 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:
@ -437,9 +488,23 @@ int usb_hub_device_c::handle_control(int request, int value, int index, int leng
hub.usb_port[n].PortChange |= PORT_STAT_C_RESET;
/* set enable bit */
hub.usb_port[n].PortStatus |= PORT_STAT_ENABLE;
hub.usb_port[n].PortStatus &= ~PORT_STAT_SUSPEND;
}
break;
case PORT_POWER:
#if USB_HUB_POWER_SWITCHING
#if USB_HUB_POWER_PER_PORT == 0
for (n = 0; n < hub.n_ports; n++) {
#endif
if ((hub.usb_port[n].PortStatus & PORT_STAT_POWER) == 0) {
hub.usb_port[n].PortStatus = PORT_STAT_POWER;
hub.device_change |= (1 << n);
runtime_config();
}
#if USB_HUB_POWER_PER_PORT == 0
}
#endif
#endif
break;
default:
BX_ERROR(("Unknown SetPortFeature: %d", value));
@ -452,6 +517,23 @@ int usb_hub_device_c::handle_control(int request, int value, int index, int leng
if (n >= hub.n_ports)
goto fail;
switch(value) {
case PORT_POWER:
#if USB_HUB_POWER_SWITCHING
#if USB_HUB_POWER_PER_PORT == 0
for (n = 0; n < hub.n_ports; n++) {
#endif
if (hub.usb_port[n].PortStatus & PORT_STAT_POWER) {
hub.usb_port[n].PortStatus &= ~PORT_STAT_POWER;
hub.device_change |= (1 << n);
runtime_config();
hub.usb_port[n].PortStatus = 0;
hub.usb_port[n].PortChange = 0;
}
#if USB_HUB_POWER_PER_PORT == 0
}
#endif
#endif
break;
case PORT_ENABLE:
hub.usb_port[n].PortStatus &= ~PORT_STAT_ENABLE;
break;
@ -480,30 +562,63 @@ int usb_hub_device_c::handle_control(int request, int value, int index, int leng
ret = 0;
break;
case DeviceClassInRequest | USB_REQ_GET_DESCRIPTOR:
{
unsigned int limit, var_hub_size = 0;
if (
#if USB_HUB_VERSION == 0x0100
((value >> 8) == 0x00) || // USB 1.0 defines it as type = zero
#endif
((value >> 8) == 0x29)) { // USB 1.1+ defines it as type = 0x29
unsigned int var_hub_size = 0, indx = 7;
memcpy(data, bx_hub_hub_descriptor,
sizeof(bx_hub_hub_descriptor));
data[2] = hub.n_ports;
/* fill DeviceRemovable bits */
limit = ((hub.n_ports + 1 + 7) / 8) + 7;
for (n = 7; n < limit; n++) {
data[n] = 0x00;
unsigned int byte_count = ((hub.n_ports + 1 + 7) / 8);
for (n = 0; n < byte_count; n++) {
data[indx + n] = 0x00;
var_hub_size++;
}
#if USB_HUB_VERSION == 0x0100
/* Build a bitmap of all present ports starting with:
bit 1 = port 0, bit 2 = port 1, etc.
We can assume there will be less than 16 ports (USB_HUB_MAX_PORTS < 16) */
unsigned int status = 0;
for (n = 0; n < hub.n_ports; n++) {
status |= (1 << (n + 1));
}
#endif
/* fill PortPwrCtrlMask bits */
limit = limit + ((hub.n_ports + 7) / 8);
for (;n < limit; n++) {
data[n] = 0xff;
/* USB 1.0: has this field set each bit indicating if a port is present
starting with bit 1. */
/* USB 1.1+: states that this field should be all 1's */
indx += var_hub_size;
for (n = 0; n < byte_count; n++) {
#if USB_HUB_VERSION == 0x0100
#if USB_HUB_POWER_SWITCHING && USB_HUB_POWER_PER_PORT
data[indx + n] = (Bit8u) (status & 0xFF);
status >>= 8;
#else
data[indx + n] = 0x00;
#endif
#else
data[indx + n] = 0xFF;
#endif
var_hub_size++;
}
ret = sizeof(bx_hub_hub_descriptor) + var_hub_size;
data[0] = ret;
break;
#if USB_HUB_VERSION != 0x0100
} else if ((value >> 8) == 0x00) { // USB 1.0 defines it as type = zero
BX_INFO(("handle_control: Hub Class: A request of zero is a USB 1.0 request. USB 1.1+ use 0x29."));
goto fail;
#endif
} else {
BX_ERROR(("handle_control: Hub Class: unknown type requested: 0x%02x", value >> 8));
goto fail;
}
break;
default:
BX_ERROR(("handle_control: unknown request: 0x%04x", request));
fail:
@ -526,7 +641,6 @@ int usb_hub_device_c::handle_data(USBPacket *p)
switch(p->pid) {
case USB_TOKEN_IN:
if (p->devep == 1) {
unsigned int status;
int i, n;
n = (hub.n_ports + 1 + 7) / 8;
if (p->len == 1) { /* FreeBSD workaround */
@ -534,19 +648,27 @@ int usb_hub_device_c::handle_data(USBPacket *p)
} else if (n > p->len) {
return USB_RET_BABBLE;
}
status = 0;
Bit16u status = 0;
for(i = 0; i < hub.n_ports; i++) {
if (hub.usb_port[i].PortChange)
status |= (1 << (i + 1));
}
if (status != hub.PortStatusC) {
hub.PortStatusC = status;
status |= 1; // bit 0 = hub change detected
}
#if USB_HUB_ALWAYS_REPORT != 1
if (status != 0) {
#endif
for(i = 0; i < n; i++) {
p->data[i] = status >> (8 * i);
}
ret = n;
#if USB_HUB_ALWAYS_REPORT != 1
} else {
ret = USB_RET_NAK; /* usb11 11.13.1 */
ret = USB_RET_NAK;
}
#endif
} else {
goto fail;
}
@ -639,7 +761,11 @@ int usb_hub_device_c::event_handler(int event, void *ptr, int port)
}
break;
// host controller events start here
// "host controller" events start here
case USB_EVENT_DEFAULT_SPEED:
// return default speed for specified port
return USB_SPEED_FULL;
case USB_EVENT_CHECK_SPEED:
if (ptr != NULL) {
usb_device_c *usb_device = (usb_device_c *) ptr;
@ -706,6 +832,7 @@ bool usb_hub_device_c::usb_set_connect_status(Bit8u port, bool connected)
hub.usb_port[port].PortStatus &= ~PORT_STAT_ENABLE;
hub.usb_port[port].PortChange |= PORT_STAT_C_ENABLE;
}
hub.usb_port[port].PortStatus &= ~PORT_STAT_SUSPEND;
remove_device(port);
}
}
@ -720,13 +847,17 @@ void usb_hub_device_c::runtime_config()
for (i = 0; i < hub.n_ports; i++) {
// device change support
if ((hub.device_change & (1 << i)) != 0) {
if ((hub.usb_port[i].PortStatus & PORT_STAT_CONNECTION) == 0) {
sprintf(pname, "port%d", i + 1);
init_device(i, (bx_list_c *) SIM->get_param(pname, hub.config));
if (hub.usb_port[i].PortStatus & PORT_STAT_POWER) {
if ((hub.usb_port[i].PortStatus & PORT_STAT_CONNECTION) == 0) {
sprintf(pname, "port%d", i + 1);
init_device(i, (bx_list_c *) SIM->get_param(pname, hub.config));
} else {
usb_set_connect_status(i, 0);
}
hub.device_change &= ~(1 << i);
} else {
usb_set_connect_status(i, 0);
}
hub.device_change &= ~(1 << i);
}
// forward to connected device
if (hub.usb_port[i].device != NULL) {
@ -798,7 +929,9 @@ Bit64s usb_hub_device_c::hub_param_oc_handler(bx_param_c *param, bool set, Bit64
hub = (usb_hub_device_c *) (port->get_parent()->get_device_param());
if (hub != NULL) {
portnum = atoi(port->get_name()+4) - 1;
#if USB_HUB_POWER_SWITCHING
hub->hub.usb_port[portnum].PortStatus &= ~PORT_STAT_POWER;
#endif
hub->hub.usb_port[portnum].PortStatus |= PORT_STAT_OVERCURRENT;
hub->hub.usb_port[portnum].PortChange |= PORT_STAT_C_OVERCURRENT;
BX_DEBUG(("Over-current signaled on port #%d.", portnum + 1));

View File

@ -30,7 +30,7 @@
#define BX_IODEV_USB_HUB_H
#define USB_HUB_MAX_PORTS 8
#define USB_HUB_MAX_PORTS 8 // must be at least 2 and less than 16
#define USB_HUB_DEF_PORTS 4
class usb_hub_device_c : public usb_device_c {
@ -67,6 +67,7 @@ private:
Bit16u PortStatus;
Bit16u PortChange;
} usb_port[USB_HUB_MAX_PORTS];
Bit16u PortStatusC;
Bit16u device_change;
} hub;

View File

@ -98,14 +98,17 @@ struct usb_msd_csw {
// to match your changes.
// Full-speed
// For full-speed devices, we set the bcdUSB to 1.1 so that an emulated host
// won't try to send the USB_DT_DEVICE_QUALIFIER or USB_DT_OTHER_SPEED_CONFIG
// requests.
static const Bit8u bx_msd_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. */
@ -172,7 +175,7 @@ static const Bit8u bx_msd_dev_descriptor2[] = {
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. */
@ -841,6 +844,25 @@ int usb_msd_device_c::handle_control(int request, int value, int index, int leng
}
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);
int limit = (get_aIface() == MSD_PROTO_BBB) ? MSD_BBB_DATAOUT_EP : MSD_UASP_DATAOUT;
if ((indx > 0) && (indx <= limit)) {
data[0] = 0x00 | (get_halted(index) ? 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:
@ -868,8 +890,13 @@ int usb_msd_device_c::handle_control(int request, int value, int index, int leng
if (d.speed == USB_SPEED_HIGH) {
data[0] = 10; // 10 bytes long
data[1] = USB_DT_DEVICE_QUALIFIER;
memcpy(data + 2, bx_msd_dev_descriptor + 2, 6);
data[8] = 1; // bNumConfigurations
data[2] = 0x00; // version 2.00
data[3] = 0x02; //
data[4] = bx_msd_dev_descriptor[4]; // class code
data[5] = bx_msd_dev_descriptor[5]; // subclass code
data[6] = bx_msd_dev_descriptor[6]; // protocol
data[7] = bx_msd_dev_descriptor[7]; // max packed size
data[8] = 1; // bNumConfigurations (number of other-speed configurations)
data[9] = 0; // reserved
ret = 10; // return a 10-byte descriptor
} else if (d.speed == USB_SPEED_FULL) {
@ -890,6 +917,21 @@ int usb_msd_device_c::handle_control(int request, int value, int index, int leng
goto fail;
}
break;
case USB_DT_OTHER_SPEED_CONFIG:
BX_DEBUG(("USB_REQ_GET_DESCRIPTOR: Other Speed Configuration"));
if (get_speed() == USB_SPEED_HIGH) {
ret = * (Bit16u *) &bx_msd_config_descriptor[2];
memcpy(data, bx_msd_config_descriptor, ret);
data[1] = USB_DT_OTHER_SPEED_CONFIG;
} else if (get_speed() == USB_SPEED_FULL) {
ret = * (Bit16u *) &bx_msd_config_descriptor2[2];
memcpy(data, bx_msd_config_descriptor2, ret);
data[1] = USB_DT_OTHER_SPEED_CONFIG;
} else {
BX_ERROR(("USB_REQ_GET_DESCRIPTOR: Other Speed Configuration: Valid on high- or full-speed only."));
goto fail;
}
break;
case USB_DT_BIN_DEV_OBJ_STORE:
BX_DEBUG(("USB_REQ_GET_DESCRIPTOR: BOS"));
// only devices with a version of 0x0210 or higher are allowed to request this descriptor.
@ -909,19 +951,6 @@ int usb_msd_device_c::handle_control(int request, int value, int index, int leng
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 {
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):"));
@ -986,7 +1015,6 @@ void usb_msd_device_c::handle_iface_change(int iface)
} 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;

View File

@ -68,7 +68,7 @@ static const Bit8u bx_printer_dev_descriptor[] = {
0x00, /* u8 bDeviceClass; */
0x00, /* u8 bDeviceSubClass; */
0x00, /* u8 bDeviceProtocol; [ low/full speeds only ] */
0x00, /* u8 bDeviceProtocol; */
0x40, /* u8 bMaxPacketSize; 64 Bytes */
0xF0, 0x03, /* u16 idVendor; */
@ -255,6 +255,24 @@ int usb_printer_device_c::handle_control(int request, int value, int index, int
case DeviceOutRequest | USB_REQ_SET_FEATURE:
goto fail;
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 ((indx >= 1) && (indx <= 2)) {
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: