| // Copyright 2016 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <ddk/debug.h> |
| #include <usb/usb-request.h> |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <unistd.h> |
| |
| #include "xhci.h" |
| #include "xhci-device-manager.h" |
| |
| #define MANUFACTURER_STRING 1 |
| #define PRODUCT_STRING_2 2 |
| #define PRODUCT_STRING_3 3 |
| |
| static const uint8_t xhci_language_list[] = |
| { 4, /* bLength */ USB_DT_STRING, 0x09, 0x04, /* language ID */ }; |
| static const uint8_t xhci_manufacturer_string [] = // "Zircon" |
| { 16, /* bLength */ USB_DT_STRING, 'Z', 0, 'i', 0, 'r', 0, 'c', 0, 'o', 0, 'n', 0, 0, 0 }; |
| static const uint8_t xhci_product_string_2 [] = // "USB 2.0 Root Hub" |
| { 36, /* bLength */ USB_DT_STRING, 'U', 0, 'S', 0, 'B', 0, ' ', 0, '2', 0, '.', 0, '0', 0,' ', 0, |
| 'R', 0, 'o', 0, 'o', 0, 't', 0, ' ', 0, 'H', 0, 'u', 0, 'b', 0, 0, 0, }; |
| static const uint8_t xhci_product_string_3 [] = // "USB 3.0 Root Hub" |
| { 36, /* bLength */ USB_DT_STRING, 'U', 0, 'S', 0, 'B', 0, ' ', 0, '3', 0, '.', 0, '0', 0,' ', 0, |
| 'R', 0, 'o', 0, 'o', 0, 't', 0, ' ', 0, 'H', 0, 'u', 0, 'b', 0, 0, 0, }; |
| |
| static const uint8_t* xhci_rh_string_table[] = { |
| xhci_language_list, |
| xhci_manufacturer_string, |
| xhci_product_string_2, |
| xhci_product_string_3, |
| }; |
| |
| // device descriptor for USB 2.0 root hub |
| static const usb_device_descriptor_t xhci_rh_device_desc_2 = { |
| .bLength = sizeof(usb_device_descriptor_t), |
| .bDescriptorType = USB_DT_DEVICE, |
| .bcdUSB = htole16(0x0200), |
| .bDeviceClass = USB_CLASS_HUB, |
| .bDeviceSubClass = 0, |
| .bDeviceProtocol = 1, // Single TT |
| .bMaxPacketSize0 = 64, |
| .idVendor = htole16(0x18D1), |
| .idProduct = htole16(0xA002), |
| .bcdDevice = htole16(0x0100), |
| .iManufacturer = MANUFACTURER_STRING, |
| .iProduct = PRODUCT_STRING_2, |
| .iSerialNumber = 0, |
| .bNumConfigurations = 1, |
| }; |
| |
| // device descriptor for USB 3.1 root hub |
| static const usb_device_descriptor_t xhci_rh_device_desc_3 = { |
| .bLength = sizeof(usb_device_descriptor_t), |
| .bDescriptorType = USB_DT_DEVICE, |
| .bcdUSB = htole16(0x0300), |
| .bDeviceClass = USB_CLASS_HUB, |
| .bDeviceSubClass = 0, |
| .bDeviceProtocol = 1, // Single TT |
| .bMaxPacketSize0 = 64, |
| .idVendor = htole16(0x18D1), |
| .idProduct = htole16(0xA003), |
| .bcdDevice = htole16(0x0100), |
| .iManufacturer = MANUFACTURER_STRING, |
| .iProduct = PRODUCT_STRING_3, |
| .iSerialNumber = 0, |
| .bNumConfigurations = 1, |
| }; |
| |
| // device descriptors for our virtual root hub devices |
| static const usb_device_descriptor_t* xhci_rh_device_descs[] = { |
| (usb_device_descriptor_t *)&xhci_rh_device_desc_2, |
| (usb_device_descriptor_t *)&xhci_rh_device_desc_3, |
| }; |
| |
| // we are currently using the same configuration descriptors for both USB 2.0 and 3.0 root hubs |
| // this is not actually correct, but our usb-hub driver isn't sophisticated enough to notice |
| static const struct { |
| usb_configuration_descriptor_t config; |
| usb_interface_descriptor_t intf; |
| usb_endpoint_descriptor_t endp; |
| } xhci_rh_config_desc = { |
| .config = { |
| .bLength = sizeof(usb_configuration_descriptor_t), |
| .bDescriptorType = USB_DT_CONFIG, |
| .wTotalLength = htole16(sizeof(xhci_rh_config_desc)), |
| .bNumInterfaces = 1, |
| .bConfigurationValue = 1, |
| .iConfiguration = 0, |
| .bmAttributes = 0xE0, // self powered |
| .bMaxPower = 0, |
| }, |
| .intf = { |
| .bLength = sizeof(usb_interface_descriptor_t), |
| .bDescriptorType = USB_DT_INTERFACE, |
| .bInterfaceNumber = 0, |
| .bAlternateSetting = 0, |
| .bNumEndpoints = 1, |
| .bInterfaceClass = USB_CLASS_HUB, |
| .bInterfaceSubClass = 0, |
| .bInterfaceProtocol = 0, |
| .iInterface = 0, |
| }, |
| .endp = { |
| .bLength = sizeof(usb_endpoint_descriptor_t), |
| .bDescriptorType = USB_DT_ENDPOINT, |
| .bEndpointAddress = USB_ENDPOINT_IN | 1, |
| .bmAttributes = USB_ENDPOINT_INTERRUPT, |
| .wMaxPacketSize = htole16(4), |
| .bInterval = 12, |
| }, |
| }; |
| |
| // speeds for our virtual root hub devices |
| static const usb_speed_t xhci_rh_speeds[] = { |
| USB_SPEED_HIGH, |
| USB_SPEED_SUPER, |
| }; |
| |
| static void print_portsc(int port, uint32_t portsc) { |
| zxlogf(SPEW, "port %d:", port); |
| if (portsc & PORTSC_CCS) zxlogf(SPEW, " CCS"); |
| if (portsc & PORTSC_PED) zxlogf(SPEW, " PED"); |
| if (portsc & PORTSC_OCA) zxlogf(SPEW, " OCA"); |
| if (portsc & PORTSC_PR) zxlogf(SPEW, " PR"); |
| uint32_t pls = (portsc >> PORTSC_PLS_START) & ((1 << PORTSC_PLS_BITS) - 1); |
| switch (pls) { |
| case 0: |
| zxlogf(SPEW, " U0"); |
| break; |
| case 1: |
| zxlogf(SPEW, " U1"); |
| break; |
| case 2: |
| zxlogf(SPEW, " U2"); |
| break; |
| case 3: |
| zxlogf(SPEW, " U3"); |
| break; |
| case 4: |
| zxlogf(SPEW, " Disabled"); |
| break; |
| case 5: |
| zxlogf(SPEW, " RxDetect"); |
| break; |
| case 6: |
| zxlogf(SPEW, " Inactive"); |
| break; |
| case 7: |
| zxlogf(SPEW, " Polling"); |
| break; |
| case 8: |
| zxlogf(SPEW, " Recovery"); |
| break; |
| case 9: |
| zxlogf(SPEW, " Hot Reset"); |
| break; |
| case 10: |
| zxlogf(SPEW, " Compliance Mode"); |
| break; |
| case 11: |
| zxlogf(SPEW, " Test Mode"); |
| break; |
| case 15: |
| zxlogf(SPEW, " Resume"); |
| break; |
| default: |
| zxlogf(SPEW, " PLS%d", pls); |
| break; |
| } |
| if (portsc & PORTSC_PP) zxlogf(SPEW, " PP"); |
| uint32_t speed = (portsc >> PORTSC_SPEED_START) & ((1 << PORTSC_SPEED_BITS) - 1); |
| switch (speed) { |
| case 1: |
| zxlogf(SPEW, " FULL_SPEED"); |
| break; |
| case 2: |
| zxlogf(SPEW, " LOW_SPEED"); |
| break; |
| case 3: |
| zxlogf(SPEW, " HIGH_SPEED"); |
| break; |
| case 4: |
| zxlogf(SPEW, " SUPER_SPEED"); |
| break; |
| } |
| uint32_t pic = (portsc >> PORTSC_PIC_START) & ((1 << PORTSC_PIC_BITS) - 1); |
| zxlogf(SPEW, " PIC%d", pic); |
| if (portsc & PORTSC_LWS) zxlogf(SPEW, " LWS"); |
| if (portsc & PORTSC_CSC) zxlogf(SPEW, " CSC"); |
| if (portsc & PORTSC_PEC) zxlogf(SPEW, " PEC"); |
| if (portsc & PORTSC_WRC) zxlogf(SPEW, " WRC"); |
| if (portsc & PORTSC_OCC) zxlogf(SPEW, " OCC"); |
| if (portsc & PORTSC_PRC) zxlogf(SPEW, " PRC"); |
| if (portsc & PORTSC_PLC) zxlogf(SPEW, " PLC"); |
| if (portsc & PORTSC_CEC) zxlogf(SPEW, " CEC"); |
| if (portsc & PORTSC_CAS) zxlogf(SPEW, " CAS"); |
| if (portsc & PORTSC_WCE) zxlogf(SPEW, " WCE"); |
| if (portsc & PORTSC_WDE) zxlogf(SPEW, " WDE"); |
| if (portsc & PORTSC_WOE) zxlogf(SPEW, " WOE"); |
| if (portsc & PORTSC_DR) zxlogf(SPEW, " DR"); |
| if (portsc & PORTSC_WPR) zxlogf(SPEW, " WPR"); |
| zxlogf(SPEW, "\n"); |
| } |
| |
| static void xhci_reset_port(xhci_t* xhci, xhci_root_hub_t* rh, int rh_port_index) { |
| volatile uint32_t* portsc = &xhci->op_regs->port_regs[rh_port_index].portsc; |
| uint32_t temp = XHCI_READ32(portsc); |
| temp = (temp & PORTSC_CONTROL_BITS) | PORTSC_PR; |
| if (rh->speed == USB_SPEED_SUPER) { |
| temp |= PORTSC_WPR; |
| } |
| XHCI_WRITE32(portsc, temp); |
| |
| int port_index = xhci->rh_port_map[rh_port_index]; |
| usb_port_status_t* status = &rh->port_status[port_index]; |
| status->wPortStatus |= USB_PORT_RESET; |
| status->wPortChange |= USB_C_PORT_RESET; |
| } |
| |
| zx_status_t xhci_root_hub_init(xhci_t* xhci, int rh_index) { |
| xhci_root_hub_t* rh = &xhci->root_hubs[rh_index]; |
| const uint8_t* rh_port_map = xhci->rh_map; |
| uint8_t rh_ports = xhci->rh_num_ports; |
| |
| list_initialize(&rh->pending_intr_reqs); |
| |
| rh->device_desc = xhci_rh_device_descs[rh_index]; |
| rh->config_desc = (usb_configuration_descriptor_t *)&xhci_rh_config_desc; |
| |
| // first count number of ports |
| uint8_t port_count = 0; |
| for (size_t i = 0; i < rh_ports; i++) { |
| if (rh_port_map[i] == rh_index) { |
| port_count++; |
| } |
| } |
| rh->num_ports = port_count; |
| |
| rh->port_status = (usb_port_status_t *)calloc(port_count, sizeof(usb_port_status_t)); |
| if (!rh->port_status) return ZX_ERR_NO_MEMORY; |
| |
| rh->port_map = static_cast<uint8_t*>(calloc(port_count, sizeof(uint8_t))); |
| if (!rh->port_map) return ZX_ERR_NO_MEMORY; |
| |
| // build map from virtual port index to actual port index |
| uint8_t port_index = 0; |
| for (uint8_t i = 0; i < rh_ports; i++) { |
| if (rh_port_map[i] == rh_index) { |
| xhci->rh_port_map[i] = port_index; |
| rh->port_map[port_index] = i; |
| port_index++; |
| } |
| } |
| |
| return ZX_OK; |
| } |
| |
| void xhci_root_hub_free(xhci_root_hub_t* rh) { |
| free(rh->port_map); |
| free(rh->port_status); |
| } |
| |
| static zx_status_t xhci_start_root_hub(xhci_t* xhci, xhci_root_hub_t* rh, int rh_index) { |
| auto* device_desc = |
| static_cast<usb_device_descriptor_t*>(malloc(sizeof(usb_device_descriptor_t))); |
| if (!device_desc) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| auto* config_desc = |
| static_cast<usb_configuration_descriptor_t*>(malloc(sizeof(xhci_rh_config_desc))); |
| if (!config_desc) { |
| free(device_desc); |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| memcpy(device_desc, rh->device_desc, sizeof(usb_device_descriptor_t)); |
| memcpy(config_desc, rh->config_desc, le16toh(rh->config_desc->wTotalLength)); |
| rh->speed = xhci_rh_speeds[rh_index]; |
| |
| // Notify bus driver that our emulated hub exists |
| return xhci_add_device(xhci, xhci->max_slots + rh_index + 1, 0, rh->speed); |
| } |
| |
| zx_status_t xhci_start_root_hubs(xhci_t* xhci) { |
| zxlogf(TRACE, "xhci_start_root_hubs\n"); |
| |
| for (int i = 0; i < XHCI_RH_COUNT; i++) { |
| zx_status_t status = xhci_start_root_hub(xhci, &xhci->root_hubs[i], i); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "xhci_start_root_hub(%d) failed: %d\n", i, status); |
| return status; |
| } |
| } |
| |
| return ZX_OK; |
| } |
| |
| void xhci_stop_root_hubs(xhci_t* xhci) { |
| zxlogf(TRACE, "xhci_stop_root_hubs\n"); |
| |
| volatile xhci_port_regs_t* port_regs = xhci->op_regs->port_regs; |
| for (uint8_t i = 0; i < xhci->rh_num_ports; i++) { |
| uint32_t portsc = XHCI_READ32(&port_regs[i].portsc); |
| portsc &= PORTSC_CONTROL_BITS; |
| portsc |= PORTSC_PED; // disable port |
| portsc &= PORTSC_PP; // power off port |
| XHCI_WRITE32(&port_regs[i].portsc, portsc); |
| } |
| |
| for (int i = 0; i < XHCI_RH_COUNT; i++) { |
| usb_request_t* req; |
| xhci_usb_request_internal_t* req_int = nullptr; |
| xhci_root_hub_t* rh = &xhci->root_hubs[i]; |
| while ((req_int = list_remove_tail_type(&rh->pending_intr_reqs, |
| xhci_usb_request_internal_t, node)) != nullptr) { |
| req = XHCI_INTERNAL_TO_USB_REQ(req_int); |
| usb_request_complete_new(req, ZX_ERR_IO_NOT_PRESENT, 0, &req_int->complete_cb); |
| } |
| } |
| } |
| |
| static zx_status_t xhci_rh_get_descriptor(xhci_t* xhci, uint8_t request_type, xhci_root_hub_t* rh, |
| uint16_t value, uint16_t index, size_t length, |
| usb_request_t* req) { |
| uint8_t type = request_type & USB_TYPE_MASK; |
| uint8_t recipient = request_type & USB_RECIP_MASK; |
| xhci_usb_request_internal_t* req_int = USB_REQ_TO_XHCI_INTERNAL(req); |
| |
| if (type == USB_TYPE_STANDARD && recipient == USB_RECIP_DEVICE) { |
| auto desc_type = static_cast<uint8_t>(value >> 8); |
| if (desc_type == USB_DT_DEVICE && index == 0) { |
| if (length > sizeof(usb_device_descriptor_t)) length = sizeof(usb_device_descriptor_t); |
| usb_request_copy_to(req, rh->device_desc, length, 0); |
| usb_request_complete_new(req, ZX_OK, length, &req_int->complete_cb); |
| return ZX_OK; |
| } else if (desc_type == USB_DT_CONFIG && index == 0) { |
| uint16_t desc_length = le16toh(rh->config_desc->wTotalLength); |
| if (length > desc_length) length = desc_length; |
| usb_request_copy_to(req, rh->config_desc, length, 0); |
| usb_request_complete_new(req, ZX_OK, length, &req_int->complete_cb); |
| return ZX_OK; |
| } else if (value >> 8 == USB_DT_STRING) { |
| auto string_index = static_cast<uint8_t>(value & 0xFF); |
| if (string_index < countof(xhci_rh_string_table)) { |
| const uint8_t* string = xhci_rh_string_table[string_index]; |
| if (length > string[0]) length = string[0]; |
| |
| usb_request_copy_to(req, string, length, 0); |
| usb_request_complete_new(req, ZX_OK, length, &req_int->complete_cb); |
| return ZX_OK; |
| } |
| } |
| } |
| else if (type == USB_TYPE_CLASS && recipient == USB_RECIP_DEVICE) { |
| if ((value == USB_HUB_DESC_TYPE_SS << 8 || value == USB_HUB_DESC_TYPE << 8) && index == 0) { |
| // return hub descriptor |
| usb_hub_descriptor_t desc; |
| memset(&desc, 0, sizeof(desc)); |
| desc.bDescLength = sizeof(desc); |
| desc.bDescriptorType = static_cast<uint8_t>(value >> 8); |
| desc.bNbrPorts = rh->num_ports; |
| desc.bPowerOn2PwrGood = 0; |
| // TODO - fill in other stuff. But usb-hub driver doesn't need anything else at this point. |
| |
| if (length > sizeof(desc)) length = sizeof(desc); |
| usb_request_copy_to(req, &desc, length, 0); |
| usb_request_complete_new(req, ZX_OK, length, &req_int->complete_cb); |
| return ZX_OK; |
| } |
| } |
| |
| zxlogf(ERROR, "xhci_rh_get_descriptor unsupported value: %d index: %d\n", value, index); |
| usb_request_complete_new(req, ZX_ERR_NOT_SUPPORTED, 0, &req_int->complete_cb); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| // handles control requests for virtual root hub devices |
| static zx_status_t xhci_rh_control(xhci_t* xhci, xhci_root_hub_t* rh, usb_setup_t* setup, |
| usb_request_t* req) { |
| uint8_t request_type = setup->bmRequestType; |
| uint8_t request = setup->bRequest; |
| uint16_t value = le16toh(setup->wValue); |
| uint16_t index = le16toh(setup->wIndex); |
| xhci_usb_request_internal_t* req_int = USB_REQ_TO_XHCI_INTERNAL(req); |
| |
| zxlogf(SPEW, "xhci_rh_control type: 0x%02X req: %d value: %d index: %d length: %d\n", |
| request_type, request, value, index, le16toh(setup->wLength)); |
| |
| if ((request_type & USB_DIR_MASK) == USB_DIR_IN && request == USB_REQ_GET_DESCRIPTOR) { |
| return xhci_rh_get_descriptor(xhci, request_type, rh, value, index, |
| le16toh(setup->wLength), req); |
| } else if ((request_type & ~USB_DIR_MASK) == (USB_TYPE_CLASS | USB_RECIP_PORT)) { |
| // index is 1-based port number |
| if (index < 1 || index > rh->num_ports) { |
| usb_request_complete_new(req, ZX_ERR_INVALID_ARGS, 0, &req_int->complete_cb); |
| return ZX_OK; |
| } |
| auto rh_port_index = rh->port_map[index - 1]; |
| auto port_index = static_cast<uint8_t>(index - 1); |
| |
| if (request == USB_REQ_SET_FEATURE) { |
| if (value == USB_FEATURE_PORT_POWER) { |
| // nothing to do - root hub ports are already powered |
| usb_request_complete_new(req, ZX_OK, 0, &req_int->complete_cb); |
| return ZX_OK; |
| } else if (value == USB_FEATURE_PORT_RESET) { |
| xhci_reset_port(xhci, rh, rh_port_index); |
| usb_request_complete_new(req, ZX_OK, 0, &req_int->complete_cb); |
| return ZX_OK; |
| } |
| } else if (request == USB_REQ_CLEAR_FEATURE) { |
| auto* change_bits = &rh->port_status[port_index].wPortChange; |
| |
| switch (value) { |
| case USB_FEATURE_C_PORT_CONNECTION: |
| *change_bits &= static_cast<uint16_t>(~USB_C_PORT_CONNECTION); |
| break; |
| case USB_FEATURE_C_PORT_ENABLE: |
| *change_bits &= static_cast<uint16_t>(~USB_C_PORT_ENABLE); |
| break; |
| case USB_FEATURE_C_PORT_SUSPEND: |
| *change_bits &= static_cast<uint16_t>(~USB_C_PORT_SUSPEND); |
| break; |
| case USB_FEATURE_C_PORT_OVER_CURRENT: |
| *change_bits &= static_cast<uint16_t>(~USB_C_PORT_OVER_CURRENT); |
| break; |
| case USB_FEATURE_C_PORT_RESET: |
| *change_bits &= static_cast<uint16_t>(~USB_C_PORT_RESET); |
| break; |
| } |
| |
| usb_request_complete_new(req, ZX_OK, 0, &req_int->complete_cb); |
| return ZX_OK; |
| } else if ((request_type & USB_DIR_MASK) == USB_DIR_IN && |
| request == USB_REQ_GET_STATUS && value == 0) { |
| usb_port_status_t* status = &rh->port_status[port_index]; |
| size_t length = req->header.length; |
| if (length > sizeof(*status)) length = sizeof(*status); |
| usb_request_copy_to(req, status, length, 0); |
| usb_request_complete_new(req, ZX_OK, length, &req_int->complete_cb); |
| return ZX_OK; |
| } |
| } else if (request_type == (USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE) && |
| request == USB_REQ_SET_CONFIGURATION && req->header.length == 0) { |
| // nothing to do here |
| usb_request_complete_new(req, ZX_OK, 0, &req_int->complete_cb); |
| return ZX_OK; |
| } |
| |
| zxlogf(ERROR, "unsupported root hub control request type: 0x%02X req: %d value: %d index: %d\n", |
| request_type, request, value, index); |
| |
| usb_request_complete_new(req, ZX_ERR_NOT_SUPPORTED, 0, &req_int->complete_cb); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| static void xhci_rh_handle_intr_req(xhci_t* xhci, xhci_root_hub_t* rh, usb_request_t* req) { |
| zxlogf(SPEW, "xhci_rh_handle_intr_req\n"); |
| uint8_t status_bits[128 / 8]; |
| bool have_status = 0; |
| uint8_t* ptr = status_bits; |
| int bit = 1; // 0 is for hub status, so start at bit 1 |
| |
| memset(status_bits, 0, sizeof(status_bits)); |
| |
| for (uint32_t i = 0; i < rh->num_ports; i++) { |
| usb_port_status_t* status = &rh->port_status[i]; |
| if (status->wPortChange) { |
| *ptr |= static_cast<uint8_t>(1 << bit); |
| have_status = true; |
| } |
| if (++bit == 8) { |
| ptr++; |
| bit = 0; |
| } |
| } |
| |
| if (have_status) { |
| size_t length = req->header.length; |
| xhci_usb_request_internal_t* req_int = USB_REQ_TO_XHCI_INTERNAL(req); |
| if (length > sizeof(status_bits)) length = sizeof(status_bits); |
| usb_request_copy_to(req, status_bits, length, 0); |
| usb_request_complete_new(req, ZX_OK, length, &req_int->complete_cb); |
| } else { |
| // queue transaction until we have something to report |
| xhci_add_to_list_tail(xhci, &rh->pending_intr_reqs, req); |
| } |
| } |
| |
| zx_status_t xhci_rh_usb_request_queue(xhci_t* xhci, usb_request_t* req, int rh_index) { |
| zxlogf(SPEW, "xhci_rh_usb_request_queue rh_index: %d\n", rh_index); |
| |
| xhci_root_hub_t* rh = &xhci->root_hubs[rh_index]; |
| xhci_usb_request_internal_t* req_int = USB_REQ_TO_XHCI_INTERNAL(req); |
| |
| uint8_t ep_index = xhci_endpoint_index(req->header.ep_address); |
| if (ep_index == 0) { |
| return xhci_rh_control(xhci, rh, &req->setup, req); |
| } else if (ep_index == 2) { |
| xhci_rh_handle_intr_req(xhci, rh, req); |
| return ZX_OK; |
| } |
| |
| usb_request_complete_new(req, ZX_ERR_NOT_SUPPORTED, 0, &req_int->complete_cb); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| void xhci_handle_root_hub_change(xhci_t* xhci) { |
| volatile xhci_port_regs_t* port_regs = xhci->op_regs->port_regs; |
| |
| zxlogf(TRACE, "xhci_handle_root_hub_change\n"); |
| |
| for (uint8_t i = 0; i < xhci->rh_num_ports; i++) { |
| uint32_t portsc = XHCI_READ32(&port_regs[i].portsc); |
| uint32_t speed = (portsc & XHCI_MASK(PORTSC_SPEED_START, PORTSC_SPEED_BITS)) >> PORTSC_SPEED_START; |
| uint32_t status_bits = portsc & PORTSC_STATUS_BITS; |
| |
| if (driver_get_log_flags() & DDK_LOG_SPEW) { |
| print_portsc(i, portsc); |
| } |
| |
| if (status_bits) { |
| bool connected = !!(portsc & PORTSC_CCS); |
| bool enabled = !!(portsc & PORTSC_PED); |
| |
| // set change bits to acknowledge |
| XHCI_WRITE32(&port_regs[i].portsc, (portsc & PORTSC_CONTROL_BITS) | status_bits); |
| |
| // map index to virtual root hub and port number |
| int rh_index = xhci->rh_map[i]; |
| xhci_root_hub_t* rh = &xhci->root_hubs[rh_index]; |
| int port_index = xhci->rh_port_map[i]; |
| usb_port_status_t* status = &rh->port_status[port_index]; |
| |
| if (portsc & PORTSC_CSC) { |
| // connect status change |
| zxlogf(TRACE, "port %d PORTSC_CSC connected: %d\n", i, connected); |
| if (connected) { |
| status->wPortStatus |= USB_PORT_CONNECTION; |
| } else { |
| if (status->wPortStatus & USB_PORT_ENABLE) { |
| status->wPortChange |= USB_C_PORT_ENABLE; |
| } |
| status->wPortStatus = 0; |
| } |
| status->wPortChange |= USB_C_PORT_CONNECTION; |
| } |
| if (portsc & PORTSC_PRC) { |
| // port reset change |
| zxlogf(TRACE, "port %d PORTSC_PRC enabled: %d\n", i, enabled); |
| if (enabled) { |
| status->wPortStatus &= static_cast<uint16_t>(~USB_PORT_RESET); |
| status->wPortChange |= USB_C_PORT_RESET; |
| if (!(status->wPortStatus & USB_PORT_ENABLE)) { |
| status->wPortStatus |= USB_PORT_ENABLE; |
| status->wPortChange |= USB_C_PORT_ENABLE; |
| } |
| |
| if (speed == USB_SPEED_LOW) { |
| status->wPortStatus |= USB_PORT_LOW_SPEED; |
| } else if (speed == USB_SPEED_HIGH) { |
| status->wPortStatus |= USB_PORT_HIGH_SPEED; |
| } |
| } |
| } |
| |
| if (status->wPortChange) { |
| usb_request_t* req; |
| if (xhci_remove_from_list_head(xhci, &rh->pending_intr_reqs, &req)) { |
| xhci_rh_handle_intr_req(xhci, rh, req); |
| } |
| } |
| } |
| } |
| } |