Introduce a mechanism to fire directed IPIs from a specified source CPU
to a specified target CPU and measure the difference in time incurred on
wakeup.

Also, introduce a mechanism to queue a HR timer on a specified CPU and
subsequently measure the time taken to wakeup the CPU.

Finally define a simple debugfs interface to control the knobs to fire
the IPI and Timer events on specified CPU and view their incurred idle
wakeup latencies.

Signed-off-by: Pratik R. Sampat <psam...@linux.ibm.com>
---
 arch/powerpc/kernel/Makefile               |   1 +
 arch/powerpc/kernel/test-cpuidle_latency.c | 157 +++++++++++++++++++++
 lib/Kconfig.debug                          |  10 ++
 3 files changed, 168 insertions(+)
 create mode 100644 arch/powerpc/kernel/test-cpuidle_latency.c

diff --git a/arch/powerpc/kernel/Makefile b/arch/powerpc/kernel/Makefile
index f66b63e81c3b..56e36e797dd4 100644
--- a/arch/powerpc/kernel/Makefile
+++ b/arch/powerpc/kernel/Makefile
@@ -56,6 +56,7 @@ obj-$(CONFIG_PPC_WATCHDOG)    += watchdog.o
 obj-$(CONFIG_HAVE_HW_BREAKPOINT)       += hw_breakpoint.o
 obj-$(CONFIG_PPC_DAWR)         += dawr.o
 obj-$(CONFIG_PPC_BOOK3S_64)    += cpu_setup_ppc970.o cpu_setup_pa6t.o
+obj-$(CONFIG_IDLE_LATENCY_SELFTEST)      += test-cpuidle_latency.o
 obj-$(CONFIG_PPC_BOOK3S_64)    += cpu_setup_power.o
 obj-$(CONFIG_PPC_BOOK3S_64)    += mce.o mce_power.o
 obj-$(CONFIG_PPC_BOOK3E_64)    += exceptions-64e.o idle_book3e.o
