blob: bedc095a1bb0fdb7dbfac710496dd0a1b401e742 [file] [log] [blame]
// 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