|  | // Copyright 2019 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 <assert.h> | 
|  | #include <lib/zircon-internal/thread_annotations.h> | 
|  | #include <stdio.h> | 
|  | #include <stdlib.h> | 
|  | #include <string.h> | 
|  | #include <unistd.h> | 
|  | #include <zircon/device/usb-peripheral.h> | 
|  | #include <zircon/hw/usb/hid.h> | 
|  | #include <zircon/process.h> | 
|  | #include <zircon/syscalls.h> | 
|  |  | 
|  | #include <atomic> | 
|  | #include <memory> | 
|  | #include <vector> | 
|  |  | 
|  | #include <ddk/binding.h> | 
|  | #include <ddk/debug.h> | 
|  | #include <ddk/device.h> | 
|  | #include <ddk/driver.h> | 
|  | #include <ddk/metadata.h> | 
|  | #include <ddk/platform-defs.h> | 
|  | #include <ddktl/device.h> | 
|  | #include <ddktl/protocol/usb/function.h> | 
|  | #include <fbl/algorithm.h> | 
|  | #include <fbl/condition_variable.h> | 
|  | #include <fbl/mutex.h> | 
|  | #include <usb/request-cpp.h> | 
|  | #include <usb/usb-request.h> | 
|  |  | 
|  | #define BULK_MAX_PACKET 512 | 
|  | #define FTDI_STATUS_SIZE 2 | 
|  |  | 
|  | namespace fake_ftdi_function { | 
|  |  | 
|  | class FakeFtdiFunction; | 
|  | using DeviceType = ddk::Device<FakeFtdiFunction, ddk::Unbindable>; | 
|  | class FakeFtdiFunction : public DeviceType { | 
|  | public: | 
|  | FakeFtdiFunction(zx_device_t* parent) : DeviceType(parent), function_(parent) {} | 
|  | zx_status_t Bind(); | 
|  | // |ddk::Device| | 
|  | void DdkUnbind(ddk::UnbindTxn txn); | 
|  | // |ddk::Device| | 
|  | void DdkRelease(); | 
|  |  | 
|  | static size_t UsbFunctionInterfaceGetDescriptorsSize(void* ctx); | 
|  |  | 
|  | static void UsbFunctionInterfaceGetDescriptors(void* ctx, void* out_descriptors_buffer, | 
|  | size_t descriptors_size, | 
|  | size_t* out_descriptors_actual); | 
|  | static zx_status_t UsbFunctionInterfaceControl(void* ctx, const usb_setup_t* setup, | 
|  | const void* write_buffer, size_t write_size, | 
|  | void* out_read_buffer, size_t read_size, | 
|  | size_t* out_read_actual); | 
|  | static zx_status_t UsbFunctionInterfaceSetConfigured(void* ctx, bool configured, | 
|  | usb_speed_t speed); | 
|  | static zx_status_t UsbFunctionInterfaceSetInterface(void* ctx, uint8_t interface, | 
|  | uint8_t alt_setting); | 
|  |  | 
|  | private: | 
|  | int Thread(); | 
|  | void DataInComplete() TA_REQ(mtx_); | 
|  | void DataOutComplete() TA_REQ(mtx_); | 
|  | void RequestQueue(usb_request_t* req, const usb_request_complete_t* completion); | 
|  | void CompletionCallback(usb_request_t* req); | 
|  |  | 
|  | usb_function_interface_protocol_ops_t function_interface_ops_{ | 
|  | .get_descriptors_size = UsbFunctionInterfaceGetDescriptorsSize, | 
|  | .get_descriptors = UsbFunctionInterfaceGetDescriptors, | 
|  | .control = UsbFunctionInterfaceControl, | 
|  | .set_configured = UsbFunctionInterfaceSetConfigured, | 
|  | .set_interface = UsbFunctionInterfaceSetInterface, | 
|  | }; | 
|  | ddk::UsbFunctionProtocolClient function_; | 
|  |  | 
|  | struct fake_ftdi_descriptor_t { | 
|  | usb_interface_descriptor_t interface; | 
|  | usb_endpoint_descriptor_t bulk_in; | 
|  | usb_endpoint_descriptor_t bulk_out; | 
|  | } __PACKED descriptor_; | 
|  |  | 
|  | size_t descriptor_size_ = 0; | 
|  |  | 
|  | size_t parent_req_size_ = 0; | 
|  | uint8_t bulk_out_addr_ = 0; | 
|  | uint8_t bulk_in_addr_ = 0; | 
|  |  | 
|  | std::optional<usb::Request<>> data_in_req_ TA_GUARDED(mtx_); | 
|  | bool data_in_req_complete_ TA_GUARDED(mtx_) = false; | 
|  |  | 
|  | std::optional<usb::Request<>> data_out_req_ TA_GUARDED(mtx_); | 
|  | bool data_out_req_complete_ TA_GUARDED(mtx_) = false; | 
|  |  | 
|  | fbl::ConditionVariable event_ TA_GUARDED(mtx_); | 
|  | fbl::Mutex mtx_; | 
|  |  | 
|  | bool configured_ = false; | 
|  | bool active_ = false; | 
|  | thrd_t thread_ = {}; | 
|  | std::atomic<int> pending_request_count_; | 
|  | }; | 
|  |  | 
|  | void FakeFtdiFunction::CompletionCallback(usb_request_t* req) { | 
|  | fbl::AutoLock lock(&mtx_); | 
|  | if (req == data_in_req_->request()) { | 
|  | data_in_req_complete_ = true; | 
|  | } else if (req == data_out_req_->request()) { | 
|  | data_out_req_complete_ = true; | 
|  | } | 
|  | event_.Signal(); | 
|  | } | 
|  |  | 
|  | void FakeFtdiFunction::RequestQueue(usb_request_t* req, const usb_request_complete_t* completion) { | 
|  | atomic_fetch_add(&pending_request_count_, 1); | 
|  | function_.RequestQueue(req, completion); | 
|  | } | 
|  |  | 
|  | void FakeFtdiFunction::DataInComplete() {} | 
|  |  | 
|  | void FakeFtdiFunction::DataOutComplete() { | 
|  | if (data_out_req_->request()->response.status != ZX_OK) { | 
|  | return; | 
|  | } | 
|  | std::vector<uint8_t> data(data_out_req_->request()->response.actual); | 
|  | // std::vector should zero-initialize | 
|  | __UNUSED size_t copied = usb_request_copy_from(data_out_req_->request(), data.data(), data.size(), 0); | 
|  |  | 
|  | usb_request_complete_t complete = { | 
|  | .callback = | 
|  | [](void* ctx, usb_request_t* req) { | 
|  | return static_cast<FakeFtdiFunction*>(ctx)->CompletionCallback(req); | 
|  | }, | 
|  | .ctx = this, | 
|  | }; | 
|  |  | 
|  | // Queue up another write. | 
|  | RequestQueue(data_out_req_->request(), &complete); | 
|  |  | 
|  | // Queue up exact same read data. The first FTDI_STATUS_SIZE bytes should | 
|  | // be left alone. | 
|  | data_in_req_->request()->header.length = data.size() + FTDI_STATUS_SIZE; | 
|  | data_in_req_->request()->header.ep_address = bulk_in_addr_; | 
|  |  | 
|  | copied = data_in_req_->CopyTo(data.data(), data.size(), FTDI_STATUS_SIZE); | 
|  |  | 
|  | RequestQueue(data_in_req_->request(), &complete); | 
|  | } | 
|  |  | 
|  | int FakeFtdiFunction::Thread() { | 
|  | while (1) { | 
|  | fbl::AutoLock lock(&mtx_); | 
|  | if (!(data_in_req_complete_ || data_out_req_complete_ || (!active_))) { | 
|  | event_.Wait(&mtx_); | 
|  | } | 
|  | if (!active_ && !atomic_load(&pending_request_count_)) { | 
|  | return 0; | 
|  | } | 
|  | if (data_in_req_complete_) { | 
|  | atomic_fetch_add(&pending_request_count_, -1); | 
|  | data_in_req_complete_ = false; | 
|  | DataInComplete(); | 
|  | } | 
|  | if (data_out_req_complete_) { | 
|  | atomic_fetch_add(&pending_request_count_, -1); | 
|  | data_out_req_complete_ = false; | 
|  | DataOutComplete(); | 
|  | } | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | size_t FakeFtdiFunction::UsbFunctionInterfaceGetDescriptorsSize(void* ctx) { | 
|  | FakeFtdiFunction* func = static_cast<FakeFtdiFunction*>(ctx); | 
|  | return func->descriptor_size_; | 
|  | } | 
|  |  | 
|  | void FakeFtdiFunction::UsbFunctionInterfaceGetDescriptors(void* ctx, void* out_descriptors_buffer, | 
|  | size_t descriptors_size, | 
|  | size_t* out_descriptors_actual) { | 
|  | FakeFtdiFunction* func = static_cast<FakeFtdiFunction*>(ctx); | 
|  | memcpy(out_descriptors_buffer, &func->descriptor_, | 
|  | std::min(descriptors_size, func->descriptor_size_)); | 
|  | *out_descriptors_actual = func->descriptor_size_; | 
|  | } | 
|  |  | 
|  | zx_status_t FakeFtdiFunction::UsbFunctionInterfaceControl(void* ctx, const usb_setup_t* setup, | 
|  | const void* write_buffer, | 
|  | size_t write_size, void* out_read_buffer, | 
|  | size_t read_size, | 
|  | size_t* out_read_actual) { | 
|  | if (out_read_actual) { | 
|  | *out_read_actual = 0; | 
|  | } | 
|  | return ZX_OK; | 
|  | } | 
|  |  | 
|  | zx_status_t FakeFtdiFunction::UsbFunctionInterfaceSetConfigured(void* ctx, bool configured, | 
|  | usb_speed_t speed) { | 
|  | FakeFtdiFunction* func = static_cast<FakeFtdiFunction*>(ctx); | 
|  | fbl::AutoLock lock(&func->mtx_); | 
|  | zx_status_t status; | 
|  |  | 
|  | if (configured) { | 
|  | if (func->configured_) { | 
|  | return ZX_OK; | 
|  | } | 
|  | func->configured_ = true; | 
|  |  | 
|  | if ((status = func->function_.ConfigEp(&func->descriptor_.bulk_in, nullptr)) != ZX_OK || | 
|  | (status = func->function_.ConfigEp(&func->descriptor_.bulk_out, nullptr)) != ZX_OK) { | 
|  | zxlogf(ERROR, "ftdi-function: usb_function_config_ep failed"); | 
|  | } | 
|  | // queue first read on OUT endpoint | 
|  | usb_request_complete_t complete = { | 
|  | .callback = | 
|  | [](void* ctx, usb_request_t* req) { | 
|  | return static_cast<FakeFtdiFunction*>(ctx)->CompletionCallback(req); | 
|  | }, | 
|  | .ctx = ctx, | 
|  | }; | 
|  | zxlogf(INFO, "ftdi-function: about to configure!"); | 
|  | if (func->data_out_req_) { | 
|  | zxlogf(INFO, "We have data out!"); | 
|  | } | 
|  | func->RequestQueue(func->data_out_req_->request(), &complete); | 
|  | } else { | 
|  | func->configured_ = false; | 
|  | } | 
|  | return ZX_OK; | 
|  | } | 
|  |  | 
|  | zx_status_t FakeFtdiFunction::UsbFunctionInterfaceSetInterface(void* ctx, uint8_t interface, | 
|  | uint8_t alt_setting) { | 
|  | return ZX_OK; | 
|  | } | 
|  |  | 
|  | zx_status_t FakeFtdiFunction::Bind() { | 
|  | fbl::AutoLock lock(&mtx_); | 
|  |  | 
|  | descriptor_size_ = sizeof(descriptor_); | 
|  | descriptor_.interface = { | 
|  | .bLength = sizeof(usb_interface_descriptor_t), | 
|  | .bDescriptorType = USB_DT_INTERFACE, | 
|  | .bInterfaceNumber = 0, | 
|  | .bAlternateSetting = 0, | 
|  | .bNumEndpoints = 2, | 
|  | .bInterfaceClass = 0xFF, | 
|  | .bInterfaceSubClass = 0xFF, | 
|  | .bInterfaceProtocol = 0xFF, | 
|  | .iInterface = 0, | 
|  | }; | 
|  | descriptor_.bulk_in = { | 
|  | .bLength = sizeof(usb_endpoint_descriptor_t), | 
|  | .bDescriptorType = USB_DT_ENDPOINT, | 
|  | .bEndpointAddress = USB_ENDPOINT_IN,  // set later | 
|  | .bmAttributes = USB_ENDPOINT_BULK, | 
|  | .wMaxPacketSize = htole16(BULK_MAX_PACKET), | 
|  | .bInterval = 0, | 
|  | }; | 
|  | descriptor_.bulk_out = { | 
|  | .bLength = sizeof(usb_endpoint_descriptor_t), | 
|  | .bDescriptorType = USB_DT_ENDPOINT, | 
|  | .bEndpointAddress = USB_ENDPOINT_OUT,  // set later | 
|  | .bmAttributes = USB_ENDPOINT_BULK, | 
|  | .wMaxPacketSize = htole16(BULK_MAX_PACKET), | 
|  | .bInterval = 0, | 
|  | }; | 
|  |  | 
|  | active_ = true; | 
|  | atomic_init(&pending_request_count_, 0); | 
|  |  | 
|  | parent_req_size_ = function_.GetRequestSize(); | 
|  |  | 
|  | zx_status_t status = function_.AllocInterface(&descriptor_.interface.bInterfaceNumber); | 
|  | if (status != ZX_OK) { | 
|  | zxlogf(ERROR, "FakeFtdiFunction: usb_function_alloc_interface failed"); | 
|  | return status; | 
|  | } | 
|  | status = function_.AllocEp(USB_DIR_IN, &descriptor_.bulk_in.bEndpointAddress); | 
|  | if (status != ZX_OK) { | 
|  | zxlogf(ERROR, "FakeFtdiFunction: usb_function_alloc_ep failed"); | 
|  | return status; | 
|  | } | 
|  | status = function_.AllocEp(USB_DIR_OUT, &descriptor_.bulk_out.bEndpointAddress); | 
|  | if (status != ZX_OK) { | 
|  | zxlogf(ERROR, "FakeFtdiFunction: usb_function_alloc_ep failed"); | 
|  | return status; | 
|  | } | 
|  |  | 
|  | bulk_in_addr_ = descriptor_.bulk_in.bEndpointAddress; | 
|  | bulk_out_addr_ = descriptor_.bulk_out.bEndpointAddress; | 
|  |  | 
|  | status = usb::Request<>::Alloc(&data_out_req_, BULK_MAX_PACKET, bulk_out_addr_, parent_req_size_); | 
|  | if (status != ZX_OK) { | 
|  | return status; | 
|  | } | 
|  | status = usb::Request<>::Alloc(&data_in_req_, BULK_MAX_PACKET, bulk_in_addr_, parent_req_size_); | 
|  | if (status != ZX_OK) { | 
|  | return status; | 
|  | } | 
|  |  | 
|  | status = DdkAdd("usb-hid-function"); | 
|  | if (status != ZX_OK) { | 
|  | return status; | 
|  | } | 
|  | function_.SetInterface(this, &function_interface_ops_); | 
|  |  | 
|  | thrd_create( | 
|  | &thread_, [](void* ctx) { return static_cast<FakeFtdiFunction*>(ctx)->Thread(); }, this); | 
|  |  | 
|  | return ZX_OK; | 
|  | } | 
|  |  | 
|  | void FakeFtdiFunction::DdkUnbind(ddk::UnbindTxn txn) { | 
|  | fbl::AutoLock lock(&mtx_); | 
|  | active_ = false; | 
|  | event_.Signal(); | 
|  | lock.release(); | 
|  |  | 
|  | int retval; | 
|  | thrd_join(thread_, &retval); | 
|  |  | 
|  | txn.Reply(); | 
|  | } | 
|  |  | 
|  | void FakeFtdiFunction::DdkRelease() { delete this; } | 
|  |  | 
|  | zx_status_t bind(void* ctx, zx_device_t* parent) { | 
|  | auto dev = std::make_unique<FakeFtdiFunction>(parent); | 
|  | zx_status_t status = dev->Bind(); | 
|  | if (status == ZX_OK) { | 
|  | // devmgr is now in charge of the memory for dev | 
|  | dev.release(); | 
|  | } | 
|  | return ZX_OK; | 
|  | } | 
|  | static constexpr zx_driver_ops_t driver_ops = []() { | 
|  | zx_driver_ops_t ops = {}; | 
|  | ops.version = DRIVER_OPS_VERSION; | 
|  | ops.bind = bind; | 
|  | return ops; | 
|  | }(); | 
|  |  | 
|  | }  // namespace fake_ftdi_function | 
|  |  | 
|  | // clang-format off | 
|  | ZIRCON_DRIVER_BEGIN(ftdi_function, fake_ftdi_function::driver_ops, "zircon", "0.1", 4) | 
|  | BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_USB_FUNCTION), | 
|  | BI_ABORT_IF(NE, BIND_USB_CLASS, USB_CLASS_VENDOR), | 
|  | BI_ABORT_IF(NE, BIND_USB_SUBCLASS, USB_SUBCLASS_VENDOR), | 
|  | BI_MATCH_IF(EQ, BIND_USB_PROTOCOL, USB_PROTOCOL_TEST_FTDI), | 
|  | ZIRCON_DRIVER_END(ftdi_function) | 
|  | // clang-format on |