blob: 3856daf62cac546f11b1f918d043c0b741c2dba8 [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 "two-endpoint-hid-function.h"
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <zircon/device/usb-peripheral.h>
#include <zircon/process.h>
#include <zircon/syscalls.h>
#include <memory>
#include <ddk/binding.h>
#include <ddk/debug.h>
#include <ddk/driver.h>
#include <ddk/metadata.h>
#include <ddk/platform-defs.h>
#include <fbl/algorithm.h>
#include <usb/usb-request.h>
constexpr int BULK_MAX_PACKET = 512;
namespace two_endpoint_hid_function {
static const uint8_t boot_mouse_r_desc[50] = {
0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
0x09, 0x02, // Usage (Mouse)
0xA1, 0x01, // Collection (Application)
0x09, 0x01, // Usage (Pointer)
0xA1, 0x00, // Collection (Physical)
0x05, 0x09, // Usage Page (Button)
0x19, 0x01, // Usage Minimum (0x01)
0x29, 0x03, // Usage Maximum (0x03)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
0x95, 0x03, // Report Count (3)
0x75, 0x01, // Report Size (1)
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,No Null Position)
0x95, 0x01, // Report Count (1)
0x75, 0x05, // Report Size (5)
0x81, 0x03, // Input (Const,Var,Abs,No Wrap,Linear,No Null Position)
0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
0x09, 0x30, // Usage (X)
0x09, 0x31, // Usage (Y)
0x15, 0x81, // Logical Minimum (-127)
0x25, 0x7F, // Logical Maximum (127)
0x75, 0x08, // Report Size (8)
0x95, 0x02, // Report Count (2)
0x81, 0x06, // Input (Data,Var,Rel,No Wrap,Linear,No Null Position)
0xC0, // End Collection
0xC0, // End Collection
};
size_t FakeUsbHidFunction::UsbFunctionInterfaceGetDescriptorsSize(void* ctx) {
FakeUsbHidFunction* func = static_cast<FakeUsbHidFunction*>(ctx);
return func->descriptor_size_;
}
void FakeUsbHidFunction::UsbFunctionInterfaceGetDescriptors(void* ctx, void* out_descriptors_buffer,
size_t descriptors_size,
size_t* out_descriptors_actual) {
FakeUsbHidFunction* func = static_cast<FakeUsbHidFunction*>(ctx);
memcpy(out_descriptors_buffer, func->descriptor_.get(),
std::min(descriptors_size, func->descriptor_size_));
*out_descriptors_actual = func->descriptor_size_;
}
zx_status_t FakeUsbHidFunction::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) {
FakeUsbHidFunction* func = static_cast<FakeUsbHidFunction*>(ctx);
fbl::AutoLock lock(&func->mtx_);
if (setup->bmRequestType == (USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_INTERFACE)) {
if (setup->bRequest == USB_REQ_GET_DESCRIPTOR) {
memcpy(out_read_buffer, func->report_desc_.data(), func->report_desc_.size());
*out_read_actual = func->report_desc_.size();
return ZX_OK;
}
}
if (setup->bmRequestType == (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE)) {
if (setup->bRequest == USB_HID_GET_REPORT) {
memcpy(out_read_buffer, func->report_.data(), func->report_.size());
*out_read_actual = func->report_.size();
return ZX_OK;
}
if (setup->bRequest == USB_HID_GET_PROTOCOL) {
memcpy(out_read_buffer, &func->hid_protocol_, sizeof(func->hid_protocol_));
*out_read_actual = sizeof(func->hid_protocol_);
return ZX_OK;
}
}
if (setup->bmRequestType == (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE)) {
if (setup->bRequest == USB_HID_SET_REPORT) {
memcpy(func->report_.data(), write_buffer, func->report_.size());
return ZX_OK;
}
if (setup->bRequest == USB_HID_SET_PROTOCOL) {
func->hid_protocol_ = static_cast<uint8_t>(setup->wValue);
return ZX_OK;
}
}
return ZX_ERR_IO_REFUSED;
}
zx_status_t FakeUsbHidFunction::UsbFunctionInterfaceSetConfigured(void* ctx, bool configured,
usb_speed_t speed) {
return ZX_OK;
}
zx_status_t FakeUsbHidFunction::UsbFunctionInterfaceSetInterface(void* ctx, uint8_t interface,
uint8_t alt_setting) {
return ZX_OK;
}
int FakeUsbHidFunction::Thread() {
while (1) {
fbl::AutoLock lock(&mtx_);
if (!data_out_req_complete_) {
event_.Wait(&mtx_);
}
if (!active_) {
return 0;
}
data_out_req_complete_ = false;
// Release the lock before queueing, as the callback acquires the lock and is sometimes run in
// the same thread.
lock.release();
usb_request_complete_t complete = {
.callback =
[](void* ctx, usb_request_t* req) {
return static_cast<FakeUsbHidFunction*>(ctx)->UsbEndpointOutCallback(req);
},
.ctx = this,
};
function_.RequestQueue(data_out_req_->request(), &complete);
}
return 0;
}
zx_status_t FakeUsbHidFunction::Bind() {
fbl::AutoLock lock(&mtx_);
report_desc_.resize(sizeof(boot_mouse_r_desc));
memcpy(report_desc_.data(), &boot_mouse_r_desc, sizeof(boot_mouse_r_desc));
report_.resize(3);
descriptor_size_ = sizeof(fake_usb_hid_descriptor_t) + sizeof(usb_hid_descriptor_entry_t);
descriptor_.reset(static_cast<fake_usb_hid_descriptor_t*>(calloc(1, descriptor_size_)));
descriptor_->interface = {
.bLength = sizeof(usb_interface_descriptor_t),
.bDescriptorType = USB_DT_INTERFACE,
.bInterfaceNumber = 0,
.bAlternateSetting = 0,
.bNumEndpoints = 1,
.bInterfaceClass = USB_CLASS_HID,
.bInterfaceSubClass = USB_HID_SUBCLASS_BOOT,
.bInterfaceProtocol = USB_HID_PROTOCOL_MOUSE,
.iInterface = 0,
};
descriptor_->interrupt_in = {
.bLength = sizeof(usb_endpoint_descriptor_t),
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = USB_ENDPOINT_IN, // set later
.bmAttributes = USB_ENDPOINT_INTERRUPT,
.wMaxPacketSize = htole16(BULK_MAX_PACKET),
.bInterval = 8,
};
descriptor_->interrupt_out = {
.bLength = sizeof(usb_endpoint_descriptor_t),
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = USB_ENDPOINT_OUT, // set later
.bmAttributes = USB_ENDPOINT_INTERRUPT,
.wMaxPacketSize = htole16(BULK_MAX_PACKET),
.bInterval = 8,
};
descriptor_->hid_descriptor = {
.bLength = sizeof(usb_hid_descriptor_t) + sizeof(usb_hid_descriptor_entry_t),
.bDescriptorType = USB_DT_HID,
.bcdHID = 0,
.bCountryCode = 0,
.bNumDescriptors = 1,
};
descriptor_->hid_descriptor.descriptors[0] = {
.bDescriptorType = 0x22, // HID TYPE REPORT
.wDescriptorLength = static_cast<uint16_t>(report_desc_.size()),
};
zx_status_t status = function_.AllocInterface(&descriptor_->interface.bInterfaceNumber);
if (status != ZX_OK) {
zxlogf(ERROR, "FakeUsbHidFunction: usb_function_alloc_interface failed");
return status;
}
status = function_.AllocEp(USB_DIR_IN, &descriptor_->interrupt_in.bEndpointAddress);
if (status != ZX_OK) {
zxlogf(ERROR, "FakeUsbHidFunction: usb_function_alloc_ep for endpoint in failed");
return status;
}
status = function_.AllocEp(USB_DIR_OUT, &descriptor_->interrupt_out.bEndpointAddress);
if (status != ZX_OK) {
zxlogf(ERROR, "FakeUsbHidFunction: usb_function_alloc_ep for endpoint out failed");
return status;
}
status = usb::Request<>::Alloc(&data_out_req_, BULK_MAX_PACKET,
descriptor_->interrupt_out.bEndpointAddress,
function_.GetRequestSize());
if (status != ZX_OK) {
return status;
}
active_ = true;
thrd_create(
&thread_, [](void* ctx) { return static_cast<FakeUsbHidFunction*>(ctx)->Thread(); }, this);
status = DdkAdd("usb-hid-function");
if (status != ZX_OK) {
return status;
}
function_.SetInterface(this, &function_interface_ops_);
return ZX_OK;
}
void FakeUsbHidFunction::DdkUnbind(ddk::UnbindTxn txn) {
{
fbl::AutoLock lock(&mtx_);
active_ = false;
event_.Signal();
}
int retval;
thrd_join(thread_, &retval);
txn.Reply();
}
void FakeUsbHidFunction::DdkRelease() { delete this; }
zx_status_t bind(void* ctx, zx_device_t* parent) {
auto dev = std::make_unique<FakeUsbHidFunction>(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;
}
void FakeUsbHidFunction::UsbEndpointOutCallback(usb_request_t* request) {
fbl::AutoLock lock(&mtx_);
if (request->response.status == ZX_OK) {
report_.resize(request->response.actual);
usb_request_copy_from(request, report_.data(), report_.size(), 0);
} else {
zxlogf(ERROR, "request status: %d", request->response.status);
active_ = false;
}
data_out_req_complete_ = true;
event_.Signal();
}
static constexpr zx_driver_ops_t two_endpoint_hid_driver_ops = []() {
zx_driver_ops_t ops = {};
ops.version = DRIVER_OPS_VERSION;
ops.bind = bind;
return ops;
}();
} // namespace two_endpoint_hid_function
// clang-format off
ZIRCON_DRIVER_BEGIN(two_endpoint_hid_function, two_endpoint_hid_function::two_endpoint_hid_driver_ops, "zircon", "0.1", 3)
BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_USB_FUNCTION),
BI_ABORT_IF(NE, BIND_USB_CLASS, USB_CLASS_HID),
BI_MATCH_IF(EQ, BIND_USB_PROTOCOL, USB_PROTOCOL_TEST_HID_TWO_ENDPOINT),
ZIRCON_DRIVER_END(two_endpoint_hid_function)
// clang-format on