diff --git a/system/banjo/ddk-protocol-usb-bus/usb-bus.banjo b/system/banjo/ddk-protocol-usb-bus/usb-bus.banjo index c52d8ea8cc267a231db47cc4c3a4e5ba9bbb2fe5..f43812b1528b3429f7f984ead4be5ccaa48f20bb 100644 --- a/system/banjo/ddk-protocol-usb-bus/usb-bus.banjo +++ b/system/banjo/ddk-protocol-usb-bus/usb-bus.banjo @@ -22,6 +22,8 @@ interface UsbBus { DeviceAdded(ddk.driver.ZxDevice? hub_device, uint32 port, UsbSpeed speed) -> (zx.status s); /// Informs the USB bus that a device has been removed. DeviceRemoved(ddk.driver.ZxDevice? hub_device, uint32 port) -> (zx.status s); + /// Informs the USB bus that a device has been reset. + DeviceReset(ddk.driver.ZxDevice? hub_device, uint32 port) -> (zx.status s); /// Used by USB hub driver to register its USB hub protocol with the USB bus driver. SetHubInterface(ddk.driver.ZxDevice? usb_device, ddk.protocol.usb.hub.UsbHubInterface hub) -> (zx.status s); @@ -36,4 +38,6 @@ interface UsbBusInterface { RemoveDevice(uint32 device_id) -> (zx.status s); /// Used by the HCI controller to reset a port on a USB hub. ResetPort(uint32 hub_id, uint32 port) -> (zx.status s); + /// Used by the HCI controller to reinitialize a device after it has been reset. + ReinitializeDevice(uint32 device_id) -> (zx.status s); }; diff --git a/system/banjo/ddk-protocol-usb-hci/usb-hci.banjo b/system/banjo/ddk-protocol-usb-hci/usb-hci.banjo index b93a02b54d96c2ac32df41c2e72d30d9b0a40ac8..3e1829fa7f3e5b414d0ec405fe43d63158b88c3b 100644 --- a/system/banjo/ddk-protocol-usb-hci/usb-hci.banjo +++ b/system/banjo/ddk-protocol-usb-hci/usb-hci.banjo @@ -31,8 +31,12 @@ interface UsbHci { HubDeviceAdded(uint32 device_id, uint32 port, zircon.hw.usb.UsbSpeed speed) -> (zx.status s); /// Used by the USB hub driver to notify the HCI driver when a device has been removed. HubDeviceRemoved(uint32 device_id, uint32 port) -> (zx.status s); + /// Used by the USB hub driver to notify the HCI driver when a device has been reset. + HubDeviceReset(uint32 device_id, uint32 port) -> (zx.status s); /// Resets an endpoint on the specified device. ResetEndpoint(uint32 device_id, uint8 ep_address) -> (zx.status s); + /// Resets the specified device. + ResetDevice(uint32 hub_address, uint32 device_id) -> (zx.status s); /// Returns the maximum size of a packet that can be queued on the specified endpoint. GetMaxTransferSize(uint32 device_id, uint8 ep_address) -> (usize size); /// Cancels all transactions currently queued on the specified endpoint. diff --git a/system/banjo/ddk-protocol-usb/usb.banjo b/system/banjo/ddk-protocol-usb/usb.banjo index 1c2999dda3ea0793f18848623a191446096d5f2a..6e222c1b524d679586801f9d9b6a4dcd3cf2a4fe 100644 --- a/system/banjo/ddk-protocol-usb/usb.banjo +++ b/system/banjo/ddk-protocol-usb/usb.banjo @@ -58,6 +58,10 @@ interface Usb { /// usb_reset_endpoint() the endpoint to normal running state. ResetEndpoint(uint8 ep_address) -> (zx.status s); + /// Resets the device and restores the prior configuration. + /// Returns ZX_ERR_BAD_STATE if the device is already being reset. + ResetDevice() -> (zx.status s); + /// Returns the maximum amount of data that can be transferred on an endpoint in a single /// transaction. GetMaxTransferSize(uint8 ep_address) -> (usize s); diff --git a/system/dev/usb/usb-bus/usb-bus.c b/system/dev/usb/usb-bus/usb-bus.c index 1d794ed9fb6607c70e82f85e690751f4d46c4ee9..18f2427085d3dfa39fd4b63a78620a5f23fb433a 100644 --- a/system/dev/usb/usb-bus/usb-bus.c +++ b/system/dev/usb/usb-bus/usb-bus.c @@ -11,6 +11,7 @@ #include "usb-bus.h" #include "usb-device.h" +#include "util.h" static zx_status_t bus_add_device(void* ctx, uint32_t device_id, uint32_t hub_id, usb_speed_t speed) { @@ -58,10 +59,46 @@ static zx_status_t bus_reset_hub_port(void* ctx, uint32_t hub_id, uint32_t port) return usb_hub_interface_reset_port(&device->hub_intf, port); } +static zx_status_t bus_reinitialize_device(void* ctx, uint32_t device_id) { + usb_bus_t* bus = ctx; + usb_device_t* device = bus->devices[device_id]; + if (!device) { + zxlogf(ERROR, "could not find device %u\n", device_id); + return ZX_ERR_INTERNAL; + } + // Check if the USB device descriptor changed, in which case we need to force the device to + // re-enumerate so we can load the uploaded device driver. + // This can happen during a Device Firmware Upgrade. + usb_device_descriptor_t updated_desc; + size_t actual; + zx_status_t status = usb_util_get_descriptor(device, USB_DT_DEVICE, 0, 0, &updated_desc, + sizeof(updated_desc), &actual); + if (status != ZX_OK || actual != sizeof(updated_desc)) { + zxlogf(ERROR, "could not get updated descriptor: %d got len %lu\n", status, actual); + // We should try reinitializing the device anyway. + goto done; + } + // TODO(jocelyndang): we may want to check other descriptors as well. + bool descriptors_changed = memcmp(&device->device_desc, &updated_desc, + sizeof(usb_device_descriptor_t)) != 0; + if (descriptors_changed) { + zxlogf(INFO, "device updated from VID 0x%x PID 0x%x to VID 0x%x PID 0x%x\n", + device->device_desc.idVendor, device->device_desc.idProduct, + updated_desc.idVendor, updated_desc.idProduct); + // TODO(jocelyndang): handle this. + } + +done: + return usb_device_reinitialize(device); + + // TODO(jocelyndang): should we notify the interfaces that the device has been reset? +} + static usb_bus_interface_ops_t _bus_interface = { .add_device = bus_add_device, .remove_device = bus_remove_device, .reset_port = bus_reset_hub_port, + .reinitialize_device = bus_reinitialize_device, }; static zx_status_t bus_get_device_id(zx_device_t* device, uint32_t* out) { @@ -102,6 +139,19 @@ static zx_status_t bus_device_removed(void* ctx, zx_device_t* hub_device, uint32 return usb_hci_hub_device_removed(&bus->hci, hub_id, port); } +static zx_status_t bus_device_reset(void* ctx, zx_device_t* hub_device, uint32_t port) { + usb_bus_t* bus = ctx; + uint32_t hub_id; + if (bus_get_device_id(hub_device, &hub_id) != ZX_OK) { + return ZX_ERR_INTERNAL; + } + zx_status_t status = usb_hci_hub_device_reset(&bus->hci, hub_id, port); + if (status != ZX_OK) { + return status; + } + return ZX_OK; +} + static zx_status_t bus_set_hub_interface(void* ctx, zx_device_t* usb_device, const usb_hub_interface_t* hub) { usb_bus_t* bus = ctx; @@ -116,13 +166,14 @@ static zx_status_t bus_set_hub_interface(void* ctx, zx_device_t* usb_device, } zxlogf(ERROR, "bus_set_hub_interface: no device for usb_device_id %u\n", usb_device_id); - return ZX_ERR_INTERNAL; + return ZX_ERR_INTERNAL; } static usb_bus_protocol_ops_t _bus_protocol = { .configure_hub = bus_configure_hub, .device_added = bus_device_added, .device_removed = bus_device_removed, + .device_reset = bus_device_reset, .set_hub_interface = bus_set_hub_interface, }; diff --git a/system/dev/usb/usb-bus/usb-device.c b/system/dev/usb/usb-bus/usb-device.c index 13370bd1d4428b0fd4beaa9731b95ed6c315254a..2696a47db825018f47f29597b3545f035f35e279 100644 --- a/system/dev/usb/usb-bus/usb-device.c +++ b/system/dev/usb/usb-bus/usb-device.c @@ -5,6 +5,7 @@ #include <ddk/debug.h> #include <ddk/metadata.h> #include <ddk/protocol/usb.h> +#include <ddk/protocol/usb/bus.h> #include <zircon/usb/device/c/fidl.h> #include <stdint.h> #include <stdlib.h> @@ -357,6 +358,32 @@ static zx_status_t usb_device_reset_endpoint(void* ctx, uint8_t ep_address) { return usb_hci_reset_endpoint(&dev->hci, dev->device_id, ep_address); } +static zx_status_t usb_device_update_state(usb_device_t* dev, bool resetting) { + mtx_lock(&dev->state_lock); + if (dev->resetting == resetting) { + zxlogf(ERROR, "usb_device_update_state: already had resetting = %d\n", resetting); + mtx_unlock(&dev->state_lock); + return ZX_ERR_BAD_STATE; + } + dev->resetting = resetting; + mtx_unlock(&dev->state_lock); + return ZX_OK; +} + +static zx_status_t usb_device_reset_device(void* ctx) { + usb_device_t* dev = ctx; + + zx_status_t status = usb_device_update_state(dev, /* resetting */ true); + if (status != ZX_OK) { + return status; + } + status = usb_hci_reset_device(&dev->hci, dev->hub_id, dev->device_id); + if (status != ZX_OK) { + return status; + } + return status; +} + static size_t usb_device_get_max_transfer_size(void* ctx, uint8_t ep_address) { usb_device_t* dev = ctx; return usb_hci_get_max_transfer_size(&dev->hci, dev->device_id, ep_address); @@ -459,6 +486,7 @@ static usb_protocol_ops_t _usb_protocol = { .set_configuration = usb_device_set_configuration, .enable_endpoint = usb_device_enable_endpoint, .reset_endpoint = usb_device_reset_endpoint, + .reset_device = usb_device_reset_device, .get_max_transfer_size = usb_device_get_max_transfer_size, .get_device_id = _usb_device_get_device_id, .get_device_descriptor = usb_device_get_device_descriptor, @@ -719,3 +747,19 @@ error_exit: free(dev); return status; } + +zx_status_t usb_device_reinitialize(usb_device_t* dev) { + zx_status_t status = usb_device_update_state(dev, /* resetting */ false); + if (status != ZX_OK) { + return status; + } + uint8_t config = dev->config_descs[dev->current_config_index]->bConfigurationValue; + status = usb_util_control(dev, USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE, + USB_REQ_SET_CONFIGURATION, config, 0, NULL, 0, NULL); + if (status != ZX_OK) { + zxlogf(ERROR, "could not restore configuration to %u, got err: %d\n", config, status); + return status; + } + // TODO(jocelyndang): we should re-enable endpoints and restore alternate settings. + return ZX_OK; +} diff --git a/system/dev/usb/usb-bus/usb-device.h b/system/dev/usb/usb-bus/usb-device.h index 4cb7fa979480c5010f5cc435cca88600f3af5954..b1e3ce1e0cd1fa06750dc8b9fe88e6750c600c62 100644 --- a/system/dev/usb/usb-bus/usb-device.h +++ b/system/dev/usb/usb-bus/usb-device.h @@ -41,6 +41,10 @@ typedef struct usb_device { atomic_bool langids_fetched; atomic_uintptr_t lang_ids; + bool resetting; + // mutex that protects the resetting state member + mtx_t state_lock; + // thread for calling client's usb request complete callback thrd_t callback_thread; bool callback_thread_stop; @@ -62,6 +66,8 @@ void usb_device_set_hub_interface(usb_device_t* dev, const usb_hub_interface_t* zx_status_t usb_device_add(usb_bus_t* bus, uint32_t device_id, uint32_t hub_id, usb_speed_t speed, usb_device_t** out_device); +zx_status_t usb_device_reinitialize(usb_device_t* dev); + #define USB_REQ_TO_DEV_INTERNAL(req, size) \ ((usb_device_req_internal_t *)((uintptr_t)(req) + (size))) #define DEV_INTERNAL_TO_USB_REQ(ctx, size) ((usb_request_t *)((uintptr_t)(ctx) - (size))) diff --git a/system/dev/usb/usb-composite/usb-interface.c b/system/dev/usb/usb-composite/usb-interface.c index 4ae95efe54584ae2c42ad0adfefddeaa7d23273d..5ec0b1b8e8c285f8298054417647df9c39e4501c 100644 --- a/system/dev/usb/usb-composite/usb-interface.c +++ b/system/dev/usb/usb-composite/usb-interface.c @@ -184,6 +184,11 @@ static zx_status_t usb_interface_reset_endpoint(void* ctx, uint8_t ep_address) { return usb_reset_endpoint(&intf->comp->usb, ep_address); } +static zx_status_t usb_interface_reset_device(void* ctx) { + usb_interface_t* intf = ctx; + return usb_reset_device(&intf->comp->usb); +} + static size_t usb_interface_get_max_transfer_size(void* ctx, uint8_t ep_address) { usb_interface_t* intf = ctx; return usb_get_max_transfer_size(&intf->comp->usb, ep_address); @@ -356,6 +361,7 @@ usb_protocol_ops_t usb_device_protocol = { .set_configuration = usb_interface_set_configuration, .enable_endpoint = usb_interface_enable_endpoint, .reset_endpoint = usb_interface_reset_endpoint, + .reset_device = usb_interface_reset_device, .get_max_transfer_size = usb_interface_get_max_transfer_size, .get_device_id = usb_interface_get_device_id, .get_device_descriptor = usb_interface_get_device_descriptor, diff --git a/system/dev/usb/usb-dfu/usb-dfu.cpp b/system/dev/usb/usb-dfu/usb-dfu.cpp index a106be6249a4cddf87e2b9889df85e5ca144ba7f..0a171a00afc43e23b53031ff85931d0ed731a9ca 100644 --- a/system/dev/usb/usb-dfu/usb-dfu.cpp +++ b/system/dev/usb/usb-dfu/usb-dfu.cpp @@ -199,9 +199,8 @@ zx_status_t Dfu::LoadFirmware(zx::vmo fw_vmo, size_t fw_size) { block_num++; vmo_offset += len_to_write; } while (len_to_write != 0); // The device expects a zero length transfer to signify the end. - // TODO(jocelyndang): issue a USB Reset to enter Application Mode. - return ZX_OK; + return usb_reset_device(&usb_); } zx_status_t Dfu::DdkMessage(fidl_msg_t* msg, fidl_txn_t* txn) { diff --git a/system/dev/usb/usb-hub/usb-hub.c b/system/dev/usb/usb-hub/usb-hub.c index fd4461cedef43a2e75888435b9462bd14d82a6a1..e80428507f33d0eb5a9181055148483f6e5f13dd 100644 --- a/system/dev/usb/usb-hub/usb-hub.c +++ b/system/dev/usb/usb-hub/usb-hub.c @@ -227,6 +227,11 @@ static zx_status_t usb_hub_port_reset(void* ctx, uint32_t port) { if (status != ZX_OK) { zxlogf(ERROR, "usb_hub_wait_for_port USB_PORT_RESET failed for USB hub, port %d\n", port); } + status = usb_bus_device_reset(&hub->bus, hub->usb_device, port); + if (status != ZX_OK) { + zxlogf(ERROR, "usb_bus_device_reset failed, err: %d\n", status); + return status; + } return status; } diff --git a/system/dev/usb/xhci/usb-xhci.cpp b/system/dev/usb/xhci/usb-xhci.cpp index 6c5fe8ef1c17130afab18c8948eb26048474879c..f6b3814442b2c9c75b0e2f3490ee04f575a9ada8 100644 --- a/system/dev/usb/xhci/usb-xhci.cpp +++ b/system/dev/usb/xhci/usb-xhci.cpp @@ -110,11 +110,31 @@ static zx_status_t xhci_hub_device_removed(void* ctx, uint32_t hub_address, uint return ZX_OK; } +static zx_status_t xhci_hub_device_reset(void* ctx, uint32_t hub_address, uint32_t port) { + auto* xhci = static_cast<xhci_t*>(ctx); + return xhci_device_reset(xhci, hub_address, port); +} + static zx_status_t xhci_reset_ep(void* ctx, uint32_t device_id, uint8_t ep_address) { auto* xhci = static_cast<xhci_t*>(ctx); return xhci_reset_endpoint(xhci, device_id, ep_address); } +static zx_status_t xhci_reset_device(void* ctx, uint32_t hub_address, uint32_t device_id) { + auto* xhci = static_cast<xhci_t*>(ctx); + + auto* slot = &xhci->slots[device_id]; + uint32_t port = slot->port; + if (slot->hub_address == 0) { + // Convert real port number to virtual root hub number. + port = xhci->rh_port_map[port - 1] + 1; + } + zxlogf(TRACE, "xhci_reset_device slot_id: %u port: %u hub_address: %u\n", + device_id, port, hub_address); + + return usb_bus_interface_reset_port(&xhci->bus, hub_address, port); +} + static size_t xhci_get_max_transfer_size(void* ctx, uint32_t device_id, uint8_t ep_address) { if (ep_address == 0) { // control requests have uint16 length field so we need to support UINT16_MAX @@ -147,7 +167,9 @@ usb_hci_protocol_ops_t xhci_hci_protocol = { .configure_hub = xhci_config_hub, .hub_device_added = xhci_hub_device_added, .hub_device_removed = xhci_hub_device_removed, + .hub_device_reset = xhci_hub_device_reset, .reset_endpoint = xhci_reset_ep, + .reset_device = xhci_reset_device, .get_max_transfer_size = xhci_get_max_transfer_size, .cancel_all = xhci_cancel_all, .get_request_size = xhci_get_request_size, diff --git a/system/dev/usb/xhci/xhci-device-manager.cpp b/system/dev/usb/xhci/xhci-device-manager.cpp index c01acabef0c6bb913a93420332879a4a87de07e7..cb86d75363f74ec629cd31b47e17de7f9dffb9a4 100644 --- a/system/dev/usb/xhci/xhci-device-manager.cpp +++ b/system/dev/usb/xhci/xhci-device-manager.cpp @@ -18,6 +18,7 @@ typedef enum { ENUMERATE_DEVICE, DISCONNECT_DEVICE, + RESET_DEVICE, START_ROOT_HUBS, STOP_THREAD, } xhci_command_t; @@ -66,32 +67,40 @@ static zx_status_t xhci_address_device(xhci_t* xhci, uint32_t slot_id, uint32_t } xhci_slot_t* slot = &xhci->slots[slot_id]; - if (slot->sc) - return ZX_ERR_BAD_STATE; slot->hub_address = hub_address; slot->port = port; slot->rh_port = (hub_address == 0 ? port : xhci->slots[hub_address].rh_port); slot->speed = speed; - // allocate a read-only DMA buffer for device context - size_t dc_length = xhci->context_size * XHCI_NUM_EPS; - zx_status_t status = io_buffer_init(&slot->buffer, xhci->bti_handle, dc_length, - IO_BUFFER_RO | IO_BUFFER_CONTIG | XHCI_IO_BUFFER_UNCACHED); - if (status != ZX_OK) { - zxlogf(ERROR, "xhci_address_device: failed to allocate io_buffer for slot\n"); - return status; - } - auto* device_context = static_cast<uint8_t*>(io_buffer_virt(&slot->buffer)); xhci_endpoint_t* ep = &slot->eps[0]; - status = xhci_transfer_ring_init(&ep->transfer_ring, xhci->bti_handle, TRANSFER_RING_SIZE); - if (status < 0) return status; - ep->transfer_state = static_cast<xhci_transfer_state_t*>( - calloc(1, sizeof(xhci_transfer_state_t))); - if (!ep->transfer_state) { - return ZX_ERR_NO_MEMORY; + + bool device_resetting = slot->sc != nullptr; + zx_status_t status; + + // Allocate the buffers if we haven't already. They will already exist in the case of a + // device reset. + if (!device_resetting) { + // allocate a read-only DMA buffer for device context + size_t dc_length = xhci->context_size * XHCI_NUM_EPS; + status = io_buffer_init(&slot->buffer, xhci->bti_handle, dc_length, + IO_BUFFER_RO | IO_BUFFER_CONTIG | XHCI_IO_BUFFER_UNCACHED); + if (status != ZX_OK) { + zxlogf(ERROR, "xhci_address_device: failed to allocate io_buffer for slot\n"); + return status; + } + + status = xhci_transfer_ring_init(&ep->transfer_ring, xhci->bti_handle, TRANSFER_RING_SIZE); + if (status < 0) return status; + ep->transfer_state = static_cast<xhci_transfer_state_t*>( + calloc(1, sizeof(xhci_transfer_state_t))); + if (!ep->transfer_state) { + return ZX_ERR_NO_MEMORY; + } + ep->ep_type = USB_ENDPOINT_CONTROL; } + + auto* device_context = static_cast<uint8_t*>(io_buffer_virt(&slot->buffer)); xhci_transfer_ring_t* transfer_ring = &ep->transfer_ring; - ep->ep_type = USB_ENDPOINT_CONTROL; mtx_lock(&xhci->input_context_lock); auto* icc = reinterpret_cast<xhci_input_control_context_t*>(xhci->input_context); @@ -147,7 +156,8 @@ static zx_status_t xhci_address_device(xhci_t* xhci, uint32_t slot_id, uint32_t tt_port_number); // Setup endpoint context for ep0 - zx_paddr_t tr_dequeue = xhci_transfer_ring_start_phys(transfer_ring); + // If this is following a device reset, the dequeue pointer may not be the start of the ring. + zx_paddr_t tr_dequeue = xhci_transfer_ring_current_phys(transfer_ring); // start off with reasonable default max packet size for ep0 based on speed int mps; @@ -182,7 +192,10 @@ static zx_status_t xhci_address_device(xhci_t* xhci, uint32_t slot_id, uint32_t if (status == ZX_OK) { break; } else if (status != ZX_ERR_TIMED_OUT) { - usb_bus_interface_reset_port(&xhci->bus, hub_address, port); + // Don't want to get into a reset loop. + if (!device_resetting) { + usb_bus_interface_reset_port(&xhci->bus, hub_address, port); + } status = xhci_send_command(xhci, TRB_CMD_ADDRESS_DEVICE, icc_phys, ((slot_id << TRB_SLOT_ID_START) | TRB_ADDRESS_DEVICE_BSR)); if (status != ZX_OK) { @@ -294,27 +307,13 @@ static void xhci_disable_slot(xhci_t* xhci, uint32_t slot_id) { slot->port = USB_SPEED_UNDEFINED; } -static zx_status_t xhci_handle_enumerate_device(xhci_t* xhci, uint32_t hub_address, uint32_t port, - usb_speed_t speed) { - zxlogf(TRACE, "xhci_handle_enumerate_device hub_address:%d port %d\n", hub_address, port); - zx_status_t result = ZX_OK; - uint32_t slot_id = 0; - - xhci_sync_command_t command; - xhci_sync_command_init(&command); - xhci_post_command(xhci, TRB_CMD_ENABLE_SLOT, 0, 0, &command.context); - int cc = xhci_sync_command_wait(&command); - if (cc == TRB_CC_SUCCESS) { - slot_id = xhci_sync_command_slot_id(&command); - } else { - zxlogf(ERROR, "xhci_handle_enumerate_device: unable to get a slot\n"); - return ZX_ERR_NO_RESOURCES; - } - - result = xhci_address_device(xhci, slot_id, hub_address, port, speed); +static zx_status_t xhci_setup_slot(xhci_t* xhci, uint32_t slot_id, uint32_t hub_address, + uint32_t port, usb_speed_t speed) { + zx_status_t result = xhci_address_device(xhci, slot_id, hub_address, port, speed); if (result != ZX_OK) { - goto disable_slot_exit; + return result; } + // Let SET_ADDRESS settle down zx_nanosleep(zx_deadline_after(ZX_MSEC(10))); // read first 8 bytes of device descriptor to fetch ep0 max packet size @@ -325,13 +324,16 @@ static zx_status_t xhci_handle_enumerate_device(xhci_t* xhci, uint32_t hub_addre &device_descriptor, 8, &actual); if (result == ZX_ERR_IO_REFUSED || result == ZX_ERR_IO_INVALID) { xhci_reset_endpoint(xhci, slot_id, 0); + } else if (result != ZX_OK) { + // Try again. The device may be flaky or slow recovering. + continue; } else { break; } } if (actual != 8) { - zxlogf(ERROR, "xhci_handle_enumerate_device: xhci_get_descriptor failed: %d\n", result); - goto disable_slot_exit; + zxlogf(ERROR, "xhci_handle_enable_device: xhci_get_descriptor failed: %d\n", result); + return ZX_ERR_BAD_STATE; } int mps; @@ -375,7 +377,30 @@ static zx_status_t xhci_handle_enumerate_device(xhci_t* xhci, uint32_t hub_addre (slot_id << TRB_SLOT_ID_START)); mtx_unlock(&xhci->input_context_lock); if (result != ZX_OK) { - zxlogf(ERROR, "xhci_handle_enumerate_device: TRB_CMD_EVAL_CONTEXT failed\n"); + zxlogf(ERROR, "xhci_handle_enable_device: TRB_CMD_EVAL_CONTEXT failed\n"); + } + return result; +} + +static zx_status_t xhci_handle_enumerate_device(xhci_t* xhci, uint32_t hub_address, uint32_t port, + usb_speed_t speed) { + zxlogf(TRACE, "xhci_handle_enumerate_device hub_address:%d port %d\n", hub_address, port); + zx_status_t result = ZX_OK; + uint32_t slot_id = 0; + + xhci_sync_command_t command; + xhci_sync_command_init(&command); + xhci_post_command(xhci, TRB_CMD_ENABLE_SLOT, 0, 0, &command.context); + int cc = xhci_sync_command_wait(&command); + if (cc == TRB_CC_SUCCESS) { + slot_id = xhci_sync_command_slot_id(&command); + } else { + zxlogf(ERROR, "xhci_handle_enumerate_device: unable to get a slot\n"); + return ZX_ERR_NO_RESOURCES; + } + + result = xhci_setup_slot(xhci, slot_id, hub_address, port, speed); + if (result != ZX_OK) { goto disable_slot_exit; } @@ -388,11 +413,42 @@ disable_slot_exit: return result; } +static zx_status_t xhci_free_endpoint_state(xhci_t* xhci, xhci_slot_t* slot, xhci_endpoint_t* ep, + zx_status_t complete_status) { + xhci_transfer_ring_t* transfer_ring = &ep->transfer_ring; + + mtx_lock(&ep->lock); + if (ep->state != EP_STATE_DISABLED && ep->state != EP_STATE_DEAD) { + mtx_unlock(&ep->lock); + return ZX_ERR_BAD_STATE; + } + mtx_unlock(&ep->lock); + + free(ep->transfer_state); + ep->transfer_state = nullptr; + xhci_transfer_ring_free(transfer_ring); + + // complete any remaining requests + usb_request_t* req = nullptr; + xhci_usb_request_internal_t* req_int = nullptr; + while ((req_int = list_remove_head_type(&ep->pending_reqs, + xhci_usb_request_internal_t, node)) != nullptr) { + req = XHCI_INTERNAL_TO_USB_REQ(req_int); + usb_request_complete(req, complete_status, 0, &req_int->complete_cb); + } + while ((req_int = list_remove_head_type(&ep->queued_reqs, + xhci_usb_request_internal_t, node)) != nullptr) { + req = XHCI_INTERNAL_TO_USB_REQ(req_int); + usb_request_complete(req, complete_status, 0, &req_int->complete_cb); + } + + return ZX_OK; +} + static zx_status_t xhci_stop_endpoint(xhci_t* xhci, uint32_t slot_id, int ep_index, xhci_ep_state_t new_state, zx_status_t complete_status) { xhci_slot_t* slot = &xhci->slots[slot_id]; xhci_endpoint_t* ep = &slot->eps[ep_index]; - xhci_transfer_ring_t* transfer_ring = &ep->transfer_ring; if (new_state != EP_STATE_DISABLED && new_state != EP_STATE_DEAD) { ZX_DEBUG_ASSERT_MSG(false, "xhci_stop_endpoint: bad state argument %d\n", new_state); @@ -420,29 +476,13 @@ static zx_status_t xhci_stop_endpoint(xhci_t* xhci, uint32_t slot_id, int ep_ind return ZX_ERR_INTERNAL; } - free(ep->transfer_state); - ep->transfer_state = nullptr; - xhci_transfer_ring_free(transfer_ring); - - // complete any remaining requests - usb_request_t* req = nullptr; - xhci_usb_request_internal_t* req_int = nullptr; - while ((req_int = list_remove_head_type(&ep->pending_reqs, - xhci_usb_request_internal_t, node)) != nullptr) { - req = XHCI_INTERNAL_TO_USB_REQ(req_int); - usb_request_complete(req, complete_status, 0, &req_int->complete_cb); - } - while ((req_int = list_remove_head_type(&ep->queued_reqs, - xhci_usb_request_internal_t, node)) != nullptr) { - req = XHCI_INTERNAL_TO_USB_REQ(req_int); - usb_request_complete(req, complete_status, 0, &req_int->complete_cb); - } - - return ZX_OK; + return xhci_free_endpoint_state(xhci, slot, ep, complete_status); } -static zx_status_t xhci_handle_disconnect_device(xhci_t* xhci, uint32_t hub_address, uint32_t port) { - zxlogf(TRACE, "xhci_handle_disconnect_device\n"); +// Returns the slot for the given |hub_address| and |port|, or nullptr if no such slot exists. +// The slot id will be stored in |out_slot_id|. +static xhci_slot_t* xhci_get_slot(xhci_t* xhci, uint32_t hub_address, uint32_t port, + uint32_t* out_slot_id) { xhci_slot_t* slot = nullptr; uint32_t slot_id; @@ -461,6 +501,16 @@ static zx_status_t xhci_handle_disconnect_device(xhci_t* xhci, uint32_t hub_addr break; } } + if (slot) { + *out_slot_id = slot_id; + } + return slot; +} + +static zx_status_t xhci_handle_disconnect_device(xhci_t* xhci, uint32_t hub_address, uint32_t port) { + zxlogf(TRACE, "xhci_handle_disconnect_device\n"); + uint32_t slot_id; + xhci_slot_t* slot = xhci_get_slot(xhci, hub_address, port, &slot_id); if (!slot) { zxlogf(ERROR, "slot not found in xhci_handle_disconnect_device\n"); return ZX_ERR_NOT_FOUND; @@ -468,7 +518,7 @@ static zx_status_t xhci_handle_disconnect_device(xhci_t* xhci, uint32_t hub_addr uint32_t drop_flags = 0; for (int i = 0; i < XHCI_NUM_EPS; i++) { - if (slot->eps[i].state != EP_STATE_DEAD) { + if (slot->eps[i].state != EP_STATE_DEAD && slot->eps[i].state != EP_STATE_DISABLED) { zx_status_t status = xhci_stop_endpoint(xhci, slot_id, i, EP_STATE_DEAD, ZX_ERR_IO_NOT_PRESENT); if (status != ZX_OK) { @@ -499,6 +549,50 @@ static zx_status_t xhci_handle_disconnect_device(xhci_t* xhci, uint32_t hub_addr return ZX_OK; } +static zx_status_t xhci_handle_reset_device(xhci_t* xhci, uint32_t hub_address, uint32_t port) { + zxlogf(TRACE, "xhci_handle_reset_device %u %u\n", hub_address, port); + zx_status_t result; + uint32_t slot_id; + xhci_slot_t* slot = xhci_get_slot(xhci, hub_address, port, &slot_id); + if (!slot) { + zxlogf(ERROR, "slot not found in xhci_handle_reset_device\n"); + result = ZX_ERR_NOT_FOUND; + goto done; + } + result = xhci_send_command(xhci, TRB_CMD_RESET_DEVICE, 0, (slot_id << TRB_SLOT_ID_START)); + if (result != ZX_OK) { + zxlogf(ERROR, "xhci_handle_reset_device: TRB_CMD_RESET_DEVICE failed\n"); + goto done; + } + + // TRB_CMD_RESET_DEVICE disables all endpoints except the control endpoint. + for (int i = 1; i < XHCI_NUM_EPS; i++) { + xhci_endpoint_t* ep = &slot->eps[i]; + + mtx_lock(&ep->lock); + ep->state = EP_STATE_DISABLED; + mtx_unlock(&ep->lock); + + result = xhci_free_endpoint_state(xhci, slot, ep, ZX_ERR_IO_NOT_PRESENT); + if (result != ZX_OK) { + zxlogf(ERROR, "xhci_free_endpoint_state failed slot %u ep %u, err: %d\n", + result, slot_id, i); + } + } + // The slot is now in the Default state and we need to address it again. + result = xhci_setup_slot(xhci, slot_id, hub_address, port, slot->speed); + if (result != ZX_OK) { + zxlogf(ERROR, "xhci_handle_reset_device: xhci_setup_slot failed: %d\n", result); + goto done; + } + zxlogf(TRACE, "xhci_handle_reset_device %u %u successful\n", hub_address, port); + +done: + // We should always call this so we can update the device state. + usb_bus_interface_reinitialize_device(&xhci->bus, slot_id); + return result; +} + static int xhci_device_thread(void* arg) { auto* xhci = static_cast<xhci_t*>(arg); @@ -530,6 +624,9 @@ static int xhci_device_thread(void* arg) { case DISCONNECT_DEVICE: xhci_handle_disconnect_device(xhci, command->hub_address, command->port); break; + case RESET_DEVICE: + xhci_handle_reset_device(xhci, command->hub_address, command->port); + break; case START_ROOT_HUBS: xhci_start_root_hubs(xhci); break; @@ -594,6 +691,10 @@ zx_status_t xhci_device_disconnected(xhci_t* xhci, uint32_t hub_address, uint32_ return xhci_queue_command(xhci, DISCONNECT_DEVICE, hub_address, port, USB_SPEED_UNDEFINED); } +zx_status_t xhci_device_reset(xhci_t* xhci, uint32_t hub_address, uint32_t port) { + return xhci_queue_command(xhci, RESET_DEVICE, hub_address, port, USB_SPEED_UNDEFINED); +} + zx_status_t xhci_queue_start_root_hubs(xhci_t* xhci) { return xhci_queue_command(xhci, START_ROOT_HUBS, 0, 0, USB_SPEED_UNDEFINED); } diff --git a/system/dev/usb/xhci/xhci-device-manager.h b/system/dev/usb/xhci/xhci-device-manager.h index 512427fa1b704451dac8ec104787c67ca7eac5f8..42744b4fdec5bbc3a96f291c3ed0c72074faafe9 100644 --- a/system/dev/usb/xhci/xhci-device-manager.h +++ b/system/dev/usb/xhci/xhci-device-manager.h @@ -13,6 +13,7 @@ typedef struct xhci xhci_t; zx_status_t xhci_enumerate_device(xhci_t* xhci, uint32_t hub_address, uint32_t port, usb_speed_t speed); zx_status_t xhci_device_disconnected(xhci_t* xhci, uint32_t hub_address, uint32_t port); +zx_status_t xhci_device_reset(xhci_t* xhci, uint32_t hub_address, uint32_t port); void xhci_start_device_thread(xhci_t* xhci); void xhci_stop_device_thread(xhci_t* xhci); zx_status_t xhci_queue_start_root_hubs(xhci_t* xhci);