| // Copyright 2017 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/connectivity/ethernet/drivers/usb-cdc-function/usb-cdc-function.h" |
| |
| #include <endian.h> |
| #include <fidl/fuchsia.boot.metadata/cpp/fidl.h> |
| #include <fidl/fuchsia.hardware.usb.endpoint/cpp/fidl.h> |
| #include <fidl/fuchsia.hardware.usb.function/cpp/fidl.h> |
| #include <lib/ddk/binding_driver.h> |
| #include <lib/ddk/debug.h> |
| #include <lib/ddk/trace/event.h> |
| #include <lib/fdf/cpp/dispatcher.h> |
| |
| #include <cinttypes> |
| #include <mutex> |
| #include <vector> |
| |
| #include <ddktl/metadata_server.h> |
| #include <usb-endpoint/usb-endpoint-client.h> |
| #include <usb/request-fidl.h> |
| #include <usb/usb-request.h> |
| |
| namespace { |
| |
| // TODO(https://fxbug.dev/436378683): Remove once usb-cdc-function no longer hangs while |
| // initializing. |
| enum class InitState { |
| kWaitingForAllocInterface1 = 0, |
| kWaitingForAllocInterface2 = 1, |
| kWaitingForAllocEp1 = 2, |
| kWaitingForAllocEp2 = 3, |
| kWaitingForAllocEp3 = 4, |
| kWaitingForMacAddress = 5, |
| kWaitingForInit1 = 6, |
| kWaitingForAddRequests1 = 7, |
| kWaitingForInit2 = 8, |
| kWaitingForAddRequests2 = 9, |
| kWaitingForInit3 = 10, |
| kWaitingForAddRequests3 = 11, |
| kWaitingForSetInterface = 12 |
| }; |
| |
| } // namespace |
| |
| namespace usb_cdc_function { |
| |
| namespace fendpoint = fuchsia_hardware_usb_endpoint; |
| namespace ffunction = fuchsia_hardware_usb_function; |
| namespace frequest = fuchsia_hardware_usb_request; |
| |
| typedef struct txn_info { |
| ethernet_netbuf_t netbuf; |
| ethernet_impl_queue_tx_callback completion_cb; |
| void* cookie; |
| list_node_t node; |
| } txn_info_t; |
| |
| static void complete_txn(txn_info_t* txn, zx_status_t status) { |
| txn->completion_cb(txn->cookie, status, &txn->netbuf); |
| } |
| |
| zx_status_t UsbCdcFunction::insert_usb_request(usb::FidlRequest&& req, |
| usb::EndpointClient<UsbCdcFunction>& ep) { |
| if (suspend_txn_.has_value()) { |
| return ZX_OK; |
| } |
| ep.PutRequest(std::move(req)); |
| return ZX_OK; |
| } |
| |
| void UsbCdcFunction::usb_request_queue(usb::FidlRequest&& req, |
| usb::EndpointClient<UsbCdcFunction>& ep) { |
| if (suspend_txn_.has_value()) { |
| return; |
| } |
| |
| std::vector<frequest::Request> reqs; |
| reqs.emplace_back(req.take_request()); |
| auto result = ep->QueueRequests({std::move(reqs)}); |
| ZX_ASSERT(result.is_ok()); |
| } |
| |
| zx_status_t UsbCdcFunction::cdc_generate_mac_address() { |
| zx::result result = ddk::GetMetadataIfExists<fuchsia_boot_metadata::MacAddressMetadata>(parent_); |
| if (result.is_error()) { |
| zxlogf(ERROR, "Failed to get MAC address metadata: %s", result.status_string()); |
| return result.status_value(); |
| } |
| if (result.value().has_value()) { |
| const auto& metadata = result.value().value(); |
| if (!metadata.mac_address().has_value()) { |
| zxlogf(ERROR, "MAC address metadata missing mac_address field"); |
| return ZX_ERR_INTERNAL; |
| } |
| mac_addr_ = metadata.mac_address().value().octets(); |
| } else { |
| zxlogf(INFO, "ethernet MAC metadata not found. Generating random address"); |
| |
| zx_cprng_draw(mac_addr_.data(), mac_addr_.size()); |
| mac_addr_[0] = 0x02; |
| } |
| |
| char buffer[sizeof(mac_addr_) * 3]; |
| snprintf(buffer, sizeof(buffer), "%02X%02X%02X%02X%02X%02X", mac_addr_[0], mac_addr_[1], |
| mac_addr_[2], mac_addr_[3], mac_addr_[4], mac_addr_[5]); |
| |
| // Make the host and device addresses different so packets are routed correctly. |
| mac_addr_[5] ^= 1; |
| |
| return function_.AllocStringDesc(buffer, &descriptors_.cdc_eth.iMACAddress); |
| } |
| |
| zx_status_t UsbCdcFunction::EthernetImplQuery(uint32_t options, ethernet_info_t* out_info) { |
| // No options are supported |
| if (options) { |
| zxlogf(ERROR, "unexpected options (0x%" PRIx32 ") to ethernet_impl_query", options); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| memset(out_info, 0, sizeof(*out_info)); |
| out_info->mtu = ETH_MTU; |
| memcpy(out_info->mac, mac_addr_.data(), mac_addr_.size()); |
| out_info->netbuf_size = sizeof(txn_info_t); |
| |
| return ZX_OK; |
| } |
| |
| void UsbCdcFunction::EthernetImplStop() { |
| std::lock_guard<std::mutex> tx(tx_mutex_); |
| std::lock_guard<std::mutex> ethernet(ethernet_mutex_); |
| ethernet_ifc_.clear(); |
| } |
| |
| zx_status_t UsbCdcFunction::EthernetImplStart(const ethernet_ifc_protocol_t* ifc) { |
| if (unbound_) { |
| return ZX_ERR_BAD_STATE; |
| } |
| std::lock_guard<std::mutex> ethernet(ethernet_mutex_); |
| if (ethernet_ifc_.is_valid()) { |
| return ZX_ERR_ALREADY_BOUND; |
| } |
| ethernet_ifc_ = ddk::EthernetIfcProtocolClient(ifc); |
| ethernet_ifc_.Status(online_ ? ETHERNET_STATUS_ONLINE : 0); |
| return ZX_OK; |
| } |
| |
| zx_status_t UsbCdcFunction::cdc_send_locked(ethernet_netbuf_t* netbuf) { |
| { |
| std::lock_guard<std::mutex> _(ethernet_mutex_); |
| if (!ethernet_ifc_.is_valid()) { |
| return ZX_ERR_BAD_STATE; |
| } |
| } |
| |
| const auto* byte_data = netbuf->data_buffer; |
| size_t length = netbuf->data_size; |
| |
| // Make sure that we can get all of the tx buffers we need to use |
| std::optional<usb::FidlRequest> tx_req = bulk_in_ep_.GetRequest(); |
| if (!tx_req.has_value()) { |
| return ZX_ERR_SHOULD_WAIT; |
| } |
| |
| // Send data |
| tx_req->clear_buffers(); |
| std::vector<size_t> actual = tx_req->CopyTo(0, byte_data, length, bulk_in_ep_.GetMappedLocked()); |
| |
| size_t actual_total = 0; |
| for (size_t i = 0; i < actual.size(); i++) { |
| // Fill in size of data. |
| (*tx_req)->data()->at(i).size(actual[i]); |
| actual_total += actual[i]; |
| } |
| |
| if (actual_total != length) { |
| insert_usb_request(std::move(tx_req.value()), bulk_in_ep_); |
| return ZX_ERR_INTERNAL; |
| } |
| |
| zx_status_t status = tx_req->CacheFlush(bulk_in_ep_.GetMappedLocked()); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "[bug] tx_req->CacheFlush(): %s", zx_status_get_string(status)); |
| return status; |
| } |
| usb_request_queue(std::move(tx_req.value()), bulk_in_ep_); |
| |
| return ZX_OK; |
| } |
| |
| void UsbCdcFunction::EthernetImplQueueTx(uint32_t options, ethernet_netbuf_t* netbuf, |
| ethernet_impl_queue_tx_callback callback, void* cookie) { |
| size_t length = netbuf->data_size; |
| zx_status_t status; |
| |
| txn_info_t* txn = containerof(netbuf, txn_info_t, netbuf); |
| txn->completion_cb = callback; |
| txn->cookie = cookie; |
| |
| { |
| std::lock_guard<std::mutex> _(ethernet_mutex_); |
| if (!online_ || length > ETH_MTU || length == 0 || unbound_) { |
| complete_txn(txn, ZX_ERR_INVALID_ARGS); |
| return; |
| } |
| } |
| |
| { |
| std::lock_guard<std::mutex> tx(tx_mutex_); |
| if (unbound_ || suspend_txn_.has_value()) { |
| status = ZX_ERR_IO_NOT_PRESENT; |
| } else { |
| status = cdc_send_locked(netbuf); |
| if (status == ZX_ERR_SHOULD_WAIT) { |
| // No buffers available, queue it up |
| txn_info_t* txn = containerof(netbuf, txn_info_t, netbuf); |
| list_add_tail(tx_pending_infos(), &txn->node); |
| } |
| } |
| } |
| |
| if (status != ZX_ERR_SHOULD_WAIT) { |
| complete_txn(txn, status); |
| } |
| } |
| |
| zx_status_t UsbCdcFunction::EthernetImplSetParam(uint32_t param, int32_t value, |
| const uint8_t* data_buffer, size_t data_size) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| void UsbCdcFunction::cdc_intr_complete(fendpoint::Completion completion) { |
| usb::FidlRequest req{std::move(completion.request().value())}; |
| |
| std::lock_guard<std::mutex> intr(intr_mutex_); |
| if (!suspend_txn_.has_value()) { |
| zx_status_t status = insert_usb_request(std::move(req), intr_ep_); |
| ZX_DEBUG_ASSERT(status == ZX_OK); |
| } |
| } |
| |
| void UsbCdcFunction::cdc_send_notifications() { |
| std::lock_guard<std::mutex> _(ethernet_mutex_); |
| |
| usb_cdc_notification_t network_notification = { |
| .bmRequestType = USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, |
| .bNotification = USB_CDC_NC_NETWORK_CONNECTION, |
| .wValue = online_, |
| .wIndex = descriptors_.cdc_intf_0.b_interface_number, |
| .wLength = 0, |
| }; |
| |
| usb_cdc_speed_change_notification_t speed_notification = { |
| .notification = |
| { |
| .bmRequestType = USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, |
| .bNotification = USB_CDC_NC_CONNECTION_SPEED_CHANGE, |
| .wValue = 0, |
| .wIndex = descriptors_.cdc_intf_0.b_interface_number, |
| .wLength = 2 * sizeof(uint32_t), |
| }, |
| .downlink_br = 0, |
| .uplink_br = 0, |
| }; |
| |
| if (online_) { |
| if (speed_ == USB_SPEED_SUPER) { |
| // Claim to be gigabit speed. |
| speed_notification.downlink_br = speed_notification.uplink_br = 1000 * 1000 * 1000; |
| } else { |
| // Claim to be 100 megabit speed. |
| speed_notification.downlink_br = speed_notification.uplink_br = 100 * 1000 * 1000; |
| } |
| } else { |
| speed_notification.downlink_br = speed_notification.uplink_br = 0; |
| } |
| std::optional<usb::FidlRequest> req = intr_ep_.GetRequest(); |
| if (!req.has_value()) { |
| zxlogf(ERROR, "[bug] intr_ep_.GetRequest(): no request available"); |
| return; |
| } |
| |
| req->clear_buffers(); |
| std::vector<size_t> actual = |
| req->CopyTo(0, &network_notification, sizeof(network_notification), intr_ep_.GetMapped()); |
| |
| size_t actual_total = 0; |
| for (size_t i = 0; i < actual.size(); i++) { |
| // Fill in size of data. |
| (*req)->data()->at(i).size(actual[i]); |
| actual_total += actual[i]; |
| } |
| |
| ZX_ASSERT(actual_total == sizeof(network_notification)); |
| |
| req->CacheFlush(intr_ep_.GetMapped()); |
| usb_request_queue(std::move(req.value()), intr_ep_); |
| std::optional<usb::FidlRequest> req2 = intr_ep_.GetRequest(); |
| if (!req2.has_value()) { |
| zxlogf(ERROR, "[bug] intr_ep_.GetRequest(): no request available"); |
| return; |
| } |
| |
| req2->clear_buffers(); |
| actual = req2->CopyTo(0, &speed_notification, sizeof(speed_notification), intr_ep_.GetMapped()); |
| |
| actual_total = 0; |
| for (size_t i = 0; i < actual.size(); i++) { |
| // Fill in size of data. |
| (*req2)->data()->at(i).size(actual[i]); |
| actual_total += actual[i]; |
| } |
| |
| ZX_ASSERT(actual_total == sizeof(speed_notification)); |
| |
| req2->CacheFlush(intr_ep_.GetMapped()); |
| usb_request_queue(std::move(req2.value()), intr_ep_); |
| } |
| |
| void UsbCdcFunction::cdc_rx_complete(fendpoint::Completion completion) { |
| usb::FidlRequest req{std::move(completion.request().value())}; |
| zx_status_t status = *completion.status(); |
| |
| if (status == ZX_ERR_IO_NOT_PRESENT) { |
| std::lock_guard<std::mutex> rx(rx_mutex_); |
| zx_status_t status = insert_usb_request(std::move(req), bulk_out_ep_); |
| ZX_DEBUG_ASSERT(status == ZX_OK); |
| return; |
| } |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "[bug] rx_completion: %s", zx_status_get_string(status)); |
| } |
| |
| if (status == ZX_OK) { |
| std::lock_guard<std::mutex> ethernet(ethernet_mutex_); |
| if (ethernet_ifc_.is_valid()) { |
| std::optional<zx_vaddr_t> addr = bulk_out_ep_.GetMappedAddr(req.request(), 0); |
| if (addr.has_value()) { |
| ethernet_ifc_.Recv(reinterpret_cast<uint8_t*>(*addr), *completion.transfer_size(), 0); |
| } |
| } |
| } |
| |
| req.reset_buffers(bulk_out_ep_.GetMapped()); |
| status = req.CacheFlushInvalidate(bulk_out_ep_.GetMapped()); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "[bug] CacheFlushInvalidate(): %s", zx_status_get_string(status)); |
| } |
| |
| usb_request_queue(std::move(req), bulk_out_ep_); |
| } |
| |
| void UsbCdcFunction::cdc_tx_complete(fendpoint::Completion completion) { |
| usb::FidlRequest req{std::move(completion.request().value())}; |
| |
| if (unbound_) { |
| return; |
| } |
| |
| std::optional additional_tx_queued = |
| [&]() -> std::optional<std::tuple<txn_info_t*, zx_status_t>> { |
| std::lock_guard<std::mutex> tx(tx_mutex_); |
| { |
| if (suspend_txn_.has_value()) { |
| return std::nullopt; |
| } |
| zx_status_t status = insert_usb_request(std::move(req), bulk_in_ep_); |
| ZX_DEBUG_ASSERT(status == ZX_OK); |
| } |
| |
| // Do not queue requests if status is ZX_ERR_IO_NOT_PRESENT, as the underlying connection could |
| // be disconnected or USB_RESET is being processed. Calling cdc_send_locked in such scenario |
| // will deadlock and crash the driver (see https://fxbug.dev/42174506). |
| if (*completion.status() != ZX_ERR_IO_NOT_PRESENT) { |
| if (txn_info_t* txn = list_peek_head_type(tx_pending_infos(), txn_info_t, node); |
| txn != nullptr) { |
| if (zx_status_t send_status = cdc_send_locked(&txn->netbuf); |
| send_status != ZX_ERR_SHOULD_WAIT) { |
| list_remove_head(tx_pending_infos()); |
| return std::make_tuple(txn, send_status); |
| } |
| } |
| } |
| return std::nullopt; |
| }(); |
| |
| if (additional_tx_queued.has_value()) { |
| std::lock_guard<std::mutex> ethernet(ethernet_mutex_); |
| auto [txn, send_status] = *additional_tx_queued; |
| complete_txn(txn, send_status); |
| } |
| } |
| |
| size_t UsbCdcFunction::UsbFunctionInterfaceGetDescriptorsSize() { return sizeof(descriptors_); } |
| |
| void UsbCdcFunction::UsbFunctionInterfaceGetDescriptors(uint8_t* out_descriptors_buffer, |
| size_t descriptors_size, |
| size_t* out_descriptors_actual) { |
| const size_t length = std::min(sizeof(descriptors_), descriptors_size); |
| memcpy(out_descriptors_buffer, &descriptors_, length); |
| *out_descriptors_actual = length; |
| } |
| |
| zx_status_t UsbCdcFunction::UsbFunctionInterfaceControl(const usb_setup_t* setup, |
| const uint8_t* write_buffer, |
| size_t write_size, uint8_t* out_read_buffer, |
| size_t read_size, size_t* out_read_actual) { |
| uint16_t w_value{le16toh(setup->w_value)}; |
| uint16_t w_index{le16toh(setup->w_index)}; |
| uint16_t w_length{le16toh(setup->w_length)}; |
| |
| zxlogf(DEBUG, |
| "bmRequestType=%02x bRequest=%02x wValue=%04x (%d) wIndex=%04x (%d) wLength=%04x (%d)", |
| setup->bm_request_type, setup->b_request, w_value, w_value, w_index, w_index, w_length, |
| w_length); |
| |
| TRACE_DURATION("cdc_eth", __func__, "write_size", write_size, "read_size", read_size); |
| if (out_read_actual != NULL) { |
| *out_read_actual = 0; |
| } |
| |
| // The following control requests are currently unsupported, though non-criticial. To avoid |
| // hanging up bus-enumeration, reply with success. |
| |
| if (setup->bm_request_type == (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) && |
| setup->b_request == USB_CDC_SET_ETHERNET_PACKET_FILTER) { |
| zxlogf(DEBUG, "setting packet filter not supported"); |
| // TODO(voydanoff) implement the requested packet filtering |
| return ZX_OK; |
| } |
| |
| if (setup->bm_request_type == (USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_ENDPOINT) && |
| setup->b_request == USB_REQ_CLEAR_FEATURE && setup->w_value == USB_ENDPOINT_HALT) { |
| zxlogf(DEBUG, "clearing endpoint-halt not supported"); |
| return ZX_OK; |
| } |
| |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t UsbCdcFunction::UsbFunctionInterfaceSetConfigured(bool configured, usb_speed_t speed) { |
| TRACE_DURATION("cdc_eth", __func__, "configured", configured, "speed", speed); |
| |
| if (configured_ == configured) { |
| return ZX_OK; |
| } |
| |
| { |
| std::lock_guard<std::mutex> ethernet(ethernet_mutex_); |
| online_ = false; |
| if (ethernet_ifc_.is_valid()) { |
| ethernet_ifc_.Status(0); |
| } |
| } |
| |
| zxlogf(INFO, "configured = %d", configured); |
| if (configured) { |
| if (zx_status_t status = function_.ConfigEp(&descriptors_.intr_ep, NULL); status != ZX_OK) { |
| zxlogf(ERROR, "[bug] ConfigEp(): %s", zx_status_get_string(status)); |
| return status; |
| } |
| speed_ = speed; |
| configured_ = configured; |
| cdc_send_notifications(); |
| } else { |
| function_.DisableEp(bulk_out_addr_); |
| function_.DisableEp(bulk_in_addr_); |
| function_.DisableEp(intr_addr_); |
| |
| // Everything is disabled, cancel pending transactions if we have any. |
| DiscardPendingTxInfos(ZX_ERR_CANCELED); |
| |
| speed_ = USB_SPEED_UNDEFINED; |
| configured_ = configured; |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t UsbCdcFunction::UsbFunctionInterfaceSetInterface(uint8_t interface, |
| uint8_t alt_setting) { |
| TRACE_DURATION("cdc_eth", __func__, "interface", interface, "alt_setting", alt_setting); |
| |
| if (interface != descriptors_.cdc_intf_0.b_interface_number || alt_setting > 1) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| // TODO(voydanoff) fullspeed and superspeed support |
| if (alt_setting) { |
| for (const auto* ep : {&descriptors_.bulk_out_ep, &descriptors_.bulk_in_ep}) { |
| if (zx_status_t status = function_.ConfigEp(ep, nullptr); status != ZX_OK) { |
| zxlogf(ERROR, "[bug] ConfigEp(): %s", zx_status_get_string(status)); |
| return status; |
| } |
| } |
| } else { |
| for (const uint8_t ep : {bulk_out_addr_, bulk_in_addr_}) { |
| if (zx_status_t status = function_.DisableEp(ep); status != ZX_OK) { |
| zxlogf(ERROR, "[bug] DisableEp(): %s", zx_status_get_string(status)); |
| return status; |
| } |
| } |
| } |
| |
| bool online; |
| if (alt_setting) { |
| online = true; |
| |
| // queue our OUT reqs |
| std::lock_guard<std::mutex> rx(rx_mutex_); |
| while (!bulk_out_ep_.RequestsEmpty()) { |
| std::optional<usb::FidlRequest> req = bulk_out_ep_.GetRequest(); |
| ZX_ASSERT(req.has_value()); // A given from the loop. |
| req->reset_buffers(bulk_out_ep_.GetMappedLocked()); |
| |
| if (zx_status_t status = req->CacheFlushInvalidate(bulk_out_ep_.GetMappedLocked()); |
| status != ZX_OK) { |
| zxlogf(ERROR, "[bug] CacheFlushInvalidate(): %s", zx_status_get_string(status)); |
| return status; |
| } |
| usb_request_queue(std::move(req.value()), bulk_out_ep_); |
| } |
| } else { |
| online = false; |
| |
| // Everything is disabled, cancel pending transactions if we have any. |
| DiscardPendingTxInfos(ZX_ERR_CANCELED); |
| } |
| |
| { |
| std::lock_guard<std::mutex> ethernet(ethernet_mutex_); |
| online_ = online; |
| if (ethernet_ifc_.is_valid()) { |
| ethernet_ifc_.Status(online ? ETHERNET_STATUS_ONLINE : 0); |
| } |
| } |
| |
| // send status notifications on interrupt endpoint |
| cdc_send_notifications(); |
| |
| return ZX_OK; |
| } |
| |
| void UsbCdcFunction::DiscardPendingTxInfos(zx_status_t status) { |
| std::lock_guard<std::mutex> l(tx_mutex_); |
| txn_info_t* txn; |
| while ((txn = list_remove_head_type(tx_pending_infos(), txn_info_t, node)) != NULL) { |
| complete_txn(txn, status); |
| } |
| } |
| |
| void UsbCdcFunction::DdkInit(ddk::InitTxn txn) { |
| list_initialize(&tx_pending_infos_); |
| |
| // TODO(https://fxbug.dev/436378683): Remove once usb-cdc-function no longer hangs while |
| // initializing. |
| std::atomic<InitState> state(InitState::kWaitingForAllocInterface1); |
| |
| // TODO(https://fxbug.dev/436378683): Remove once usb-cdc-function no longer hangs while |
| // initializing. |
| sync_completion_t state_dispatcher_shutdown; |
| |
| // TODO(https://fxbug.dev/436378683): Remove once usb-cdc-function no longer hangs while |
| // initializing. |
| auto state_dispatcher = fdf::SynchronizedDispatcher::Create( |
| {}, "init-state-checker", |
| [&](fdf_dispatcher_t*) { sync_completion_signal(&state_dispatcher_shutdown); }); |
| if (state_dispatcher.is_error()) { |
| zxlogf(ERROR, "Failed to create synchronized dispatcher: %s", state_dispatcher.status_string()); |
| txn.Reply(state_dispatcher.error_value()); |
| return; |
| } |
| |
| async::PostDelayedTask( |
| state_dispatcher.value().async_dispatcher(), |
| [start = zx::clock::get_monotonic(), &state]() { |
| auto now = zx::clock::get_monotonic(); |
| std::string reason = "Unknown"; |
| switch (state) { |
| case InitState::kWaitingForAllocInterface1: |
| reason = "Waiting for AllocInterface(1)"; |
| break; |
| case InitState::kWaitingForAllocInterface2: |
| reason = "Waiting for AllocInterface(2)"; |
| break; |
| case InitState::kWaitingForAllocEp1: |
| reason = "Waiting for AllocEp1(1)"; |
| break; |
| case InitState::kWaitingForAllocEp2: |
| reason = "Waiting for AllocEp1(2)"; |
| break; |
| case InitState::kWaitingForAllocEp3: |
| reason = "Waiting for AllocEp1(3)"; |
| break; |
| case InitState::kWaitingForMacAddress: |
| reason = "Waiting for mac address"; |
| break; |
| case InitState::kWaitingForInit1: |
| reason = "Waiting for Init(1)"; |
| break; |
| case InitState::kWaitingForAddRequests1: |
| reason = "Waiting for AddRequests(1)"; |
| break; |
| case InitState::kWaitingForInit2: |
| reason = "Waiting for Init(2)"; |
| break; |
| case InitState::kWaitingForAddRequests2: |
| reason = "Waiting for AddRequests(2)"; |
| break; |
| case InitState::kWaitingForInit3: |
| reason = "Waiting for Init(3)"; |
| break; |
| case InitState::kWaitingForAddRequests3: |
| reason = "Waiting for AddRequests(3)"; |
| break; |
| case InitState::kWaitingForSetInterface: |
| reason = "Waiting for SetInterface()"; |
| break; |
| } |
| zxlogf(WARNING, "Initialization still has not completed after %li seconds: %s", |
| (now - start).to_secs(), reason.c_str()); |
| }, |
| zx::sec(20)); |
| |
| auto status = function_.AllocInterface(&descriptors_.comm_intf.b_interface_number); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "[bug] AllocInterface(comm_intf): %s", zx_status_get_string(status)); |
| txn.Reply(status); |
| return; |
| } |
| state = InitState::kWaitingForAllocInterface2; |
| status = function_.AllocInterface(&descriptors_.cdc_intf_0.b_interface_number); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "[bug] AllocInterface(data_intf): %s", zx_status_get_string(status)); |
| txn.Reply(status); |
| return; |
| } |
| descriptors_.cdc_intf_1.b_interface_number = descriptors_.cdc_intf_0.b_interface_number; |
| descriptors_.cdc_union.bControlInterface = descriptors_.comm_intf.b_interface_number; |
| descriptors_.cdc_union.bSubordinateInterface = descriptors_.cdc_intf_0.b_interface_number; |
| |
| state = InitState::kWaitingForAllocEp1; |
| status = function_.AllocEp(USB_DIR_OUT, &bulk_out_addr_); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "[bug] AllocEp(bulk_out): %s", zx_status_get_string(status)); |
| txn.Reply(status); |
| return; |
| } |
| state = InitState::kWaitingForAllocEp2; |
| status = function_.AllocEp(USB_DIR_IN, &bulk_in_addr_); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "[bug] AllocEp(bulk_in): %s", zx_status_get_string(status)); |
| txn.Reply(status); |
| return; |
| } |
| state = InitState::kWaitingForAllocEp3; |
| status = function_.AllocEp(USB_DIR_IN, &intr_addr_); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "[bug] AllocEp(intr): %s", zx_status_get_string(status)); |
| txn.Reply(status); |
| return; |
| } |
| |
| descriptors_.bulk_out_ep.b_endpoint_address = bulk_out_addr_; |
| descriptors_.bulk_in_ep.b_endpoint_address = bulk_in_addr_; |
| descriptors_.intr_ep.b_endpoint_address = intr_addr_; |
| |
| state = InitState::kWaitingForMacAddress; |
| status = cdc_generate_mac_address(); |
| if (status != ZX_OK) { |
| txn.Reply(status); |
| return; |
| } |
| |
| auto dispatcher = |
| fdf::SynchronizedDispatcher::Create({}, "cdc-ep-dispatcher", [](fdf_dispatcher_t*) {}); |
| if (dispatcher.is_error()) { |
| zxlogf(ERROR, "[bug] fdf::SynchronizedDispatcher::Create(): %s", dispatcher.status_string()); |
| txn.Reply(dispatcher.error_value()); |
| return; |
| } |
| dispatcher_ = std::move(dispatcher.value()); |
| |
| auto result = DdkConnectFidlProtocol<ffunction::UsbFunctionService::Device>(parent()); |
| if (result.is_error()) { |
| zxlogf(ERROR, "could not connect to UsbFunctionService: %s", result.status_string()); |
| txn.Reply(result.error_value()); |
| return; |
| } |
| |
| state = InitState::kWaitingForInit1; |
| // allocate bulk out usb requests |
| status = bulk_out_ep_.Init(bulk_out_addr_, result.value(), dispatcher_.async_dispatcher()); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "[bug] bulk_out_ep_.Init(): %s", zx_status_get_string(status)); |
| txn.Reply(status); |
| return; |
| } |
| |
| state = InitState::kWaitingForAddRequests1; |
| size_t actual = |
| bulk_out_ep_.AddRequests(BULK_RX_COUNT, BULK_REQ_SIZE, frequest::Buffer::Tag::kVmoId); |
| if (actual != BULK_RX_COUNT) { |
| zxlogf(ERROR, "[bug] bulk_out_ep_.AddRequests(): want %d, got %zu", BULK_RX_COUNT, actual); |
| txn.Reply(ZX_ERR_INTERNAL); |
| return; |
| } |
| |
| state = InitState::kWaitingForInit2; |
| // allocate bulk in usb requests |
| status = bulk_in_ep_.Init(bulk_in_addr_, result.value(), dispatcher_.async_dispatcher()); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "[bug] bulk_in_ep_.Init(): %s", zx_status_get_string(status)); |
| txn.Reply(status); |
| return; |
| } |
| |
| state = InitState::kWaitingForAddRequests2; |
| actual = bulk_in_ep_.AddRequests(BULK_TX_COUNT, BULK_REQ_SIZE, frequest::Buffer::Tag::kVmoId); |
| if (actual != BULK_TX_COUNT) { |
| zxlogf(ERROR, "[bug] bulk_in_ep_.AddRequests(): want %d, got %zu", BULK_TX_COUNT, actual); |
| txn.Reply(ZX_ERR_INTERNAL); |
| return; |
| } |
| |
| state = InitState::kWaitingForInit3; |
| // allocate interrupt requests |
| status = intr_ep_.Init(intr_addr_, result.value(), dispatcher_.async_dispatcher()); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "[bug] intr_ep_.Init(): %s", zx_status_get_string(status)); |
| txn.Reply(status); |
| return; |
| } |
| |
| state = InitState::kWaitingForAddRequests3; |
| actual = intr_ep_.AddRequests(INTR_COUNT, BULK_REQ_SIZE, frequest::Buffer::Tag::kVmoId); |
| if (actual != INTR_COUNT) { |
| zxlogf(ERROR, "[bug] intr_ep_.AddRequests(): want %d, got %zu", INTR_COUNT, actual); |
| txn.Reply(ZX_ERR_INTERNAL); |
| return; |
| } |
| |
| state = InitState::kWaitingForSetInterface; |
| status = function_.SetInterface(this, &usb_function_interface_protocol_ops_); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "[bug] function_.SetInterface(): %s", zx_status_get_string(status)); |
| txn.Reply(status); |
| return; |
| } |
| |
| state_dispatcher.value().ShutdownAsync(); |
| sync_completion_wait(&state_dispatcher_shutdown, ZX_TIME_INFINITE); |
| txn.Reply(ZX_OK); |
| } |
| |
| void UsbCdcFunction::DdkUnbind(ddk::UnbindTxn txn) { |
| unbound_ = true; |
| DiscardPendingTxInfos(ZX_ERR_PEER_CLOSED); |
| |
| txn.Reply(); |
| } |
| |
| void UsbCdcFunction::DdkRelease() { |
| if (suspend_thread_.has_value()) { |
| suspend_thread_->join(); |
| } |
| |
| dispatcher_.ShutdownAsync(); |
| dispatcher_.release(); |
| |
| delete this; |
| } |
| |
| void UsbCdcFunction::DdkSuspend(ddk::SuspendTxn txn) { |
| // Start the suspend process by setting the suspend txn |
| // When the pipeline tries to submit requests, they will be immediately free'd. |
| suspend_txn_.emplace(std::move(txn)); |
| suspend_thread_.emplace([this]() { |
| // Disable endpoints to prevent new requests present in our |
| // pipeline from getting queued. |
| function_.DisableEp(bulk_out_addr_); |
| function_.DisableEp(bulk_in_addr_); |
| function_.DisableEp(intr_addr_); |
| |
| // Cancel all requests in the pipeline -- the completion handler |
| // will free these requests as they come in. |
| function_.CancelAll(intr_addr_); |
| function_.CancelAll(bulk_out_addr_); |
| function_.CancelAll(bulk_in_addr_); |
| |
| list_node_t list; |
| { |
| std::lock_guard<std::mutex> l(tx_mutex_); |
| list_move(tx_pending_infos(), &list); |
| } |
| txn_info_t* tx_txn; |
| while ((tx_txn = list_remove_head_type(&list, txn_info_t, node)) != NULL) { |
| complete_txn(tx_txn, ZX_ERR_PEER_CLOSED); |
| } |
| |
| suspend_txn_->Reply(ZX_OK, 0); |
| }); |
| } |
| |
| zx_status_t UsbCdcFunction::Bind(void* ctx, zx_device_t* parent) { |
| auto cdc = std::make_unique<UsbCdcFunction>(parent); |
| if (!cdc) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| zx_status_t status = cdc->DdkAdd("cdc-eth-function"); |
| |
| // Either the DDK now owns this reference, or (in the case of failure) it will be freed in the |
| // block below. In either case, we want to avoid the unique_ptr destructing the allocated |
| // UsbCdcFunction instance. |
| [[maybe_unused]] auto released = cdc.release(); |
| |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "adding device fails: %s", zx_status_get_string(status)); |
| cdc->DdkRelease(); |
| return status; |
| } |
| |
| return ZX_OK; |
| } |
| |
| static constexpr zx_driver_ops_t driver_ops = []() { |
| zx_driver_ops_t ops = {}; |
| ops.version = DRIVER_OPS_VERSION; |
| ops.bind = UsbCdcFunction::Bind; |
| return ops; |
| }(); |
| |
| } // namespace usb_cdc_function |
| |
| // clang-format off |
| ZIRCON_DRIVER(usb_cdc_function, usb_cdc_function::driver_ops, "zircon", "0.1"); |