diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
index 90a62cf75008..29fc77168aa7 100644
--- a/tools/testing/selftests/Makefile
+++ b/tools/testing/selftests/Makefile
@@ -28,6 +28,7 @@ TARGETS += futex
 TARGETS += gpio
 TARGETS += hid
 TARGETS += intel_pstate
+TARGETS += input
 TARGETS += iommu
 TARGETS += ipc
 TARGETS += ir
diff --git a/tools/testing/selftests/input/.gitignore b/tools/testing/selftests/input/.gitignore
new file mode 100644
index 000000000000..37f5dff3255b
--- /dev/null
+++ b/tools/testing/selftests/input/.gitignore
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0
+evioc-test
diff --git a/tools/testing/selftests/input/Makefile b/tools/testing/selftests/input/Makefile
new file mode 100644
index 000000000000..031729be0628
--- /dev/null
+++ b/tools/testing/selftests/input/Makefile
@@ -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
diff --git a/tools/testing/selftests/input/config b/tools/testing/selftests/input/config
new file mode 100644
index 000000000000..b7512f3e6d8d
--- /dev/null
+++ b/tools/testing/selftests/input/config
@@ -0,0 +1,3 @@
+CONFIG_INPUT=y
+CONFIG_INPUT_EVDEV=y
+CONFIG_INPUT_UINPUT=m
diff --git a/tools/testing/selftests/input/evioc-test.c b/tools/testing/selftests/input/evioc-test.c
new file mode 100644
index 000000000000..ad7b93fe39cf
--- /dev/null
+++ b/tools/testing/selftests/input/evioc-test.c
@@ -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
