|  | // 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 <lib/ddk/debug.h> | 
|  | #include <lib/ddk/driver.h> | 
|  | #include <lib/ddk/metadata.h> | 
|  | #include <lib/ddk/platform-defs.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 <fbl/algorithm.h> | 
|  | #include <fbl/auto_lock.h> | 
|  | #include <usb/usb-request.h> | 
|  |  | 
|  | #include "src/ui/input/drivers/usb-hid/function/two_endpoint_hid-bind.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, | 
|  | uint8_t* 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 uint8_t* write_buffer, size_t write_size, | 
|  | uint8_t* 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->bm_request_type == (USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_INTERFACE)) { | 
|  | if (setup->b_request == 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->bm_request_type == (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE)) { | 
|  | if (setup->b_request == 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->b_request == 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->bm_request_type == (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE)) { | 
|  | if (setup->b_request == USB_HID_SET_REPORT) { | 
|  | memcpy(func->report_.data(), write_buffer, func->report_.size()); | 
|  | return ZX_OK; | 
|  | } | 
|  | if (setup->b_request == USB_HID_SET_PROTOCOL) { | 
|  | func->hid_protocol_ = static_cast<uint8_t>(setup->w_value); | 
|  | 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_callback_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 = { | 
|  | .b_length = sizeof(usb_interface_descriptor_t), | 
|  | .b_descriptor_type = USB_DT_INTERFACE, | 
|  | .b_interface_number = 0, | 
|  | .b_alternate_setting = 0, | 
|  | .b_num_endpoints = 1, | 
|  | .b_interface_class = USB_CLASS_HID, | 
|  | .b_interface_sub_class = USB_HID_SUBCLASS_BOOT, | 
|  | .b_interface_protocol = USB_HID_PROTOCOL_MOUSE, | 
|  | .i_interface = 0, | 
|  | }; | 
|  | descriptor_->interrupt_in = { | 
|  | .b_length = sizeof(usb_endpoint_descriptor_t), | 
|  | .b_descriptor_type = USB_DT_ENDPOINT, | 
|  | .b_endpoint_address = USB_ENDPOINT_IN,  // set later | 
|  | .bm_attributes = USB_ENDPOINT_INTERRUPT, | 
|  | .w_max_packet_size = htole16(BULK_MAX_PACKET), | 
|  | .b_interval = 8, | 
|  | }; | 
|  | descriptor_->interrupt_out = { | 
|  | .b_length = sizeof(usb_endpoint_descriptor_t), | 
|  | .b_descriptor_type = USB_DT_ENDPOINT, | 
|  | .b_endpoint_address = USB_ENDPOINT_OUT,  // set later | 
|  | .bm_attributes = USB_ENDPOINT_INTERRUPT, | 
|  | .w_max_packet_size = htole16(BULK_MAX_PACKET), | 
|  | .b_interval = 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.b_interface_number); | 
|  | if (status != ZX_OK) { | 
|  | zxlogf(ERROR, "FakeUsbHidFunction: usb_function_alloc_interface failed"); | 
|  | return status; | 
|  | } | 
|  | status = function_.AllocEp(USB_DIR_IN, &descriptor_->interrupt_in.b_endpoint_address); | 
|  | 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.b_endpoint_address); | 
|  | 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.b_endpoint_address, | 
|  | 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); | 
|  | size_t result = usb_request_copy_from(request, report_.data(), report_.size(), 0); | 
|  | ZX_ASSERT(result == request->response.actual); | 
|  | } 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 | 
|  |  | 
|  | ZIRCON_DRIVER(two_endpoint_hid_function, two_endpoint_hid_function::two_endpoint_hid_driver_ops, | 
|  | "zircon", "0.1"); |