blob: 8280b7e9a559ee17caa9c3e1af263fe382cc39ff [file] [log] [blame]
// Copyright 2020 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.
#ifndef SRC_DEVICES_USB_DRIVERS_USB_HUB_USB_HUB_H_
#define SRC_DEVICES_USB_DRIVERS_USB_HUB_USB_HUB_H_
#include <fuchsia/hardware/usb/bus/cpp/banjo.h>
#include <fuchsia/hardware/usb/cpp/banjo.h>
#include <fuchsia/hardware/usb/hub/cpp/banjo.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/loop.h>
#include <lib/async/cpp/executor.h>
#include <lib/async/cpp/task.h>
#include <lib/ddk/debug.h>
#include <lib/ddk/driver.h>
#include <lib/fpromise/bridge.h>
#include <lib/fpromise/promise.h>
#include <lib/fpromise/result.h>
#include <lib/inspect/cpp/inspector.h>
#include <lib/sync/completion.h>
#include <lib/zx/result.h>
#include <zircon/compiler.h>
#include <zircon/errors.h>
#include <zircon/status.h>
#include <memory>
#include <vector>
#include <ddktl/device.h>
#include <fbl/array.h>
#include <fbl/auto_lock.h>
#include <fbl/hard_int.h>
#include <fbl/intrusive_double_list.h>
#include <fbl/null_lock.h>
#include <usb/request-cpp.h>
#include <usb/usb-request.h>
#include <usb/usb.h>
namespace usb_hub {
// Number of requests to pre-allocate
constexpr auto kPreallocatedRequestCount = 7;
// Maximum length of a control request.
constexpr auto kMaxRequestLength = 32;
// Hub Status Bit
constexpr auto hubStatusBit = 1;
DEFINE_HARD_INT(PortNumber, uint8_t)
DEFINE_HARD_INT(PortArrayIndex, uint8_t)
struct PortStatus : public fbl::DoublyLinkedListable<PortStatus*> {
usb_port_status_t status{};
bool connected = false;
bool reset_pending = false;
bool enumeration_pending = false;
bool link_active = false;
usb_speed_t GetSpeed(usb_speed_t hub_speed) const;
void Reset();
};
class UsbHubDevice;
using UsbHub = ddk::Device<UsbHubDevice, ddk::Unbindable, ddk::Initializable, ddk::GetProtocolable>;
using Request = usb::Request<void>;
using CallbackRequest = usb::CallbackRequest<sizeof(std::max_align_t) * 4>;
class UsbHubDevice : public UsbHub, public ddk::UsbHubInterfaceProtocol<UsbHubDevice> {
public:
explicit UsbHubDevice(zx_device_t* parent)
: UsbHub(parent), loop_(&kAsyncLoopConfigNeverAttachToThread) {}
UsbHubDevice(zx_device_t* parent, std::unique_ptr<fpromise::executor> executor)
: UsbHub(parent),
loop_(&kAsyncLoopConfigNeverAttachToThread),
executor_(std::move(executor)) {}
~UsbHubDevice() { loop_.Shutdown(); }
zx_status_t DdkGetProtocol(uint32_t proto_id, void* out) {
switch (proto_id) {
case ZX_PROTOCOL_USB:
usb_.GetProto(static_cast<usb_protocol_t*>(out));
return ZX_OK;
}
return ZX_ERR_PROTOCOL_NOT_SUPPORTED;
}
// Synchronously resets a port
zx_status_t UsbHubInterfaceResetPort(uint32_t port);
// Retrieves the status of a port.
zx::result<usb_port_status_t> GetPortStatus(PortNumber port);
zx_status_t SetFeature(uint8_t request_type, uint16_t feature, uint16_t index);
zx_status_t ClearFeature(uint8_t request_type, uint16_t feature, uint16_t index);
void DdkInit(ddk::InitTxn txn);
static zx_status_t Bind(void* ctx, zx_device_t* parent);
static zx_status_t Bind(std::unique_ptr<fpromise::executor> executor, zx_device_t* parent);
void DdkUnbind(ddk::UnbindTxn txn);
// Public for testing
zx_status_t Shutdown();
void DdkRelease() { delete this; }
zx::result<usb_hub_descriptor_t> GetUsbHubDescriptor(uint16_t type) {
size_t length = sizeof(usb_hub_descriptor_t);
auto result = ControlIn(USB_TYPE_CLASS | USB_RECIP_DEVICE | USB_DIR_IN, USB_REQ_GET_DESCRIPTOR,
static_cast<uint16_t>(type << 8), 0, length);
if (result.is_error()) {
return zx::error(result.error_value());
}
size_t request_size = result.value().size();
usb_hub_descriptor_t hub_descriptor;
if (sizeof(hub_descriptor) < request_size) {
zxlogf(ERROR, "Size of hub descriptor less than request size");
return zx::error(ZX_ERR_NO_MEMORY);
}
memcpy(&hub_descriptor, result.value().data(), request_size);
auto* usb_descriptor = reinterpret_cast<usb_descriptor_header_t*>(&hub_descriptor);
if (usb_descriptor->b_length != request_size) {
zxlogf(ERROR, "Mismatched descriptor length");
return zx::error(ZX_ERR_BAD_STATE);
}
return zx::ok(hub_descriptor);
}
private:
zx_status_t Init();
// Powers on all ports on the hub
zx_status_t PowerOnPorts();
void InterruptCallback();
// Updates the status of all ports on the hub
zx_status_t GetPortStatus();
void HandlePortStatusChanged(PortNumber port) __TA_REQUIRES(async_execution_context_);
// Starts the interrupt loop
zx_status_t StartInterruptLoop();
// Resets a port
zx_status_t ResetPort(PortNumber port);
// Obtains the 1-based port number from a PortStatus reference
PortNumber GetPortNumber(const PortStatus& status);
// Begins enumeration of the next device on the hub
void EnumerateNext() __TA_REQUIRES(async_execution_context_);
// Starts the enumeration process for a specified port number
void BeginEnumeration(PortNumber port);
// Invoked when a device is attached to the hub
void HandleDeviceConnected(PortNumber port) __TA_REQUIRES(async_execution_context_);
// Invoked when a device is disconnected from the hub
void HandleDeviceDisconnected(PortNumber port) __TA_REQUIRES(async_execution_context_);
// Invoked when a device finishes resetting. Not called when invoked from usb-fwloader.
void HandleResetComplete(PortNumber port) __TA_REQUIRES(async_execution_context_);
PortArrayIndex PortNumberToIndex(PortNumber port) {
fbl::AutoLock lock(&async_execution_context_);
// NOTE -- This read is safe even if not done from async context
// since size() is expected to be constant after initialization.
ZX_ASSERT((port.value() > 0) && (port.value() <= port_status_.size()));
return PortArrayIndex(port.value() - 1);
}
PortNumber IndexToPortNumber(PortArrayIndex index) {
fbl::AutoLock lock(&async_execution_context_);
// NOTE -- This read is safe even if not done from async context
// since size() is expected to be constant after initialization.
ZX_ASSERT((index.value() >= 0) && (index.value() < port_status_.size()));
return PortNumber(index.value() + 1);
}
zx::result<std::vector<uint8_t>> ControlIn(uint8_t request_type, uint8_t request, uint16_t value,
uint16_t index, size_t read_size);
zx_status_t ControlOut(uint8_t request_type, uint8_t request, uint16_t value, uint16_t index,
const void* write_buffer, size_t write_size);
std::optional<Request> AllocRequest();
fpromise::promise<Request, void> RequestQueue(Request request);
std::atomic<bool> shutting_down_ = false;
fbl::Array<PortStatus> port_status_ __TA_GUARDED(async_execution_context_);
fbl::DoublyLinkedList<PortStatus*> pending_enumeration_list_
__TA_GUARDED(async_execution_context_);
usb_hub_descriptor_t hub_descriptor_;
usb::RequestPool<void> request_pool_;
inspect::Inspector inspector_;
usb_speed_t speed_;
usb_endpoint_descriptor_t interrupt_endpoint_;
ddk::UsbProtocolClient usb_;
ddk::UsbBusProtocolClient bus_;
async::Loop loop_;
std::unique_ptr<fpromise::executor> executor_;
sync_completion_t thread_start_; // Completion signaled when thread starts running.
std::optional<thrd_t> callback_thread_;
sync_completion_t xfer_done_;
std::optional<ddk::InitTxn> txn_;
fbl::NullLock async_execution_context_;
};
} // namespace usb_hub
#endif // SRC_DEVICES_USB_DRIVERS_USB_HUB_USB_HUB_H_