| // 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 "usb-hub.h" |
| |
| #include <lib/fit/function.h> |
| #include <lib/sync/completion.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <threads.h> |
| #include <unistd.h> |
| #include <zircon/errors.h> |
| #include <zircon/hw/usb/hub.h> |
| #include <zircon/listnode.h> |
| #include <zircon/status.h> |
| |
| #include <fbl/auto_lock.h> |
| #include <fbl/hard_int.h> |
| |
| #include "src/devices/usb/drivers/usb-hub-rewrite/usb_hub_rewrite_bind.h" |
| |
| namespace { |
| // Collapses a vector of promises into a single promise which returns a user-provided value on |
| // success, and an error code on failure. |
| template <typename Success, typename Error, typename ReturnType> |
| fit::promise<ReturnType, Error> Fold(fit::promise<std::vector<fit::result<Success, Error>>> promise, |
| ReturnType ok_value) { |
| return promise.then([success_value = std::move(ok_value)]( |
| fit::result<std::vector<fit::result<void, zx_status_t>>, void>& results) |
| -> fit::result<ReturnType, Error> { |
| for (auto& result : results.value()) { |
| if (result.is_error()) { |
| return fit::error(result.error()); |
| } |
| } |
| return fit::ok(success_value); |
| }); |
| } |
| |
| // Collapses a vector of promises into a single promise which returns void on success, |
| // and an error code on failure. |
| template <typename Success, typename Error> |
| fit::promise<void, Error> Fold(fit::promise<std::vector<fit::result<Success, Error>>> promise) { |
| return promise |
| .then([](fit::result<std::vector<fit::result<void, zx_status_t>>, void>& results) |
| -> fit::result<void, Error> { |
| for (auto& result : results.value()) { |
| if (result.is_error()) { |
| return fit::error(result.error()); |
| } |
| } |
| return fit::ok(); |
| }) |
| .box(); |
| } |
| |
| } // namespace |
| |
| namespace usb_hub { |
| |
| 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 & USB_PORT_LOW_SPEED) { |
| speed = USB_SPEED_LOW; |
| } else if (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 RunSynchronously( |
| SetFeature(USB_RECIP_PORT, USB_FEATURE_PORT_RESET, static_cast<uint8_t>(port))); |
| } |
| |
| zx_status_t UsbHubDevice::RunSynchronously(fit::promise<void, zx_status_t> promise) { |
| sync_completion_t completion; |
| zx_status_t status; |
| bool complete = false; |
| executor_->schedule_task(promise.then([&](fit::result<void, zx_status_t>& result) { |
| status = ZX_OK; |
| if (result.is_error()) { |
| status = result.error(); |
| } |
| complete = true; |
| sync_completion_signal(&completion); |
| })); |
| while (!complete) { |
| sync_completion_wait(&completion, ZX_TIME_INFINITE); |
| } |
| return status; |
| } |
| |
| fit::promise<void, zx_status_t> UsbHubDevice::GetPortStatus() { |
| std::vector<fit::promise<usb_port_status_t, zx_status_t>> pending_actions; |
| pending_actions.reserve(hub_descriptor_.bNbrPorts); |
| for (uint8_t i = 1; i <= hub_descriptor_.bNbrPorts; i++) { |
| pending_actions.push_back(GetPortStatus(PortNumber(i))); |
| } |
| return fit::join_promise_vector(std::move(pending_actions)) |
| .then([this](fit::result<std::vector<fit::result<usb_port_status_t, zx_status_t>>, void>& |
| results) -> fit::result<void, zx_status_t> { |
| ZX_ASSERT(results.is_ok()); |
| size_t index = 0; |
| for (auto& result : results.value()) { |
| if (result.is_error()) { |
| return fit::error(result.error()); |
| } |
| fbl::AutoLock l(&async_execution_context_); |
| ZX_ASSERT(index <= port_status_.size()); |
| port_status_[index].status = result.value().wPortStatus; |
| index++; |
| } |
| return fit::ok(); |
| }) |
| .box(); |
| } |
| |
| 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; |
| } |
| status = loop_.StartThread(); |
| if (status != ZX_OK) { |
| txn_->Reply(status); |
| return; |
| } |
| executor_ = std::make_unique<async::Executor>(loop_.dispatcher()); |
| executor_->schedule_task(fit::make_promise([this]() mutable { |
| std::optional<usb::InterfaceList> interfaces; |
| usb::InterfaceList::Create(usb_, false, &interfaces); |
| zx_status_t 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()->bNumEndpoints == 1) { |
| auto eplist = interface.GetEndpointList(); |
| auto ep_iter = eplist.begin(); |
| auto ep = ep_iter.endpoint(); |
| interrupt_endpoint_ = ep->descriptor; |
| if (ep->has_companion) { |
| status = usb_.EnableEndpoint(&interrupt_endpoint_, &ep->ss_companion, true); |
| } else { |
| status = usb_.EnableEndpoint(&interrupt_endpoint_, 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); |
| executor_->schedule_task( |
| GetVariableLengthDescriptor<usb_hub_descriptor_t>(USB_TYPE_CLASS | USB_RECIP_DEVICE, |
| desc_type, 0) |
| .and_then([this](VariableLengthDescriptor<usb_hub_descriptor_t>& descriptor) |
| -> fit::promise<void, zx_status_t> { |
| fbl::AutoLock l(&async_execution_context_); |
| constexpr auto kMinDescriptorLength = 7; |
| if (descriptor.length < kMinDescriptorLength) { |
| return fit::make_error_promise(ZX_ERR_IO); |
| } |
| hub_descriptor_ = descriptor.descriptor; |
| { |
| port_status_ = fbl::Array<PortStatus>(new PortStatus[hub_descriptor_.bNbrPorts], |
| hub_descriptor_.bNbrPorts); |
| } |
| auto raw_desc = descriptor.descriptor; |
| // TODO (fxbug.dev/57998): Don't pass zxdev() around. |
| return RunBlocking<zx_status_t>([raw_desc, this]() { |
| auto status = |
| bus_.SetHubInterface(zxdev(), this, &usb_hub_interface_protocol_ops_); |
| if (status != ZX_OK) { |
| return status; |
| } |
| // TODO (fxbug.dev/56002): Support multi-TT hubs properly. Currently, we |
| // operate in single-TT mode even if the hub supports multiple TTs. |
| return bus_.ConfigureHub(zxdev(), speed_, &raw_desc, false); |
| }) |
| .then( |
| [](fit::result<zx_status_t, void>& status) -> fit::result<void, zx_status_t> { |
| if (status.value() != ZX_OK) { |
| return fit::error(status.value()); |
| } |
| return fit::ok(); |
| }) |
| .and_then([this]() { |
| // Once the hub is initialized, power on the ports |
| return PowerOnPorts() |
| .and_then([this]() { |
| // then wait for bPwrOn2PwrGood (2 millisecond intervals) |
| return Sleep( |
| zx::deadline_after(zx::msec(2 * hub_descriptor_.bPowerOn2PwrGood))); |
| }) |
| .and_then([this]() { |
| // Next -- we retrieve the port status |
| return GetPortStatus(); |
| }) |
| .and_then([this]() { |
| // Finally -- we can start the interrupt loop |
| // and bringup our initial set of devices |
| StartInterruptLoop(); |
| fbl::AutoLock l(&async_execution_context_); |
| for (size_t i = 0; i < port_status_.size(); i++) { |
| HandlePortStatusChanged( |
| IndexToPortNumber(PortArrayIndex(static_cast<uint8_t>(i)))); |
| } |
| }); |
| }); |
| }) |
| .then([this](fit::result<void, zx_status_t>& status) { |
| if (status.is_error()) { |
| zxlogf(ERROR, "Failed to initialize hub -- error %s", |
| zx_status_get_string(status.error())); |
| txn_->Reply(status.error()); |
| } else { |
| txn_->Reply(ZX_OK); |
| } |
| })); |
| })); |
| } |
| |
| void UsbHubDevice::HandlePortStatusChanged(PortNumber port) { |
| const auto& status = port_status_[PortNumberToIndex(port).value()]; |
| if (!status.connected && (status.status & USB_C_PORT_CONNECTION)) { |
| HandleDeviceConnected(port); |
| } |
| if (status.connected && !(status.status & USB_C_PORT_CONNECTION)) { |
| HandleDeviceDisconnected(port); |
| } |
| if (status.reset_pending && (status.status & USB_C_PORT_ENABLE) && |
| !(status.status & USB_C_PORT_RESET)) { |
| HandleResetComplete(port); |
| } |
| } |
| |
| void UsbHubDevice::InterruptCallback(CallbackRequest request) { |
| request_pending_ = false; |
| if (shutting_down_ || (request.request()->response.status != ZX_OK)) { |
| return; |
| } |
| |
| uint8_t* bitmap; |
| request.Mmap(reinterpret_cast<void**>(&bitmap)); |
| uint8_t* bitmap_end = bitmap + request.request()->response.actual; |
| |
| // bit zero is hub status |
| if (bitmap[0] & 1) { |
| // TODO(fxbug.dev/58148) what to do here? |
| zxlogf(ERROR, "usb_hub_interrupt_complete hub status changed"); |
| } |
| int port = 1; |
| int bit = 1; |
| while (bitmap < bitmap_end && port <= hub_descriptor_.bNbrPorts) { |
| if (*bitmap & (1 << bit)) { |
| executor_->schedule_task( |
| GetPortStatus(PortNumber(static_cast<uint8_t>(port))) |
| .and_then([this, port_number = PortNumber(static_cast<uint8_t>(port))]( |
| usb_port_status_t& status) { |
| fbl::AutoLock l(&async_execution_context_); |
| port_status_[PortNumberToIndex(port_number).value()].status = status.wPortStatus; |
| HandlePortStatusChanged(port_number); |
| return fit::ok(); |
| })); |
| } |
| port++; |
| if (++bit == 8) { |
| bitmap++; |
| bit = 0; |
| } |
| } |
| request_pending_ = true; |
| request.Queue(usb_); |
| } |
| |
| void UsbHubDevice::StartInterruptLoop() { |
| std::optional<CallbackRequest> request; |
| CallbackRequest::Alloc(&request, usb_ep_max_packet(&interrupt_endpoint_), |
| interrupt_endpoint_.bEndpointAddress, usb_.GetRequestSize(), |
| fit::bind_member(this, &UsbHubDevice::InterruptCallback)); |
| request_pending_ = true; |
| request->Queue(usb_); |
| } |
| |
| fit::promise<void, zx_status_t> UsbHubDevice::ResetPort(PortNumber port) { |
| return SetFeature(USB_RECIP_PORT, USB_FEATURE_PORT_RESET, port.value()).and_then([this, port]() { |
| fbl::AutoLock l(&async_execution_context_); |
| port_status_[PortNumberToIndex(port).value()].reset_pending = true; |
| }); |
| } |
| |
| 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) { |
| executor_->schedule_task(ResetPort(port).or_else([this, port](zx_status_t& status) { |
| // 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()]; |
| status.connected = true; |
| bool was_empty = pending_enumeration_list_.is_empty(); |
| pending_enumeration_list_.push_back(&status); |
| if (was_empty) { |
| EnumerateNext(); |
| } |
| } |
| |
| void UsbHubDevice::HandleDeviceDisconnected(PortNumber port) { |
| bool link_status = port_status_[PortNumberToIndex(port).value()].link_active; |
| port_status_[PortNumberToIndex(port).value()].Reset(); |
| if (link_status) { |
| async::PostTask(loop_.dispatcher(), [=]() { bus_.DeviceRemoved(zxdev(), port.value()); }); |
| } |
| } |
| |
| void UsbHubDevice::HandleResetComplete(PortNumber port) { |
| port_status_[PortNumberToIndex(port).value()].reset_pending = false; |
| port_status_[PortNumberToIndex(port).value()].enumeration_pending = true; |
| auto speed = port_status_[PortNumberToIndex(port).value()].GetSpeed(speed_); |
| async::PostTask(loop_.dispatcher(), [this, port, speed]() { |
| // Online the device in xHCI |
| zx_status_t status = bus_.DeviceAdded(zxdev(), port.value(), speed); |
| executor_->schedule_task(fit::make_promise([this, port, status]() { |
| fbl::AutoLock lock(&async_execution_context_); |
| { |
| 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(); |
| })); |
| }); |
| } |
| |
| fit::promise<void, zx_status_t> UsbHubDevice::PowerOnPorts() { |
| std::vector<fit::promise<void, zx_status_t>> promises; |
| promises.reserve(hub_descriptor_.bNbrPorts); |
| for (uint8_t i = 0; i < hub_descriptor_.bNbrPorts; i++) { |
| promises.push_back(SetFeature(USB_RECIP_PORT, USB_FEATURE_PORT_POWER, i + 1)); |
| } |
| return Fold(fit::join_promise_vector(std::move(promises)).box()); |
| } |
| fit::promise<usb_port_status_t, zx_status_t> UsbHubDevice::GetPortStatus(PortNumber port) { |
| return ControlIn(USB_RECIP_PORT | USB_DIR_IN, USB_REQ_GET_STATUS, 0, port.value(), |
| sizeof(usb_port_status_t)) |
| .and_then([](std::vector<uint8_t>& data) -> fit::result<usb_port_status_t, zx_status_t> { |
| if (data.size() != sizeof(usb_port_status_t)) { |
| return fit::error(ZX_ERR_IO); |
| } |
| usb_port_status_t status; |
| memcpy(&status, data.data(), sizeof(status)); |
| return fit::ok(status); |
| }) |
| .and_then([this, port](usb_port_status_t& status) { |
| uint16_t port_change = status.wPortChange; |
| std::vector<fit::promise<void, 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())); |
| } |
| return Fold(fit::join_promise_vector(std::move(pending_operations)).box(), status); |
| }); |
| } |
| |
| fit::promise<void, zx_status_t> UsbHubDevice::Sleep(zx::time deadline) { |
| fit::bridge<void, zx_status_t> bridge; |
| zx_status_t status = async::PostTaskForTime( |
| loop_.dispatcher(), |
| [completer = std::move(bridge.completer)]() mutable { completer.complete_ok(); }, deadline); |
| if (status != ZX_OK) { |
| return fit::make_error_promise(status); |
| } |
| return bridge.consumer.promise().box(); |
| } |
| |
| void UsbHubDevice::DdkUnbind(ddk::UnbindTxn txn) { |
| async::PostTask(loop_.dispatcher(), [transaction = std::move(txn), this]() mutable { |
| shutting_down_ = true; |
| zx_status_t status = usb_.CancelAll(interrupt_endpoint_.bEndpointAddress); |
| if (status != ZX_OK) { |
| // Fatal -- unable to shut down properly |
| zxlogf(ERROR, "Error %s during CancelAll for interrupt endpoint\n", |
| zx_status_get_string(status)); |
| return; |
| } |
| status = usb_.CancelAll(0); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "Error %s during CancelAll for control endpoint", zx_status_get_string(status)); |
| return; |
| } |
| transaction.Reply(); |
| }); |
| } |
| |
| zx_status_t UsbHubDevice::Bind(std::unique_ptr<fit::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. |
| auto __UNUSED ref = dev.release(); |
| } |
| return status; |
| } |
| |
| fit::promise<void, 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); |
| } |
| |
| fit::promise<void, 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); |
| } |
| |
| fit::promise<std::vector<uint8_t>, zx_status_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 fit::make_result_promise<std::vector<uint8_t>, zx_status_t>( |
| fit::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.bmRequestType = request_type; |
| usb_request->request()->setup.bRequest = request; |
| usb_request->request()->setup.wIndex = index; |
| usb_request->request()->setup.wValue = value; |
| usb_request->request()->setup.wLength = static_cast<uint16_t>(read_size); |
| |
| return RequestQueue(*std::move(usb_request)) |
| .then([this, read_size](fit::result<Request, void>& value) |
| -> fit::result<std::vector<uint8_t>, zx_status_t> { |
| auto request = std::move(value.take_ok_result().value); |
| auto status = request.request()->response.status; |
| if (status != ZX_OK) { |
| request_pool_.Add(std::move(request)); |
| return fit::error(status); |
| } |
| std::vector<uint8_t> data; |
| 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)); |
| return fit::ok(data); |
| }) |
| .box(); |
| } |
| |
| fit::promise<void, 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 fit::make_result_promise<void, zx_status_t>(fit::error(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.bmRequestType = request_type; |
| usb_request->request()->setup.bRequest = request; |
| usb_request->request()->setup.wIndex = index; |
| usb_request->request()->setup.wValue = value; |
| usb_request->request()->setup.wLength = static_cast<uint16_t>(write_size); |
| size_t result = usb_request->CopyTo(write_buffer, write_size, 0); |
| ZX_ASSERT(result == write_size); |
| return RequestQueue(*std::move(usb_request)) |
| .then([this](fit::result<Request, void>& value) -> fit::result<void, zx_status_t> { |
| auto request = std::move(value.take_ok_result().value); |
| auto status = request.request()->response.status; |
| if (status != ZX_OK) { |
| request_pool_.Add(std::move(request)); |
| return fit::error(status); |
| } |
| request_pool_.Add(std::move(request)); |
| return fit::ok(); |
| }) |
| .box(); |
| } |
| |
| 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; |
| } |
| |
| fit::promise<Request, void> UsbHubDevice::RequestQueue(Request request) { |
| fit::bridge<Request, void> bridge; |
| usb_request_complete_t completion; |
| completion.callback = [](void* ctx, usb_request_t* req) { |
| std::unique_ptr<fit::completer<Request, void>> completer( |
| static_cast<fit::completer<Request, void>*>(ctx)); |
| if (req->response.status != ZX_ERR_CANCELED) { |
| completer->complete_ok(Request(req, sizeof(usb_request_t))); |
| } |
| }; |
| completion.ctx = new fit::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. |
| auto __UNUSED ref = dev.release(); |
| } |
| return status; |
| } |
| |
| void UsbHubDevice::DdkRelease() { delete this; } |
| |
| UsbHubDevice::~UsbHubDevice() { |
| loop_.Shutdown(); |
| ZX_ASSERT(!request_pending_); |
| } |
| |
| } // 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_rewrite, usb_hub_driver_ops, "fuchsia", "0.1"); |