linux/arch/s390/lib/delay.c
Heiko Carstens d3d238c774 [S390] nohz: Fix __udelay.
This fixes a regression that came with 934b2857cc
("[S390] nohz/sclp: disable timer on synchronous waits.").
If udelay() gets called from a disabled context it sets the clock comparator
to a value where it expects the next interrupt. When the interrupt happens
the clock comparator gets not reset and therefore the interrupt condition
doesn't get cleared. The result is an endless timer interrupt loop.

In addition this patch fixes also the following:

rcutorture reveals that our __udelay implementation is still buggy,
since it might schedule tasklets, but prevents their execution:

NOHZ: local_softirq_pending 42
NOHZ: local_softirq_pending 02
NOHZ: local_softirq_pending 142
NOHZ: local_softirq_pending 02

To fix this we make sure that only the clock comparator interrupt
is enabled when the enabled wait psw is loaded.
Also no code gets called anymore which might schedule tasklets.

Signed-off-by: Heiko Carstens <heiko.carstens@de.ibm.com>
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
2008-10-03 21:55:54 +02:00

95 lines
2.4 KiB
C

/*
* Precise Delay Loops for S390
*
* Copyright IBM Corp. 1999,2008
* Author(s): Martin Schwidefsky <schwidefsky@de.ibm.com>,
* Heiko Carstens <heiko.carstens@de.ibm.com>,
*/
#include <linux/sched.h>
#include <linux/delay.h>
#include <linux/timex.h>
#include <linux/irqflags.h>
#include <linux/interrupt.h>
void __delay(unsigned long loops)
{
/*
* To end the bloody studid and useless discussion about the
* BogoMips number I took the liberty to define the __delay
* function in a way that that resulting BogoMips number will
* yield the megahertz number of the cpu. The important function
* is udelay and that is done using the tod clock. -- martin.
*/
asm volatile("0: brct %0,0b" : : "d" ((loops/2) + 1));
}
static void __udelay_disabled(unsigned long usecs)
{
unsigned long mask, cr0, cr0_saved;
u64 clock_saved;
clock_saved = local_tick_disable();
set_clock_comparator(get_clock() + ((u64) usecs << 12));
__ctl_store(cr0_saved, 0, 0);
cr0 = (cr0_saved & 0xffff00e0) | 0x00000800;
__ctl_load(cr0 , 0, 0);
mask = psw_kernel_bits | PSW_MASK_WAIT | PSW_MASK_EXT;
trace_hardirqs_on();
__load_psw_mask(mask);
local_irq_disable();
__ctl_load(cr0_saved, 0, 0);
local_tick_enable(clock_saved);
set_clock_comparator(S390_lowcore.clock_comparator);
}
static void __udelay_enabled(unsigned long usecs)
{
unsigned long mask;
u64 end, time;
mask = psw_kernel_bits | PSW_MASK_WAIT | PSW_MASK_EXT | PSW_MASK_IO;
end = get_clock() + ((u64) usecs << 12);
do {
time = end < S390_lowcore.clock_comparator ?
end : S390_lowcore.clock_comparator;
set_clock_comparator(time);
trace_hardirqs_on();
__load_psw_mask(mask);
local_irq_disable();
} while (get_clock() < end);
set_clock_comparator(S390_lowcore.clock_comparator);
}
/*
* Waits for 'usecs' microseconds using the TOD clock comparator.
*/
void __udelay(unsigned long usecs)
{
unsigned long flags;
preempt_disable();
local_irq_save(flags);
if (in_irq()) {
__udelay_disabled(usecs);
goto out;
}
if (in_softirq()) {
if (raw_irqs_disabled_flags(flags))
__udelay_disabled(usecs);
else
__udelay_enabled(usecs);
goto out;
}
if (raw_irqs_disabled_flags(flags)) {
local_bh_disable();
__udelay_disabled(usecs);
_local_bh_enable();
goto out;
}
__udelay_enabled(usecs);
out:
local_irq_restore(flags);
preempt_enable();
}