| // 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_ |