[usb-hid] Add usb-virtual-bus integration test

ZX-3815 #comment

This creates the framework for the integration
tests for usb-hid. It does this by adding a
fake peripheral usb driver `usb-hid-function`
which pretends to be a USB HID boot mouse.

GetReport and SetReport are currently tested.
More tests for other functions can be added in
the future.

TEST: runtests -t usb-hid-test
Change-Id: Ia0eab0a7d72ce59f0f925a167fb78b4836771875
diff --git a/zircon/system/dev/input/BUILD.gn b/zircon/system/dev/input/BUILD.gn
index b2ae529..eaaeb8bc 100644
--- a/zircon/system/dev/input/BUILD.gn
+++ b/zircon/system/dev/input/BUILD.gn
@@ -12,6 +12,7 @@
     "hidctl",
     "i2c-hid",
     "usb-hid",
+    "usb-hid:usb-hid-fake-usb",
   ]
   if (current_cpu == "x64") {
     deps += [ "pc-ps2" ]
diff --git a/zircon/system/dev/input/usb-hid/BUILD.gn b/zircon/system/dev/input/usb-hid/BUILD.gn
index 0caaae3..edcec4d 100644
--- a/zircon/system/dev/input/usb-hid/BUILD.gn
+++ b/zircon/system/dev/input/usb-hid/BUILD.gn
@@ -18,3 +18,21 @@
     "$zx/system/ulib/zircon",
   ]
 }
