| // 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. |
| |
| #include "src/devices/usb/drivers/usb-hub/usb-hub.h" |
| |
| #include <fuchsia/hardware/usb/hubdescriptor/c/banjo.h> |
| #include <lib/ddk/binding_driver.h> |
| #include <lib/zx/time.h> |
| #include <stdlib.h> |
| #include <threads.h> |
| #include <unistd.h> |
| #include <zircon/listnode.h> |
| #include <zircon/time.h> |
| #include <zircon/types.h> |
| |
| #include "lib/sync/completion.h" |
| |
| namespace usb_hub { |
| |
| // A timeout guarding DDK routines from blocking their thread indefinitely. |
| static constexpr auto kSafetyTimeout = ZX_SEC(5); |
| |
| usb_speed_t PortStatus::GetSpeed(usb_speed_t hub_speed) const { |
| usb_speed_t speed; |
| if (hub_speed == USB_SPEED_SUPER) { |
| speed = USB_SPEED_SUPER; |
| } else if (status.w_port_status & USB_PORT_LOW_SPEED) { |
| speed = USB_SPEED_LOW; |
| } else if (status.w_port_status & USB_PORT_HIGH_SPEED) { |
| speed = USB_SPEED_HIGH; |
| } else { |
| speed = USB_SPEED_FULL; |
| } |
| return speed; |
| } |
| |
| void PortStatus::Reset() { |
| connected = false; |
| reset_pending = false; |
| enumeration_pending = false; |
| link_active = true; |
| } |
| |
| zx_status_t UsbHubDevice::Init() { |
| usb_ = ddk::UsbProtocolClient(parent()); |
| bus_ = ddk::UsbBusProtocolClient(parent()); |
| zx_status_t status = |
| DdkAdd(ddk::DeviceAddArgs("usb-hub").set_inspect_vmo(inspector_.DuplicateVmo())); |
| return status; |
| } |
| |
| zx_status_t UsbHubDevice::UsbHubInterfaceResetPort(uint32_t port) { |
| return SetFeature(USB_RECIP_PORT, USB_FEATURE_PORT_RESET, static_cast<uint8_t>(port)); |
| } |
| |
| zx_status_t UsbHubDevice::GetPortStatus() { |
| std::vector<zx::result<usb_port_status_t>> pending_actions; |
| pending_actions.reserve(hub_descriptor_.b_nbr_ports); |
| for (uint8_t i = 1; i <= hub_descriptor_.b_nbr_ports; i++) { |
| pending_actions.push_back(GetPortStatus(PortNumber(i))); |
| } |
| size_t index = 0; |
| for (auto& result : pending_actions) { |
| if (result.is_error()) { |
| return result.error_value(); |
| } |
| fbl::AutoLock l(&async_execution_context_); |
| ZX_ASSERT(index <= port_status_.size()); |
| port_status_[index].status = result.value(); |
| index++; |
| } |
| return ZX_OK; |
| } |
| |
| void UsbHubDevice::DdkInit(ddk::InitTxn txn) { |
| txn_ = std::move(txn); |
| // First -- configure all endpoints and initialize the hub interface |
| zx_status_t status = loop_.StartThread(); |
| if (status != ZX_OK) { |
| txn_->Reply(status); |
| return; |
| } |
| executor_ = std::make_unique<async::Executor>(loop_.dispatcher()); |
| |
| std::optional<usb::InterfaceList> interfaces; |
| status = usb::InterfaceList::Create(usb_, false, &interfaces); |
| if (status != ZX_OK) { |
| txn_->Reply(status); |
| return; |
| } |
| status = ZX_ERR_IO; |
| // According to USB 2.0 Specification section 11.12.1 a hub should have exactly one |
| // interrupt endpoint and no other endpoints. |
| for (auto& interface : *interfaces) { |
| if (interface.descriptor()->b_num_endpoints == 1) { |
| auto eplist = interface.GetEndpointList(); |
| auto ep = eplist.begin(); |
| interrupt_endpoint_ = *ep->descriptor(); |
| status = |
| usb_.EnableEndpoint(&interrupt_endpoint_, ep->ss_companion().value_or(nullptr), true); |
| break; |
| } |
| } |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "Initialization failed due to %s", zx_status_get_string(status)); |
| txn_->Reply(status); |
| } |
| speed_ = usb_.GetSpeed(); |
| uint16_t desc_type = (speed_ == USB_SPEED_SUPER ? USB_HUB_DESC_TYPE_SS : USB_HUB_DESC_TYPE); |
| |
| auto result = GetUsbHubDescriptor(desc_type); |
| if (result.is_error()) { |
| zxlogf(ERROR, "Could not create usb hub descriptor"); |
| txn_->Reply(result.error_value()); |
| return; |
| } |
| usb_hub_descriptor_t descriptor = result.value(); |
| fbl::AutoLock l(&async_execution_context_); |
| hub_descriptor_ = descriptor; |
| { |
| port_status_ = fbl::Array<PortStatus>(new PortStatus[hub_descriptor_.b_nbr_ports], |
| hub_descriptor_.b_nbr_ports); |
| } |
| auto raw_desc = descriptor; |
| |
| // Config bus |
| status = bus_.SetHubInterface(reinterpret_cast<uint64_t>(zxdev()), this, |
| &usb_hub_interface_protocol_ops_); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "Could not set hub interface"); |
| txn_->Reply(status); |
| return; |
| } |
| // TODO(https://fxbug.dev/42133694): Support multi-TT hubs properly. Currently, we |
| // operate in single-TT mode even if the hub supports multiple TTs. |
| status = bus_.ConfigureHub(reinterpret_cast<uint64_t>(zxdev()), speed_, &raw_desc, false); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "Could not configure hub"); |
| txn_->Reply(status); |
| return; |
| } |
| |
| // Once the hub is initialized, power on the ports and wait as per spec |
| |
| status = PowerOnPorts(); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "Could not power on all ports"); |
| txn_->Reply(status); |
| return; |
| } |
| |
| status = zx::nanosleep(zx::deadline_after(zx::msec(2 * hub_descriptor_.b_power_on2_pwr_good))); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "Failed to sleep"); |
| txn_->Reply(status); |
| return; |
| } |
| |
| // Next -- we retrieve the port status |
| status = GetPortStatus(); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "Could not get port status\n"); |
| txn_->Reply(status); |
| return; |
| } |
| |
| // Finally -- we can start the interrupt loop |
| // and bringup our initial set of devices |
| status = StartInterruptLoop(); |
| if (status != ZX_OK) { |
| txn_->Reply(status); |
| return; |
| } |
| status = sync_completion_wait( |
| &thread_start_, kSafetyTimeout); // Make sure thread is running before replying to txn_ |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "Thread start timed out!"); |
| txn_->Reply(status); |
| return; |
| } |
| txn_->Reply(ZX_OK); |
| } |
| |
| void UsbHubDevice::HandlePortStatusChanged(PortNumber port) { |
| const auto& status = port_status_[PortNumberToIndex(port).value()]; |
| if (status.status.w_port_change & USB_C_PORT_CONNECTION) { |
| if (status.status.w_port_status & USB_C_PORT_CONNECTION) { |
| HandleDeviceConnected(port); |
| } |
| |
| if (status.connected && !(status.status.w_port_status & USB_C_PORT_CONNECTION)) { |
| HandleDeviceDisconnected(port); |
| } |
| } |
| if (status.reset_pending && (status.status.w_port_status & USB_C_PORT_ENABLE) && |
| !(status.status.w_port_status & USB_C_PORT_RESET)) { |
| HandleResetComplete(port); |
| } |
| } |
| |
| void UsbHubDevice::InterruptCallback() { |
| sync_completion_signal(&thread_start_); // Signal completion. Thread is running. |
| { |
| // Process any pre-attached ports |
| fbl::AutoLock l(&async_execution_context_); |
| for (size_t i = 0; i < port_status_.size(); i++) { |
| HandlePortStatusChanged(IndexToPortNumber(PortArrayIndex(static_cast<uint8_t>(i)))); |
| } |
| } |
| |
| // Data to be extracted from the request |
| size_t hub_bit_count = |
| hub_descriptor_.b_nbr_ports + 1; // Number of ports including the hub itself |
| size_t byte_padding = |
| ((hub_bit_count - 1) / sizeof(uint8_t)) + 1; // byte padding for handling response |
| auto data = std::make_unique<uint8_t[]>(hub_bit_count + byte_padding); |
| zx_status_t data_status = ZX_OK; |
| size_t data_length; |
| auto handleCallback = [&](CallbackRequest cr) mutable { |
| if (shutting_down_) { |
| sync_completion_signal(&xfer_done_); |
| return; |
| } |
| data_status = cr.request()->response.status; |
| data_length = cr.request()->response.actual; |
| if (data_status != ZX_OK) { |
| sync_completion_signal(&xfer_done_); |
| return; |
| } |
| if (data_length > hub_bit_count) { |
| data_status = ZX_ERR_BAD_STATE; |
| zxlogf(ERROR, "Data received from request is more than number of ports"); |
| return; |
| } |
| size_t val = cr.CopyFrom(data.get(), cr.request()->response.actual, 0); |
| if (val != data_length) { |
| data_status = ZX_ERR_BAD_STATE; |
| zxlogf(ERROR, "Could not copy data correctly"); |
| return; |
| } |
| sync_completion_signal(&xfer_done_); |
| cr.Queue(usb_); |
| }; |
| // Run until device unbinds |
| std::optional<CallbackRequest> request; |
| CallbackRequest::Alloc(&request, usb_ep_max_packet(&interrupt_endpoint_), |
| interrupt_endpoint_.b_endpoint_address, usb_.GetRequestSize(), |
| handleCallback); |
| request->Queue(usb_); |
| while (!shutting_down_) { |
| sync_completion_wait(&xfer_done_, ZX_TIME_INFINITE); |
| sync_completion_reset(&xfer_done_); |
| |
| if (shutting_down_ || data_status != ZX_OK) { |
| break; |
| } |
| |
| // bit zero is hub status |
| if (data[0] & hubStatusBit) { |
| // TODO(https://fxbug.dev/42136073) what to do here? |
| zxlogf(ERROR, "usb_hub_interrupt_complete hub status changed"); |
| } |
| // Iterate through the bitmap (bitmap length and port count is the same) |
| for (uint8_t port = 1; port <= hub_descriptor_.b_nbr_ports; port++) { |
| uint8_t bit = port % 8; |
| uint8_t byte = port / 8; |
| if (data[byte] & (1 << bit)) { |
| auto port_result = GetPortStatus(PortNumber(static_cast<uint8_t>(port))); |
| if (port_result.is_error()) { |
| zxlogf(ERROR, "Could not get port status"); |
| return; |
| } |
| auto port_number = PortNumber(static_cast<uint8_t>(port)); |
| fbl::AutoLock l(&async_execution_context_); |
| port_status_[PortNumberToIndex(port_number).value()].status = port_result.value(); |
| HandlePortStatusChanged(port_number); |
| } |
| } |
| } |
| |
| // At this point, we are no longer handling interrupts (either because there was an error during a |
| // transaction or because the device is being unbound). |
| // Tell the rest of the system that any devices connected to this hub are no longer present. |
| for (uint8_t port = 1; port <= hub_descriptor_.b_nbr_ports; port++) { |
| auto index = PortNumberToIndex(PortNumber(port)).value(); |
| fbl::AutoLock l(&async_execution_context_); |
| if (port_status_[index].connected) { |
| port_status_[index].connected = false; |
| port_status_[index].status.w_port_status &= ~USB_C_PORT_CONNECTION; |
| HandleDeviceDisconnected(PortNumber(port)); |
| } |
| } |
| } |
| |
| zx_status_t UsbHubDevice::StartInterruptLoop() { |
| auto callback_func = [](void* ctx) { |
| static_cast<UsbHubDevice*>(ctx)->InterruptCallback(); |
| return 0; |
| }; |
| thrd_t callback_thread; |
| int thread_status = |
| thrd_create_with_name(&callback_thread, callback_func, this, "usb-hub-interrupt"); |
| if (thread_status != thrd_success) { |
| zxlogf(ERROR, "Could not create thread to handle port changes"); |
| return ZX_ERR_BAD_STATE; |
| } |
| callback_thread_ = callback_thread; |
| return ZX_OK; |
| } |
| |
| zx_status_t UsbHubDevice::ResetPort(PortNumber port) { |
| zx_status_t status = SetFeature(USB_RECIP_PORT, USB_FEATURE_PORT_RESET, port.value()); |
| if (status != ZX_OK) { |
| return status; |
| } |
| fbl::AutoLock l(&async_execution_context_); |
| port_status_[PortNumberToIndex(port).value()].reset_pending = true; |
| return ZX_OK; |
| } |
| |
| PortNumber UsbHubDevice::GetPortNumber(const PortStatus& status) { |
| // This is safe even from non-async context as we are not actually accessing |
| // the data in port_status, just fetching the pointer. |
| fbl::AutoLock l(&async_execution_context_); |
| return PortNumber(static_cast<uint8_t>(&status - port_status_.data()) + 1); |
| } |
| |
| void UsbHubDevice::EnumerateNext() { |
| if (!pending_enumeration_list_.is_empty()) { |
| BeginEnumeration(GetPortNumber(pending_enumeration_list_.front())); |
| } |
| } |
| |
| void UsbHubDevice::BeginEnumeration(PortNumber port) { |
| zx_status_t status = ResetPort(port); |
| if (status != ZX_OK) { |
| // Port reset failed -- stop enumeration and enumerate the next device. |
| fbl::AutoLock l(&async_execution_context_); |
| pending_enumeration_list_.erase(port_status_[PortNumberToIndex(port).value()]); |
| EnumerateNext(); |
| } |
| } |
| |
| void UsbHubDevice::HandleDeviceConnected(PortNumber port) { |
| auto& status = port_status_[PortNumberToIndex(port).value()]; |
| if (status.connected) { |
| // Disconnect existing device. |
| HandleDeviceDisconnected(port); |
| } |
| |
| // Connect new device. |
| status.connected = true; |
| bool was_empty = pending_enumeration_list_.is_empty(); |
| pending_enumeration_list_.push_back(&status); |
| status.enumeration_pending = true; |
| if (was_empty) { |
| EnumerateNext(); |
| } |
| } |
| |
| void UsbHubDevice::HandleDeviceDisconnected(PortNumber port) { |
| auto& status = port_status_[PortNumberToIndex(port).value()]; |
| if (status.enumeration_pending) { |
| pending_enumeration_list_.erase(port_status_[PortNumberToIndex(port).value()]); |
| } |
| bool link_status = status.link_active; |
| status.Reset(); |
| if (link_status) { |
| bus_.DeviceRemoved(reinterpret_cast<uint64_t>(zxdev()), port.value()); |
| } |
| } |
| |
| void UsbHubDevice::HandleResetComplete(PortNumber port) { |
| port_status_[PortNumberToIndex(port).value()].reset_pending = false; |
| auto speed = port_status_[PortNumberToIndex(port).value()].GetSpeed(speed_); |
| zx_status_t status = bus_.DeviceAdded(reinterpret_cast<uint64_t>(zxdev()), port.value(), speed); |
| |
| port_status_[PortNumberToIndex(port).value()].enumeration_pending = false; |
| port_status_[PortNumberToIndex(port).value()].link_active = (status == ZX_OK); |
| pending_enumeration_list_.erase(port_status_[PortNumberToIndex(port).value()]); |
| EnumerateNext(); |
| } |
| |
| zx_status_t UsbHubDevice::PowerOnPorts() { |
| std::vector<zx_status_t> port_statuses; |
| port_statuses.reserve(hub_descriptor_.b_nbr_ports); |
| for (uint8_t i = 0; i < hub_descriptor_.b_nbr_ports; i++) { |
| port_statuses.push_back(SetFeature(USB_RECIP_PORT, USB_FEATURE_PORT_POWER, i + 1)); |
| } |
| |
| for (auto& status : port_statuses) { |
| if (status != ZX_OK) { |
| return status; |
| } |
| } |
| return ZX_OK; |
| } |
| |
| zx::result<usb_port_status_t> UsbHubDevice::GetPortStatus(PortNumber port) { |
| zx::result<std::vector<uint8_t>> result = ControlIn( |
| USB_RECIP_PORT | USB_DIR_IN, USB_REQ_GET_STATUS, 0, port.value(), sizeof(usb_port_status_t)); |
| if (result.is_error()) { |
| return zx::error(result.error_value()); |
| } |
| auto data = result.value(); |
| if (data.size() != sizeof(usb_port_status_t)) { |
| zxlogf(ERROR, "ERROR: Request response size does not match port_status size"); |
| return zx::error(ZX_ERR_BAD_STATE); |
| } |
| usb_port_status_t status; |
| memcpy(&status, data.data(), sizeof(status)); |
| uint16_t port_change = status.w_port_change; |
| std::vector<zx_status_t> pending_operations; |
| if (port_change & USB_C_PORT_CONNECTION) { |
| zxlogf(DEBUG, "USB_C_PORT_CONNECTION "); |
| pending_operations.push_back( |
| ClearFeature(USB_RECIP_PORT, USB_FEATURE_C_PORT_CONNECTION, port.value())); |
| } |
| if (port_change & USB_C_PORT_ENABLE) { |
| zxlogf(DEBUG, "USB_C_PORT_ENABLE "); |
| pending_operations.push_back( |
| ClearFeature(USB_RECIP_PORT, USB_FEATURE_C_PORT_ENABLE, port.value())); |
| } |
| if (port_change & USB_C_PORT_SUSPEND) { |
| zxlogf(DEBUG, "USB_C_PORT_SUSPEND "); |
| pending_operations.push_back( |
| ClearFeature(USB_RECIP_PORT, USB_FEATURE_C_PORT_SUSPEND, port.value())); |
| } |
| if (port_change & USB_C_PORT_OVER_CURRENT) { |
| zxlogf(DEBUG, "USB_C_PORT_OVER_CURRENT "); |
| pending_operations.push_back( |
| ClearFeature(USB_RECIP_PORT, USB_FEATURE_C_PORT_OVER_CURRENT, port.value())); |
| } |
| if (port_change & USB_C_PORT_RESET) { |
| zxlogf(DEBUG, "USB_C_PORT_RESET"); |
| pending_operations.push_back( |
| ClearFeature(USB_RECIP_PORT, USB_FEATURE_C_PORT_RESET, port.value())); |
| } |
| if (port_change & USB_C_BH_PORT_RESET) { |
| zxlogf(DEBUG, "USB_C_BH_PORT_RESET"); |
| pending_operations.push_back( |
| ClearFeature(USB_RECIP_PORT, USB_FEATURE_C_BH_PORT_RESET, port.value())); |
| } |
| if (port_change & USB_C_PORT_LINK_STATE) { |
| zxlogf(DEBUG, "USB_C_PORT_LINK_STATE"); |
| pending_operations.push_back( |
| ClearFeature(USB_RECIP_PORT, USB_FEATURE_C_PORT_LINK_STATE, port.value())); |
| } |
| if (port_change & USB_C_PORT_CONFIG_ERROR) { |
| zxlogf(DEBUG, "USB_C_PORT_CONFIG_ERROR"); |
| pending_operations.push_back( |
| ClearFeature(USB_RECIP_PORT, USB_FEATURE_C_PORT_CONFIG_ERROR, port.value())); |
| } |
| for (auto& feature_status : pending_operations) { |
| if (feature_status != ZX_OK) { |
| return zx::error(ZX_ERR_BAD_STATE); |
| } |
| } |
| return zx::ok(status); |
| } |
| |
| void UsbHubDevice::DdkUnbind(ddk::UnbindTxn txn) { |
| zx_status_t status = Shutdown(); |
| if (status != ZX_OK) { |
| return; |
| } |
| if (callback_thread_) { |
| thrd_join(*callback_thread_, &status); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "Could not join interrupt thread"); |
| return; |
| } |
| } |
| txn.Reply(); |
| } |
| |
| zx_status_t UsbHubDevice::Shutdown() { |
| shutting_down_ = true; |
| zx_status_t status = usb_.CancelAll(interrupt_endpoint_.b_endpoint_address); |
| if (status == ZX_ERR_IO_NOT_PRESENT) { |
| zxlogf(DEBUG, "CancelAll() for endpoint of disconnected device"); |
| } else if (status != ZX_OK) { |
| // Fatal -- unable to shut down properly |
| zxlogf(ERROR, "CancelAll() error: %s", zx_status_get_string(status)); |
| return status; |
| } |
| // TODO(https://fxbug.dev/42163107): Cancel Control endpoint when supported |
| sync_completion_signal(&xfer_done_); |
| return ZX_OK; |
| } |
| |
| zx_status_t UsbHubDevice::Bind(std::unique_ptr<fpromise::executor> executor, zx_device_t* parent) { |
| auto dev = std::make_unique<UsbHubDevice>(parent, std::move(executor)); |
| zx_status_t status = dev->Init(); |
| if (status == ZX_OK) { |
| // DDK now owns this pointer. |
| [[maybe_unused]] auto ref = dev.release(); |
| } |
| return status; |
| } |
| |
| zx_status_t UsbHubDevice::SetFeature(uint8_t request_type, uint16_t feature, uint16_t index) { |
| return ControlOut(request_type, USB_REQ_SET_FEATURE, feature, index, nullptr, 0); |
| } |
| |
| zx_status_t UsbHubDevice::ClearFeature(uint8_t request_type, uint16_t feature, uint16_t index) { |
| return ControlOut(request_type, USB_REQ_CLEAR_FEATURE, feature, index, nullptr, 0); |
| } |
| |
| zx::result<std::vector<uint8_t>> UsbHubDevice::ControlIn(uint8_t request_type, uint8_t request, |
| uint16_t value, uint16_t index, |
| size_t read_size) { |
| if ((request_type & USB_DIR_MASK) != USB_DIR_IN) { |
| return zx::error(ZX_ERR_INVALID_ARGS); |
| } |
| |
| ZX_ASSERT(read_size <= kMaxRequestLength); |
| std::optional<Request> usb_request = AllocRequest(); |
| usb_request->request()->header.length = read_size; |
| usb_request->request()->setup.bm_request_type = request_type; |
| usb_request->request()->setup.b_request = request; |
| usb_request->request()->setup.w_index = index; |
| usb_request->request()->setup.w_value = value; |
| usb_request->request()->setup.w_length = static_cast<uint16_t>(read_size); |
| |
| std::vector<uint8_t> data; |
| zx_status_t status; |
| sync_completion_t completion; |
| executor_->schedule_task( |
| RequestQueue(*std::move(usb_request)).then([&](fpromise::result<Request, void>& value) { |
| auto request = std::move(value.take_ok_result().value); |
| auto rq_status = request.request()->response.status; |
| if (rq_status != ZX_OK) { |
| request_pool_.Add(std::move(request)); |
| status = rq_status; |
| } else { |
| if (read_size != 0) { |
| data.resize(request.request()->response.actual); |
| size_t copied = request.CopyFrom(data.data(), data.size(), 0); |
| ZX_ASSERT(copied == data.size()); |
| } |
| request_pool_.Add(std::move(request)); |
| } |
| sync_completion_signal(&completion); |
| })); |
| sync_completion_wait(&completion, ZX_TIME_INFINITE); |
| return zx::ok(data); |
| } |
| |
| zx_status_t UsbHubDevice::ControlOut(uint8_t request_type, uint8_t request, uint16_t value, |
| uint16_t index, const void* write_buffer, size_t write_size) { |
| if ((request_type & USB_DIR_MASK) != USB_DIR_OUT) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| ZX_ASSERT(write_size <= kMaxRequestLength); |
| std::optional<Request> usb_request = AllocRequest(); |
| usb_request->request()->header.length = write_size; |
| usb_request->request()->setup.bm_request_type = request_type; |
| usb_request->request()->setup.b_request = request; |
| usb_request->request()->setup.w_index = index; |
| usb_request->request()->setup.w_value = value; |
| usb_request->request()->setup.w_length = static_cast<uint16_t>(write_size); |
| size_t result = usb_request->CopyTo(write_buffer, write_size, 0); |
| ZX_ASSERT(result == write_size); |
| std::atomic<zx_status_t> status = ZX_OK; |
| sync_completion_t completion; |
| executor_->schedule_task( |
| RequestQueue(*std::move(usb_request)).then([&](fpromise::result<Request, void>& value) { |
| auto request = std::move(value.take_ok_result().value); |
| auto request_status = request.request()->response.status; |
| if (request_status != ZX_OK) { |
| request_pool_.Add(std::move(request)); |
| status = request_status; |
| sync_completion_signal(&completion); |
| return; |
| } |
| request_pool_.Add(std::move(request)); |
| sync_completion_signal(&completion); |
| })); |
| sync_completion_wait(&completion, ZX_TIME_INFINITE); |
| return status; |
| } |
| |
| std::optional<Request> UsbHubDevice::AllocRequest() { |
| std::optional<Request> request; |
| if ((request = request_pool_.Get(kMaxRequestLength))) { |
| return request; |
| } |
| Request::Alloc(&request, kMaxRequestLength, 0, usb_.GetRequestSize()); |
| ZX_ASSERT(request.has_value()); |
| return request; |
| } |
| |
| fpromise::promise<Request, void> UsbHubDevice::RequestQueue(Request request) { |
| fpromise::bridge<Request, void> bridge; |
| usb_request_complete_callback_t completion; |
| completion.callback = [](void* ctx, usb_request_t* req) { |
| std::unique_ptr<fpromise::completer<Request, void>> completer( |
| static_cast<fpromise::completer<Request, void>*>(ctx)); |
| if (req->response.status != ZX_ERR_CANCELED) { |
| completer->complete_ok(Request(req, sizeof(usb_request_t))); |
| } |
| }; |
| completion.ctx = new fpromise::completer<Request, void>(std::move(bridge.completer)); |
| usb_.RequestQueue(request.take(), &completion); |
| return bridge.consumer.promise().box(); |
| } |
| |
| zx_status_t UsbHubDevice::Bind(void* ctx, zx_device_t* parent) { |
| auto dev = std::make_unique<UsbHubDevice>(parent); |
| zx_status_t status = dev->Init(); |
| if (status == ZX_OK) { |
| // DDK now owns this pointer. |
| [[maybe_unused]] auto ref = dev.release(); |
| } |
| return status; |
| } |
| |
| } // namespace usb_hub |
| static zx_driver_ops_t usb_hub_driver_ops = { |
| .version = DRIVER_OPS_VERSION, |
| .bind = usb_hub::UsbHubDevice::Bind, |
| }; |
| |
| ZIRCON_DRIVER(usb_hub, usb_hub_driver_ops, "fuchsia", "0.1"); |