[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