blob: 9c4c0195bebc87628a969348989eb4e82f11483a [file] [log] [blame]
// Copyright 2022 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 <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/hid/boot.h>
#include <lib/sync/completion.h>
#include <lib/zx/clock.h>
#include <lib/zx/interrupt.h>
#include <zircon/compiler.h>
#include <condition_variable>
#include <zxtest/zxtest.h>
#include "src/devices/testing/mock-ddk/mock-device.h"
#include "src/ui/input/drivers/pc-ps2/commands.h"
#include "src/ui/input/drivers/pc-ps2/controller.h"
#include "src/ui/input/drivers/pc-ps2/device.h"
#include "src/ui/input/drivers/pc-ps2/keymap.h"
#include "src/ui/input/drivers/pc-ps2/registers.h"
class Fake8042;
namespace {
Fake8042* FAKE_INSTANCE = nullptr;
}
class Fake8042 {
public:
explicit Fake8042() {
status_.set_reg_value(0);
ctrl_.set_reg_value(0);
zx::interrupt::create(zx::resource(), 0, ZX_INTERRUPT_VIRTUAL, &port1_irq_);
zx::interrupt::create(zx::resource(), 0, ZX_INTERRUPT_VIRTUAL, &port2_irq_);
FAKE_INSTANCE = this;
}
~Fake8042() { FAKE_INSTANCE = nullptr; }
uint8_t inp(uint16_t port) {
switch (port) {
case i8042::kStatusReg:
return status_.reg_value();
case i8042::kDataReg: {
ZX_ASSERT(status_.obf());
uint8_t value = data_.front();
data_.pop_front();
status_.set_obf(!data_.empty());
return value;
}
default:
ZX_ASSERT_MSG(false, "Unexpected register 0x%x", port);
}
}
void outp(uint16_t port, uint8_t data) {
switch (port) {
case i8042::kCommandReg:
HandleCommand(data);
break;
case i8042::kDataReg:
HandleData(data);
break;
}
}
void EnablePort2() {
has_port2_ = true;
ctrl_.set_auxdis(1);
}
void SendDataAndIrq(bool port2, uint8_t byte) {
SendData(byte);
if (port2) {
port2_irq_.trigger(0, zx::clock::get_monotonic());
} else {
port1_irq_.trigger(0, zx::clock::get_monotonic());
}
}
void SendData(uint8_t data) {
data_.emplace_back(data);
status_.set_obf(1);
}
zx::interrupt& port1_irq() { return port1_irq_; }
zx::interrupt& port2_irq() { return port2_irq_; }
private:
enum State {
// Default state (next write goes to port 1).
kPort1Write = 0,
// Next write goes to port 2.
kPort2Write = 1,
// Next write goes to control register.
kControlWrite = 2,
};
i8042::StatusReg status_;
i8042::ControlReg ctrl_;
State data_state_ = State::kPort1Write;
bool has_port2_ = false;
std::list<uint8_t> data_;
zx::interrupt port1_irq_;
zx::interrupt port2_irq_;
void HandleCommand(uint8_t cmd) {
switch (cmd) {
case i8042::kCmdReadCtl.cmd:
SendData(ctrl_.reg_value());
break;
case i8042::kCmdWriteCtl.cmd:
data_state_ = State::kControlWrite;
break;
case i8042::kCmdSelfTest.cmd:
SendData(0x55);
break;
case i8042::kCmdWriteAux.cmd:
ZX_ASSERT(has_port2_);
data_state_ = State::kPort2Write;
break;
case i8042::kCmdPort1Disable.cmd:
case i8042::kCmdPort1Enable.cmd:
case i8042::kCmdPort2Disable.cmd:
case i8042::kCmdPort2Enable.cmd:
break;
case i8042::kCmdPort2Test.cmd:
ZX_ASSERT(has_port2_);
__FALLTHROUGH;
case i8042::kCmdPort1Test.cmd:
SendData(0x00);
break;
default:
ZX_ASSERT_MSG(false, "Unknown command");
}
}
void HandleData(uint8_t data) {
switch (data_state_) {
case State::kControlWrite:
ctrl_.set_reg_value(data);
break;
case State::kPort1Write:
HandleDeviceCommand(false, data);
break;
case State::kPort2Write:
HandleDeviceCommand(true, data);
break;
}
data_state_ = State::kPort1Write;
}
void HandleDeviceCommand(bool is_port2, uint8_t command) {
switch (command) {
case i8042::kCmdDeviceIdentify.cmd:
SendData(i8042::kAck);
SendData(is_port2 ? 0x00 : 0xab);
break;
case i8042::kCmdDeviceScanDisable.cmd:
case i8042::kCmdDeviceScanEnable.cmd:
SendData(i8042::kAck);
break;
}
}
};
uint8_t TEST_inp(uint16_t port) { return FAKE_INSTANCE->inp(port); }
void TEST_outp(uint16_t port, uint8_t data) { FAKE_INSTANCE->outp(port, data); }
zx::interrupt GetInterrupt(uint32_t irq_no) {
zx::interrupt* irq = nullptr;
if (irq_no == 0x1) {
irq = &FAKE_INSTANCE->port1_irq();
} else if (irq_no == 0xc) {
irq = &FAKE_INSTANCE->port2_irq();
} else {
ZX_ASSERT_MSG(false, "unexpected irq_no 0x%x", irq_no);
}
zx::interrupt clone;
ZX_ASSERT(irq->duplicate(ZX_RIGHT_SAME_RIGHTS, &clone) == ZX_OK);
return clone;
}
class ControllerTest : public zxtest::Test {
public:
void SetUp() override {
zx_status_t status = i8042::Controller::Bind(nullptr, root_.get());
ASSERT_OK(status);
controller_dev_ = root_->GetLatestChild();
}
void TearDown() override {
auto result = fdf::RunOnDispatcherSync(dispatcher_->async_dispatcher(), [&]() {
device_async_remove(controller_dev_);
mock_ddk::ReleaseFlaggedDevices(controller_dev_);
});
EXPECT_OK(result.status_value());
}
void InitDevices() {
auto result = fdf::RunOnDispatcherSync(dispatcher_->async_dispatcher(),
[&]() { controller_dev_->InitOp(); });
EXPECT_OK(result.status_value());
controller_dev_->WaitUntilInitReplyCalled(zx::time::infinite());
ASSERT_OK(controller_dev_->InitReplyCallStatus());
sync_completion_wait(&controller_dev_->GetDeviceContext<i8042::Controller>()->added_children(),
ZX_TIME_INFINITE);
auto endpoints = fidl::Endpoints<fuchsia_input_report::InputDevice>::Create();
binding_ =
fidl::BindServer(dispatcher_->async_dispatcher(), std::move(endpoints.server),
controller_dev_->GetLatestChild()->GetDeviceContext<i8042::I8042Device>());
client_.Bind(std::move(endpoints.client));
}
protected:
Fake8042 i8042_;
std::shared_ptr<MockDevice> root_ = MockDevice::FakeRootParent();
fdf::UnownedSynchronizedDispatcher dispatcher_ =
fdf_testing::DriverRuntime::GetInstance()->StartBackgroundDispatcher();
zx_device* controller_dev_;
fidl::WireSyncClient<fuchsia_input_report::InputDevice> client_;
private:
std::optional<fidl::ServerBindingRef<fuchsia_input_report::InputDevice>> binding_;
};
TEST_F(ControllerTest, GetKbdDescriptorTest) {
InitDevices();
auto response = client_->GetDescriptor();
ASSERT_TRUE(response.ok());
ASSERT_TRUE(response.value().descriptor.has_device_info());
EXPECT_EQ(response.value().descriptor.device_info().vendor_id,
static_cast<uint32_t>(fuchsia_input_report::wire::VendorId::kGoogle));
EXPECT_EQ(
response.value().descriptor.device_info().product_id,
static_cast<uint32_t>(fuchsia_input_report::wire::VendorGoogleProductId::kPcPs2Keyboard));
ASSERT_TRUE(response.value().descriptor.has_keyboard());
ASSERT_TRUE(response.value().descriptor.keyboard().has_input());
ASSERT_TRUE(response.value().descriptor.keyboard().input().has_keys3());
ASSERT_EQ(response.value().descriptor.keyboard().input().keys3().count(), 106);
ASSERT_TRUE(response.value().descriptor.keyboard().has_output());
ASSERT_TRUE(response.value().descriptor.keyboard().output().has_leds());
ASSERT_EQ(response.value().descriptor.keyboard().output().leds().count(), 5);
const auto& leds = response.value().descriptor.keyboard().output().leds();
EXPECT_EQ(leds[0], fuchsia_input_report::wire::LedType::kNumLock);
EXPECT_EQ(leds[1], fuchsia_input_report::wire::LedType::kCapsLock);
EXPECT_EQ(leds[2], fuchsia_input_report::wire::LedType::kScrollLock);
EXPECT_EQ(leds[3], fuchsia_input_report::wire::LedType::kCompose);
EXPECT_EQ(leds[4], fuchsia_input_report::wire::LedType::kKana);
}
TEST_F(ControllerTest, KeyboardPressTest) {
InitDevices();
zx_device* dev = controller_dev_->GetLatestChild();
auto keyboard = dev->GetDeviceContext<i8042::I8042Device>();
fidl::WireSyncClient<fuchsia_input_report::InputReportsReader> reader;
{
auto endpoints = fidl::Endpoints<fuchsia_input_report::InputReportsReader>::Create();
auto result = client_->GetInputReportsReader(std::move(endpoints.server));
ASSERT_OK(result.status());
reader =
fidl::WireSyncClient<fuchsia_input_report::InputReportsReader>(std::move(endpoints.client));
ASSERT_OK(keyboard->WaitForNextReader(zx::duration::infinite()));
}
{
i8042_.SendDataAndIrq(false, 0x2);
auto result = reader->ReadInputReports();
ASSERT_OK(result.status());
ASSERT_FALSE(result.value().is_error());
auto& reports = result.value().value()->reports;
ASSERT_EQ(1, reports.count());
auto& report = reports[0];
ASSERT_TRUE(report.has_event_time());
ASSERT_TRUE(report.has_keyboard());
auto& keyboard_report = report.keyboard();
ASSERT_TRUE(keyboard_report.has_pressed_keys3());
ASSERT_EQ(keyboard_report.pressed_keys3().count(), 1);
EXPECT_EQ(keyboard_report.pressed_keys3()[0], fuchsia_input::wire::Key::kKey1);
}
{
i8042_.SendDataAndIrq(false, i8042::kKeyUp | 0x2);
auto result = reader->ReadInputReports();
ASSERT_OK(result.status());
ASSERT_FALSE(result.value().is_error());
auto& reports = result.value().value()->reports;
ASSERT_EQ(1, reports.count());
auto& report = reports[0];
ASSERT_TRUE(report.has_event_time());
ASSERT_TRUE(report.has_keyboard());
auto& keyboard_report = report.keyboard();
ASSERT_TRUE(keyboard_report.has_pressed_keys3());
EXPECT_EQ(keyboard_report.pressed_keys3().count(), 0);
}
}
TEST_F(ControllerTest, GetMouseDescriptorTest) {
i8042_.EnablePort2();
InitDevices();
auto response = client_->GetDescriptor();
ASSERT_TRUE(response.ok());
ASSERT_TRUE(response.value().descriptor.has_device_info());
EXPECT_EQ(response.value().descriptor.device_info().vendor_id,
static_cast<uint32_t>(fuchsia_input_report::wire::VendorId::kGoogle));
EXPECT_EQ(response.value().descriptor.device_info().product_id,
static_cast<uint32_t>(fuchsia_input_report::wire::VendorGoogleProductId::kPcPs2Mouse));
ASSERT_TRUE(response.value().descriptor.has_mouse());
ASSERT_TRUE(response.value().descriptor.mouse().has_input());
ASSERT_TRUE(response.value().descriptor.mouse().input().has_buttons());
ASSERT_EQ(response.value().descriptor.mouse().input().buttons().count(), 3);
EXPECT_EQ(response.value().descriptor.mouse().input().buttons()[0], 0x01);
EXPECT_EQ(response.value().descriptor.mouse().input().buttons()[1], 0x02);
EXPECT_EQ(response.value().descriptor.mouse().input().buttons()[2], 0x03);
ASSERT_TRUE(response.value().descriptor.mouse().input().has_movement_x());
EXPECT_EQ(response.value().descriptor.mouse().input().movement_x().range.min, -127);
EXPECT_EQ(response.value().descriptor.mouse().input().movement_x().range.max, 127);
EXPECT_EQ(response.value().descriptor.mouse().input().movement_x().unit.type,
fuchsia_input_report::wire::UnitType::kNone);
EXPECT_EQ(response.value().descriptor.mouse().input().movement_x().unit.exponent, 0);
ASSERT_TRUE(response.value().descriptor.mouse().input().has_movement_y());
EXPECT_EQ(response.value().descriptor.mouse().input().movement_y().range.min, -127);
EXPECT_EQ(response.value().descriptor.mouse().input().movement_y().range.max, 127);
EXPECT_EQ(response.value().descriptor.mouse().input().movement_y().unit.type,
fuchsia_input_report::wire::UnitType::kNone);
EXPECT_EQ(response.value().descriptor.mouse().input().movement_y().unit.exponent, 0);
}
TEST_F(ControllerTest, MouseMoveTest) {
i8042_.EnablePort2();
InitDevices();
zx_device* dev = controller_dev_->GetLatestChild();
auto mouse = dev->GetDeviceContext<i8042::I8042Device>();
fidl::WireSyncClient<fuchsia_input_report::InputReportsReader> reader;
{
auto endpoints = fidl::Endpoints<fuchsia_input_report::InputReportsReader>::Create();
auto result = client_->GetInputReportsReader(std::move(endpoints.server));
ASSERT_OK(result.status());
reader =
fidl::WireSyncClient<fuchsia_input_report::InputReportsReader>(std::move(endpoints.client));
ASSERT_OK(mouse->WaitForNextReader(zx::duration::infinite()));
}
i8042_.SendData(0x09 /* button_left | always_one */);
i8042_.SendData(0x70) /* rel_x */;
i8042_.SendDataAndIrq(true, 0x10 /* rel_y */);
auto result = reader->ReadInputReports();
ASSERT_OK(result.status());
ASSERT_FALSE(result.value().is_error());
auto& reports = result.value().value()->reports;
ASSERT_EQ(1, reports.count());
auto& report = reports[0];
ASSERT_TRUE(report.has_event_time());
ASSERT_TRUE(report.has_mouse());
auto& mouse_report = report.mouse();
ASSERT_TRUE(mouse_report.has_pressed_buttons());
ASSERT_EQ(mouse_report.pressed_buttons().count(), 1);
EXPECT_EQ(mouse_report.pressed_buttons()[0], 0x1);
ASSERT_TRUE(mouse_report.has_movement_x());
EXPECT_EQ(mouse_report.movement_x(), 0x70);
ASSERT_TRUE(mouse_report.has_movement_y());
EXPECT_EQ(mouse_report.movement_y(), -16);
}