@@ -28,6 +28,7 @@ TARGETS += futex
TARGETS += gpio
TARGETS += hid
TARGETS += intel_pstate
+TARGETS += input
TARGETS += iommu
TARGETS += ipc
TARGETS += ir
new file mode 100644
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0
+evioc-test
new file mode 100644
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0
+CFLAGS += -D_GNU_SOURCE -std=gnu99 -Wl,-no-as-needed -Wall $(KHDR_INCLUDES)
+
+TEST_GEN_PROGS := evioc-test
+include ../lib.mk
new file mode 100644
@@ -0,0 +1,3 @@
+CONFIG_INPUT=y
+CONFIG_INPUT_EVDEV=y
+CONFIG_INPUT_UINPUT=m
new file mode 100644
@@ -0,0 +1,237 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright © 2023 Red Hat, Inc.
+ *
+ * Part of the code in this file is inspired and copied from the libevdev wrapper library
+ * for evdev devices written by Peter Hutterer.
+ */
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/uinput.h>
+#include <poll.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "../kselftest_harness.h"
+
+#define TEST_DEVICE_NAME "selftest input device"
+
+struct selftest_uinput {
+ int uinput_fd; /** file descriptor to uinput */
+ int evdev_fd; /** file descriptor to evdev */
+};
+
+static int is_event_device(const struct dirent *dent)
+{
+ return strncmp("event", dent->d_name, 5) == 0;
+}
+
+static int open_devnode(struct selftest_uinput *uidev)
+{
+#define SYS_INPUT_DIR "/sys/devices/virtual/input/"
+ char buf[sizeof(SYS_INPUT_DIR) + 64] = SYS_INPUT_DIR;
+ struct dirent **namelist;
+ char *devnode = NULL;
+ int ndev, i;
+ int rc;
+
+ rc = ioctl(uidev->uinput_fd,
+ UI_GET_SYSNAME(sizeof(buf) - strlen(SYS_INPUT_DIR)),
+ &buf[strlen(SYS_INPUT_DIR)]);
+ if (rc == -1) {
+ fprintf(stderr, "cannot get the sysfs name of the uinput device (%d)\n", rc);
+ return rc;
+ }
+
+ ndev = scandir(buf, &namelist, is_event_device, alphasort);
+ if (ndev <= 0)
+ return -1;
+
+ /* ndev should only ever be 1 */
+
+ for (i = 0; i < ndev; i++) {
+ if (!devnode && asprintf(&devnode, "/dev/input/%s",
+ namelist[i]->d_name) == -1)
+ devnode = NULL;
+ free(namelist[i]);
+ }
+
+ free(namelist);
+
+ return open(devnode, O_RDONLY);
+#undef SYS_INPUT_DIR
+}
+
+static void selftest_uinput_destroy(struct selftest_uinput *uidev)
+{
+ if (!uidev)
+ return;
+
+ if (uidev->uinput_fd >= 0)
+ ioctl(uidev->uinput_fd, UI_DEV_DESTROY, NULL);
+
+ close(uidev->evdev_fd);
+ close(uidev->uinput_fd);
+
+ free(uidev);
+}
+
+static int selftest_uinput_create_device(struct selftest_uinput **uidev, ...)
+{
+ struct selftest_uinput *new_device;
+ struct uinput_setup setup;
+ va_list args;
+ int rc, fd;
+ int type;
+
+ new_device = calloc(1, sizeof(struct selftest_uinput));
+ if (!new_device)
+ return -ENOMEM;
+
+ memset(&setup, 0, sizeof(setup));
+ strncpy(setup.name, TEST_DEVICE_NAME, UINPUT_MAX_NAME_SIZE - 1);
+ setup.id.vendor = 0x1234; /* sample vendor */
+ setup.id.product = 0x5678; /* sample product */
+ setup.id.bustype = BUS_USB;
+
+ fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);
+ if (fd < 0) {
+ fprintf(stderr, "cannot open uinput (%d): %m\n", errno);
+ goto error;
+ }
+
+ va_start(args, uidev);
+ rc = 0;
+ do {
+ type = va_arg(args, int);
+ if (type == -1)
+ break;
+ rc = ioctl(fd, UI_SET_EVBIT, type);
+ } while (rc == 0);
+ va_end(args);
+
+ rc = ioctl(fd, UI_DEV_SETUP, &setup);
+ if (rc == -1)
+ goto error;
+
+ rc = ioctl(fd, UI_DEV_CREATE, NULL);
+ if (rc == -1)
+ goto error;
+
+ new_device->uinput_fd = fd;
+
+ fd = open_devnode(new_device);
+ if (fd < 0) {
+ fprintf(stderr, "cannot open event device node (%d): %m\n", errno);
+ goto error;
+ }
+
+ new_device->evdev_fd = fd;
+ *uidev = new_device;
+
+ return 0;
+
+error:
+ rc = -errno;
+ selftest_uinput_destroy(new_device);
+ return rc;
+}
+
+TEST(eviocgname_get_device_name)
+{
+ struct selftest_uinput *uidev;
+ char buf[256];
+ int rc;
+
+ rc = selftest_uinput_create_device(&uidev);
+ ASSERT_EQ(0, rc);
+ ASSERT_NE(NULL, uidev);
+
+ memset(buf, 0, sizeof(buf));
+ /* ioctl to get the name */
+ rc = ioctl(uidev->evdev_fd, EVIOCGNAME(sizeof(buf) - 1), buf);
+ ASSERT_GE(rc, 0);
+ ASSERT_STREQ(TEST_DEVICE_NAME, buf);
+
+ selftest_uinput_destroy(uidev);
+}
+
+TEST(eviocgrep_get_repeat_settings)
+{
+ struct selftest_uinput *uidev;
+ int rep_values[2];
+ int rc;
+
+ memset(rep_values, 0, sizeof(rep_values));
+
+ rc = selftest_uinput_create_device(&uidev, -1);
+ ASSERT_EQ(0, rc);
+ ASSERT_NE(NULL, uidev);
+
+ /* ioctl to get the repeat rates values */
+ rc = ioctl(uidev->evdev_fd, EVIOCGREP, rep_values);
+ /* should fail because EV_REP is not set */
+ ASSERT_EQ(-1, rc);
+
+ selftest_uinput_destroy(uidev);
+
+ rc = selftest_uinput_create_device(&uidev, EV_REP, -1);
+ ASSERT_EQ(0, rc);
+ ASSERT_NE(NULL, uidev);
+
+ /* ioctl to get the repeat rates values */
+ rc = ioctl(uidev->evdev_fd, EVIOCGREP, rep_values);
+ ASSERT_EQ(0, rc);
+ /* should get the default delay and period values set by the kernel */
+ ASSERT_EQ(rep_values[0], 250);
+ ASSERT_EQ(rep_values[1], 33);
+
+ selftest_uinput_destroy(uidev);
+}
+
+TEST(eviocsrep_set_repeat_settings)
+{
+ struct selftest_uinput *uidev;
+ int rep_values[2];
+ int rc;
+
+ memset(rep_values, 0, sizeof(rep_values));
+
+ rc = selftest_uinput_create_device(&uidev, -1);
+ ASSERT_EQ(0, rc);
+ ASSERT_NE(NULL, uidev);
+
+ /* ioctl to set the repeat rates values */
+ rc = ioctl(uidev->evdev_fd, EVIOCSREP, rep_values);
+ /* should fail because EV_REP is not set */
+ ASSERT_EQ(-1, rc);
+
+ selftest_uinput_destroy(uidev);
+
+ rc = selftest_uinput_create_device(&uidev, EV_REP, -1);
+ ASSERT_EQ(0, rc);
+ ASSERT_NE(NULL, uidev);
+
+ /* ioctl to set the repeat rates values */
+ rep_values[0] = 500;
+ rep_values[1] = 200;
+ rc = ioctl(uidev->evdev_fd, EVIOCSREP, rep_values);
+ ASSERT_EQ(0, rc);
+
+ /* ioctl to get the repeat rates values */
+ rc = ioctl(uidev->evdev_fd, EVIOCGREP, rep_values);
+ ASSERT_EQ(0, rc);
+ /* should get the delay and period values set previously */
+ ASSERT_EQ(rep_values[0], 500);
+ ASSERT_EQ(rep_values[1], 200);
+
+ selftest_uinput_destroy(uidev);
+}
+
+TEST_HARNESS_MAIN