blob: c7d5034af3a156ce326d1f0f9c180cc47bf36196 [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 <fcntl.h>
#include <fidl/fuchsia.hardware.hidctl/cpp/wire.h>
#include <fidl/fuchsia.hardware.input/cpp/wire.h>
#include <fidl/fuchsia.input.report/cpp/wire.h>
#include <fuchsia/driver/test/cpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/component/incoming/cpp/protocol.h>
#include <lib/ddk/platform-defs.h>
#include <lib/device-watcher/cpp/device-watcher.h>
#include <lib/driver_test_realm/realm_builder/cpp/lib.h>
#include <lib/fdio/cpp/caller.h>
#include <lib/fdio/fd.h>
#include <lib/hid/boot.h>
#include <lib/sys/component/cpp/testing/realm_builder.h>
#include <lib/sys/component/cpp/testing/realm_builder_types.h>
#include <stdio.h>
#include <stdlib.h>
#include <zircon/syscalls.h>
#include <fbl/unique_fd.h>
#include <zxtest/zxtest.h>
namespace {
class HidDriverTest : public zxtest::Test {
protected:
void SetUp() override {
// Create and build the realm.
auto realm_builder = component_testing::RealmBuilder::Create();
driver_test_realm::Setup(realm_builder);
realm_ =
std::make_unique<component_testing::RealmRoot>(realm_builder.Build(loop_.dispatcher()));
// Start DriverTestRealm.
ASSERT_OK(realm_->component().Connect(driver_test_realm.NewRequest()));
fuchsia::driver::test::Realm_Start_Result realm_result;
auto args = fuchsia::driver::test::RealmArgs();
ASSERT_OK(driver_test_realm->Start(std::move(args), &realm_result));
ASSERT_FALSE(realm_result.is_err());
// Connect to dev.
fidl::InterfaceHandle<fuchsia::io::Node> dev;
ASSERT_OK(realm_->component().Connect("dev-topological", dev.NewRequest().TakeChannel()));
ASSERT_OK(fdio_fd_create(dev.TakeChannel().release(), dev_fd_.reset_and_get_address()));
// Wait for HidCtl to be created.
zx::result hidctl_channel =
device_watcher::RecursiveWaitForFile(dev_fd_.get(), "sys/test/hidctl");
ASSERT_OK(hidctl_channel.status_value());
fidl::ClientEnd<fuchsia_hardware_hidctl::Device> client_end(std::move(hidctl_channel.value()));
hidctl_client_.Bind(std::move(client_end));
}
template <typename Protocol>
zx::result<fidl::ClientEnd<Protocol>> WaitForFirstDevice(const std::string& path) {
fdio_cpp::UnownedFdioCaller caller(dev_fd_);
zx::result directory_result =
component::ConnectAt<fuchsia_io::Directory>(caller.directory(), path);
if (directory_result.is_error()) {
return directory_result.take_error();
}
auto& directory = directory_result.value();
zx::result watch_result =
device_watcher::WatchDirectoryForItems<zx::result<fidl::ClientEnd<Protocol>>>(
directory, [&directory](std::string_view devpath) {
return component::ConnectAt<Protocol>(directory, devpath);
});
if (watch_result.is_error()) {
return watch_result.take_error();
}
return std::move(watch_result.value());
}
async::Loop loop_ = async::Loop(&kAsyncLoopConfigNoAttachToCurrentThread);
fbl::unique_fd dev_fd_;
fidl::WireSyncClient<fuchsia_hardware_hidctl::Device> hidctl_client_;
std::unique_ptr<component_testing::RealmRoot> realm_;
fidl::SynchronousInterfacePtr<fuchsia::driver::test::Realm> driver_test_realm;
};
const uint8_t kBootMouseReportDesc[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
};
TEST_F(HidDriverTest, BootMouseTest) {
// Create a fake mouse device
fuchsia_hardware_hidctl::wire::HidCtlConfig config = {};
config.dev_num = 5;
config.boot_device = false;
config.dev_class = 0;
auto result = hidctl_client_->MakeHidDevice(
config, fidl::VectorView<uint8_t>::FromExternal(const_cast<uint8_t*>(kBootMouseReportDesc),
std::size(kBootMouseReportDesc)));
ASSERT_OK(result.status());
zx::socket hidctl_socket = std::move(result.value().report_socket);
fbl::unique_fd fd_device;
// Wait for input-report driver to bind to avoid binding to a device which is being torn down.
ASSERT_OK(
WaitForFirstDevice<fuchsia_input_report::InputDevice>("class/input-report").status_value());
// Open the /dev/class/input/ device created by MakeHidDevice.
zx::result input_client = WaitForFirstDevice<fuchsia_hardware_input::Controller>("class/input");
ASSERT_OK(input_client.status_value());
// Open a FIDL channel to the HID device
auto [device, server] = fidl::Endpoints<fuchsia_hardware_input::Device>::Create();
ASSERT_OK(fidl::WireCall(input_client.value())->OpenSession(std::move(server)));
fidl::WireSyncClient client(std::move(device));
// Make a synchronous FIDL call to ensure the session connection is alive.
ASSERT_OK(client->GetBootProtocol());
// Send a single mouse report
hid_boot_mouse_report_t mouse_report = {
.rel_x = 50,
.rel_y = 100,
};
ASSERT_OK(hidctl_socket.write(0, &mouse_report, sizeof(mouse_report), nullptr));
// Get the report event.
zx::event report_event;
{
auto result = client->GetReportsEvent();
ASSERT_OK(result.status());
ASSERT_OK(result.value().status);
report_event = std::move(result.value().event);
}
// Check that the report comes through
{
report_event.wait_one(ZX_USER_SIGNAL_0, zx::time::infinite(), nullptr);
hid_boot_mouse_report_t test_report = {};
auto response = client->ReadReport();
ASSERT_OK(response.status());
ASSERT_OK(response.value().status);
ASSERT_EQ(response.value().data.count(), sizeof(test_report));
memcpy(&test_report, response.value().data.data(), sizeof(test_report));
ASSERT_EQ(mouse_report.rel_x, test_report.rel_x);
ASSERT_EQ(mouse_report.rel_y, test_report.rel_y);
}
// Check that report descriptors match completely
{
auto response = client->GetReportDesc();
ASSERT_OK(response.status());
ASSERT_EQ(response.value().desc.count(), sizeof(kBootMouseReportDesc));
for (size_t i = 0; i < sizeof(kBootMouseReportDesc); i++) {
if (kBootMouseReportDesc[i] != response.value().desc[i]) {
printf("Index %ld of the report descriptor doesn't match\n", i);
}
EXPECT_EQ(kBootMouseReportDesc[i], response.value().desc[i]);
}
}
}
TEST_F(HidDriverTest, BootMouseTestInputReport) {
// Create a fake mouse device
fuchsia_hardware_hidctl::wire::HidCtlConfig config;
config.dev_num = 5;
config.boot_device = false;
config.dev_class = 0;
auto result = hidctl_client_->MakeHidDevice(
config, fidl::VectorView<uint8_t>::FromExternal(const_cast<uint8_t*>(kBootMouseReportDesc),
std::size(kBootMouseReportDesc)));
ASSERT_OK(result.status());
zx::socket hidctl_socket = std::move(result.value().report_socket);
// Open the /dev/class/input/ device created by MakeHidDevice.
zx::result input_client =
WaitForFirstDevice<fuchsia_input_report::InputDevice>("class/input-report");
ASSERT_OK(input_client.status_value());
fidl::WireSyncClient client(std::move(input_client.value()));
auto reader_endpoints = fidl::Endpoints<fuchsia_input_report::InputReportsReader>::Create();
auto reader = fidl::WireSyncClient<fuchsia_input_report::InputReportsReader>(
std::move(reader_endpoints.client));
ASSERT_OK(client->GetInputReportsReader(std::move(reader_endpoints.server)).status());
// Check the Descriptor.
{
auto response = client->GetDescriptor();
ASSERT_OK(response.status());
ASSERT_TRUE(response.value().descriptor.has_mouse());
ASSERT_TRUE(response.value().descriptor.mouse().has_input());
ASSERT_TRUE(response.value().descriptor.mouse().input().has_movement_x());
ASSERT_TRUE(response.value().descriptor.mouse().input().has_movement_y());
}
// Send a single mouse report
hid_boot_mouse_report_t mouse_report = {};
mouse_report.rel_x = 50;
mouse_report.rel_y = 100;
ASSERT_OK(hidctl_socket.write(0, &mouse_report, sizeof(mouse_report), nullptr));
// Get the mouse InputReport.
auto response = reader->ReadInputReports();
ASSERT_OK(response.status());
ASSERT_OK(response.status());
ASSERT_EQ(1, response->value()->reports.count());
ASSERT_EQ(50, response->value()->reports[0].mouse().movement_x());
ASSERT_EQ(100, response->value()->reports[0].mouse().movement_y());
}
} // namespace