356 lines
8.6 KiB
C
356 lines
8.6 KiB
C
/* 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 <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/hrtimer.h>
|
|
#include <linux/ktime.h>
|
|
#include <linux/kthread.h>
|
|
#include <linux/init.h>
|
|
#include <linux/io.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/jiffies.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/workqueue.h>
|
|
#include <linux/moduleparam.h>
|
|
#include <linux/atomic.h>
|
|
#include <linux/cpu.h>
|
|
#include <linux/debugfs.h>
|
|
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/slab.h>
|
|
|
|
#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);
|