/* Copyright (c) 2014, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define MODULE_NAME "hrtimer_test" #define MS_TO_NS(x) (x * 1000000) #define SEC_TO_NS(x) (x * 1000000000) #define INNER_TEST_DELAY_MS 20 #define KTHREAD_INIT_DELAY_MS 2 #define KTHREAD_DELAY_DELTA_MS 3 #define ESTIMATED_DELAY_RATE (5/4) static struct hrtimer *my_timer; struct task_struct *k[NR_CPUS]; static unsigned int pstate[NR_CPUS]; static atomic_t counter = ATOMIC_INIT(0); static DEFINE_MUTEX(hrtimer_mutex); static struct dentry *dent; static int hrtimer_num = 2, loop = 1000, start = 0, kill_test = 0; static ssize_t hrtimer_debug_read_stats(struct file *file, char __user *ubuf, size_t count, loff_t *ppos) { char buf[20]; int ret = 0; ret += scnprintf(buf, sizeof(buf), "%d\n", counter.counter); return simple_read_from_buffer(ubuf, count, ppos, buf, ret); } static int set_hrtimer_num(void *data, u64 val) { if (val != 0) { pr_debug("set_hrtimer_num: %llu\n", val); hrtimer_num = val; } return 0; } static int get_hrtimer_num(void *data, u64 *val) { *val = hrtimer_num; return 0; } static int set_hrtimer_loop(void *data, u64 val) { if (val != 0) { pr_debug("set_hrtimer_count: %llu\n", val); loop = val; } return 0; } static int get_hrtimer_loop(void *data, u64 *val) { *val = loop; return 0; } static int hrtimer_test_start(void); static int hrtimer_test_stop(void); static int set_hrtimer_start(void *data, u64 val) { start = val; if (val == 1) { pr_debug("set_hrtimer_start: %llu\n", val); kill_test = 0; hrtimer_test_start(); } else if (val == 0) { kill_test = 1; hrtimer_test_stop(); } return 0; } static int get_hrtimer_start(void *data, u64 *val) { *val = start; return 0; } DEFINE_SIMPLE_ATTRIBUTE(hrtimer_num_ops, get_hrtimer_num, set_hrtimer_num, "%llu\n"); DEFINE_SIMPLE_ATTRIBUTE(hrtimer_loop_ops, get_hrtimer_loop, set_hrtimer_loop, "%llu\n"); DEFINE_SIMPLE_ATTRIBUTE(hrtimer_start_ops, get_hrtimer_start, set_hrtimer_start, "%llu\n"); const struct file_operations hrtimer_stats_ops = { .read = hrtimer_debug_read_stats, }; static void creat_hrtimer_debugfs(void) { /* * Create a simple debugfs with the name of "hrtimer-test", * * As this is a simple directory, no uevent will be sent to * userspace. */ struct dentry *dfile_status, *dfile_hrtimer_num, *dfile_loop, *dfile_start; dent = debugfs_create_dir("hrtimer-test", 0); if (IS_ERR(dent)) return; dfile_status = debugfs_create_file("status", 0666, dent, 0, &hrtimer_stats_ops); if (!dfile_status || IS_ERR(dfile_status)) debugfs_remove(dent); dfile_hrtimer_num = debugfs_create_file("timer_num", 0666, dent, 0, &hrtimer_num_ops); if (!dfile_hrtimer_num || IS_ERR(dfile_hrtimer_num)) debugfs_remove(dent); dfile_loop = debugfs_create_file("count", 0666, dent, 0, &hrtimer_loop_ops); if (!dfile_loop || IS_ERR(dfile_loop)) debugfs_remove(dent); dfile_start = debugfs_create_file("start", 0666, dent, 0, &hrtimer_start_ops); if (!dfile_start || IS_ERR(dfile_start)) debugfs_remove(dent); } static enum hrtimer_restart my_hrtimer_callback(struct hrtimer *timer) { pr_debug("hrtimer_callback: Jiffies (%lu). Process Name(%s) PID(%d) timer expired: %lu\n", jiffies, timer->start_comm, timer->start_pid, (unsigned long)ktime_to_us(timer->_softexpires)); atomic_dec(&counter); return HRTIMER_NORESTART; } static void wait_to_die(void) { set_current_state(TASK_INTERRUPTIBLE); while (!kthread_should_stop()) { schedule(); set_current_state(TASK_INTERRUPTIBLE); } __set_current_state(TASK_RUNNING); } static int hrtimer_test_handler(void *data) { unsigned long delay_in_ms = (unsigned long)data; ktime_t ktime; int ret; int i = loop, j; int cpu = smp_processor_id(); atomic_inc(&counter); while (i-- && !kill_test) { for(j = 0; j < hrtimer_num && !kill_test; j++) { mutex_lock(&hrtimer_mutex); ret = hrtimer_cancel(&my_timer[j]); if (ret == 1) { pr_debug("HANDLER: The Timer is canceled ...\n"); atomic_dec(&counter); } hrtimer_set_expires(&my_timer[j], ns_to_ktime(0)); ktime = ktime_set(0, MS_TO_NS(delay_in_ms)); pr_debug("HANDLER: Starting timer fire in %ldms (jiffies:%lu); loop = %d \n", delay_in_ms, jiffies, i); atomic_inc(&counter); hrtimer_start(&my_timer[j], ktime, HRTIMER_MODE_REL); mutex_unlock(&hrtimer_mutex); } mdelay(INNER_TEST_DELAY_MS); } atomic_dec(&counter); mdelay(delay_in_ms); pstate[cpu] = 0; wait_to_die(); pr_info("HANDLER: Starting timer fire in %ldms finished\n", delay_in_ms); return 0; } static int hrtimer_test_start(void) { ktime_t ktime; char thread_str[20] = ""; unsigned int cpu, i; long total_delay_ms; unsigned long init_delay_ms = KTHREAD_INIT_DELAY_MS; unsigned long delay_in_ms = 200L; atomic_set(&counter, 0); my_timer = kzalloc(hrtimer_num * sizeof(struct hrtimer), GFP_KERNEL); if (!my_timer) { pr_err("Alloc memory for my_timer failed!\n"); start = 0; return -1; } ktime = ktime_set(0, MS_TO_NS(delay_in_ms)); for(i = 0; i < hrtimer_num; i++) { hrtimer_init(&my_timer[i], CLOCK_MONOTONIC, HRTIMER_MODE_REL); my_timer[i].function = &my_hrtimer_callback; atomic_inc(&counter); pr_info("PROBE: Starting timer to fire in %ldms (%lu)\n", delay_in_ms, jiffies); hrtimer_start(&my_timer[i], ktime, HRTIMER_MODE_REL); } #ifdef CONFIG_HOTPLUG_CPU for_each_present_cpu(cpu) { /*Initial to bring all cpu cores online. It is to create thread for each CPU core and wait up afterwards.*/ if (!cpu_online(cpu)) cpu_up(cpu); } #endif get_online_cpus(); for_each_online_cpu(cpu) { snprintf(thread_str, sizeof(thread_str), "hrtimer_thread%d", cpu); k[cpu] = kthread_create(&hrtimer_test_handler, (void *)init_delay_ms,thread_str); kthread_bind(k[cpu],cpu); wake_up_process(k[cpu]); pstate[cpu] = 1; init_delay_ms += KTHREAD_DELAY_DELTA_MS; } put_online_cpus(); /* * Estimate and calculate total wait time according to * - inner delay within each hrtimer test loop * - number of loops * - Estimated additional delay during test */ total_delay_ms = ESTIMATED_DELAY_RATE * INNER_TEST_DELAY_MS * loop; pr_info("hrtimer test might take about %ld(s)\n", total_delay_ms); while(total_delay_ms > 0 && !kill_test) { for(i = 0; i < NR_CPUS; i++) { if(pstate[i]) break; } if(i == NR_CPUS) { pr_info("hrtimer test is Okay!\n"); break; } total_delay_ms -= 1000; mdelay(1000); } hrtimer_test_stop(); return 0; } static int hrtimer_test_stop(void) { unsigned int cpu; start = 0; /* We need to destroy also the parked threads of offline cpus */ for_each_possible_cpu(cpu) { if (k[cpu]) { kthread_stop(k[cpu]); k[cpu] = NULL; } pstate[cpu] = 0; } if (my_timer) { kfree(my_timer); my_timer = NULL; } return 0; } static int hrtimer_test_remove(struct platform_device *pdev) { hrtimer_test_stop(); if (dent) { debugfs_remove_recursive(dent); dent = NULL; } return 0; } static int hrtimer_test_probe(struct platform_device *pdev) { creat_hrtimer_debugfs(); return 0; } static struct platform_driver hrtimer_test_driver = { .probe = hrtimer_test_probe, .remove = hrtimer_test_remove, .driver = { .name = MODULE_NAME, .owner = THIS_MODULE, }, }; static void platform_hrtimer_release(struct device* dev) { return; } static struct platform_device hrtimer_test_device = { .name = MODULE_NAME, .id = -1, .dev = { .release = platform_hrtimer_release, } }; static int init_hrtimer_test(void) { platform_device_register(&hrtimer_test_device); return platform_driver_register(&hrtimer_test_driver); } static void exit_hrtimer_test(void) { platform_driver_unregister(&hrtimer_test_driver); platform_device_unregister(&hrtimer_test_device); } MODULE_DESCRIPTION("HRTIMER TEST"); MODULE_LICENSE("GPL v2"); module_init(init_hrtimer_test); module_exit(exit_hrtimer_test);