![Martin Schwidefsky](/assets/img/avatar_default.png)
A machine check can interrupt the i/o and external interrupt handler anytime. If the machine check occurs while the interrupt handler is waking up from idle vtime_start_cpu can get executed a second time and the int_clock / async_enter_timer values in the lowcore get clobbered. This can confuse the cpu time accounting. To fix this problem two changes are needed. First the machine check handler has to use its own copies of int_clock and async_enter_timer, named mcck_clock and mcck_enter_timer. Second the nested execution of vtime_start_cpu has to be prevented. This is done in s390_idle_check by checking the wait bit in the program status word. Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
143 lines
3.5 KiB
C
143 lines
3.5 KiB
C
/*
|
|
* arch/s390/kernel/s390_ext.c
|
|
*
|
|
* S390 version
|
|
* Copyright (C) 1999,2000 IBM Deutschland Entwicklung GmbH, IBM Corporation
|
|
* Author(s): Holger Smolinski (Holger.Smolinski@de.ibm.com),
|
|
* Martin Schwidefsky (schwidefsky@de.ibm.com)
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/ftrace.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/kernel_stat.h>
|
|
#include <linux/interrupt.h>
|
|
#include <asm/cputime.h>
|
|
#include <asm/lowcore.h>
|
|
#include <asm/s390_ext.h>
|
|
#include <asm/irq_regs.h>
|
|
#include <asm/irq.h>
|
|
#include "entry.h"
|
|
|
|
/*
|
|
* ext_int_hash[index] is the start of the list for all external interrupts
|
|
* that hash to this index. With the current set of external interrupts
|
|
* (0x1202 external call, 0x1004 cpu timer, 0x2401 hwc console, 0x4000
|
|
* iucv and 0x2603 pfault) this is always the first element.
|
|
*/
|
|
ext_int_info_t *ext_int_hash[256] = { NULL, };
|
|
|
|
static inline int ext_hash(__u16 code)
|
|
{
|
|
return (code + (code >> 9)) & 0xff;
|
|
}
|
|
|
|
int register_external_interrupt(__u16 code, ext_int_handler_t handler)
|
|
{
|
|
ext_int_info_t *p;
|
|
int index;
|
|
|
|
p = kmalloc(sizeof(ext_int_info_t), GFP_ATOMIC);
|
|
if (p == NULL)
|
|
return -ENOMEM;
|
|
p->code = code;
|
|
p->handler = handler;
|
|
index = ext_hash(code);
|
|
p->next = ext_int_hash[index];
|
|
ext_int_hash[index] = p;
|
|
return 0;
|
|
}
|
|
|
|
int register_early_external_interrupt(__u16 code, ext_int_handler_t handler,
|
|
ext_int_info_t *p)
|
|
{
|
|
int index;
|
|
|
|
if (p == NULL)
|
|
return -EINVAL;
|
|
p->code = code;
|
|
p->handler = handler;
|
|
index = ext_hash(code);
|
|
p->next = ext_int_hash[index];
|
|
ext_int_hash[index] = p;
|
|
return 0;
|
|
}
|
|
|
|
int unregister_external_interrupt(__u16 code, ext_int_handler_t handler)
|
|
{
|
|
ext_int_info_t *p, *q;
|
|
int index;
|
|
|
|
index = ext_hash(code);
|
|
q = NULL;
|
|
p = ext_int_hash[index];
|
|
while (p != NULL) {
|
|
if (p->code == code && p->handler == handler)
|
|
break;
|
|
q = p;
|
|
p = p->next;
|
|
}
|
|
if (p == NULL)
|
|
return -ENOENT;
|
|
if (q != NULL)
|
|
q->next = p->next;
|
|
else
|
|
ext_int_hash[index] = p->next;
|
|
kfree(p);
|
|
return 0;
|
|
}
|
|
|
|
int unregister_early_external_interrupt(__u16 code, ext_int_handler_t handler,
|
|
ext_int_info_t *p)
|
|
{
|
|
ext_int_info_t *q;
|
|
int index;
|
|
|
|
if (p == NULL || p->code != code || p->handler != handler)
|
|
return -EINVAL;
|
|
index = ext_hash(code);
|
|
q = ext_int_hash[index];
|
|
if (p != q) {
|
|
while (q != NULL) {
|
|
if (q->next == p)
|
|
break;
|
|
q = q->next;
|
|
}
|
|
if (q == NULL)
|
|
return -ENOENT;
|
|
q->next = p->next;
|
|
} else
|
|
ext_int_hash[index] = p->next;
|
|
return 0;
|
|
}
|
|
|
|
void __irq_entry do_extint(struct pt_regs *regs, unsigned short code)
|
|
{
|
|
ext_int_info_t *p;
|
|
int index;
|
|
struct pt_regs *old_regs;
|
|
|
|
old_regs = set_irq_regs(regs);
|
|
s390_idle_check(regs, S390_lowcore.int_clock,
|
|
S390_lowcore.async_enter_timer);
|
|
irq_enter();
|
|
if (S390_lowcore.int_clock >= S390_lowcore.clock_comparator)
|
|
/* Serve timer interrupts first. */
|
|
clock_comparator_work();
|
|
kstat_cpu(smp_processor_id()).irqs[EXTERNAL_INTERRUPT]++;
|
|
if (code != 0x1004)
|
|
__get_cpu_var(s390_idle).nohz_delay = 1;
|
|
index = ext_hash(code);
|
|
for (p = ext_int_hash[index]; p; p = p->next) {
|
|
if (likely(p->code == code))
|
|
p->handler(code);
|
|
}
|
|
irq_exit();
|
|
set_irq_regs(old_regs);
|
|
}
|
|
|
|
EXPORT_SYMBOL(register_external_interrupt);
|
|
EXPORT_SYMBOL(unregister_external_interrupt);
|