2005-04-17 02:20:36 +04:00
|
|
|
/*
|
|
|
|
* hangcheck-timer.c
|
|
|
|
*
|
|
|
|
* Driver for a little io fencing timer.
|
|
|
|
*
|
2005-05-01 19:59:08 +04:00
|
|
|
* Copyright (C) 2002, 2003 Oracle. All rights reserved.
|
2005-04-17 02:20:36 +04:00
|
|
|
*
|
|
|
|
* Author: Joel Becker <joel.becker@oracle.com>
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU General Public
|
|
|
|
* License 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.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public
|
|
|
|
* License along with this program; if not, write to the
|
|
|
|
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
|
|
|
* Boston, MA 021110-1307, USA.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The hangcheck-timer driver uses the TSC to catch delays that
|
|
|
|
* jiffies does not notice. A timer is set. When the timer fires, it
|
|
|
|
* checks whether it was delayed and if that delay exceeds a given
|
2008-02-03 18:11:42 +03:00
|
|
|
* margin of error. The hangcheck_tick module parameter takes the timer
|
2005-04-17 02:20:36 +04:00
|
|
|
* duration in seconds. The hangcheck_margin parameter defines the
|
|
|
|
* margin of error, in seconds. The defaults are 60 seconds for the
|
|
|
|
* timer and 180 seconds for the margin of error. IOW, a timer is set
|
|
|
|
* for 60 seconds. When the timer fires, the callback checks the
|
|
|
|
* actual duration that the timer waited. If the duration exceeds the
|
|
|
|
* alloted time and margin (here 60 + 180, or 240 seconds), the machine
|
|
|
|
* is restarted. A healthy machine will have the duration match the
|
|
|
|
* expected timeout very closely.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/moduleparam.h>
|
|
|
|
#include <linux/types.h>
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/fs.h>
|
|
|
|
#include <linux/mm.h>
|
|
|
|
#include <linux/reboot.h>
|
|
|
|
#include <linux/init.h>
|
2005-05-01 19:59:08 +04:00
|
|
|
#include <linux/delay.h>
|
2005-04-17 02:20:36 +04:00
|
|
|
#include <asm/uaccess.h>
|
2005-05-01 19:59:08 +04:00
|
|
|
#include <linux/sysrq.h>
|
Detach sched.h from mm.h
First thing mm.h does is including sched.h solely for can_do_mlock() inline
function which has "current" dereference inside. By dealing with can_do_mlock()
mm.h can be detached from sched.h which is good. See below, why.
This patch
a) removes unconditional inclusion of sched.h from mm.h
b) makes can_do_mlock() normal function in mm/mlock.c
c) exports can_do_mlock() to not break compilation
d) adds sched.h inclusions back to files that were getting it indirectly.
e) adds less bloated headers to some files (asm/signal.h, jiffies.h) that were
getting them indirectly
Net result is:
a) mm.h users would get less code to open, read, preprocess, parse, ... if
they don't need sched.h
b) sched.h stops being dependency for significant number of files:
on x86_64 allmodconfig touching sched.h results in recompile of 4083 files,
after patch it's only 3744 (-8.3%).
Cross-compile tested on
all arm defconfigs, all mips defconfigs, all powerpc defconfigs,
alpha alpha-up
arm
i386 i386-up i386-defconfig i386-allnoconfig
ia64 ia64-up
m68k
mips
parisc parisc-up
powerpc powerpc-up
s390 s390-up
sparc sparc-up
sparc64 sparc64-up
um-x86_64
x86_64 x86_64-up x86_64-defconfig x86_64-allnoconfig
as well as my two usual configs.
Signed-off-by: Alexey Dobriyan <adobriyan@gmail.com>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2007-05-21 01:22:52 +04:00
|
|
|
#include <linux/timer.h>
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2005-05-01 19:59:08 +04:00
|
|
|
#define VERSION_STR "0.9.0"
|
2005-04-17 02:20:36 +04:00
|
|
|
|
|
|
|
#define DEFAULT_IOFENCE_MARGIN 60 /* Default fudge factor, in seconds */
|
|
|
|
#define DEFAULT_IOFENCE_TICK 180 /* Default timer timeout, in seconds */
|
|
|
|
|
|
|
|
static int hangcheck_tick = DEFAULT_IOFENCE_TICK;
|
|
|
|
static int hangcheck_margin = DEFAULT_IOFENCE_MARGIN;
|
|
|
|
static int hangcheck_reboot; /* Defaults to not reboot */
|
2005-05-01 19:59:08 +04:00
|
|
|
static int hangcheck_dump_tasks; /* Defaults to not dumping SysRQ T */
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2005-05-01 19:59:08 +04:00
|
|
|
/* options - modular */
|
2005-04-17 02:20:36 +04:00
|
|
|
module_param(hangcheck_tick, int, 0);
|
|
|
|
MODULE_PARM_DESC(hangcheck_tick, "Timer delay.");
|
|
|
|
module_param(hangcheck_margin, int, 0);
|
|
|
|
MODULE_PARM_DESC(hangcheck_margin, "If the hangcheck timer has been delayed more than hangcheck_margin seconds, the driver will fire.");
|
|
|
|
module_param(hangcheck_reboot, int, 0);
|
|
|
|
MODULE_PARM_DESC(hangcheck_reboot, "If nonzero, the machine will reboot when the timer margin is exceeded.");
|
2005-05-01 19:59:08 +04:00
|
|
|
module_param(hangcheck_dump_tasks, int, 0);
|
|
|
|
MODULE_PARM_DESC(hangcheck_dump_tasks, "If nonzero, the machine will dump the system task state when the timer margin is exceeded.");
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2005-05-01 19:59:08 +04:00
|
|
|
MODULE_AUTHOR("Oracle");
|
2005-04-17 02:20:36 +04:00
|
|
|
MODULE_DESCRIPTION("Hangcheck-timer detects when the system has gone out to lunch past a certain margin.");
|
|
|
|
MODULE_LICENSE("GPL");
|
2005-05-01 19:59:08 +04:00
|
|
|
MODULE_VERSION(VERSION_STR);
|
|
|
|
|
|
|
|
/* options - nonmodular */
|
|
|
|
#ifndef MODULE
|
|
|
|
|
|
|
|
static int __init hangcheck_parse_tick(char *str)
|
|
|
|
{
|
|
|
|
int par;
|
|
|
|
if (get_option(&str,&par))
|
|
|
|
hangcheck_tick = par;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int __init hangcheck_parse_margin(char *str)
|
|
|
|
{
|
|
|
|
int par;
|
|
|
|
if (get_option(&str,&par))
|
|
|
|
hangcheck_margin = par;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int __init hangcheck_parse_reboot(char *str)
|
|
|
|
{
|
|
|
|
int par;
|
|
|
|
if (get_option(&str,&par))
|
|
|
|
hangcheck_reboot = par;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int __init hangcheck_parse_dump_tasks(char *str)
|
|
|
|
{
|
|
|
|
int par;
|
|
|
|
if (get_option(&str,&par))
|
|
|
|
hangcheck_dump_tasks = par;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
__setup("hcheck_tick", hangcheck_parse_tick);
|
|
|
|
__setup("hcheck_margin", hangcheck_parse_margin);
|
|
|
|
__setup("hcheck_reboot", hangcheck_parse_reboot);
|
|
|
|
__setup("hcheck_dump_tasks", hangcheck_parse_dump_tasks);
|
|
|
|
#endif /* not MODULE */
|
|
|
|
|
2007-02-16 12:28:20 +03:00
|
|
|
#if defined(CONFIG_S390)
|
2005-05-01 19:59:08 +04:00
|
|
|
# define HAVE_MONOTONIC
|
|
|
|
# define TIMER_FREQ 1000000000ULL
|
|
|
|
#elif defined(CONFIG_IA64)
|
|
|
|
# define TIMER_FREQ ((unsigned long long)local_cpu_data->itc_freq)
|
2006-06-26 11:25:09 +04:00
|
|
|
#else
|
2005-05-01 19:59:08 +04:00
|
|
|
# define TIMER_FREQ (HZ*loops_per_jiffy)
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef HAVE_MONOTONIC
|
|
|
|
extern unsigned long long monotonic_clock(void);
|
|
|
|
#else
|
|
|
|
static inline unsigned long long monotonic_clock(void)
|
|
|
|
{
|
|
|
|
return get_cycles();
|
|
|
|
}
|
|
|
|
#endif /* HAVE_MONOTONIC */
|
2005-04-17 02:20:36 +04:00
|
|
|
|
|
|
|
|
|
|
|
/* Last time scheduled */
|
|
|
|
static unsigned long long hangcheck_tsc, hangcheck_tsc_margin;
|
|
|
|
|
|
|
|
static void hangcheck_fire(unsigned long);
|
|
|
|
|
2005-09-10 00:10:40 +04:00
|
|
|
static DEFINE_TIMER(hangcheck_ticktock, hangcheck_fire, 0, 0);
|
2005-04-17 02:20:36 +04:00
|
|
|
|
|
|
|
|
|
|
|
static void hangcheck_fire(unsigned long data)
|
|
|
|
{
|
|
|
|
unsigned long long cur_tsc, tsc_diff;
|
|
|
|
|
|
|
|
cur_tsc = monotonic_clock();
|
|
|
|
|
|
|
|
if (cur_tsc > hangcheck_tsc)
|
|
|
|
tsc_diff = cur_tsc - hangcheck_tsc;
|
|
|
|
else
|
|
|
|
tsc_diff = (cur_tsc + (~0ULL - hangcheck_tsc)); /* or something */
|
|
|
|
|
|
|
|
if (tsc_diff > hangcheck_tsc_margin) {
|
2005-05-01 19:59:08 +04:00
|
|
|
if (hangcheck_dump_tasks) {
|
|
|
|
printk(KERN_CRIT "Hangcheck: Task state:\n");
|
|
|
|
#ifdef CONFIG_MAGIC_SYSRQ
|
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers
Maintain a per-CPU global "struct pt_regs *" variable which can be used instead
of passing regs around manually through all ~1800 interrupt handlers in the
Linux kernel.
The regs pointer is used in few places, but it potentially costs both stack
space and code to pass it around. On the FRV arch, removing the regs parameter
from all the genirq function results in a 20% speed up of the IRQ exit path
(ie: from leaving timer_interrupt() to leaving do_IRQ()).
Where appropriate, an arch may override the generic storage facility and do
something different with the variable. On FRV, for instance, the address is
maintained in GR28 at all times inside the kernel as part of general exception
handling.
Having looked over the code, it appears that the parameter may be handed down
through up to twenty or so layers of functions. Consider a USB character
device attached to a USB hub, attached to a USB controller that posts its
interrupts through a cascaded auxiliary interrupt controller. A character
device driver may want to pass regs to the sysrq handler through the input
layer which adds another few layers of parameter passing.
I've build this code with allyesconfig for x86_64 and i386. I've runtested the
main part of the code on FRV and i386, though I can't test most of the drivers.
I've also done partial conversion for powerpc and MIPS - these at least compile
with minimal configurations.
This will affect all archs. Mostly the changes should be relatively easy.
Take do_IRQ(), store the regs pointer at the beginning, saving the old one:
struct pt_regs *old_regs = set_irq_regs(regs);
And put the old one back at the end:
set_irq_regs(old_regs);
Don't pass regs through to generic_handle_irq() or __do_IRQ().
In timer_interrupt(), this sort of change will be necessary:
- update_process_times(user_mode(regs));
- profile_tick(CPU_PROFILING, regs);
+ update_process_times(user_mode(get_irq_regs()));
+ profile_tick(CPU_PROFILING);
I'd like to move update_process_times()'s use of get_irq_regs() into itself,
except that i386, alone of the archs, uses something other than user_mode().
Some notes on the interrupt handling in the drivers:
(*) input_dev() is now gone entirely. The regs pointer is no longer stored in
the input_dev struct.
(*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does
something different depending on whether it's been supplied with a regs
pointer or not.
(*) Various IRQ handler function pointers have been moved to type
irq_handler_t.
Signed-Off-By: David Howells <dhowells@redhat.com>
(cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2006-10-05 17:55:46 +04:00
|
|
|
handle_sysrq('t', NULL);
|
2005-05-01 19:59:08 +04:00
|
|
|
#endif /* CONFIG_MAGIC_SYSRQ */
|
|
|
|
}
|
2005-04-17 02:20:36 +04:00
|
|
|
if (hangcheck_reboot) {
|
|
|
|
printk(KERN_CRIT "Hangcheck: hangcheck is restarting the machine.\n");
|
2005-07-26 21:55:59 +04:00
|
|
|
emergency_restart();
|
2005-04-17 02:20:36 +04:00
|
|
|
} else {
|
|
|
|
printk(KERN_CRIT "Hangcheck: hangcheck value past margin!\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
mod_timer(&hangcheck_ticktock, jiffies + (hangcheck_tick*HZ));
|
|
|
|
hangcheck_tsc = monotonic_clock();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int __init hangcheck_init(void)
|
|
|
|
{
|
|
|
|
printk("Hangcheck: starting hangcheck timer %s (tick is %d seconds, margin is %d seconds).\n",
|
|
|
|
VERSION_STR, hangcheck_tick, hangcheck_margin);
|
2005-05-01 19:59:08 +04:00
|
|
|
#if defined (HAVE_MONOTONIC)
|
|
|
|
printk("Hangcheck: Using monotonic_clock().\n");
|
|
|
|
#else
|
|
|
|
printk("Hangcheck: Using get_cycles().\n");
|
|
|
|
#endif /* HAVE_MONOTONIC */
|
|
|
|
hangcheck_tsc_margin =
|
|
|
|
(unsigned long long)(hangcheck_margin + hangcheck_tick);
|
|
|
|
hangcheck_tsc_margin *= (unsigned long long)TIMER_FREQ;
|
2005-04-17 02:20:36 +04:00
|
|
|
|
|
|
|
hangcheck_tsc = monotonic_clock();
|
|
|
|
mod_timer(&hangcheck_ticktock, jiffies + (hangcheck_tick*HZ));
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void __exit hangcheck_exit(void)
|
|
|
|
{
|
|
|
|
del_timer_sync(&hangcheck_ticktock);
|
2005-05-01 19:59:08 +04:00
|
|
|
printk("Hangcheck: Stopped hangcheck timer.\n");
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
module_init(hangcheck_init);
|
|
|
|
module_exit(hangcheck_exit);
|