+
+driver("usb-hid-fake-usb") {
+  sources = [
+    "usb-hid-function.cpp",
+  ]
+  deps = [
+    "$zx/system/banjo/ddk.protocol.usb",
+    "$zx/system/banjo/ddk.protocol.usb.function",
+    "$zx/system/banjo/ddk.protocol.usb.request",
+    "$zx/system/dev/lib/usb",
+    "$zx/system/ulib/ddk",
+    "$zx/system/ulib/ddktl",
+    "$zx/system/ulib/fbl",
+    "$zx/system/ulib/hid",
+    "$zx/system/ulib/sync",
+    "$zx/system/ulib/zircon",
+  ]
+}
diff --git a/zircon/system/dev/input/usb-hid/usb-hid-function.cpp b/zircon/system/dev/input/usb-hid/usb-hid-function.cpp
new file mode 100644
index 0000000..87369fe
--- /dev/null
+++ b/zircon/system/dev/input/usb-hid/usb-hid-function.cpp
@@ -0,0 +1,200 @@
+// 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 "usb-hid-function.h"
+
+#include <assert.h>
+#include <memory>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#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>
+#include <zircon/device/usb-peripheral.h>
+#include <zircon/process.h>
+#include <zircon/syscalls.h>
+
+namespace usb_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);
+
+    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->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 (out_read_actual) {
+        *out_read_actual = 0;
+    }
+    return ZX_OK;
+}
+
+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;
+}
+
+zx_status_t FakeUsbHidFunction::Bind() {
+
+    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 = 0,
+        .bInterfaceProtocol = 0,
+        .iInterface = 0,
+    };
+    descriptor_->interrupt = {
+        .bLength = sizeof(usb_endpoint_descriptor_t),
+        .bDescriptorType = USB_DT_ENDPOINT,
+        .bEndpointAddress = USB_ENDPOINT_IN, // set later
+        .bmAttributes = USB_ENDPOINT_INTERRUPT,
+        .wMaxPacketSize = htole16(0x1000),
+        .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,
+        .descriptors = {},
+    };
+    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\n");
+        return status;
+    }
+    status = function_.AllocEp(USB_DIR_IN, &descriptor_->interrupt.bEndpointAddress);
+    if (status != ZX_OK) {
+        zxlogf(ERROR, "FakeUsbHidFunction: usb_function_alloc_ep failed\n");
+        return status;
+    }
+
+    status = DdkAdd("usb-hid-function");
+    if (status != ZX_OK) {
+        return status;
+    }
+    function_.SetInterface(this, &function_interface_ops_);
+    return ZX_OK;
+}
+
+void FakeUsbHidFunction::DdkUnbind() {
+    DdkRemove();
+}
+
+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;
+}
+static zx_driver_ops_t driver_ops = []() {
+    zx_driver_ops_t ops = {};
+    ops.version = DRIVER_OPS_VERSION;
+    ops.bind = bind;
+    return ops;
+}();
+
+} // namespace usb_hid_function
+
+// clang-format off
+ZIRCON_DRIVER_BEGIN(usb_hid_function, usb_hid_function::driver_ops, "zircon", "0.1", 2)
+    BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_USB_FUNCTION),
+    BI_MATCH_IF(EQ, BIND_USB_CLASS, USB_CLASS_HID),
+ZIRCON_DRIVER_END(usb_hid_function)
+    // clang-format on
diff --git a/zircon/system/dev/input/usb-hid/usb-hid-function.h b/zircon/system/dev/input/usb-hid/usb-hid-function.h
new file mode 100644
index 0000000..db08c2c
--- /dev/null
+++ b/zircon/system/dev/input/usb-hid/usb-hid-function.h
@@ -0,0 +1,72 @@
+// 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.
+
+#pragma once
+
+#include <memory>
+#include <vector>
+
+#include <ddk/device.h>
+#include <ddktl/device.h>
+#include <ddktl/protocol/usb/function.h>
+#include <zircon/hw/usb/hid.h>
+
+namespace usb_hid_function {
+
+// This driver is for testing the USB-HID driver. It binds as a peripheral USB
+// device and sends fake HID report descriptors and HID reports. The tests for
+// this driver and the USB-HID driver are with the other usb-virtual-bus tests.
+class FakeUsbHidFunction;
+using DeviceType = ddk::Device<FakeUsbHidFunction, ddk::Unbindable>;
+class FakeUsbHidFunction : public DeviceType {
+public:
+    FakeUsbHidFunction(zx_device_t* parent)
+        : DeviceType(parent), function_(parent) {}
+    zx_status_t Bind();
+    // |ddk::Device|
+    void DdkUnbind();
+    // |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:
+    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_;
+
+    std::vector<uint8_t> report_desc_;
+    std::vector<uint8_t> report_;
+
+    struct fake_usb_hid_descriptor_t {
+        usb_interface_descriptor_t interface;
+        usb_endpoint_descriptor_t interrupt;
+        usb_hid_descriptor_t hid_descriptor;
+    } __PACKED;
+
+    struct DescriptorDeleter {
+          void operator()(fake_usb_hid_descriptor_t* desc) { free(desc); }
+    };
+    std::unique_ptr<fake_usb_hid_descriptor_t, DescriptorDeleter> descriptor_;
+    size_t descriptor_size_;
+};
+
+} // namespace usb_hid_function
diff --git a/zircon/system/dev/input/usb-hid/usb-hid.c b/zircon/system/dev/input/usb-hid/usb-hid.c
index 7bd907e..d0df95b 100644
--- a/zircon/system/dev/input/usb-hid/usb-hid.c
+++ b/zircon/system/dev/input/usb-hid/usb-hid.c
@@ -26,6 +26,8 @@
 
 #define to_usb_hid(d) containerof(d, usb_hid_device_t, hiddev)
 
+// This driver binds on any USB device that exposes HID reports. It passes the
+// reports to the HID driver by implementing the HidBus protocol.
 typedef struct usb_hid_device {
     zx_device_t* zxdev;
     zx_device_t* usbdev;
diff --git a/zircon/system/public/zircon/hw/usb/hid.h b/zircon/system/public/zircon/hw/usb/hid.h
index 301a3ec..879e114 100644
--- a/zircon/system/public/zircon/hw/usb/hid.h
+++ b/zircon/system/public/zircon/hw/usb/hid.h
@@ -7,7 +7,7 @@
 
 // clang-format off
 
-__BEGIN_CDECLS;
+__BEGIN_CDECLS
 
 /* HID Request Values */
 #define USB_HID_GET_REPORT                  0x01
@@ -31,6 +31,6 @@
     usb_hid_descriptor_entry_t descriptors[];
 } __attribute__((packed)) usb_hid_descriptor_t;
 
-__END_CDECLS;
+__END_CDECLS
 
 #endif  // SYSROOT_ZIRCON_HW_USB_HID_H_
