2014-01-27 23:49:39 +04:00
/*
* Common functions for in - kernel torture tests .
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* 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 .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , you can access it online at
* http : //www.gnu.org/licenses/gpl-2.0.html.
*
* Copyright ( C ) IBM Corporation , 2014
*
* Author : Paul E . McKenney < paulmck @ us . ibm . com >
* Based on kernel / rcu / torture . c .
*/
# include <linux/types.h>
# include <linux/kernel.h>
# include <linux/init.h>
# include <linux/module.h>
# include <linux/kthread.h>
# include <linux/err.h>
# include <linux/spinlock.h>
# include <linux/smp.h>
# include <linux/interrupt.h>
# include <linux/sched.h>
# include <linux/atomic.h>
# include <linux/bitops.h>
# include <linux/completion.h>
# include <linux/moduleparam.h>
# include <linux/percpu.h>
# include <linux/notifier.h>
# include <linux/reboot.h>
# include <linux/freezer.h>
# include <linux/cpu.h>
# include <linux/delay.h>
# include <linux/stat.h>
# include <linux/slab.h>
# include <linux/trace_clock.h>
# include <asm/byteorder.h>
# include <linux/torture.h>
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " Paul E. McKenney <paulmck@us.ibm.com> " ) ;
2014-01-29 19:40:27 +04:00
int fullstop = FULLSTOP_RMMOD ;
EXPORT_SYMBOL_GPL ( fullstop ) ;
DEFINE_MUTEX ( fullstop_mutex ) ;
EXPORT_SYMBOL_GPL ( fullstop_mutex ) ;
2014-01-27 23:49:39 +04:00
# define TORTURE_RANDOM_MULT 39916801 /* prime */
# define TORTURE_RANDOM_ADD 479001701 /* prime */
# define TORTURE_RANDOM_REFRESH 10000
/*
* Crude but fast random - number generator . Uses a linear congruential
* generator , with occasional help from cpu_clock ( ) .
*/
unsigned long
torture_random ( struct torture_random_state * trsp )
{
if ( - - trsp - > trs_count < 0 ) {
trsp - > trs_state + = ( unsigned long ) local_clock ( ) ;
trsp - > trs_count = TORTURE_RANDOM_REFRESH ;
}
trsp - > trs_state = trsp - > trs_state * TORTURE_RANDOM_MULT +
TORTURE_RANDOM_ADD ;
return swahw32 ( trsp - > trs_state ) ;
}
EXPORT_SYMBOL_GPL ( torture_random ) ;
2014-01-29 19:40:27 +04:00
2014-01-29 03:29:21 +04:00
/*
* Variables for shuffling . The idea is to ensure that each CPU stays
* idle for an extended period to test interactions with dyntick idle ,
* as well as interactions with any per - CPU varibles .
*/
struct shuffle_task {
struct list_head st_l ;
struct task_struct * st_t ;
} ;
static long shuffle_interval ; /* In jiffies. */
static struct task_struct * shuffler_task ;
static cpumask_var_t shuffle_tmp_mask ;
static int shuffle_idle_cpu ; /* Force all torture tasks off this CPU */
static struct list_head shuffle_task_list = LIST_HEAD_INIT ( shuffle_task_list ) ;
static DEFINE_MUTEX ( shuffle_task_mutex ) ;
/*
* Register a task to be shuffled . If there is no memory , just splat
* and don ' t bother registering .
*/
void torture_shuffle_task_register ( struct task_struct * tp )
{
struct shuffle_task * stp ;
if ( WARN_ON_ONCE ( tp = = NULL ) )
return ;
stp = kmalloc ( sizeof ( * stp ) , GFP_KERNEL ) ;
if ( WARN_ON_ONCE ( stp = = NULL ) )
return ;
stp - > st_t = tp ;
mutex_lock ( & shuffle_task_mutex ) ;
list_add ( & stp - > st_l , & shuffle_task_list ) ;
mutex_unlock ( & shuffle_task_mutex ) ;
}
EXPORT_SYMBOL_GPL ( torture_shuffle_task_register ) ;
/*
* Unregister all tasks , for example , at the end of the torture run .
*/
static void torture_shuffle_task_unregister_all ( void )
{
struct shuffle_task * stp ;
struct shuffle_task * p ;
mutex_lock ( & shuffle_task_mutex ) ;
list_for_each_entry_safe ( stp , p , & shuffle_task_list , st_l ) {
list_del ( & stp - > st_l ) ;
kfree ( stp ) ;
}
mutex_unlock ( & shuffle_task_mutex ) ;
}
/* Shuffle tasks such that we allow shuffle_idle_cpu to become idle.
* A special case is when shuffle_idle_cpu = - 1 , in which case we allow
* the tasks to run on all CPUs .
*/
static void torture_shuffle_tasks ( void )
{
struct shuffle_task * stp ;
cpumask_setall ( shuffle_tmp_mask ) ;
get_online_cpus ( ) ;
/* No point in shuffling if there is only one online CPU (ex: UP) */
if ( num_online_cpus ( ) = = 1 ) {
put_online_cpus ( ) ;
return ;
}
/* Advance to the next CPU. Upon overflow, don't idle any CPUs. */
shuffle_idle_cpu = cpumask_next ( shuffle_idle_cpu , shuffle_tmp_mask ) ;
if ( shuffle_idle_cpu > = nr_cpu_ids )
shuffle_idle_cpu = - 1 ;
if ( shuffle_idle_cpu ! = - 1 ) {
cpumask_clear_cpu ( shuffle_idle_cpu , shuffle_tmp_mask ) ;
if ( cpumask_empty ( shuffle_tmp_mask ) ) {
put_online_cpus ( ) ;
return ;
}
}
mutex_lock ( & shuffle_task_mutex ) ;
list_for_each_entry ( stp , & shuffle_task_list , st_l )
set_cpus_allowed_ptr ( stp - > st_t , shuffle_tmp_mask ) ;
mutex_unlock ( & shuffle_task_mutex ) ;
put_online_cpus ( ) ;
}
/* Shuffle tasks across CPUs, with the intent of allowing each CPU in the
* system to become idle at a time and cut off its timer ticks . This is meant
* to test the support for such tickless idle CPU in RCU .
*/
static int torture_shuffle ( void * arg )
{
VERBOSE_TOROUT_STRING ( " torture_shuffle task started " ) ;
do {
schedule_timeout_interruptible ( shuffle_interval ) ;
torture_shuffle_tasks ( ) ;
torture_shutdown_absorb ( " torture_shuffle " ) ;
} while ( ! torture_must_stop ( ) ) ;
VERBOSE_TOROUT_STRING ( " torture_shuffle task stopping " ) ;
return 0 ;
}
/*
* Start the shuffler , with shuffint in jiffies .
*/
int torture_shuffle_init ( long shuffint )
{
int ret ;
shuffle_interval = shuffint ;
shuffle_idle_cpu = - 1 ;
if ( ! alloc_cpumask_var ( & shuffle_tmp_mask , GFP_KERNEL ) ) {
VERBOSE_TOROUT_ERRSTRING ( " Failed to alloc mask " ) ;
return - ENOMEM ;
}
/* Create the shuffler thread */
shuffler_task = kthread_run ( torture_shuffle , NULL , " torture_shuffle " ) ;
if ( IS_ERR ( shuffler_task ) ) {
ret = PTR_ERR ( shuffler_task ) ;
free_cpumask_var ( shuffle_tmp_mask ) ;
VERBOSE_TOROUT_ERRSTRING ( " Failed to create shuffler " ) ;
shuffler_task = NULL ;
return ret ;
}
torture_shuffle_task_register ( shuffler_task ) ;
return 0 ;
}
EXPORT_SYMBOL_GPL ( torture_shuffle_init ) ;
/*
* Stop the shuffling .
*/
void torture_shuffle_cleanup ( void )
{
torture_shuffle_task_unregister_all ( ) ;
if ( shuffler_task ) {
VERBOSE_TOROUT_STRING ( " Stopping torture_shuffle task " ) ;
kthread_stop ( shuffler_task ) ;
free_cpumask_var ( shuffle_tmp_mask ) ;
}
shuffler_task = NULL ;
}
EXPORT_SYMBOL_GPL ( torture_shuffle_cleanup ) ;
2014-01-29 19:40:27 +04:00
/*
* Absorb kthreads into a kernel function that won ' t return , so that
* they won ' t ever access module text or data again .
*/
void torture_shutdown_absorb ( const char * title )
{
while ( ACCESS_ONCE ( fullstop ) = = FULLSTOP_SHUTDOWN ) {
pr_notice (
" torture thread %s parking due to system shutdown \n " ,
title ) ;
schedule_timeout_uninterruptible ( MAX_SCHEDULE_TIMEOUT ) ;
}
}
EXPORT_SYMBOL_GPL ( torture_shutdown_absorb ) ;