@@ -2585,6 +2585,15 @@ config TEST_FIRMWARE
If unsure, say N.
+config TEST_PERF_QOS
+ tristate "Create a dummy device for the performance QoS testing"
+ help
+ This builds the "test_perf_qos" module which creates an
+ userspace interface for testing the performance QoS API. It
+ is needed for the performance QoS selftests.
+
+ If unsure, say N.
+
config TEST_SYSCTL
tristate "sysctl test driver"
depends on PROC_SYSCTL
@@ -61,6 +61,7 @@ obj-$(CONFIG_TEST_BPF) += test_bpf.o
test_dhry-objs := dhry_1.o dhry_2.o dhry_run.o
obj-$(CONFIG_TEST_DHRY) += test_dhry.o
obj-$(CONFIG_TEST_FIRMWARE) += test_firmware.o
+obj-$(CONFIG_TEST_PERF_QOS) += test_perf_qos.o
obj-$(CONFIG_TEST_BITOPS) += test_bitops.o
CFLAGS_test_bitops.o += -Werror
obj-$(CONFIG_CPUMASK_KUNIT_TEST) += cpumask_kunit.o
new file mode 100644
@@ -0,0 +1,69 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Kernel module for testing performance QoS
+ *
+ * Copyright (2024) Linaro Ltd
+ *
+ * Author: Daniel Lezcano <daniel.lezcano@kernel.org>
+ *
+ */
+#include <linux/module.h>
+#include <linux/perf_qos.h>
+
+static struct perf_qos *pq;
+
+static int test_set_perf_limit_min(int limit)
+{
+ static int prev_limit = -1;
+
+ pr_info("Performance minimal limit set to %d->%d\n",
+ prev_limit, limit);
+
+ WARN_ON_ONCE(prev_limit == limit);
+
+ return 0;
+}
+
+static int test_set_perf_limit_max(int limit)
+{
+ static int prev_limit = -1;
+
+ pr_info("Performance maximal limit set to %d->%d\n",
+ prev_limit, limit);
+
+ WARN_ON_ONCE(prev_limit == limit);
+
+ return 0;
+}
+
+static int __init test_perf_qos_init(void)
+{
+ struct perf_qos_ops ops = {
+ .set_perf_limit_max = test_set_perf_limit_max,
+ .set_perf_limit_min = test_set_perf_limit_min,
+ };
+
+ struct perf_qos_value_descr descr = {
+ .unit = PERF_QOS_UNIT_NORMAL,
+ .limit_min = 0,
+ .limit_max = 1024,
+ };
+
+ pq = perf_qos_device_create("dummy", &ops, &descr);
+ if (IS_ERR(pq))
+ return PTR_ERR(pq);
+
+ return 0;
+}
+
+static void __exit test_perf_qos_exit(void)
+{
+ perf_qos_device_destroy(pq);
+}
+
+module_init(test_perf_qos_init);
+module_exit(test_perf_qos_exit);
+
+MODULE_AUTHOR("Daniel Lezcano <daniel.lezcano@kernel.org>");
+MODULE_DESCRIPTION("Kernel module for testing the performance QoS");
+MODULE_LICENSE("GPL");
new file mode 100644
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: LGPL-2.0+ WITH Linux-syscall-note */
+/*
+ * Performance QoS device abstraction
+ *
+ * Copyright (2024) Linaro Ltd
+ *
+ * Author: Daniel Lezcano <daniel.lezcano@linaro.org>
+ *
+ */
+#ifndef __PERF_QOS_IOCTL_H
+#define __PERF_QOS_IOCTL_H
+
+#include <linux/types.h>
+
+enum {
+ PERF_QOS_IOC_SET_MIN_CMD,
+ PERF_QOS_IOC_GET_MIN_CMD,
+ PERF_QOS_IOC_SET_MAX_CMD,
+ PERF_QOS_IOC_GET_MAX_CMD,
+ PERF_QOS_IOC_GET_UNIT_CMD,
+ PERF_QOS_IOC_GET_LIMITS_CMD,
+ PERF_QOS_IOC_MAX_CMD,
+};
+
+typedef enum {
+ PERF_QOS_UNIT_NORMAL,
+ PERF_QOS_UNIT_KBPS,
+ PERF_QOS_UNIT_MAX
+} perf_qos_unit_t;
+
+struct perf_qos_ioctl_arg {
+ int value;
+ int limit_min;
+ int limit_max;
+ perf_qos_unit_t unit;
+};
+
+#define PERF_QOS_IOCTL_TYPE 'P'
+
+#define PERF_QOS_IOC_SET_MIN _IOW(PERF_QOS_IOCTL_TYPE, PERF_QOS_IOC_SET_MIN_CMD, struct perf_qos_ioctl_arg *)
+#define PERF_QOS_IOC_GET_MIN _IOR(PERF_QOS_IOCTL_TYPE, PERF_QOS_IOC_GET_MIN_CMD, struct perf_qos_ioctl_arg *)
+#define PERF_QOS_IOC_SET_MAX _IOW(PERF_QOS_IOCTL_TYPE, PERF_QOS_IOC_SET_MAX_CMD, struct perf_qos_ioctl_arg *)
+#define PERF_QOS_IOC_GET_MAX _IOR(PERF_QOS_IOCTL_TYPE, PERF_QOS_IOC_GET_MAX_CMD, struct perf_qos_ioctl_arg *)
+#define PERF_QOS_IOC_GET_UNIT _IOR(PERF_QOS_IOCTL_TYPE, PERF_QOS_IOC_GET_UNIT_CMD, struct perf_qos_ioctl_arg *)
+#define PERF_QOS_IOC_GET_LIMITS _IOR(PERF_QOS_IOCTL_TYPE, PERF_QOS_IOC_GET_LIMITS_CMD, struct perf_qos_ioctl_arg *)
+
+#endif
@@ -73,6 +73,7 @@ TARGETS += net/rds
TARGETS += net/tcp_ao
TARGETS += nsfs
TARGETS += perf_events
+TARGETS += perf_qos
TARGETS += pidfd
TARGETS += pid_namespace
TARGETS += power_supply
new file mode 100644
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0
+
+TEST_GEN_PROGS = get_unit get_limits
+TEST_GEN_PROGS += get_set_min get_set_max
+TEST_GEN_PROGS += get_set_min_forked get_set_max_forked
+TEST_GEN_PROGS += set_min_forked set_max_forked
+TEST_GEN_PROGS += set_multiple_mins set_multiple_maxs
+TEST_GEN_PROGS += set_same_multiple_mins set_same_multiple_maxs
+
+include ../lib.mk
+
+TOOLSDIR := $(top_srcdir)/tools
+TOOLSINCDIR := $(TOOLSDIR)/include
+APIDIR := $(TOOLSINCDIR)/uapi
+
+CFLAGS += -Wall -O2 -I$(APIDIR)
new file mode 100644
@@ -0,0 +1,61 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Performance Quality of Service (Perf QoS) support base.
+ *
+ * Copyright (C) 2024 Linaro Ltd
+ *
+ * Author: Daniel Lezcano <daniel.lezcano@linaro.org>
+ *
+ */
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/poll.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <linux/perf_qos_ioctl.h>
+
+int main(int argc, char *argv[])
+{
+ struct perf_qos_ioctl_arg arg = {};
+ const char *path = "/dev/perf_qos/dummy";
+ int fd;
+
+ if (argc == 2)
+ path = argv[1];
+
+ fd = open(path, 0, O_RDWR);
+ if (fd < 0) {
+ fprintf(stderr, "Failed to open '%s': %m\n", path);
+ return 1;
+ }
+
+ if (ioctl(fd, PERF_QOS_IOC_GET_LIMITS, &arg)) {
+ fprintf(stderr, "Failed to ioctl: %m\n");
+ return 1;
+ }
+
+ if (arg.unit != PERF_QOS_UNIT_NORMAL) {
+ fprintf(stderr, "Invalid unit, expected 'PERF_QOS_UNIT_NORMAL'\n");
+ return 1;
+ }
+
+ if (arg.limit_min != 0) {
+ fprintf(stderr, "Invalid minimum unit %d != 0\n", arg.limit_min);
+ return 1;
+ }
+
+ if (arg.limit_max != 1024) {
+ fprintf(stderr, "Invalid maximum unit %d != 1024\n", arg.limit_max);
+ return 1;
+ }
+
+ return 0;
+}
new file mode 100644
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Performance Quality of Service (Perf QoS) support base.
+ *
+ * Copyright (C) 2024 Linaro Ltd
+ *
+ * Author: Daniel Lezcano <daniel.lezcano@linaro.org>
+ *
+ */
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/poll.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <linux/perf_qos_ioctl.h>
+
+int main(int argc, char *argv[])
+{
+ struct perf_qos_ioctl_arg arg = { .value = 512 };
+ const char *path = "/dev/perf_qos/dummy";
+ int fd;
+
+ if (argc == 2)
+ path = argv[1];
+
+ fd = open(path, 0, O_RDWR);
+ if (fd < 0) {
+ fprintf(stderr, "Failed to open '%s': %m\n", path);
+ return 1;
+ }
+
+ /*
+ * Test 1: Check the value is set
+ */
+ if (ioctl(fd, PERF_QOS_IOC_SET_MAX, &arg)) {
+ fprintf(stderr, "Failed to ioctl: %m\n");
+ return 1;
+ }
+
+ arg.value = 0;
+
+ if (ioctl(fd, PERF_QOS_IOC_GET_MAX, &arg)) {
+ fprintf(stderr, "Failed to ioctl: %m\n");
+ return 1;
+ }
+
+ if (arg.value != 512) {
+ fprintf(stderr, "max value differs with set/get (arg=%d)\n",
+ arg.value);
+ return 1;
+ }
+
+ /*
+ * Test 2: Check we can not set the same constraint
+ */
+ if (ioctl(fd, PERF_QOS_IOC_SET_MAX, &arg) == 0) {
+ fprintf(stderr, "ioctl should have failed\n");
+ return 1;
+ }
+
+ /*
+ * Test 3: Check the constraint is removed
+ */
+ if (ioctl(fd, PERF_QOS_IOC_GET_LIMITS, &arg)) {
+ fprintf(stderr, "Failed to ioctl: %m\n");
+ return 1;
+ }
+
+ arg.value = arg.limit_max;
+
+ if (ioctl(fd, PERF_QOS_IOC_SET_MAX, &arg)) {
+ fprintf(stderr, "Failed to ioctl: %m\n");
+ return 1;
+ }
+
+ if (!ioctl(fd, PERF_QOS_IOC_GET_MAX, &arg)) {
+ fprintf(stderr, "ioctl should have failed\n");
+ return 1;
+ }
+
+ if (errno != ENODATA) {
+ fprintf(stderr, "errno should have been ENODATA\n");
+ return 1;
+ }
+
+ return 0;
+}
new file mode 100644
@@ -0,0 +1,150 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Performance Quality of Service (Perf QoS) support base.
+ *
+ * Copyright (C) 2024 Linaro Ltd
+ *
+ * Author: Daniel Lezcano <daniel.lezcano@linaro.org>
+ *
+ */
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/poll.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <linux/perf_qos_ioctl.h>
+
+static int test_forked_get_set_max(int fd, const char *path)
+{
+ pid_t pid;
+ int fds[2];
+ int result;
+ const int init_value = 256;
+ struct perf_qos_ioctl_arg arg = { .value = init_value };
+
+ if (ioctl(fd, PERF_QOS_IOC_SET_MAX, &arg)) {
+ fprintf(stderr, "Failed to ioctl: %m\n");
+ return 1;
+ }
+
+ if (pipe(fds)) {
+ fprintf(stderr, "Failed to pipe: %m\n");
+ return 1;
+ }
+
+ pid = fork();
+ if (pid < 0) {
+ fprintf(stderr, "Failed to fork: %m\n");
+ return 1;
+ }
+
+ if (!pid) {
+ close(fd);
+ close(fds[0]);
+
+ fd = open(path, 0, O_RDWR);
+ if (fd < 0) {
+ fprintf(stderr, "Failed to open '%s': %m\n", path);
+ return 1;
+ }
+
+ arg.value = 0;
+
+ /*
+ * At this point, we must have a 'init_value'
+ * constraint created by the parent process
+ */
+ if (ioctl(fd, PERF_QOS_IOC_GET_MAX, &arg)) {
+ fprintf(stderr, "Failed to ioctl: %m\n");
+ return 1;
+ }
+
+ result = arg.value;
+
+ if (write(fds[1], &result, sizeof(result)) < 0) {
+ fprintf(stderr, "Failed to write result to pipe: %m\n");
+ exit(1);
+ }
+
+ exit(0);
+ }
+
+ close(fds[1]);
+
+ if (read(fds[0], &result, sizeof(result)) < 0) {
+ fprintf(stderr, "Failed to read pipe: %m\n");
+ return 1;
+ }
+
+ if (result != init_value) {
+ fprintf(stderr, "Child test failed: %d\n", result);
+ return 1;
+ }
+
+ if (waitpid(pid, NULL, 0) < 0) {
+ fprintf(stderr, "Failed to wait child pid: %m\n");
+ return 1;
+ }
+
+ arg.value = 0;
+
+ if (ioctl(fd, PERF_QOS_IOC_GET_MAX, &arg)) {
+ fprintf(stderr, "Failed to ioctl: %m\n");
+ return 1;
+ }
+
+ if (arg.value != init_value) {
+ fprintf(stderr, "Perf constraints differ %d <> %d\n",
+ arg.value, init_value);
+ return 1;
+ }
+
+ if (ioctl(fd, PERF_QOS_IOC_GET_LIMITS, &arg)) {
+ fprintf(stderr, "Failed to ioctl: %m\n");
+ return 1;
+ }
+
+ arg.value = arg.limit_max;
+
+ if (ioctl(fd, PERF_QOS_IOC_SET_MAX, &arg)) {
+ fprintf(stderr, "Failed to ioctl: %m\n");
+ return 1;
+ }
+
+ if (!ioctl(fd, PERF_QOS_IOC_GET_MAX, &arg)) {
+ fprintf(stderr, "ioctl should have failed\n");
+ return 1;
+ }
+
+ if (errno != ENODATA) {
+ fprintf(stderr, "errno should have been ENODATA\n");
+ return 1;
+ }
+
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ const char *path = "/dev/perf_qos/dummy";
+ int fd;
+
+ if (argc == 2)
+ path = argv[1];
+
+ fd = open(path, 0, O_RDWR);
+ if (fd < 0) {
+ fprintf(stderr, "Failed to open '%s': %m\n", path);
+ return 1;
+ }
+
+ return test_forked_get_set_max(fd, path);
+}
new file mode 100644
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Performance Quality of Service (Perf QoS) support base.
+ *
+ * Copyright (C) 2024 Linaro Ltd
+ *
+ * Author: Daniel Lezcano <daniel.lezcano@linaro.org>
+ *
+ */
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/poll.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <linux/perf_qos_ioctl.h>
+
+int main(int argc, char *argv[])
+{
+ struct perf_qos_ioctl_arg arg = { .value = 512 };
+ const char *path = "/dev/perf_qos/dummy";
+ int fd;
+
+ if (argc == 2)
+ path = argv[1];
+
+ fd = open(path, 0, O_RDWR);
+ if (fd < 0) {
+ fprintf(stderr, "Failed to open '%s': %m\n", path);
+ return 1;
+ }
+
+ /*
+ * Test 1: Check the value is set
+ */
+ if (ioctl(fd, PERF_QOS_IOC_SET_MIN, &arg)) {
+ fprintf(stderr, "Failed to ioctl: %m\n");
+ return 1;
+ }
+
+ arg.value = 0;
+
+ if (ioctl(fd, PERF_QOS_IOC_GET_MIN, &arg)) {
+ fprintf(stderr, "Failed to ioctl: %m\n");
+ return 1;
+ }
+
+ if (arg.value != 512) {
+ fprintf(stderr, "min value differs with set/get (arg=%d)\n",
+ arg.value);
+ return 1;
+ }
+
+ /*
+ * Test 2: Check we can not set the same constraint
+ */
+ if (ioctl(fd, PERF_QOS_IOC_SET_MIN, &arg) == 0) {
+ fprintf(stderr, "ioctl should have failed\n");
+ return 1;
+ }
+
+ /*
+ * Test 3: Check the constraint is removed
+ */
+ if (ioctl(fd, PERF_QOS_IOC_GET_LIMITS, &arg)) {
+ fprintf(stderr, "Failed to ioctl: %m\n");
+ return 1;
+ }
+
+ arg.value = arg.limit_min;
+
+ if (ioctl(fd, PERF_QOS_IOC_SET_MIN, &arg)) {
+ fprintf(stderr, "Failed to ioctl: %m\n");
+ return 1;
+ }
+
+ if (!ioctl(fd, PERF_QOS_IOC_GET_MIN, &arg)) {
+ fprintf(stderr, "ioctl should have failed\n");
+ return 1;
+ }
+
+ if (errno != ENODATA) {
+ fprintf(stderr, "errno should have been ENODATA\n");
+ return 1;
+ }
+
+ return 0;
+}
new file mode 100644
@@ -0,0 +1,150 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Performance Quality of Service (Perf QoS) support base.
+ *
+ * Copyright (C) 2024 Linaro Ltd
+ *
+ * Author: Daniel Lezcano <daniel.lezcano@linaro.org>
+ *
+ */
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/poll.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <linux/perf_qos_ioctl.h>
+
+static int test_forked_get_set_min(int fd, const char *path)
+{
+ pid_t pid;
+ int fds[2];
+ int result;
+ const int init_value = 256;
+ struct perf_qos_ioctl_arg arg = { .value = init_value };
+
+ if (ioctl(fd, PERF_QOS_IOC_SET_MIN, &arg)) {
+ fprintf(stderr, "Failed to ioctl: %m\n");
+ return 1;
+ }
+
+ if (pipe(fds)) {
+ fprintf(stderr, "Failed to pipe: %m\n");
+ return 1;
+ }
+
+ pid = fork();
+ if (pid < 0) {
+ fprintf(stderr, "Failed to fork: %m\n");
+ return 1;
+ }
+
+ if (!pid) {
+ close(fd);
+ close(fds[0]);
+
+ fd = open(path, 0, O_RDWR);
+ if (fd < 0) {
+ fprintf(stderr, "Failed to open '%s': %m\n", path);
+ return 1;
+ }
+
+ arg.value = 0;
+
+ /*
+ * At this point, we must have a 'init_value'
+ * constraint created by the parent process
+ */
+ if (ioctl(fd, PERF_QOS_IOC_GET_MIN, &arg)) {
+ fprintf(stderr, "Failed to ioctl: %m\n");
+ return 1;
+ }
+
+ result = arg.value;
+
+ if (write(fds[1], &result, sizeof(result)) < 0) {
+ fprintf(stderr, "Failed to write result to pipe: %m\n");
+ exit(1);
+ }
+
+ exit(0);
+ }
+
+ close(fds[1]);
+
+ if (read(fds[0], &result, sizeof(result)) < 0) {
+ fprintf(stderr, "Failed to read pipe: %m\n");
+ return 1;
+ }
+
+ if (result != init_value) {
+ fprintf(stderr, "Child test failed: %d\n", result);
+ return 1;
+ }
+
+ if (waitpid(pid, NULL, 0) < 0) {
+ fprintf(stderr, "Failed to wait child pid: %m\n");
+ return 1;
+ }
+
+ arg.value = 0;
+
+ if (ioctl(fd, PERF_QOS_IOC_GET_MIN, &arg)) {
+ fprintf(stderr, "Failed to ioctl: %m\n");
+ return 1;
+ }
+
+ if (arg.value != init_value) {
+ fprintf(stderr, "Perf constraints differ %d <> %d\n",
+ arg.value, init_value);
+ return 1;
+ }
+
+ if (ioctl(fd, PERF_QOS_IOC_GET_LIMITS, &arg)) {
+ fprintf(stderr, "Failed to ioctl: %m\n");
+ return 1;
+ }
+
+ arg.value = arg.limit_min;
+
+ if (ioctl(fd, PERF_QOS_IOC_SET_MIN, &arg)) {
+ fprintf(stderr, "Failed to ioctl: %m\n");
+ return 1;
+ }
+
+ if (!ioctl(fd, PERF_QOS_IOC_GET_MIN, &arg)) {
+ fprintf(stderr, "ioctl should have failed\n");
+ return 1;
+ }
+
+ if (errno != ENODATA) {
+ fprintf(stderr, "errno should have been ENODATA\n");
+ return 1;
+ }
+
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ const char *path = "/dev/perf_qos/dummy";
+ int fd;
+
+ if (argc == 2)
+ path = argv[1];
+
+ fd = open(path, 0, O_RDWR);
+ if (fd < 0) {
+ fprintf(stderr, "Failed to open '%s': %m\n", path);
+ return 1;
+ }
+
+ return test_forked_get_set_min(fd, path);
+}
new file mode 100644
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Performance Quality of Service (Perf QoS) support base.
+ *
+ * Copyright (C) 2024 Linaro Ltd
+ *
+ * Author: Daniel Lezcano <daniel.lezcano@linaro.org>
+ *
+ */
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/poll.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <linux/perf_qos_ioctl.h>
+
+int main(int argc, char *argv[])
+{
+ struct perf_qos_ioctl_arg arg = {};
+ const char *path = "/dev/perf_qos/dummy";
+ int fd;
+
+ if (argc == 2)
+ path = argv[1];
+
+ fd = open(path, 0, O_RDWR);
+ if (fd < 0) {
+ fprintf(stderr, "Failed to open '%s': %m\n", path);
+ return 1;
+ }
+
+ if (ioctl(fd, PERF_QOS_IOC_GET_UNIT, &arg)) {
+ fprintf(stderr, "Failed to ioctl: %m\n");
+ return 1;
+ }
+
+ return 0;
+}
new file mode 100644
@@ -0,0 +1,147 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Performance Quality of Service (Perf QoS) support base.
+ *
+ * Copyright (C) 2024 Linaro Ltd
+ *
+ * Author: Daniel Lezcano <daniel.lezcano@linaro.org>
+ *
+ */
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/poll.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <linux/perf_qos_ioctl.h>
+
+static int integer_cmp(const void *a, const void *b)
+{
+ int *ia = (typeof(ia))(a);
+ int *ib = (typeof(ib))(b);
+
+ return (*ia) - (*ib);
+}
+
+static int test_forked_set_max(int fd, const char *path)
+{
+ struct perf_qos_ioctl_arg arg;
+
+ int i;
+ int nr_pids = 100;
+ pid_t pids[nr_pids];
+ int value, values[nr_pids];
+ int fds[nr_pids][2];
+ int ret = 1;
+
+ memset(pids, 0, sizeof(pid_t) * nr_pids);
+
+ /*
+ * Random values in the interval 0-1024, to be set by each
+ * child process. The underlying framework will sort them out
+ * so when reading them, they should be ordered and while the
+ * child process exits, the new maximal will be set each time.
+ */
+ for (i = 0; i < nr_pids; i++) {
+ value = rand() % 1023;
+
+ if (pipe(fds[i])) {
+ fprintf(stderr, "Failed to pipe: %m\n");
+ goto out;
+ }
+
+ pids[i] = fork();
+ if (pids[i] < 0) {
+ fprintf(stderr, "Failed to fork: %m\n");
+ goto out;
+ }
+
+ if (!pids[i]) {
+
+ arg.value = value;
+
+ close(fd);
+ close(fds[i][0]);
+
+ fd = open(path, 0, O_RDWR);
+ if (fd < 0) {
+ fprintf(stderr, "Failed to open '%s': %m\n", path);
+ goto out;
+ }
+
+ if (ioctl(fd, PERF_QOS_IOC_SET_MAX, &arg)) {
+ fprintf(stderr, "Failed to ioctl: %m\n");
+ goto out;
+ }
+
+ if (write(fds[i][1], &value, sizeof(value)) < 0) {
+ fprintf(stderr, "Failed to write in the pipe: %m\n");
+ goto out;
+ }
+
+ poll(0, 0, -1);
+
+ exit(0);
+ }
+
+ close(fds[i][1]);
+ values[i] = value;
+ }
+
+ /*
+ * Wait for all the children to set the constraint and write
+ * to the pipe
+ */
+ for (i = 0; i < nr_pids; i++) {
+ if (read(fds[i][0], &value, sizeof(value)) < 0) {
+ fprintf(stderr, "Failed to read pipe: %m\n");
+ goto out;
+ }
+ }
+
+ qsort(values, nr_pids, sizeof(values[0]), integer_cmp);
+
+ if (ioctl(fd, PERF_QOS_IOC_GET_MAX, &arg)) {
+ fprintf(stderr, "Failed to ioctl: %m\n");
+ goto out;
+ }
+
+ if (arg.value != values[0]) {
+ fprintf(stderr, "Unexcepted value order %d <> %d\n",
+ arg.value, values[0]);
+ goto out;
+ }
+
+ ret = 0;
+out:
+ for (i = 0; i < nr_pids; i++) {
+ kill(pids[i], SIGTERM);
+ waitpid(pids[i], NULL, 0);
+ }
+
+ return ret;
+}
+
+int main(int argc, char *argv[])
+{
+ const char *path = "/dev/perf_qos/dummy";
+ int fd;
+
+ if (argc == 2)
+ path = argv[1];
+
+ fd = open(path, 0, O_RDWR);
+ if (fd < 0) {
+ fprintf(stderr, "Failed to open '%s': %m\n", path);
+ return 1;
+ }
+
+ return test_forked_set_max(fd, path);
+}
new file mode 100644
@@ -0,0 +1,147 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Performance Quality of Service (Perf QoS) support base.
+ *
+ * Copyright (C) 2024 Linaro Ltd
+ *
+ * Author: Daniel Lezcano <daniel.lezcano@linaro.org>
+ *
+ */
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/poll.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <linux/perf_qos_ioctl.h>
+
+static int integer_cmp(const void *a, const void *b)
+{
+ int *ia = (typeof(ia))(a);
+ int *ib = (typeof(ib))(b);
+
+ return (*ib) - (*ia);
+}
+
+static int test_forked_set_min(int fd, const char *path)
+{
+ struct perf_qos_ioctl_arg arg;
+
+ int i;
+ int nr_pids = 100;
+ pid_t pids[nr_pids];
+ int value, values[nr_pids];
+ int fds[nr_pids][2];
+ int ret = 1;
+
+ memset(pids, 0, sizeof(pid_t) * nr_pids);
+
+ /*
+ * Random values in the interval 0-1024, to be set by each
+ * child process. The underlying framework will sort them out
+ * so when reading them, they should be ordered and while the
+ * child process exits, the new minimal will be set each time.
+ */
+ for (i = 0; i < nr_pids; i++) {
+ value = rand() % 1023;
+
+ if (pipe(fds[i])) {
+ fprintf(stderr, "Failed to pipe: %m\n");
+ goto out;
+ }
+
+ pids[i] = fork();
+ if (pids[i] < 0) {
+ fprintf(stderr, "Failed to fork: %m\n");
+ goto out;
+ }
+
+ if (!pids[i]) {
+
+ arg.value = value;
+
+ close(fd);
+ close(fds[i][0]);
+
+ fd = open(path, 0, O_RDWR);
+ if (fd < 0) {
+ fprintf(stderr, "Failed to open '%s': %m\n", path);
+ goto out;
+ }
+
+ if (ioctl(fd, PERF_QOS_IOC_SET_MIN, &arg)) {
+ fprintf(stderr, "Failed to ioctl: %m\n");
+ goto out;
+ }
+
+ if (write(fds[i][1], &value, sizeof(value)) < 0) {
+ fprintf(stderr, "Failed to write in the pipe: %m\n");
+ goto out;
+ }
+
+ poll(0, 0, -1);
+
+ exit(0);
+ }
+
+ close(fds[i][1]);
+ values[i] = value;
+ }
+
+ /*
+ * Wait for all the children to set the constraint and write
+ * to the pipe
+ */
+ for (i = 0; i < nr_pids; i++) {
+ if (read(fds[i][0], &value, sizeof(value)) < 0) {
+ fprintf(stderr, "Failed to read pipe: %m\n");
+ goto out;
+ }
+ }
+
+ qsort(values, nr_pids, sizeof(values[0]), integer_cmp);
+
+ if (ioctl(fd, PERF_QOS_IOC_GET_MIN, &arg)) {
+ fprintf(stderr, "Failed to ioctl: %m\n");
+ goto out;
+ }
+
+ if (arg.value != values[0]) {
+ fprintf(stderr, "Unexcepted value order %d <> %d\n",
+ arg.value, values[0]);
+ goto out;
+ }
+
+ ret = 0;
+out:
+ for (i = 0; i < nr_pids; i++) {
+ kill(pids[i], SIGTERM);
+ waitpid(pids[i], NULL, 0);
+ }
+
+ return ret;
+}
+
+int main(int argc, char *argv[])
+{
+ const char *path = "/dev/perf_qos/dummy";
+ int fd;
+
+ if (argc == 2)
+ path = argv[1];
+
+ fd = open(path, 0, O_RDWR);
+ if (fd < 0) {
+ fprintf(stderr, "Failed to open '%s': %m\n", path);
+ return 1;
+ }
+
+ return test_forked_set_min(fd, path);
+}
new file mode 100644
@@ -0,0 +1,93 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Performance Quality of Service (Perf QoS) support base.
+ *
+ * Copyright (C) 2024 Linaro Ltd
+ *
+ * Author: Daniel Lezcano <daniel.lezcano@linaro.org>
+ *
+ */
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/poll.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <linux/perf_qos_ioctl.h>
+
+int main(int argc, char *argv[])
+{
+ struct perf_qos_ioctl_arg arg;
+ const char *path = "/dev/perf_qos/dummy";
+ const int nr_fds = 256;
+ int i, fd[nr_fds];
+
+ if (argc == 2)
+ path = argv[1];
+
+ for (i = 0; i < nr_fds; i++) {
+
+ fd[i] = open(path, 0, O_RDWR);
+ if (fd[i] < 0) {
+ fprintf(stderr, "Failed to open '%s': %m\n", path);
+ return 1;
+ }
+
+ /*
+ * We want a value increasing so the value we set is
+ * always the first entry in the list of constraints
+ * and when we get the max, we get the last value we
+ * set.
+ */
+ arg.value = nr_fds - i;
+
+ if (ioctl(fd[i], PERF_QOS_IOC_SET_MAX, &arg)) {
+ fprintf(stderr, "Failed to ioctl: %m\n");
+ return 1;
+ }
+
+ arg.value = 0;
+
+ if (ioctl(fd[i], PERF_QOS_IOC_GET_MAX, &arg)) {
+ fprintf(stderr, "Failed to ioctl: %m\n");
+ return 1;
+ }
+
+ if (arg.value != (nr_fds - i)) {
+ fprintf(stderr, "max value differs with set/get (arg=%d <> %d)\n",
+ arg.value, nr_fds - i);
+ return 1;
+ }
+ }
+
+ for (i = 0; i < nr_fds; i++)
+ close(fd[i]);
+
+ fd[0] = open(path, 0, O_RDWR);
+ if (fd[0] < 0) {
+ fprintf(stderr, "Failed to open '%s': %m\n", path);
+ return 1;
+ }
+
+ /*
+ * Test: Check the constraint is removed
+ */
+ if (!ioctl(fd[0], PERF_QOS_IOC_GET_MAX, &arg)) {
+ fprintf(stderr, "ioctl should have failed\n");
+ return 1;
+ }
+
+ if (errno != ENODATA) {
+ fprintf(stderr, "errno should have been ENODATA\n");
+ return 1;
+ }
+
+ return 0;
+}
new file mode 100644
@@ -0,0 +1,87 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Performance Quality of Service (Perf QoS) support base.
+ *
+ * Copyright (C) 2024 Linaro Ltd
+ *
+ * Author: Daniel Lezcano <daniel.lezcano@linaro.org>
+ *
+ */
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/poll.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <linux/perf_qos_ioctl.h>
+
+int main(int argc, char *argv[])
+{
+ struct perf_qos_ioctl_arg arg;
+ const char *path = "/dev/perf_qos/dummy";
+ const int nr_fds = 256;
+ int i, fd[nr_fds];
+
+ if (argc == 2)
+ path = argv[1];
+
+ for (i = 0; i < nr_fds; i++) {
+
+ fd[i] = open(path, 0, O_RDWR);
+ if (fd[i] < 0) {
+ fprintf(stderr, "Failed to open '%s': %m\n", path);
+ return 1;
+ }
+
+ arg.value = i + 1;
+
+ if (ioctl(fd[i], PERF_QOS_IOC_SET_MIN, &arg)) {
+ fprintf(stderr, "Failed to ioctl: %m\n");
+ return 1;
+ }
+
+ arg.value = 0;
+
+ if (ioctl(fd[i], PERF_QOS_IOC_GET_MIN, &arg)) {
+ fprintf(stderr, "Failed to ioctl: %m\n");
+ return 1;
+ }
+
+ if (arg.value != i + 1) {
+ fprintf(stderr, "min value differs with set/get (arg=%d <> %d)\n",
+ arg.value, nr_fds + 1);
+ return 1;
+ }
+ }
+
+ for (i = 0; i < nr_fds; i++)
+ close(fd[i]);
+
+ fd[0] = open(path, 0, O_RDWR);
+ if (fd[0] < 0) {
+ fprintf(stderr, "Failed to open '%s': %m\n", path);
+ return 1;
+ }
+
+ /*
+ * Test: Check the constraint is removed
+ */
+ if (!ioctl(fd[0], PERF_QOS_IOC_GET_MIN, &arg)) {
+ fprintf(stderr, "ioctl should have failed\n");
+ return 1;
+ }
+
+ if (errno != ENODATA) {
+ fprintf(stderr, "errno should have been ENODATA\n");
+ return 1;
+ }
+
+ return 0;
+}
new file mode 100644
@@ -0,0 +1,84 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Performance Quality of Service (Perf QoS) support base.
+ *
+ * Copyright (C) 2024 Linaro Ltd
+ *
+ * Author: Daniel Lezcano <daniel.lezcano@linaro.org>
+ *
+ */
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/poll.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <linux/perf_qos_ioctl.h>
+
+int main(int argc, char *argv[])
+{
+ struct perf_qos_ioctl_arg arg = { .value = 512 };
+ const char *path = "/dev/perf_qos/dummy";
+ const int nr_fds = 256;
+ int i, fd[nr_fds];
+
+ if (argc == 2)
+ path = argv[1];
+
+ for (i = 0; i < nr_fds; i++) {
+ fd[i] = open(path, 0, O_RDWR);
+ if (fd[i] < 0) {
+ fprintf(stderr, "Failed to open '%s': %m\n", path);
+ return 1;
+ }
+
+ if (ioctl(fd[i], PERF_QOS_IOC_SET_MAX, &arg)) {
+ fprintf(stderr, "Failed to ioctl: %m\n");
+ return 1;
+ }
+
+ arg.value = 0;
+
+ if (ioctl(fd[i], PERF_QOS_IOC_GET_MAX, &arg)) {
+ fprintf(stderr, "Failed to ioctl: %m\n");
+ return 1;
+ }
+
+ if (arg.value != 512) {
+ fprintf(stderr, "max value differs with set/get (arg=%d)\n",
+ arg.value);
+ return 1;
+ }
+ }
+
+ for (i = 0; i < nr_fds; i++)
+ close(fd[i]);
+
+ fd[0] = open(path, 0, O_RDWR);
+ if (fd[0] < 0) {
+ fprintf(stderr, "Failed to open '%s': %m\n", path);
+ return 1;
+ }
+
+ /*
+ * Test: Check the constraint is removed
+ */
+ if (!ioctl(fd[0], PERF_QOS_IOC_GET_MAX, &arg)) {
+ fprintf(stderr, "ioctl should have failed\n");
+ return 1;
+ }
+
+ if (errno != ENODATA) {
+ fprintf(stderr, "errno should have been ENODATA\n");
+ return 1;
+ }
+
+ return 0;
+}
new file mode 100644
@@ -0,0 +1,84 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Performance Quality of Service (Perf QoS) support base.
+ *
+ * Copyright (C) 2024 Linaro Ltd
+ *
+ * Author: Daniel Lezcano <daniel.lezcano@linaro.org>
+ *
+ */
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/poll.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <linux/perf_qos_ioctl.h>
+
+int main(int argc, char *argv[])
+{
+ struct perf_qos_ioctl_arg arg = { .value = 512 };
+ const char *path = "/dev/perf_qos/dummy";
+ const int nr_fds = 256;
+ int i, fd[nr_fds];
+
+ if (argc == 2)
+ path = argv[1];
+
+ for (i = 0; i < nr_fds; i++) {
+ fd[i] = open(path, 0, O_RDWR);
+ if (fd[i] < 0) {
+ fprintf(stderr, "Failed to open '%s': %m\n", path);
+ return 1;
+ }
+
+ if (ioctl(fd[i], PERF_QOS_IOC_SET_MIN, &arg)) {
+ fprintf(stderr, "Failed to ioctl: %m\n");
+ return 1;
+ }
+
+ arg.value = 0;
+
+ if (ioctl(fd[i], PERF_QOS_IOC_GET_MIN, &arg)) {
+ fprintf(stderr, "Failed to ioctl: %m\n");
+ return 1;
+ }
+
+ if (arg.value != 512) {
+ fprintf(stderr, "min value differs with set/get (arg=%d)\n",
+ arg.value);
+ return 1;
+ }
+ }
+
+ for (i = 0; i < nr_fds; i++)
+ close(fd[i]);
+
+ fd[0] = open(path, 0, O_RDWR);
+ if (fd[0] < 0) {
+ fprintf(stderr, "Failed to open '%s': %m\n", path);
+ return 1;
+ }
+
+ /*
+ * Test: Check the constraint is removed
+ */
+ if (!ioctl(fd[0], PERF_QOS_IOC_GET_MIN, &arg)) {
+ fprintf(stderr, "ioctl should have failed\n");
+ return 1;
+ }
+
+ if (errno != ENODATA) {
+ fprintf(stderr, "errno should have been ENODATA\n");
+ return 1;
+ }
+
+ return 0;
+}
The performance QoS is a framework to allow the userspace to set the performance limits of a device which is exported as a char device in /dev. The performance constraints are set from userspace and their life cycle is tied with the opened file descriptor and other processes requesting the same constraint via another instance of the file descriptor. This kind of non trivial behavior, involving constraint limits to be enqueued in a sorted list and depending on the process holding a file descriptor, deserves a set of testing programs. This patch provides somes tests which depend on a kernel module creating a dummy performance QoS device. More tests will be added later. Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org> --- lib/Kconfig.debug | 9 ++ lib/Makefile | 1 + lib/test_perf_qos.c | 69 ++++++++ tools/include/uapi/linux/perf_qos_ioctl.h | 47 ++++++ tools/testing/selftests/Makefile | 1 + tools/testing/selftests/perf_qos/Makefile | 16 ++ tools/testing/selftests/perf_qos/get_limits.c | 61 +++++++ .../testing/selftests/perf_qos/get_set_max.c | 95 +++++++++++ .../selftests/perf_qos/get_set_max_forked.c | 150 ++++++++++++++++++ .../testing/selftests/perf_qos/get_set_min.c | 95 +++++++++++ .../selftests/perf_qos/get_set_min_forked.c | 150 ++++++++++++++++++ tools/testing/selftests/perf_qos/get_unit.c | 46 ++++++ .../selftests/perf_qos/set_max_forked.c | 147 +++++++++++++++++ .../selftests/perf_qos/set_min_forked.c | 147 +++++++++++++++++ .../selftests/perf_qos/set_multiple_maxs.c | 93 +++++++++++ .../selftests/perf_qos/set_multiple_mins.c | 87 ++++++++++ .../perf_qos/set_same_multiple_maxs.c | 84 ++++++++++ .../perf_qos/set_same_multiple_mins.c | 84 ++++++++++ 18 files changed, 1382 insertions(+) create mode 100644 lib/test_perf_qos.c create mode 100644 tools/include/uapi/linux/perf_qos_ioctl.h create mode 100644 tools/testing/selftests/perf_qos/Makefile create mode 100644 tools/testing/selftests/perf_qos/get_limits.c create mode 100644 tools/testing/selftests/perf_qos/get_set_max.c create mode 100644 tools/testing/selftests/perf_qos/get_set_max_forked.c create mode 100644 tools/testing/selftests/perf_qos/get_set_min.c create mode 100644 tools/testing/selftests/perf_qos/get_set_min_forked.c create mode 100644 tools/testing/selftests/perf_qos/get_unit.c create mode 100644 tools/testing/selftests/perf_qos/set_max_forked.c create mode 100644 tools/testing/selftests/perf_qos/set_min_forked.c create mode 100644 tools/testing/selftests/perf_qos/set_multiple_maxs.c create mode 100644 tools/testing/selftests/perf_qos/set_multiple_mins.c create mode 100644 tools/testing/selftests/perf_qos/set_same_multiple_maxs.c create mode 100644 tools/testing/selftests/perf_qos/set_same_multiple_mins.c