diff --git a/zircon/system/utest/usb-virtual-bus/BUILD.gn b/zircon/system/utest/usb-virtual-bus/BUILD.gn
index 9131ab3..c10aea1 100644
--- a/zircon/system/utest/usb-virtual-bus/BUILD.gn
+++ b/zircon/system/utest/usb-virtual-bus/BUILD.gn
@@ -9,6 +9,7 @@
   ]
   deps = [
     "$zx/system/fidl/fuchsia-hardware-block:c",
+    "$zx/system/fidl/fuchsia-hardware-input:c",
     "$zx/system/fidl/fuchsia-hardware-nand:c",
     "$zx/system/fidl/fuchsia-hardware-usb-peripheral:c",
     "$zx/system/fidl/fuchsia-hardware-usb-peripheral-block:c",
@@ -24,6 +25,8 @@
     "$zx/system/ulib/fbl",
     "$zx/system/ulib/fdio",
     "$zx/system/ulib/fidl-async",
+    "$zx/system/ulib/fzl",
+    "$zx/system/ulib/hid",
     "$zx/system/ulib/launchpad",
     "$zx/system/ulib/ramdevice-client",
     "$zx/system/ulib/sync",
diff --git a/zircon/system/utest/usb-virtual-bus/usb-virtual-bus.cpp b/zircon/system/utest/usb-virtual-bus/usb-virtual-bus.cpp
index 597278db..353cea2 100644
--- a/zircon/system/utest/usb-virtual-bus/usb-virtual-bus.cpp
+++ b/zircon/system/utest/usb-virtual-bus/usb-virtual-bus.cpp
@@ -12,9 +12,11 @@
 #include <fbl/string.h>
 #include <fbl/unique_ptr.h>
 #include <fcntl.h>
+#include <fuchsia/hardware/input/c/fidl.h>
 #include <fuchsia/hardware/usb/peripheral/block/c/fidl.h>
 #include <fuchsia/hardware/usb/peripheral/c/fidl.h>
 #include <fuchsia/usb/virtualbus/c/fidl.h>
+#include <hid/boot.h>
 #include <lib/async-loop/cpp/loop.h>
 #include <lib/async-loop/loop.h>
 #include <lib/driver-integration-test/fixture.h>
@@ -25,6 +27,7 @@
 #include <lib/fdio/unsafe.h>
 #include <lib/fdio/watcher.h>
 #include <lib/fidl-async/bind.h>
+#include <lib/fzl/fdio.h>
 #include <sys/stat.h>
 #include <unistd.h>
 #include <zircon/hw/usb.h>
@@ -53,7 +56,8 @@
         ctx, txn, msg, reinterpret_cast<const fuchsia_hardware_usb_peripheral_Events_ops_t*>(ops));
 }
 
