blob: 2b7f14778292d10034f5d19fddb2361f8d93cbfb [file] [log] [blame]
// Copyright 2023 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 <sys/epoll.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <cstring>
#include <string>
#include <gtest/gtest.h>
#include <linux/input-event-codes.h>
#include <linux/input.h>
#include "src/starnix/tests/syscalls/cpp/test_helper.h"
namespace {
const uint32_t kTouchInputMinor = 0;
const uint32_t kKeyboardInputMinor = 1;
const uint32_t kMouseInputMinor = 2;
constexpr size_t min_bytes(size_t n_bits) { return (n_bits + 7) / 8; }
template <size_t SIZE>
bool get_bit(const std::array<uint8_t, SIZE>& buf, size_t bit_num) {
size_t byte_index = bit_num / 8;
size_t bit_index = bit_num % 8;
EXPECT_LT(byte_index, SIZE) << "get_bit(" << bit_num << ") with array of only " << SIZE
<< " elements";
return buf[byte_index] & (1 << bit_index);
}
// TODO(quiche): Maybe move this to a test fixture, and guarantee removal of the input
// node between test cases.
fbl::unique_fd GetInputFile(const uint32_t kInputMinor) {
// TODO(b/310963779): Here should directly /dev/input/eventX.
// Typically, this would be `/dev/input/event0` or `/dev/input/event1`, but there's
// not much to be gained by exercising `mkdir()` in these tests.
std::string kInputFile = "/dev/input" + std::to_string(kInputMinor);
// Create device node. Allow `EEXIST`, to avoid requiring each test case to remove the
// input device node.
const uint32_t kInputMajor = 13;
if (mknod(kInputFile.c_str(), 0600 | S_IFCHR, makedev(kInputMajor, kInputMinor)) != 0 &&
errno != EEXIST) {
ADD_FAILURE() << " creating " << kInputFile << " failed: " << strerror(errno);
};
// Open device node.
fbl::unique_fd fd(open(kInputFile.c_str(), O_RDONLY));
EXPECT_TRUE(fd.is_valid()) << " failed to open " << kInputFile << ": " << strerror(errno);
return fd;
}
TEST(InputTest, DevicePropertiesMatchTouchProperties) {
// TODO(https://fxbug.dev/317285180) don't skip on baseline
if (getuid() != 0) {
GTEST_SKIP() << "Can only be run as root.";
}
auto fd = GetInputFile(kTouchInputMinor);
ASSERT_TRUE(fd.is_valid());
// Getting the driver version must succeed, but the actual value doesn't matter.
{
uint32_t buf;
ASSERT_EQ(0, ioctl(fd.get(), EVIOCGVERSION, &buf)) << "get version failed: " << strerror(errno);
}
// Getting the device identifier must succeed, but the actual value doesn't matter.
{
input_id buf;
ASSERT_EQ(0, ioctl(fd.get(), EVIOCGID, &buf)) << "get identifier failed: " << strerror(errno);
}
// Getting the supported keys must succeed, with `BTN_TOUCH` supported.
{
constexpr auto kBufSize = min_bytes(KEY_MAX);
std::array<uint8_t, kBufSize> buf{};
ASSERT_EQ(0, ioctl(fd.get(), EVIOCGBIT(EV_KEY, kBufSize), &buf))
<< "get supported keys failed: " << strerror(errno);
ASSERT_TRUE(get_bit(buf, BTN_TOUCH)) << " BTN_TOUCH not supported (but should be)";
ASSERT_FALSE(get_bit(buf, BTN_TOOL_FINGER)) << " BTN_TOOL_FINGER should not be supported";
}
// Getting the supported absolute position attributes must succeed, with `ABS_X` and
// `ABS_Y` not supported, and ABS_MT_ supported.
{
constexpr auto kBufSize = min_bytes(ABS_MAX);
std::array<uint8_t, kBufSize> buf{};
ASSERT_EQ(0, ioctl(fd.get(), EVIOCGBIT(EV_ABS, kBufSize), &buf))
<< "get supported absolute position failed: " << strerror(errno);
ASSERT_FALSE(get_bit(buf, ABS_X)) << " ABS_X should not be supported";
ASSERT_FALSE(get_bit(buf, ABS_Y)) << " ABS_Y should not be supported";
ASSERT_TRUE(get_bit(buf, ABS_MT_SLOT)) << " ABS_MT_SLOT should not be supported";
ASSERT_TRUE(get_bit(buf, ABS_MT_TRACKING_ID))
<< " ABS_MT_TRACKING_ID not supported (but should be)";
ASSERT_TRUE(get_bit(buf, ABS_MT_POSITION_X))
<< " ABS_MT_POSITION_X not supported (but should be)";
ASSERT_TRUE(get_bit(buf, ABS_MT_POSITION_Y))
<< " ABS_MT_POSITION_Y not supported (but should be)";
}
// Getting the supported relative motive attributes must succeed, but the actual values
// don't matter.
{
constexpr auto kBufSize = min_bytes(REL_MAX);
std::array<uint8_t, kBufSize> buf{};
ASSERT_EQ(0, ioctl(fd.get(), EVIOCGBIT(EV_REL, kBufSize), &buf))
<< "get supported relative motion failed: " << strerror(errno);
}
// Getting the supported switches must succeed, but the actual values don't matter.
{
constexpr auto kBufSize = min_bytes(SW_MAX);
std::array<uint8_t, kBufSize> buf{};
ASSERT_EQ(0, ioctl(fd.get(), EVIOCGBIT(EV_SW, kBufSize), &buf))
<< "get supported switches failed: " << strerror(errno);
}
// Getting the supported LEDs must succeed, but the actual values don't matter.
{
constexpr auto kBufSize = min_bytes(LED_MAX);
std::array<uint8_t, kBufSize> buf{};
ASSERT_EQ(0, ioctl(fd.get(), EVIOCGBIT(EV_LED, kBufSize), &buf))
<< "get supported LEDs failed: " << strerror(errno);
}
// Getting the supported force feedbacks must succeed, but the actual values don't matter.
{
constexpr auto kBufSize = min_bytes(FF_MAX);
std::array<uint8_t, kBufSize> buf{};
ASSERT_EQ(0, ioctl(fd.get(), EVIOCGBIT(EV_FF, kBufSize), &buf))
<< "get supported force feedbacks failed: " << strerror(errno);
}
// Getting the supported miscellaneous features must succeed, but the actual values don't matter.
{
constexpr auto kBufSize = min_bytes(MSC_MAX);
std::array<uint8_t, kBufSize> buf{};
ASSERT_EQ(0, ioctl(fd.get(), EVIOCGBIT(EV_MSC, kBufSize), &buf))
<< "get supported miscellaneous features failed: " << strerror(errno);
}
// Getting the input properties must succeed, with `INPUT_PROP_DIRECT` set.
{
constexpr auto kBufSize = min_bytes(INPUT_PROP_MAX);
std::array<uint8_t, kBufSize> buf{};
ASSERT_EQ(0, ioctl(fd.get(), EVIOCGPROP(kBufSize), &buf))
<< "get supported input properties features failed: " << strerror(errno);
ASSERT_TRUE(get_bit(buf, INPUT_PROP_DIRECT))
<< " INPUT_PROP_DIRECT not supported (but should be)";
}
{
input_absinfo buf{};
ASSERT_EQ(0, ioctl(fd.get(), EVIOCGABS(ABS_MT_SLOT), &buf))
<< "get slot info failed: " << strerror(errno);
ASSERT_EQ(0.0, buf.minimum);
ASSERT_EQ(buf.maximum, 10.0);
}
{
input_absinfo buf{};
ASSERT_EQ(0, ioctl(fd.get(), EVIOCGABS(ABS_MT_TRACKING_ID), &buf))
<< "get tracking id info failed: " << strerror(errno);
ASSERT_EQ(0.0, buf.minimum);
ASSERT_EQ(buf.maximum, 0x7FFFFFFF);
}
// Getting the x-axis range must succeed. The exact axis parameters are device dependent,
// but some basic validation is possible.
{
input_absinfo buf{};
ASSERT_EQ(0, ioctl(fd.get(), EVIOCGABS(ABS_MT_POSITION_X), &buf))
<< "get x-axis info failed: " << strerror(errno);
ASSERT_EQ(0.0, buf.minimum);
ASSERT_GT(buf.maximum, 0.0);
}
// Getting the y-axis range must succeed. The exact axis parameters are device dependent,
// but some basic validation is possible.
{
input_absinfo buf{};
ASSERT_EQ(0, ioctl(fd.get(), EVIOCGABS(ABS_MT_POSITION_Y), &buf))
<< "get y-axis info failed: " << strerror(errno);
ASSERT_EQ(0.0, buf.minimum);
ASSERT_GT(buf.maximum, 0.0);
}
}
TEST(InputTest, DevicePropertiesMatchKeyboardProperties) {
// TODO(https://fxbug.dev/317285180) don't skip on baseline
if (getuid() != 0) {
GTEST_SKIP() << "Can only be run as root.";
}
auto fd = GetInputFile(kKeyboardInputMinor);
ASSERT_TRUE(fd.is_valid());
// Getting the driver version must succeed, but the actual value doesn't matter.
{
uint32_t buf;
ASSERT_EQ(0, ioctl(fd.get(), EVIOCGVERSION, &buf)) << "get version failed: " << strerror(errno);
}
// Getting the device identifier must succeed, but the actual value doesn't matter.
{
input_id buf;
ASSERT_EQ(0, ioctl(fd.get(), EVIOCGID, &buf)) << "get identifier failed: " << strerror(errno);
}
// Getting the supported keys must succeed, with `BTN_MISC` and `KEY_POWER` supported.
{
constexpr auto kBufSize = min_bytes(KEY_MAX);
std::array<uint8_t, kBufSize> buf{};
ASSERT_EQ(0, ioctl(fd.get(), EVIOCGBIT(EV_KEY, kBufSize), &buf))
<< "get supported keys failed: " << strerror(errno);
ASSERT_TRUE(get_bit(buf, BTN_MISC)) << " BTN_MISC not supported (but should be)";
ASSERT_TRUE(get_bit(buf, KEY_POWER)) << " KEY_POWER not supported (but should be)";
}
// Getting the supported absolute position attributes must succeed, but Keyboard should
// not support touch attributes.
{
constexpr auto kBufSize = min_bytes(ABS_MAX);
std::array<uint8_t, kBufSize> buf{};
ASSERT_EQ(0, ioctl(fd.get(), EVIOCGBIT(EV_ABS, kBufSize), &buf))
<< "get supported absolute position failed: " << strerror(errno);
ASSERT_FALSE(get_bit(buf, ABS_X)) << " ABS_X should not be supported";
ASSERT_FALSE(get_bit(buf, ABS_Y)) << " ABS_Y should not be supported";
ASSERT_FALSE(get_bit(buf, ABS_MT_SLOT)) << " ABS_MT_SLOT should not be supported";
ASSERT_FALSE(get_bit(buf, ABS_MT_TRACKING_ID)) << " ABS_MT_TRACKING_ID should not be supported";
ASSERT_FALSE(get_bit(buf, ABS_MT_POSITION_X)) << " ABS_MT_POSITION_X should not be supported";
ASSERT_FALSE(get_bit(buf, ABS_MT_POSITION_Y)) << " ABS_MT_POSITION_Y should not be supported";
}
// Getting the supported relative motive attributes must succeed, but the actual values
// don't matter.
{
constexpr auto kBufSize = min_bytes(REL_MAX);
std::array<uint8_t, kBufSize> buf{};
ASSERT_EQ(0, ioctl(fd.get(), EVIOCGBIT(EV_REL, kBufSize), &buf))
<< "get supported relative motion failed: " << strerror(errno);
}
// Getting the supported switches must succeed, but the actual values don't matter.
{
constexpr auto kBufSize = min_bytes(SW_MAX);
std::array<uint8_t, kBufSize> buf{};
ASSERT_EQ(0, ioctl(fd.get(), EVIOCGBIT(EV_SW, kBufSize), &buf))
<< "get supported switches failed: " << strerror(errno);
}
// Getting the supported LEDs must succeed, but the actual values don't matter.
{
constexpr auto kBufSize = min_bytes(LED_MAX);
std::array<uint8_t, kBufSize> buf{};
ASSERT_EQ(0, ioctl(fd.get(), EVIOCGBIT(EV_LED, kBufSize), &buf))
<< "get supported LEDs failed: " << strerror(errno);
}
// Getting the supported force feedbacks must succeed, but the actual values don't matter.
{
constexpr auto kBufSize = min_bytes(FF_MAX);
std::array<uint8_t, kBufSize> buf{};
ASSERT_EQ(0, ioctl(fd.get(), EVIOCGBIT(EV_FF, kBufSize), &buf))
<< "get supported force feedbacks failed: " << strerror(errno);
}
// Getting the supported miscellaneous features must succeed, but the actual values don't matter.
{
constexpr auto kBufSize = min_bytes(MSC_MAX);
std::array<uint8_t, kBufSize> buf{};
ASSERT_EQ(0, ioctl(fd.get(), EVIOCGBIT(EV_MSC, kBufSize), &buf))
<< "get supported miscellaneous features failed: " << strerror(errno);
}
// Getting the input properties must succeed, with `INPUT_PROP_DIRECT` set.
{
constexpr auto kBufSize = min_bytes(INPUT_PROP_MAX);
std::array<uint8_t, kBufSize> buf{};
ASSERT_EQ(0, ioctl(fd.get(), EVIOCGPROP(kBufSize), &buf))
<< "get supported input properties features failed: " << strerror(errno);
ASSERT_TRUE(get_bit(buf, INPUT_PROP_DIRECT))
<< " INPUT_PROP_DIRECT not supported (but should be)";
}
// Getting the ABS_MT_SLOT range must succeed, but the actual values don't matter.
{
input_absinfo buf{};
ASSERT_EQ(0, ioctl(fd.get(), EVIOCGABS(ABS_MT_SLOT), &buf))
<< "get slot info failed: " << strerror(errno);
}
// Getting the ABS_MT_TRACKING_ID range must succeed, but the actual values don't matter.
{
input_absinfo buf{};
ASSERT_EQ(0, ioctl(fd.get(), EVIOCGABS(ABS_MT_TRACKING_ID), &buf))
<< "get tracking id info failed: " << strerror(errno);
}
// Getting the x-axis range must succeed, but the actual values don't matter.
{
input_absinfo buf{};
ASSERT_EQ(0, ioctl(fd.get(), EVIOCGABS(ABS_MT_POSITION_X), &buf))
<< "get x-axis info failed: " << strerror(errno);
}
// Getting the y-axis range must succeed, but the actual values don't matter.
{
input_absinfo buf{};
ASSERT_EQ(0, ioctl(fd.get(), EVIOCGABS(ABS_MT_POSITION_Y), &buf))
<< "get y-axis info failed: " << strerror(errno);
}
}
TEST(InputTest, DevicePropertiesMatchMouseWheelProperties) {
// TODO(https://fxbug.dev/317285180) don't skip on baseline
if (getuid() != 0) {
GTEST_SKIP() << "Can only be run as root.";
}
auto fd = GetInputFile(kMouseInputMinor);
ASSERT_TRUE(fd.is_valid());
// Getting the driver version must succeed, but the actual value doesn't matter.
{
uint32_t buf;
ASSERT_EQ(0, ioctl(fd.get(), EVIOCGVERSION, &buf)) << "get version failed: " << strerror(errno);
}
// Getting the device identifier must succeed, but the actual value doesn't matter.
{
input_id buf;
ASSERT_EQ(0, ioctl(fd.get(), EVIOCGID, &buf)) << "get identifier failed: " << strerror(errno);
}
// Getting the supported keys must succeed, with `BTN_MOUSE` unsupported so a cursor is not
// drawn on the screen.
{
constexpr auto kBufSize = min_bytes(KEY_MAX);
std::array<uint8_t, kBufSize> buf{};
ASSERT_EQ(0, ioctl(fd.get(), EVIOCGBIT(EV_KEY, kBufSize), &buf))
<< "get supported keys failed: " << strerror(errno);
ASSERT_FALSE(get_bit(buf, BTN_MOUSE)) << " BTN_MOUSE should not be supported";
}
// Getting the supported absolute position attributes must succeed, but Mouse should
// not support touch attributes.
{
constexpr auto kBufSize = min_bytes(ABS_MAX);
std::array<uint8_t, kBufSize> buf{};
ASSERT_EQ(0, ioctl(fd.get(), EVIOCGBIT(EV_ABS, kBufSize), &buf))
<< "get supported absolute position failed: " << strerror(errno);
ASSERT_FALSE(get_bit(buf, ABS_X)) << " ABS_X should not be supported";
ASSERT_FALSE(get_bit(buf, ABS_Y)) << " ABS_Y should not be supported";
ASSERT_FALSE(get_bit(buf, ABS_MT_SLOT)) << " ABS_MT_SLOT should not be supported";
ASSERT_FALSE(get_bit(buf, ABS_MT_TRACKING_ID)) << " ABS_MT_TRACKING_ID should not be supported";
ASSERT_FALSE(get_bit(buf, ABS_MT_POSITION_X)) << " ABS_MT_POSITION_X should not be supported";
ASSERT_FALSE(get_bit(buf, ABS_MT_POSITION_Y)) << " ABS_MT_POSITION_Y should not be supported";
}
// Getting the supported relative motion attributes must succeed, with `REL_WHEEL` supported
// but `REL_X` and `REL_Y` unsupported so a cursor is not drawn on the screen.
{
constexpr auto kBufSize = min_bytes(REL_MAX);
std::array<uint8_t, kBufSize> buf{};
ASSERT_EQ(0, ioctl(fd.get(), EVIOCGBIT(EV_REL, kBufSize), &buf))
<< "get supported relative motion failed: " << strerror(errno);
ASSERT_TRUE(get_bit(buf, REL_WHEEL)) << " REL_WHEEL not supported (but should be)";
ASSERT_FALSE(get_bit(buf, REL_X)) << " REL_X should not be supported";
ASSERT_FALSE(get_bit(buf, REL_Y)) << " REL_Y should not be supported";
}
// Getting the supported switches must succeed, but the actual values don't matter.
{
constexpr auto kBufSize = min_bytes(SW_MAX);
std::array<uint8_t, kBufSize> buf{};
ASSERT_EQ(0, ioctl(fd.get(), EVIOCGBIT(EV_SW, kBufSize), &buf))
<< "get supported switches failed: " << strerror(errno);
}
// Getting the supported LEDs must succeed, but the actual values don't matter.
{
constexpr auto kBufSize = min_bytes(LED_MAX);
std::array<uint8_t, kBufSize> buf{};
ASSERT_EQ(0, ioctl(fd.get(), EVIOCGBIT(EV_LED, kBufSize), &buf))
<< "get supported LEDs failed: " << strerror(errno);
}
// Getting the supported force feedbacks must succeed, but the actual values don't matter.
{
constexpr auto kBufSize = min_bytes(FF_MAX);
std::array<uint8_t, kBufSize> buf{};
ASSERT_EQ(0, ioctl(fd.get(), EVIOCGBIT(EV_FF, kBufSize), &buf))
<< "get supported force feedbacks failed: " << strerror(errno);
}
// Getting the supported miscellaneous features must succeed, but the actual values don't matter.
{
constexpr auto kBufSize = min_bytes(MSC_MAX);
std::array<uint8_t, kBufSize> buf{};
ASSERT_EQ(0, ioctl(fd.get(), EVIOCGBIT(EV_MSC, kBufSize), &buf))
<< "get supported miscellaneous features failed: " << strerror(errno);
}
// Getting the input properties must succeed, with `INPUT_PROP_DIRECT` and `INPUT_PROP_POINTER`
// unsupported.
{
constexpr auto kBufSize = min_bytes(INPUT_PROP_MAX);
std::array<uint8_t, kBufSize> buf{};
ASSERT_EQ(0, ioctl(fd.get(), EVIOCGPROP(kBufSize), &buf))
<< "get supported input properties features failed: " << strerror(errno);
ASSERT_FALSE(get_bit(buf, INPUT_PROP_DIRECT)) << " INPUT_PROP_DIRECT should not be supported";
ASSERT_FALSE(get_bit(buf, INPUT_PROP_POINTER)) << " INPUT_PROP_POINTER should not be supported";
}
// Getting the ABS_MT_SLOT range must succeed, but the actual values don't matter.
{
input_absinfo buf{};
ASSERT_EQ(0, ioctl(fd.get(), EVIOCGABS(ABS_MT_SLOT), &buf))
<< "get slot info failed: " << strerror(errno);
}
// Getting the ABS_MT_TRACKING_ID range must succeed, but the actual values don't matter.
{
input_absinfo buf{};
ASSERT_EQ(0, ioctl(fd.get(), EVIOCGABS(ABS_MT_TRACKING_ID), &buf))
<< "get tracking id info failed: " << strerror(errno);
}
// Getting the x-axis range must succeed, but the actual values don't matter.
{
input_absinfo buf{};
ASSERT_EQ(0, ioctl(fd.get(), EVIOCGABS(ABS_MT_POSITION_X), &buf))
<< "get x-axis info failed: " << strerror(errno);
}
// Getting the y-axis range must succeed, but the actual values don't matter.
{
input_absinfo buf{};
ASSERT_EQ(0, ioctl(fd.get(), EVIOCGABS(ABS_MT_POSITION_Y), &buf))
<< "get y-axis info failed: " << strerror(errno);
}
}
TEST(InputTest, DeviceCanBeRegisteredWithEpoll) {
// TODO(https://fxbug.dev/317285180) don't skip on baseline
if (getuid() != 0) {
GTEST_SKIP() << "Can only be run as root.";
}
auto input_fd = GetInputFile(kTouchInputMinor);
ASSERT_TRUE(input_fd.is_valid());
fbl::unique_fd epoll_fd(epoll_create(1)); // Per `man` page, must be >0.
ASSERT_TRUE(epoll_fd.is_valid()) << "failed to create epoll fd: " << strerror(errno);
epoll_event epoll_params = {.events = EPOLLIN | EPOLLWAKEUP, .data = {.fd = input_fd.get()}};
ASSERT_EQ(0, epoll_ctl(epoll_fd.get(), EPOLL_CTL_ADD, input_fd.get(), &epoll_params))
<< " epoll_ctl() failed: " << strerror(errno);
epoll_event event_buf[1];
ASSERT_EQ(0, epoll_wait(epoll_fd.get(), event_buf, 1, 0));
}
} // namespace