blob: f2abf3f412f9407398245aa819e03e15a350699e [file] [log] [blame]
// 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);
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_;
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