M7350/qcom-opensource/kernel/kernel-tests/spinlock/spinlock_test_module.c
2024-09-09 08:57:42 +00:00

523 lines
13 KiB
C

/* Copyright (c) 2014-2015, 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.
*/
#define pr_fmt(fmt) "spinlock-test: " fmt
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/workqueue.h>
#include <linux/cpu.h>
#include <linux/debugfs.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/ktime.h>
#include <linux/dma-mapping.h>
#define MODULE_NAME "spinlock_test"
#define CPU_NUM_MAX NR_CPUS
#define TEST_ITERS_MAX 100
#define INNER_TEST_TIME_SECS_DEFAULT 5
#define INNER_TEST_TIME_SECS_MAX 100
#define SPINLOCK_TEST_DELAY_MS 1000
#define IRQ_DIALBE_TIME_MS (MSEC_PER_SEC/HZ)
static void spinwork_fn(struct work_struct *work);
static struct delayed_work spinworks[CPU_NUM_MAX];
static struct dentry *dent;
static DEFINE_PER_CPU(int, cpu_test_idx);
static u32 start;
static u32 num_test_iters;
static u32 canceltest;
static u32 cpu_start[CPU_NUM_MAX];
static u32 inner_test_time_secs = INNER_TEST_TIME_SECS_DEFAULT;
static struct locks {
spinlock_t lock1;
spinlock_t lock2;
} testlocks __cacheline_aligned;
/*
* global_lock_static will be in the kernel static (BSS?) region.
* global_inner_lock will be kmalloc'ed. The idea here is to use
* different cache-lines for the two locks.
*/
static DEFINE_SPINLOCK(global_lock_static);
static spinlock_t *global_lock_heap;
static spinlock_t *uncached_lock;
static phys_addr_t phys;
enum {
IRQS_DISABLED_TEST = 1,
STACK_LOCK_TEST,
TWOLOCKS_TEST,
SIMPLE_TEST,
UNCACHED_LOCK_TEST,
NUM_OF_SPINLOCK_TEST = 5,
};
static int set_inner_test_time(void *data, u64 val)
{
if (val != 0) {
inner_test_time_secs = val < INNER_TEST_TIME_SECS_MAX ? \
val : INNER_TEST_TIME_SECS_MAX;
pr_debug("set_inner_test_time: %u\n", inner_test_time_secs);
}
return 0;
}
static int get_inner_test_time(void *data, u64 *val)
{
*val = inner_test_time_secs;
return 0;
}
static int spinlock_test_start(void);
static int spinlock_test_stop(void);
static int set_spinlock_start(void *data, u64 val)
{
int ret = 0;
if (val > 0) {
if (start) {
pr_err("The last test is not done yet. Exitting...\n");
return 0;
}
num_test_iters = val < TEST_ITERS_MAX ? val : TEST_ITERS_MAX;
ret = spinlock_test_start();
} else if (val == 0) {
if (!start) {
pr_err("No spinlock test is running. Exitting...\n");
return 0;
}
ret = spinlock_test_stop();
}
return ret;
}
static int get_spinlock_start(void *data, u64 *val)
{
*val = start;
return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(spinlock_start_ops,
get_spinlock_start, set_spinlock_start, "%llu\n");
DEFINE_SIMPLE_ATTRIBUTE(inner_test_time_ops,
get_inner_test_time, set_inner_test_time, "%llu\n");
static int creat_spinlock_debugfs(void)
{
/*
* Create a simple debugfs with the name of "spinlock-test",
*
* As this is a simple directory, no uevent will be sent to
* userspace.
*/
struct dentry *phandle;
dent = debugfs_create_dir("spinlock-test", 0);
if (IS_ERR(dent))
return -ENOMEM;
phandle = debugfs_create_file("start", 0666, dent, 0, &spinlock_start_ops);
if (!phandle || IS_ERR(phandle))
goto err;
phandle = debugfs_create_file("inner_test_time", 0666, dent, 0, &inner_test_time_ops);
if (!phandle || IS_ERR(phandle))
goto err;
return 0;
err:
debugfs_remove_recursive(dent);
return -ENOMEM;
}
static void sync_cpus(int test_idx, int cpu)
{
int ocpu, flag;
per_cpu(cpu_test_idx, cpu) = test_idx;
/*
Wait until all other cpus finished the same test case
within the same iteration.
*/
do {
flag = 0;
for_each_online_cpu(ocpu) {
if (per_cpu(cpu_test_idx, ocpu) != test_idx ||
cpu_start[ocpu] != cpu_start[cpu]) {
flag = 1;
break;
}
}
} while (flag);
}
static void irqs_disabled_spintest(int cpu)
{
unsigned long flags, irqflags;
int incritical = 0;
ktime_t test_time = ktime_add_us(ktime_get(),
(inner_test_time_secs * USEC_PER_SEC));
pr_info("(irq_disabled): Running test on CPU %d for %d seconds.\n", cpu,
inner_test_time_secs);
while (ktime_compare(ktime_get(), test_time) < 0)
{
ktime_t irq_disable_test_time = ktime_add_us(ktime_get(),
(IRQ_DIALBE_TIME_MS * USEC_PER_MSEC));
local_irq_save(irqflags);
while (ktime_compare(ktime_get(), irq_disable_test_time) < 0) {
spin_lock_irqsave(&global_lock_static, flags);
spin_lock(global_lock_heap);
incritical++;
spin_unlock(global_lock_heap);
spin_unlock_irqrestore(&global_lock_static, flags);
}
local_irq_restore(irqflags);
}
pr_info("(irqs disabled) CPU%d entered critical section %d times.\n",
cpu, incritical);
sync_cpus(IRQS_DISABLED_TEST, cpu);
}
static void spintest_two_lock_single_cacheline(int cpu)
{
unsigned long flags;
int incritical = 0;
ktime_t test_time = ktime_add_us(ktime_get(),
(inner_test_time_secs * USEC_PER_SEC));
pr_info("(two-locks): Running test on CPU %d for %d seconds.\n", cpu,
inner_test_time_secs);
while (ktime_compare(ktime_get(), test_time) < 0)
{
spin_lock_irqsave(&testlocks.lock1, flags);
spin_lock(&testlocks.lock2);
incritical++;
spin_unlock(&testlocks.lock2);
spin_unlock_irqrestore(&testlocks.lock1, flags);
}
pr_info("(two-locks): CPU%d entered critical section %d times.\n",
cpu, incritical);
sync_cpus(TWOLOCKS_TEST, cpu);
}
static void spintest_simple(int cpu)
{
unsigned long flags;
int incritical = 0;
ktime_t test_time = ktime_add_us(ktime_get(),
(inner_test_time_secs * USEC_PER_SEC));
pr_info("(simple): Running test on CPU %d for %d seconds.\n", cpu,
inner_test_time_secs);
while (ktime_compare(ktime_get(), test_time) < 0)
{
spin_lock_irqsave(&global_lock_static, flags);
incritical++;
spin_unlock_irqrestore(&global_lock_static, flags);
}
pr_info("(simple): CPU%d entered critical section %d times.\n",
cpu, incritical);
sync_cpus(SIMPLE_TEST, cpu);
}
static void spintest_stack_lock(int cpu)
{
unsigned long flags;
int incritical = 0;
ktime_t test_time = ktime_add_us(ktime_get(),
(inner_test_time_secs * USEC_PER_SEC));
spinlock_t stack_lock;
spin_lock_init(&stack_lock);
pr_info("(stack-lock): Running test on CPU %d for %d seconds.\n", cpu,
inner_test_time_secs);
while (ktime_compare(ktime_get(), test_time) < 0)
{
spin_lock_irqsave(&global_lock_static, flags);
spin_lock(&stack_lock);
incritical++;
spin_unlock(&stack_lock);
spin_unlock_irqrestore(&global_lock_static, flags);
}
pr_info("(stack-lock): CPU%d entered critical section %d times.\n",
cpu, incritical);
sync_cpus(STACK_LOCK_TEST, cpu);
}
static void spintest_uncached_lock_test(int cpu)
{
unsigned long flags;
int incritical = 0;
ktime_t test_time = ktime_add_us(ktime_get(),
(inner_test_time_secs * USEC_PER_SEC));
pr_info("(uncached-lock): Running test on CPU %d for %d seconds.\n", cpu,
inner_test_time_secs);
while (ktime_compare(ktime_get(), test_time) < 0)
{
spin_lock_irqsave(&global_lock_static, flags);
spin_lock(uncached_lock);
incritical++;
spin_unlock(uncached_lock);
spin_unlock_irqrestore(&global_lock_static, flags);
}
pr_info("(uncached-lock): CPU%d entered critical section %d times.\n",
cpu, incritical);
sync_cpus(UNCACHED_LOCK_TEST, cpu);
}
static void spinwork_fn(struct work_struct *work)
{
struct delayed_work *dwork = (struct delayed_work *)work;
int cpu = smp_processor_id();
if (canceltest) {
pr_err("test cancelled!\n");
return;
}
/*
* Start interrupt disabled test. This should provide maximum
* contention.
*/
irqs_disabled_spintest(cpu);
pr_info("irqs_disabled_spintest is done (CPU:%d iteration:%d)\n",
cpu, cpu_start[cpu]);
msleep(SPINLOCK_TEST_DELAY_MS);
spintest_simple(cpu);
pr_info("spintest_simple is done (CPU:%d iteration:%d)\n",
cpu, cpu_start[cpu]);
msleep(SPINLOCK_TEST_DELAY_MS);
/* Use a spinlock allocated on the stack */
spintest_stack_lock(cpu);
pr_info("spintest_stack_lock is done (CPU:%d iteration:%d)\n",
cpu, cpu_start[cpu]);
msleep(SPINLOCK_TEST_DELAY_MS);
spintest_two_lock_single_cacheline(cpu);
pr_info("spintest_two_lock is done (CPU:%d iteration:%d)\n",
cpu, cpu_start[cpu]);
msleep(SPINLOCK_TEST_DELAY_MS);
spintest_uncached_lock_test(cpu);
pr_info("spintest_uncached_lock_test is done (CPU:%d iteration:%d)\n",
cpu, cpu_start[cpu]);
msleep(SPINLOCK_TEST_DELAY_MS);
if (++cpu_start[cpu] < num_test_iters) {
schedule_work_on(cpu, &dwork->work);
}
}
static int spinlock_test_start(void)
{
int i, flag;
ktime_t test_time;
unsigned long total_wait_time;
start = 1;
canceltest = 0;
pr_info("Starting spinlock test\n");
#ifdef CONFIG_HOTPLUG_CPU
/* Online all the cores */
for_each_present_cpu(i) {
if (!cpu_online(i)) {
cpu_up(i);
}
}
#endif
get_online_cpus();
for_each_online_cpu(i) {
cpu_start[i] = 0;
INIT_DEFERRABLE_WORK(&spinworks[i], spinwork_fn);
schedule_delayed_work_on(i, &spinworks[i], msecs_to_jiffies(150));
pr_info("Scheduling spinlock test on cpu %d\n", i);
}
/*
* Estimate and calculate total wait time according to
* - number of toal test cases. (e.g irq_disable, stack spinlock, etc..)
* - inner test time for each test case
* - number of iterations for each test case
* - sleep time during two test cases
*/
total_wait_time = (NUM_OF_SPINLOCK_TEST + 1) * (inner_test_time_secs * \
num_test_iters + SPINLOCK_TEST_DELAY_MS / MSEC_PER_SEC);
pr_info("Total test time might be ~%lu seconds\n", total_wait_time);
test_time = ktime_add_us(ktime_get(),
(total_wait_time * USEC_PER_SEC));
while (ktime_compare(ktime_get(), test_time) < 0) {
flag = 0;
for_each_online_cpu(i) {
if (cpu_start[i] != num_test_iters) {
flag = 1;
break;
}
}
if (!flag) {
start = 0;
break;
}
msleep(SPINLOCK_TEST_DELAY_MS);
}
if (!start) {
pr_info("spinlock test is done successfully!\n");
put_online_cpus();
return 0;
} else {
pr_err("spinlock test Failed!\n");
spinlock_test_stop();
return -1;
}
}
static int spinlock_test_stop(void)
{
int i;
canceltest = 1;
if (!start) {
pr_err("No spinlock test is running. Exitting...\n");
return -1;
}
for(i = 0; i < CPU_NUM_MAX; i++){
if (spinworks[i].wq != NULL) {
pr_info("canceling work oncpu %d\n", i);
cancel_delayed_work_sync(&spinworks[i]);
}
}
/* "start" might be updated by the test work thread */
if (start)
put_online_cpus();
start = 0;
return 0;
}
static int spinlock_test_remove(struct platform_device *pdev)
{
/* Cancel the test if it's not done yet */
if (start)
spinlock_test_stop();
if (dent) {
debugfs_remove_recursive(dent);
dent = NULL;
}
if (global_lock_heap) {
kfree(global_lock_heap);
global_lock_heap = NULL;
}
if (uncached_lock) {
dma_free_coherent(&pdev->dev, sizeof(spinlock_t), uncached_lock, phys);
uncached_lock = NULL;
}
return 0;
}
static int spinlock_test_probe(struct platform_device *pdev)
{
int ret = 0;
if (num_possible_cpus() > CPU_NUM_MAX) {
dev_err(&pdev->dev, "Number of possible cpus on this target \
exceeds the limit %d\n", CPU_NUM_MAX);
return -EINVAL;
}
global_lock_heap = kzalloc(sizeof(spinlock_t), GFP_KERNEL);
if (!global_lock_heap) {
dev_err(&pdev->dev, "unable to alloc memory for global lock\n");
return -ENOMEM;
}
uncached_lock = dma_alloc_coherent(&pdev->dev, sizeof(spinlock_t), &phys, GFP_KERNEL);
if (!uncached_lock) {
dev_err(&pdev->dev ,"Failed to allocate Uncached memory for lock\n");
kfree(global_lock_heap);
return -ENOMEM;
}
spin_lock_init(&testlocks.lock1);
spin_lock_init(&testlocks.lock2);
spin_lock_init(global_lock_heap);
spin_lock_init(uncached_lock);
ret = creat_spinlock_debugfs();
if (ret) {
dev_err(&pdev->dev, "create spinlock debugfs failed!\n");
kfree(global_lock_heap);
dma_free_coherent(&pdev->dev, sizeof(spinlock_t), uncached_lock, phys);
uncached_lock = NULL;
return ret;
}
return 0;
}
static struct platform_driver spinlock_test_driver = {
.probe = spinlock_test_probe,
.remove = spinlock_test_remove,
.driver = {
.name = MODULE_NAME,
.owner = THIS_MODULE,
},
};
static void platform_spinlock_release(struct device* dev)
{
return;
}
static struct platform_device spinlock_test_device = {
.name = MODULE_NAME,
.id = -1,
.dev = {
.release = platform_spinlock_release,
}
};
static int __init spinlock_test_init(void)
{
platform_device_register(&spinlock_test_device);
return platform_driver_register(&spinlock_test_driver);
}
static void __exit spinlock_test_exit(void)
{
platform_driver_unregister(&spinlock_test_driver);
platform_device_unregister(&spinlock_test_device);
}
MODULE_DESCRIPTION("SPINLOCK TEST");
MODULE_LICENSE("GPL v2");
module_init(spinlock_test_init);
module_exit(spinlock_test_exit);