-template <typename F, typename... Args> zx_status_t FidlCall(const F& function, Args... args) {
+template <typename F, typename... Args>
+zx_status_t FidlCall(const F& function, Args... args) {
     zx_status_t status;
     zx_status_t status1;
     status = function(args..., &status1);
@@ -158,6 +162,49 @@
         *devpath = fbl::String::Concat({fbl::String("class/block/"), *devpath});
     }
 
+    // Initialize a Usb HID device. Asserts on failure.
+    void InitUsbHid(fbl::String* devpath) {
+        fuchsia_hardware_usb_peripheral_DeviceDescriptor device_desc = {};
+        device_desc.bcdUSB = htole16(0x0200);
+        device_desc.bMaxPacketSize0 = 64;
+        device_desc.bcdDevice = htole16(0x0100);
+        device_desc.bNumConfigurations = 1;
+        ASSERT_EQ(ZX_OK, FidlCall(fuchsia_hardware_usb_peripheral_DeviceSetDeviceDescriptor,
+                                  peripheral_.get(), &device_desc));
+
+        fuchsia_hardware_usb_peripheral_FunctionDescriptor usb_hid_function_desc = {
+            .interface_class = USB_CLASS_HID,
+            .interface_subclass = 0,
+            .interface_protocol = 0,
+        };
+
+        ASSERT_EQ(ZX_OK, FidlCall(fuchsia_hardware_usb_peripheral_DeviceAddFunction,
+                                  peripheral_.get(), &usb_hid_function_desc));
+        zx::channel handles[2];
+        ASSERT_EQ(ZX_OK, zx::channel::create(0, handles, handles + 1));
+        ASSERT_EQ(ZX_OK, fuchsia_hardware_usb_peripheral_DeviceSetStateChangeListener(
+                             peripheral_.get(), handles[1].get()));
+        ASSERT_EQ(ZX_OK,
+                  FidlCall(fuchsia_hardware_usb_peripheral_DeviceBindFunctions, peripheral_.get()));
+        async_loop_config_t config = {};
+        async::Loop loop(&config);
+        DispatchContext context = {};
+        context.loop = &loop;
+        fuchsia_hardware_usb_peripheral_Events_ops ops;
+        ops.FunctionRegistered = DispatchStateChange;
+        async_dispatcher_t* dispatcher = loop.dispatcher();
+        fidl_bind(dispatcher, handles[0].get(), dispatch_wrapper, &context, &ops);
+        loop.Run();
+
+        ASSERT_TRUE(context.state_changed);
+        ASSERT_EQ(ZX_OK, FidlCall(fuchsia_usb_virtualbus_BusConnect, virtual_bus_handle_.get()));
+        fbl::unique_fd fd(openat(devmgr_.devfs_root().get(), "class/input", O_RDONLY));
+        while (fdio_watch_directory(fd.get(), WaitForAnyFile, ZX_TIME_INFINITE, devpath) !=
+               ZX_ERR_STOP) {
+        }
+        *devpath = fbl::String::Concat({fbl::String("class/input/"), *devpath});
+    }
+
     USBVirtualBus() {
         zx::channel chn;
         args_.disable_block_watcher = true;
@@ -476,4 +523,52 @@
     EXPECT_EQ(proc_info.return_code, 0);
 }
 
+class UsbHidTest : public zxtest::Test {
+public:
+    void SetUp() override {
+        ASSERT_NO_FATAL_FAILURES(bus_.InitUsbHid(&devpath_));
+        bus_.GetHandles(&peripheral_, &virtual_bus_handle_);
+    }
+    void TearDown() override {
+        ASSERT_EQ(ZX_OK,
+                  FidlCall(fuchsia_hardware_usb_peripheral_DeviceClearFunctions, peripheral_->get()));
+        ASSERT_EQ(ZX_OK, FidlCall(fuchsia_usb_virtualbus_BusDisable, virtual_bus_handle_->get()));
+    }
+
+protected:
+    USBVirtualBus bus_;
+    fbl::String devpath_;
+    zx::unowned_channel peripheral_;
+    zx::unowned_channel virtual_bus_handle_;
+};
+
+TEST_F(UsbHidTest, SetAndGetReport) {
+    fbl::unique_fd fd_input(openat(bus_.GetRootFd(), devpath_.c_str(), O_RDWR));
+    ASSERT_GT(fd_input.get(), 0);
+
+    fzl::FdioCaller input_fdio_caller_;
+    input_fdio_caller_.reset(std::move(fd_input));
+
+    uint8_t buf[sizeof(hid_boot_mouse_report_t)] = {0xab, 0xbc, 0xde};
+    zx_status_t out_stat;
+    size_t out_report_count;
+
+    zx_status_t status = fuchsia_hardware_input_DeviceSetReport(
+        input_fdio_caller_.borrow_channel(), fuchsia_hardware_input_ReportType_INPUT, 0,
+        buf, sizeof(buf), &out_stat);
+    ASSERT_EQ(status, ZX_OK);
+    ASSERT_EQ(out_stat, ZX_OK);
+
+    status = fuchsia_hardware_input_DeviceGetReport(
+        input_fdio_caller_.borrow_channel(), fuchsia_hardware_input_ReportType_INPUT, 0, &out_stat,
+        buf, sizeof(buf), &out_report_count);
+    ASSERT_EQ(status, ZX_OK);
+    ASSERT_EQ(out_stat, ZX_OK);
+
+    ASSERT_EQ(out_report_count, sizeof(hid_boot_mouse_report_t));
+    ASSERT_EQ(0xab, buf[0]);
+    ASSERT_EQ(0xbc, buf[1]);
+    ASSERT_EQ(0xde, buf[2]);
+}
+
 } // namespace