diff --git a/arch/powerpc/kernel/test-cpuidle_latency.c 
b/arch/powerpc/kernel/test-cpuidle_latency.c
new file mode 100644
index 000000000000..f138011ac225
--- /dev/null
+++ b/arch/powerpc/kernel/test-cpuidle_latency.c
@@ -0,0 +1,157 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Module-based API test facility for cpuidle latency using IPIs and timers
+ */
+
+#include <linux/debugfs.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+
+/*
+ * IPI based wakeup latencies
+ * Measure time taken for a CPU to wakeup on a IPI sent from another CPU
+ * The latency measured also includes the latency of sending the IPI
+ */
+struct latency {
+       unsigned int src_cpu;
+       unsigned int dest_cpu;
+       ktime_t time_start;
+       ktime_t time_end;
+       u64 latency_ns;
+} ipi_wakeup;
+
+static void measure_latency(void *info)
+{
+       struct latency *v;
+       ktime_t time_diff;
+
+       v = (struct latency *)info;
+       v->time_end = ktime_get();
+       time_diff = ktime_sub(v->time_end, v->time_start);
+       v->latency_ns = ktime_to_ns(time_diff);
+}
+
+void run_smp_call_function_test(unsigned int cpu)
+{
+       ipi_wakeup.src_cpu = smp_processor_id();
+       ipi_wakeup.dest_cpu = cpu;
+       ipi_wakeup.time_start = ktime_get();
+       smp_call_function_single(cpu, measure_latency, &ipi_wakeup, 1);
+}
+
+/*
+ * Timer based wakeup latencies
+ * Measure time taken for a CPU to wakeup on a timer being armed and fired
+ */
+struct timer_data {
+       unsigned int src_cpu;
+       u64 timeout;
+       ktime_t time_start;
+       ktime_t time_end;
+       struct hrtimer timer;
+       u64 timeout_diff_ns;
+} timer_wakeup;
+
+static enum hrtimer_restart timer_called(struct hrtimer *hrtimer)
+{
+       struct timer_data *w;
+       ktime_t time_diff;
+
+       w = container_of(hrtimer, struct timer_data, timer);
+       w->time_end = ktime_get();
+
+       time_diff = ktime_sub(w->time_end, w->time_start);
+       time_diff = ktime_sub(time_diff, ns_to_ktime(w->timeout));
+       w->timeout_diff_ns = ktime_to_ns(time_diff);
+       return HRTIMER_NORESTART;
+}
+
+static void run_timer_test(unsigned int ns)
+{
+       hrtimer_init(&timer_wakeup.timer, CLOCK_MONOTONIC,
+                    HRTIMER_MODE_REL);
+       timer_wakeup.timer.function = timer_called;
+       timer_wakeup.src_cpu = smp_processor_id();
+       timer_wakeup.timeout = ns;
+       timer_wakeup.time_start = ktime_get();
+
+       hrtimer_start(&timer_wakeup.timer, ns_to_ktime(ns),
+                     HRTIMER_MODE_REL_PINNED);
+}
+
+static struct dentry *dir;
+
+static int cpu_read_op(void *data, u64 *dest_cpu)
+{
+       *dest_cpu = ipi_wakeup.dest_cpu;
+       return 0;
+}
+
+static int cpu_write_op(void *data, u64 value)
+{
+       run_smp_call_function_test(value);
+       return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(ipi_ops, cpu_read_op, cpu_write_op, "%llu\n");
+
+static int timeout_read_op(void *data, u64 *timeout)
+{
+       *timeout = timer_wakeup.timeout;
+       return 0;
+}
+
+static int timeout_write_op(void *data, u64 value)
+{
+       run_timer_test(value);
+       return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(timeout_ops, timeout_read_op, timeout_write_op, 
"%llu\n");
+
+static int __init latency_init(void)
+{
+       struct dentry *temp;
+
+       dir = debugfs_create_dir("latency_test", 0);
+       if (!dir) {
+               pr_alert("latency_test: failed to create 
/sys/kernel/debug/latency_test\n");
+               return -1;
+       }
+       temp = debugfs_create_file("ipi_cpu_dest",
+                                  0666,
+                                  dir,
+                                  NULL,
+                                  &ipi_ops);
+       if (!temp) {
+               pr_alert("latency_test: failed to create 
/sys/kernel/debug/ipi_cpu_dest\n");
+               return -1;
+       }
+       debugfs_create_u64("ipi_latency_ns", 0444, dir, &ipi_wakeup.latency_ns);
+       debugfs_create_u32("ipi_cpu_src", 0444, dir, &ipi_wakeup.src_cpu);
+
+       temp = debugfs_create_file("timeout_expected_ns",
+                                  0666,
+                                  dir,
+                                  NULL,
+                                  &timeout_ops);
+       if (!temp) {
+               pr_alert("latency_test: failed to create 
/sys/kernel/debug/timeout_expected_ns\n");
+               return -1;
+       }
+       debugfs_create_u64("timeout_diff_ns", 0444, dir, 
&timer_wakeup.timeout_diff_ns);
+       debugfs_create_u32("timeout_cpu_src", 0444, dir, &timer_wakeup.src_cpu);
+       pr_info("Latency Test module loaded\n");
+       return 0;
+}
+
+static void __exit latency_cleanup(void)
+{
+       pr_info("Cleaning up Latency Test module.\n");
+       debugfs_remove_recursive(dir);
+}
+
+module_init(latency_init);
+module_exit(latency_cleanup);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("IBM Corporation");
+MODULE_DESCRIPTION("Measuring idle latency for IPIs and Timers");
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index 417c3d3e521b..70264687e0c2 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -1513,6 +1513,16 @@ config DEBUG_KOBJECT
          If you say Y here, some extra kobject debugging messages will be sent
          to the syslog.
 
+config IDLE_LATENCY_SELFTEST
+       tristate "Cpuidle latency selftests"
+       depends on CPU_IDLE
+       help
+         This option provides a kernel module that runs tests using the IPI and
+         timers to measure latency.
+
+         Say M if you want these self tests to build as a module.
+         Say N if you are unsure.
+
 config DEBUG_KOBJECT_RELEASE
        bool "kobject release debugging"
        depends on DEBUG_OBJECTS_TIMERS
-- 
2.17.1

Reply via email to