| // 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_CONNECTIVITY_ETHERNET_DRIVERS_RNDIS_FUNCTION_RNDIS_FUNCTION_H_ |
| #define SRC_CONNECTIVITY_ETHERNET_DRIVERS_RNDIS_FUNCTION_RNDIS_FUNCTION_H_ |
| |
| #include <fuchsia/hardware/ethernet/cpp/banjo.h> |
| #include <fuchsia/hardware/usb/c/banjo.h> |
| #include <fuchsia/hardware/usb/function/cpp/banjo.h> |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/async-loop/default.h> |
| #include <lib/async-loop/loop.h> |
| #include <lib/async/cpp/task.h> |
| #include <lib/driver/compat/cpp/compat.h> |
| #include <lib/driver/component/cpp/driver_base.h> |
| #include <lib/driver/component/cpp/node_add_args.h> |
| #include <lib/fit/function.h> |
| #include <lib/operation/ethernet.h> |
| |
| #include <array> |
| #include <queue> |
| #include <thread> |
| |
| #include <fbl/mutex.h> |
| #include <usb/cdc.h> |
| #include <usb/request-cpp.h> |
| #include <usb/usb-request.h> |
| #include <usb/usb.h> |
| |
| #include "src/connectivity/ethernet/lib/rndis/rndis.h" |
| |
| class RndisFunction : public fdf::DriverBase, |
| public ddk::UsbFunctionInterfaceProtocol<RndisFunction>, |
| public ddk::EthernetImplProtocol<RndisFunction> { |
| public: |
| static constexpr std::string_view kDriverName = "rndis-function"; |
| static constexpr std::string_view kChildNodeName = "rndis-function"; |
| |
| RndisFunction(fdf::DriverStartArgs start_args, |
| fdf::UnownedSynchronizedDispatcher driver_dispatcher) |
| : DriverBase(kDriverName, std::move(start_args), std::move(driver_dispatcher)) {} |
| |
| zx::result<> Start() override; |
| void PrepareStop(fdf::PrepareStopCompleter completer) override; |
| |
| // ddk::UsbFunctionInterfaceProtocol<RndisFunction> implementation. |
| size_t UsbFunctionInterfaceGetDescriptorsSize(); |
| void UsbFunctionInterfaceGetDescriptors(uint8_t* out_descriptors_buffer, size_t descriptors_size, |
| size_t* out_descriptors_actual); |
| zx_status_t 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); |
| zx_status_t UsbFunctionInterfaceSetConfigured(bool configured, usb_speed_t speed); |
| zx_status_t UsbFunctionInterfaceSetInterface(uint8_t interface, uint8_t alt_setting); |
| |
| // ddk::EthernetImplProtocol<RndisFunction> implementation. |
| zx_status_t EthernetImplQuery(uint32_t options, ethernet_info_t* info); |
| void EthernetImplStop(); |
| zx_status_t EthernetImplStart(const ethernet_ifc_protocol_t* ifc_); |
| void EthernetImplQueueTx(uint32_t options, ethernet_netbuf_t* netbuf, |
| ethernet_impl_queue_tx_callback completion_cb, void* cookie); |
| zx_status_t EthernetImplSetParam(uint32_t param, int32_t value, const uint8_t* data, |
| size_t data_size); |
| void EthernetImplGetBti(zx::bti* bti) { bti->reset(); } |
| |
| private: |
| zx_status_t HandleCommand(const void* buffer, size_t size); |
| zx_status_t HandleResponse(void* buffer, size_t size, size_t* actual); |
| zx_status_t Halt(); |
| void Reset(); |
| |
| std::optional<std::vector<uint8_t>> QueryOid(uint32_t oid, void* input, size_t length); |
| zx_status_t SetOid(uint32_t oid, const uint8_t* input, size_t length); |
| |
| void Shutdown(); |
| // The driver framework may call DdkRelease at any point after we trigger the shutdown callback, |
| // so we should not hold the lock when executing the callback or we might get a use-after-free |
| // when the lock is released. |
| void ShutdownComplete() __TA_EXCLUDES(lock_); |
| |
| void ReadComplete(usb_request_t* request); |
| void WriteComplete(usb_request_t* request); |
| void NotificationComplete(usb_request_t* request); |
| |
| void ReceiveLocked(usb::Request<>& request) __TA_REQUIRES(lock_); |
| void NotifyLocked() __TA_REQUIRES(lock_); |
| void IndicateConnectionStatus(bool connected); |
| |
| uint8_t NotificationAddress() { return descriptors_.notification_ep.b_endpoint_address; } |
| uint8_t BulkInAddress() { return descriptors_.in_ep.b_endpoint_address; } |
| uint8_t BulkOutAddress() { return descriptors_.out_ep.b_endpoint_address; } |
| |
| bool Online() const __TA_REQUIRES(lock_) { return ifc_.is_valid() && rndis_ready_; } |
| |
| static constexpr size_t kNotificationMaxPacketSize = 8; |
| static constexpr size_t kRequestPoolSize = 8; |
| static constexpr size_t kMtu = RNDIS_MAX_XFER_SIZE; |
| |
| static constexpr uint32_t kVendorId = 0x44070b00; |
| static constexpr char kVendorDescription[] = "Google"; |
| static constexpr uint16_t kVendorDriverVersionMajor = 1; |
| static constexpr uint16_t kVendorDriverVersionMinor = 0; |
| |
| async::Loop loop_{&kAsyncLoopConfigNoAttachToCurrentThread}; |
| |
| ddk::EthernetIfcProtocolClient ifc_ __TA_GUARDED(lock_); |
| ddk::UsbFunctionProtocolClient function_; |
| size_t usb_request_size_; |
| |
| fbl::Mutex lock_; |
| bool rndis_ready_ __TA_GUARDED(lock_) = false; |
| bool shutting_down_ __TA_GUARDED(lock_) = false; |
| uint32_t link_speed_ __TA_GUARDED(lock_) = 0; |
| std::array<uint8_t, ETH_MAC_SIZE> mac_addr_; |
| |
| // Stats. |
| std::atomic<uint32_t> transmit_ok_ = 0; |
| std::atomic<uint32_t> receive_ok_ = 0; |
| std::atomic<uint32_t> transmit_errors_ = 0; |
| std::atomic<uint32_t> receive_errors_ = 0; |
| std::atomic<uint32_t> transmit_no_buffer_ = 0; |
| |
| std::queue<std::vector<uint8_t>> control_responses_ __TA_GUARDED(lock_); |
| |
| usb::RequestPool<> free_notify_pool_ __TA_GUARDED(lock_); |
| usb::RequestPool<> free_read_pool_ __TA_GUARDED(lock_); |
| usb::RequestPool<> free_write_pool_ __TA_GUARDED(lock_); |
| |
| size_t pending_requests_ __TA_GUARDED(lock_) = 0; |
| |
| std::optional<fdf::PrepareStopCompleter> prepare_stop_completer_; |
| |
| usb_request_complete_callback_t read_request_complete_ = { |
| .callback = |
| [](void* ctx, usb_request_t* request) { |
| auto rndis = reinterpret_cast<RndisFunction*>(ctx); |
| async::PostTask(rndis->loop_.dispatcher(), |
| [rndis, request]() { rndis->ReadComplete(request); }); |
| }, |
| .ctx = this, |
| }; |
| |
| usb_request_complete_callback_t write_request_complete_ = { |
| .callback = |
| [](void* ctx, usb_request_t* request) { |
| auto rndis = reinterpret_cast<RndisFunction*>(ctx); |
| async::PostTask(rndis->loop_.dispatcher(), |
| [rndis, request]() { rndis->WriteComplete(request); }); |
| }, |
| .ctx = this, |
| }; |
| |
| usb_request_complete_callback_t notification_request_complete_ = { |
| .callback = |
| [](void* ctx, usb_request_t* request) { |
| auto rndis = reinterpret_cast<RndisFunction*>(ctx); |
| async::PostTask(rndis->loop_.dispatcher(), |
| [rndis, request]() { rndis->NotificationComplete(request); }); |
| }, |
| .ctx = this, |
| }; |
| |
| struct { |
| usb_interface_assoc_descriptor_t assoc; |
| usb_interface_descriptor_t communication_interface; |
| usb_cs_header_interface_descriptor_t cdc_header; |
| usb_cs_call_mgmt_interface_descriptor_t call_mgmt; |
| usb_cs_abstract_ctrl_mgmt_interface_descriptor_t acm; |
| usb_cs_union_interface_descriptor_1_t cdc_union; |
| usb_endpoint_descriptor_t notification_ep; |
| |
| usb_interface_descriptor_t data_interface; |
| usb_endpoint_descriptor_t out_ep; |
| usb_endpoint_descriptor_t in_ep; |
| } __PACKED descriptors_; |
| |
| compat::SyncInitializedDeviceServer compat_server_; |
| fidl::ClientEnd<fuchsia_driver_framework::NodeController> child_; |
| compat::BanjoServer usb_function_interface_banjo_server_{ZX_PROTOCOL_USB_FUNCTION, this, |
| &usb_function_interface_protocol_ops_}; |
| compat::BanjoServer ethernet_impl_banjo_server_{ZX_PROTOCOL_ETHERNET_IMPL, this, |
| ðernet_impl_protocol_ops_}; |
| }; |
| |
| #endif // SRC_CONNECTIVITY_ETHERNET_DRIVERS_RNDIS_FUNCTION_RNDIS_FUNCTION_H_ |