| // Copyright 2019 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 "usb-root-hub.h" |
| |
| #include <lib/zx/time.h> |
| #include <zircon/status.h> |
| |
| #include <algorithm> |
| #include <memory> |
| #include <optional> |
| |
| #include <ddk/debug.h> |
| #include <soc/mt8167/mt8167-usb.h> |
| |
| namespace mt_usb_hci { |
| namespace regs = board_mt8167; |
| |
| void HubPort::Connect() { |
| // We need to atomically both update the port status bits and signal a port status change. |
| fbl::AutoLock _(&status_lock_); |
| |
| status_.wPortChange |= USB_C_PORT_CONNECTION; |
| status_.wPortStatus |= USB_PORT_CONNECTION | USB_PORT_ENABLE | USB_PORT_POWER; |
| connected_ = true; |
| |
| { |
| fbl::AutoLock _(&change_lock_); |
| change_.Signal(); |
| } |
| } |
| |
| void HubPort::Disconnect() { |
| fbl::AutoLock _(&status_lock_); |
| |
| status_.wPortChange |= USB_C_PORT_CONNECTION; |
| status_.wPortStatus &= static_cast<uint16_t>(~(USB_PORT_CONNECTION | USB_PORT_ENABLE)); |
| |
| connected_ = false; |
| |
| { |
| fbl::AutoLock _(&change_lock_); |
| change_.Broadcast(); |
| } |
| } |
| |
| void HubPort::Disable() { |
| fbl::AutoLock _(&status_lock_); |
| status_.wPortStatus &= static_cast<uint16_t>(~USB_PORT_ENABLE); |
| } |
| |
| void HubPort::Reset() { |
| fbl::AutoLock _(&status_lock_); |
| status_.wPortStatus |= USB_PORT_RESET; |
| |
| auto power = regs::POWER_HOST::Get().ReadFrom(&usb_); |
| power.set_hsenab(1).set_reset(1).WriteTo(&usb_); |
| // Controller spec. requires at least 20ms for speed negotiation. |
| zx::nanosleep(zx::deadline_after(zx::msec(25))); |
| power.set_reset(0).WriteTo(&usb_); |
| |
| // Determine the controller's post-reset negotiated speed. |
| power = regs::POWER_HOST::Get().ReadFrom(&usb_); |
| auto devctl = regs::DEVCTL::Get().ReadFrom(&usb_); |
| if (devctl.lsdev()) { // Low-speed mode. |
| status_.wPortStatus &= static_cast<uint16_t>(~USB_PORT_HIGH_SPEED); |
| status_.wPortStatus |= USB_PORT_LOW_SPEED; |
| } else if (power.hsmode()) { // High-speed mode. |
| status_.wPortStatus &= static_cast<uint16_t>(~USB_PORT_LOW_SPEED); |
| status_.wPortStatus |= USB_PORT_HIGH_SPEED; |
| } else { // Full-speed mode. |
| status_.wPortStatus &= static_cast<uint16_t>(~USB_PORT_LOW_SPEED); |
| status_.wPortStatus &= static_cast<uint16_t>(~USB_PORT_HIGH_SPEED); |
| } |
| |
| // See: 11.24.2.13 (USB 2.0 spec) |
| status_.wPortStatus |= USB_PORT_ENABLE; |
| status_.wPortStatus &= static_cast<uint16_t>(~USB_PORT_RESET); |
| status_.wPortChange |= USB_C_PORT_RESET; |
| } |
| |
| void HubPort::PowerOff() { |
| fbl::AutoLock _(&status_lock_); |
| status_.wPortStatus &= static_cast<uint16_t>(~USB_PORT_POWER); |
| } |
| |
| void HubPort::PowerOn() { |
| fbl::AutoLock _(&status_lock_); |
| status_.wPortStatus |= USB_PORT_POWER; |
| } |
| |
| void HubPort::Suspend() { |
| fbl::AutoLock _(&status_lock_); |
| status_.wPortStatus |= USB_PORT_SUSPEND; |
| } |
| |
| void HubPort::Resume() { |
| fbl::AutoLock _(&status_lock_); |
| status_.wPortStatus &= static_cast<uint16_t>(~USB_PORT_SUSPEND); |
| } |
| |
| void HubPort::ClearChangeBits(int mask) { |
| fbl::AutoLock _(&status_lock_); |
| status_.wPortChange &= static_cast<uint16_t>(~mask); |
| } |
| |
| void HubPort::Wait() { |
| if (!(status().wPortChange & USB_C_PORT_CONNECTION)) { |
| // Wait only if there is no outstanding port status change. |
| fbl::AutoLock _(&change_lock_); |
| change_.Wait(&change_lock_); |
| } |
| } |
| |
| zx_status_t UsbRootHub::HandleRequest(usb::BorrowedRequest<> req) { |
| uint8_t ep_addr = static_cast<uint8_t>(req.request()->header.ep_address & 0xf); |
| |
| if (ep_addr > 1) { // A USB hub only suports two endpoints: control and interrupt. |
| zxlogf(ERROR, "unsupported hub endpoint address: %d", ep_addr); |
| req.Complete(ZX_ERR_INTERNAL, 0); |
| return ZX_ERR_INTERNAL; |
| } |
| |
| if (ep_addr == 0) { // Endpoint-0 control transfers. |
| switch (req.request()->setup.bRequest) { |
| case USB_REQ_GET_DESCRIPTOR: |
| return GetDescriptor(std::move(req)); |
| case USB_REQ_SET_CONFIGURATION: |
| return SetConfiguration(std::move(req)); |
| case USB_REQ_SET_FEATURE: |
| return SetFeature(std::move(req)); |
| case USB_REQ_GET_STATUS: |
| return GetStatus(std::move(req)); |
| case USB_REQ_CLEAR_FEATURE: |
| return ClearFeature(std::move(req)); |
| default: |
| zxlogf(ERROR, "unsupported device request: 0x%02x", req.request()->setup.bRequest); |
| req.Complete(ZX_ERR_NOT_SUPPORTED, 0); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| } else { // Endpoint-1 port-status interrupt transfers. |
| endpoint_queue_.push(std::move(req)); |
| |
| // Defer endpoint completion until we know we have activity on the port. |
| auto t = [](void* arg) { return static_cast<UsbRootHub*>(arg)->EndpointHandlerThread(); }; |
| auto status = thrd_create_with_name(&endpoint_thread_, t, this, "hub_endpoint_thread"); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "root hub thread init error: %s", zx_status_get_string(status)); |
| req.Complete(status, 0); |
| return status; |
| } |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t UsbRootHub::PortConnect() { |
| port_.Connect(); |
| return ZX_OK; |
| } |
| |
| zx_status_t UsbRootHub::PortDisconnect() { |
| port_.Disconnect(); |
| return ZX_OK; |
| } |
| |
| zx_status_t UsbRootHub::PortReset() { |
| port_.Reset(); |
| return ZX_OK; |
| } |
| |
| zx_status_t UsbRootHub::ClearFeature(usb::BorrowedRequest<> req) { |
| uint16_t index = le16toh(req.request()->setup.wIndex); |
| if (index != 1) { |
| zxlogf(ERROR, "unsupported ClearFeature() index: %d", index); |
| req.Complete(ZX_ERR_OUT_OF_RANGE, 0); |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| |
| uint8_t bm_req_type = req.request()->setup.bmRequestType; |
| switch (bm_req_type) { |
| case 0x20: // See: 11.24.2 (USB 2.0 spec) |
| return ClearHubFeature(std::move(req)); |
| case 0x23: // See: 11.24.2 (USB 2.0 spec) |
| return ClearPortFeature(std::move(req)); |
| default: |
| zxlogf(ERROR, "unsupported ClearFeature() request type: 0x%02x", bm_req_type); |
| req.Complete(ZX_ERR_NOT_SUPPORTED, 0); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t UsbRootHub::ClearHubFeature(usb::BorrowedRequest<> req) { |
| // Currently hub-level features are not supported. |
| zx_status_t status = ZX_ERR_NOT_SUPPORTED; |
| req.Complete(status, 0); |
| return status; |
| } |
| |
| zx_status_t UsbRootHub::ClearPortFeature(usb::BorrowedRequest<> req) { |
| uint16_t feature = static_cast<uint16_t>(letoh16(req.request()->setup.wValue)); |
| |
| switch (feature) { |
| case USB_FEATURE_PORT_ENABLE: |
| port_.Disable(); |
| break; |
| case USB_FEATURE_PORT_SUSPEND: |
| port_.Resume(); |
| break; |
| case USB_FEATURE_PORT_POWER: |
| port_.PowerOff(); |
| break; |
| case USB_FEATURE_C_PORT_CONNECTION: |
| port_.ClearChangeBits(USB_C_PORT_CONNECTION); |
| break; |
| case USB_FEATURE_C_PORT_RESET: |
| port_.ClearChangeBits(USB_C_PORT_RESET); |
| break; |
| case USB_FEATURE_C_PORT_ENABLE: |
| port_.ClearChangeBits(USB_C_PORT_ENABLE); |
| break; |
| case USB_FEATURE_C_PORT_SUSPEND: |
| port_.ClearChangeBits(USB_C_PORT_SUSPEND); |
| break; |
| case USB_FEATURE_C_PORT_OVER_CURRENT: |
| port_.ClearChangeBits(USB_C_PORT_OVER_CURRENT); |
| break; |
| default: |
| zxlogf(ERROR, "unsupported ClearFeature() selector: 0x%02x", feature); |
| req.Complete(ZX_ERR_INVALID_ARGS, 0); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| req.Complete(ZX_OK, 0); |
| return ZX_OK; |
| } |
| |
| zx_status_t UsbRootHub::GetDescriptor(usb::BorrowedRequest<> req) { |
| uint8_t type = static_cast<uint8_t>(req.request()->setup.wValue >> 8); |
| |
| switch (type) { |
| case USB_DT_DEVICE: |
| return GetDeviceDescriptor(std::move(req)); |
| case USB_DT_CONFIG: |
| return GetConfigDescriptor(std::move(req)); |
| case USB_DT_STRING: |
| return GetStringDescriptor(std::move(req)); |
| case USB_HUB_DESC_TYPE: // HUB-class descriptor. |
| return GetHubDescriptor(std::move(req)); |
| default: |
| zxlogf(ERROR, "unsupported GetDescriptor() descriptor type: 0x%02x", type); |
| req.Complete(ZX_ERR_NOT_SUPPORTED, 0); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t UsbRootHub::GetDeviceDescriptor(usb::BorrowedRequest<> req) { |
| uint16_t len = le16toh(req.request()->setup.wLength); |
| ZX_ASSERT(len <= sizeof(usb_device_descriptor_t)); |
| ssize_t actual = req.CopyTo(&device_descriptor_, len, 0); |
| req.Complete(ZX_OK, actual); |
| return ZX_OK; |
| } |
| |
| zx_status_t UsbRootHub::GetConfigDescriptor(usb::BorrowedRequest<> req) { |
| uint8_t index = static_cast<uint8_t>(req.request()->setup.wValue & 0xff); |
| size_t len = static_cast<size_t>(le16toh(req.request()->setup.wLength)); |
| |
| if (index > 0) { |
| req.Complete(ZX_ERR_OUT_OF_RANGE, 0); |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| |
| size_t w_total_len = static_cast<size_t>(le16toh(config_descriptor_.config.wTotalLength)); |
| len = std::min(len, w_total_len); |
| |
| ssize_t actual = req.CopyTo(&config_descriptor_, len, 0); |
| req.Complete(ZX_OK, actual); |
| return ZX_OK; |
| } |
| |
| zx_status_t UsbRootHub::GetStringDescriptor(usb::BorrowedRequest<> req) { |
| uint8_t index = static_cast<uint8_t>(req.request()->setup.wValue & 0xff); |
| size_t len = static_cast<size_t>(le16toh(req.request()->setup.wLength)); |
| |
| if (index >= countof(string_descriptor_)) { |
| req.Complete(ZX_ERR_OUT_OF_RANGE, 0); |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| |
| size_t b_len = static_cast<size_t>(string_descriptor_[index]->bLength); |
| len = std::min(len, b_len); |
| |
| ssize_t actual = req.CopyTo(string_descriptor_[index], len, 0); |
| req.Complete(ZX_OK, actual); |
| return ZX_OK; |
| } |
| |
| zx_status_t UsbRootHub::GetHubDescriptor(usb::BorrowedRequest<> req) { |
| uint16_t len = le16toh(req.request()->setup.wLength); |
| ZX_ASSERT(len <= sizeof(usb_hub_descriptor_t)); |
| ssize_t actual = req.CopyTo(&hub_descriptor_, len, 0); |
| req.Complete(ZX_OK, actual); |
| return ZX_OK; |
| } |
| |
| zx_status_t UsbRootHub::GetStatus(usb::BorrowedRequest<> req) { |
| uint8_t bm_req_type = req.request()->setup.bmRequestType; |
| switch (bm_req_type) { |
| case 0xa0: // See: 11.24.2 (USB 2.0 spec) |
| return GetHubStatus(std::move(req)); |
| break; |
| case 0xa3: // See: 11.24.2 (USB 2.0 spec) |
| return GetPortStatus(std::move(req)); |
| break; |
| default: |
| zxlogf(ERROR, "unsupported GetStatus() request type: 0x%02x", bm_req_type); |
| req.Complete(ZX_ERR_NOT_SUPPORTED, 0); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t UsbRootHub::GetPortStatus(usb::BorrowedRequest<> req) { |
| ssize_t actual = req.CopyTo(&port_.status(), sizeof(usb_port_status_t), 0); |
| req.Complete(ZX_OK, actual); |
| return ZX_OK; |
| } |
| |
| zx_status_t UsbRootHub::GetHubStatus(usb::BorrowedRequest<> req) { |
| // Currently hub-level status is not supported. |
| zx_status_t status = ZX_ERR_NOT_SUPPORTED; |
| req.Complete(status, 0); |
| return status; |
| } |
| |
| zx_status_t UsbRootHub::SetConfiguration(usb::BorrowedRequest<> req) { |
| uint8_t index = static_cast<uint8_t>(req.request()->setup.wValue & 0xff); |
| if (index != 1) { |
| zxlogf(ERROR, "unsupported SetConfiguration() index: %d", index); |
| req.Complete(ZX_ERR_OUT_OF_RANGE, 0); |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| |
| // This is a no-op for the hub. |
| req.Complete(ZX_OK, 0); |
| return ZX_OK; |
| } |
| |
| zx_status_t UsbRootHub::SetFeature(usb::BorrowedRequest<> req) { |
| uint16_t index = le16toh(req.request()->setup.wIndex); |
| if (index != 1) { |
| zxlogf(ERROR, "unsupported SetFeature() index: %d", index); |
| req.Complete(ZX_ERR_OUT_OF_RANGE, 0); |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| |
| uint8_t bm_req_type = req.request()->setup.bmRequestType; |
| switch (bm_req_type) { |
| case 0x20: // See: 11.24.2 (USB 2.0 spec) |
| return SetHubFeature(std::move(req)); |
| case 0x23: // See: 11.24.2 (USB 2.0 spec) |
| return SetPortFeature(std::move(req)); |
| default: |
| zxlogf(ERROR, "unsupported SetFeature() request type: 0x%02x", bm_req_type); |
| req.Complete(ZX_ERR_NOT_SUPPORTED, 0); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t UsbRootHub::SetHubFeature(usb::BorrowedRequest<> req) { |
| // Currently hub-level features are not supported. |
| zx_status_t status = ZX_ERR_NOT_SUPPORTED; |
| req.Complete(status, 0); |
| return status; |
| } |
| |
| zx_status_t UsbRootHub::SetPortFeature(usb::BorrowedRequest<> req) { |
| uint16_t feature = static_cast<uint16_t>(letoh16(req.request()->setup.wValue)); |
| |
| switch (feature) { |
| case USB_FEATURE_PORT_RESET: |
| port_.Reset(); |
| break; |
| case USB_FEATURE_PORT_SUSPEND: |
| port_.Suspend(); |
| break; |
| case USB_FEATURE_PORT_POWER: |
| port_.PowerOn(); |
| break; |
| default: |
| zxlogf(ERROR, "unsupported SetFeature() selector: 0x%02x", feature); |
| req.Complete(ZX_ERR_INVALID_ARGS, 0); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| req.Complete(ZX_OK, 0); |
| return ZX_OK; |
| } |
| |
| int UsbRootHub::EndpointHandlerThread() { |
| port_.Wait(); |
| std::optional<usb::BorrowedRequest<>> req = endpoint_queue_.pop(); |
| uint8_t status = 1 << 1; // Signal change to port-1 status, see: 11.12.4 (USB 2.0 spec) |
| ssize_t actual = req->CopyTo(&status, sizeof(uint8_t), 0); |
| req->Complete(ZX_OK, actual); |
| return 0; |
| } |
| |
| } // namespace mt_usb_hci |