powerpc: Add NMI IPI infrastructure
Add a simple NMI IPI system that handles concurrency and reentrancy. The platform does not have to implement a true non-maskable interrupt, the default is to simply use the debugger break IPI message. This has now been co-opted for a general IPI message, and users (debugger and crash) have been reimplemented on top of the NMI system. Signed-off-by: Nicholas Piggin <npiggin@gmail.com> [mpe: Incorporate incremental fixes from Nick] Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
This commit is contained in:
parent
2b4f3ac564
commit
ddd703ca06
@ -80,6 +80,11 @@ config NR_IRQS
|
|||||||
/proc/interrupts. If you configure your system to have too few,
|
/proc/interrupts. If you configure your system to have too few,
|
||||||
drivers will fail to load or worse - handle with care.
|
drivers will fail to load or worse - handle with care.
|
||||||
|
|
||||||
|
config NMI_IPI
|
||||||
|
bool
|
||||||
|
depends on SMP && (DEBUGGER || KEXEC_CORE)
|
||||||
|
default y
|
||||||
|
|
||||||
config STACKTRACE_SUPPORT
|
config STACKTRACE_SUPPORT
|
||||||
bool
|
bool
|
||||||
default y
|
default y
|
||||||
|
@ -112,14 +112,22 @@ extern int cpu_to_core_id(int cpu);
|
|||||||
*
|
*
|
||||||
* Make sure this matches openpic_request_IPIs in open_pic.c, or what shows up
|
* Make sure this matches openpic_request_IPIs in open_pic.c, or what shows up
|
||||||
* in /proc/interrupts will be wrong!!! --Troy */
|
* in /proc/interrupts will be wrong!!! --Troy */
|
||||||
#define PPC_MSG_CALL_FUNCTION 0
|
#define PPC_MSG_CALL_FUNCTION 0
|
||||||
#define PPC_MSG_RESCHEDULE 1
|
#define PPC_MSG_RESCHEDULE 1
|
||||||
#define PPC_MSG_TICK_BROADCAST 2
|
#define PPC_MSG_TICK_BROADCAST 2
|
||||||
#define PPC_MSG_DEBUGGER_BREAK 3
|
#define PPC_MSG_NMI_IPI 3
|
||||||
|
|
||||||
/* This is only used by the powernv kernel */
|
/* This is only used by the powernv kernel */
|
||||||
#define PPC_MSG_RM_HOST_ACTION 4
|
#define PPC_MSG_RM_HOST_ACTION 4
|
||||||
|
|
||||||
|
#define NMI_IPI_ALL_OTHERS -2
|
||||||
|
|
||||||
|
#ifdef CONFIG_NMI_IPI
|
||||||
|
extern int smp_handle_nmi_ipi(struct pt_regs *regs);
|
||||||
|
#else
|
||||||
|
static inline int smp_handle_nmi_ipi(struct pt_regs *regs) { return 0; }
|
||||||
|
#endif
|
||||||
|
|
||||||
/* for irq controllers that have dedicated ipis per message (4) */
|
/* for irq controllers that have dedicated ipis per message (4) */
|
||||||
extern int smp_request_message_ipi(int virq, int message);
|
extern int smp_request_message_ipi(int virq, int message);
|
||||||
extern const char *smp_ipi_name[];
|
extern const char *smp_ipi_name[];
|
||||||
|
@ -87,8 +87,6 @@ volatile unsigned int cpu_callin_map[NR_CPUS];
|
|||||||
|
|
||||||
int smt_enabled_at_boot = 1;
|
int smt_enabled_at_boot = 1;
|
||||||
|
|
||||||
static void (*crash_ipi_function_ptr)(struct pt_regs *) = NULL;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Returns 1 if the specified cpu should be brought up during boot.
|
* Returns 1 if the specified cpu should be brought up during boot.
|
||||||
* Used to inhibit booting threads if they've been disabled or
|
* Used to inhibit booting threads if they've been disabled or
|
||||||
@ -159,32 +157,33 @@ static irqreturn_t tick_broadcast_ipi_action(int irq, void *data)
|
|||||||
return IRQ_HANDLED;
|
return IRQ_HANDLED;
|
||||||
}
|
}
|
||||||
|
|
||||||
static irqreturn_t debug_ipi_action(int irq, void *data)
|
#ifdef CONFIG_NMI_IPI
|
||||||
|
static irqreturn_t nmi_ipi_action(int irq, void *data)
|
||||||
{
|
{
|
||||||
if (crash_ipi_function_ptr) {
|
smp_handle_nmi_ipi(get_irq_regs());
|
||||||
crash_ipi_function_ptr(get_irq_regs());
|
|
||||||
return IRQ_HANDLED;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef CONFIG_DEBUGGER
|
|
||||||
debugger_ipi(get_irq_regs());
|
|
||||||
#endif /* CONFIG_DEBUGGER */
|
|
||||||
|
|
||||||
return IRQ_HANDLED;
|
return IRQ_HANDLED;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
static irq_handler_t smp_ipi_action[] = {
|
static irq_handler_t smp_ipi_action[] = {
|
||||||
[PPC_MSG_CALL_FUNCTION] = call_function_action,
|
[PPC_MSG_CALL_FUNCTION] = call_function_action,
|
||||||
[PPC_MSG_RESCHEDULE] = reschedule_action,
|
[PPC_MSG_RESCHEDULE] = reschedule_action,
|
||||||
[PPC_MSG_TICK_BROADCAST] = tick_broadcast_ipi_action,
|
[PPC_MSG_TICK_BROADCAST] = tick_broadcast_ipi_action,
|
||||||
[PPC_MSG_DEBUGGER_BREAK] = debug_ipi_action,
|
#ifdef CONFIG_NMI_IPI
|
||||||
|
[PPC_MSG_NMI_IPI] = nmi_ipi_action,
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The NMI IPI is a fallback and not truly non-maskable. It is simpler
|
||||||
|
* than going through the call function infrastructure, and strongly
|
||||||
|
* serialized, so it is more appropriate for debugging.
|
||||||
|
*/
|
||||||
const char *smp_ipi_name[] = {
|
const char *smp_ipi_name[] = {
|
||||||
[PPC_MSG_CALL_FUNCTION] = "ipi call function",
|
[PPC_MSG_CALL_FUNCTION] = "ipi call function",
|
||||||
[PPC_MSG_RESCHEDULE] = "ipi reschedule",
|
[PPC_MSG_RESCHEDULE] = "ipi reschedule",
|
||||||
[PPC_MSG_TICK_BROADCAST] = "ipi tick-broadcast",
|
[PPC_MSG_TICK_BROADCAST] = "ipi tick-broadcast",
|
||||||
[PPC_MSG_DEBUGGER_BREAK] = "ipi debugger",
|
[PPC_MSG_NMI_IPI] = "nmi ipi",
|
||||||
};
|
};
|
||||||
|
|
||||||
/* optional function to request ipi, for controllers with >= 4 ipis */
|
/* optional function to request ipi, for controllers with >= 4 ipis */
|
||||||
@ -192,14 +191,13 @@ int smp_request_message_ipi(int virq, int msg)
|
|||||||
{
|
{
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
if (msg < 0 || msg > PPC_MSG_DEBUGGER_BREAK) {
|
if (msg < 0 || msg > PPC_MSG_NMI_IPI)
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
#ifndef CONFIG_NMI_IPI
|
||||||
#if !defined(CONFIG_DEBUGGER) && !defined(CONFIG_KEXEC_CORE)
|
if (msg == PPC_MSG_NMI_IPI)
|
||||||
if (msg == PPC_MSG_DEBUGGER_BREAK) {
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
err = request_irq(virq, smp_ipi_action[msg],
|
err = request_irq(virq, smp_ipi_action[msg],
|
||||||
IRQF_PERCPU | IRQF_NO_THREAD | IRQF_NO_SUSPEND,
|
IRQF_PERCPU | IRQF_NO_THREAD | IRQF_NO_SUSPEND,
|
||||||
smp_ipi_name[msg], NULL);
|
smp_ipi_name[msg], NULL);
|
||||||
@ -277,8 +275,10 @@ irqreturn_t smp_ipi_demux_relaxed(void)
|
|||||||
scheduler_ipi();
|
scheduler_ipi();
|
||||||
if (all & IPI_MESSAGE(PPC_MSG_TICK_BROADCAST))
|
if (all & IPI_MESSAGE(PPC_MSG_TICK_BROADCAST))
|
||||||
tick_broadcast_ipi_handler();
|
tick_broadcast_ipi_handler();
|
||||||
if (all & IPI_MESSAGE(PPC_MSG_DEBUGGER_BREAK))
|
#ifdef CONFIG_NMI_IPI
|
||||||
debug_ipi_action(0, NULL);
|
if (all & IPI_MESSAGE(PPC_MSG_NMI_IPI))
|
||||||
|
nmi_ipi_action(0, NULL);
|
||||||
|
#endif
|
||||||
} while (info->messages);
|
} while (info->messages);
|
||||||
|
|
||||||
return IRQ_HANDLED;
|
return IRQ_HANDLED;
|
||||||
@ -315,6 +315,184 @@ void arch_send_call_function_ipi_mask(const struct cpumask *mask)
|
|||||||
do_message_pass(cpu, PPC_MSG_CALL_FUNCTION);
|
do_message_pass(cpu, PPC_MSG_CALL_FUNCTION);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef CONFIG_NMI_IPI
|
||||||
|
|
||||||
|
/*
|
||||||
|
* "NMI IPI" system.
|
||||||
|
*
|
||||||
|
* NMI IPIs may not be recoverable, so should not be used as ongoing part of
|
||||||
|
* a running system. They can be used for crash, debug, halt/reboot, etc.
|
||||||
|
*
|
||||||
|
* NMI IPIs are globally single threaded. No more than one in progress at
|
||||||
|
* any time.
|
||||||
|
*
|
||||||
|
* The IPI call waits with interrupts disabled until all targets enter the
|
||||||
|
* NMI handler, then the call returns.
|
||||||
|
*
|
||||||
|
* No new NMI can be initiated until targets exit the handler.
|
||||||
|
*
|
||||||
|
* The IPI call may time out without all targets entering the NMI handler.
|
||||||
|
* In that case, there is some logic to recover (and ignore subsequent
|
||||||
|
* NMI interrupts that may eventually be raised), but the platform interrupt
|
||||||
|
* handler may not be able to distinguish this from other exception causes,
|
||||||
|
* which may cause a crash.
|
||||||
|
*/
|
||||||
|
|
||||||
|
static atomic_t __nmi_ipi_lock = ATOMIC_INIT(0);
|
||||||
|
static struct cpumask nmi_ipi_pending_mask;
|
||||||
|
static int nmi_ipi_busy_count = 0;
|
||||||
|
static void (*nmi_ipi_function)(struct pt_regs *) = NULL;
|
||||||
|
|
||||||
|
static void nmi_ipi_lock_start(unsigned long *flags)
|
||||||
|
{
|
||||||
|
raw_local_irq_save(*flags);
|
||||||
|
hard_irq_disable();
|
||||||
|
while (atomic_cmpxchg(&__nmi_ipi_lock, 0, 1) == 1) {
|
||||||
|
raw_local_irq_restore(*flags);
|
||||||
|
cpu_relax();
|
||||||
|
raw_local_irq_save(*flags);
|
||||||
|
hard_irq_disable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nmi_ipi_lock(void)
|
||||||
|
{
|
||||||
|
while (atomic_cmpxchg(&__nmi_ipi_lock, 0, 1) == 1)
|
||||||
|
cpu_relax();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nmi_ipi_unlock(void)
|
||||||
|
{
|
||||||
|
smp_mb();
|
||||||
|
WARN_ON(atomic_read(&__nmi_ipi_lock) != 1);
|
||||||
|
atomic_set(&__nmi_ipi_lock, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nmi_ipi_unlock_end(unsigned long *flags)
|
||||||
|
{
|
||||||
|
nmi_ipi_unlock();
|
||||||
|
raw_local_irq_restore(*flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Platform NMI handler calls this to ack
|
||||||
|
*/
|
||||||
|
int smp_handle_nmi_ipi(struct pt_regs *regs)
|
||||||
|
{
|
||||||
|
void (*fn)(struct pt_regs *);
|
||||||
|
unsigned long flags;
|
||||||
|
int me = raw_smp_processor_id();
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Unexpected NMIs are possible here because the interrupt may not
|
||||||
|
* be able to distinguish NMI IPIs from other types of NMIs, or
|
||||||
|
* because the caller may have timed out.
|
||||||
|
*/
|
||||||
|
nmi_ipi_lock_start(&flags);
|
||||||
|
if (!nmi_ipi_busy_count)
|
||||||
|
goto out;
|
||||||
|
if (!cpumask_test_cpu(me, &nmi_ipi_pending_mask))
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
fn = nmi_ipi_function;
|
||||||
|
if (!fn)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
cpumask_clear_cpu(me, &nmi_ipi_pending_mask);
|
||||||
|
nmi_ipi_busy_count++;
|
||||||
|
nmi_ipi_unlock();
|
||||||
|
|
||||||
|
ret = 1;
|
||||||
|
|
||||||
|
fn(regs);
|
||||||
|
|
||||||
|
nmi_ipi_lock();
|
||||||
|
nmi_ipi_busy_count--;
|
||||||
|
out:
|
||||||
|
nmi_ipi_unlock_end(&flags);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void do_smp_send_nmi_ipi(int cpu)
|
||||||
|
{
|
||||||
|
if (cpu >= 0) {
|
||||||
|
do_message_pass(cpu, PPC_MSG_NMI_IPI);
|
||||||
|
} else {
|
||||||
|
int c;
|
||||||
|
|
||||||
|
for_each_online_cpu(c) {
|
||||||
|
if (c == raw_smp_processor_id())
|
||||||
|
continue;
|
||||||
|
do_message_pass(c, PPC_MSG_NMI_IPI);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* - cpu is the target CPU (must not be this CPU), or NMI_IPI_ALL_OTHERS.
|
||||||
|
* - fn is the target callback function.
|
||||||
|
* - delay_us > 0 is the delay before giving up waiting for targets to
|
||||||
|
* enter the handler, == 0 specifies indefinite delay.
|
||||||
|
*/
|
||||||
|
static int smp_send_nmi_ipi(int cpu, void (*fn)(struct pt_regs *), u64 delay_us)
|
||||||
|
{
|
||||||
|
unsigned long flags;
|
||||||
|
int me = raw_smp_processor_id();
|
||||||
|
int ret = 1;
|
||||||
|
|
||||||
|
BUG_ON(cpu == me);
|
||||||
|
BUG_ON(cpu < 0 && cpu != NMI_IPI_ALL_OTHERS);
|
||||||
|
|
||||||
|
if (unlikely(!smp_ops))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* Take the nmi_ipi_busy count/lock with interrupts hard disabled */
|
||||||
|
nmi_ipi_lock_start(&flags);
|
||||||
|
while (nmi_ipi_busy_count) {
|
||||||
|
nmi_ipi_unlock_end(&flags);
|
||||||
|
cpu_relax();
|
||||||
|
nmi_ipi_lock_start(&flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
nmi_ipi_function = fn;
|
||||||
|
|
||||||
|
if (cpu < 0) {
|
||||||
|
/* ALL_OTHERS */
|
||||||
|
cpumask_copy(&nmi_ipi_pending_mask, cpu_online_mask);
|
||||||
|
cpumask_clear_cpu(me, &nmi_ipi_pending_mask);
|
||||||
|
} else {
|
||||||
|
/* cpumask starts clear */
|
||||||
|
cpumask_set_cpu(cpu, &nmi_ipi_pending_mask);
|
||||||
|
}
|
||||||
|
nmi_ipi_busy_count++;
|
||||||
|
nmi_ipi_unlock();
|
||||||
|
|
||||||
|
do_smp_send_nmi_ipi(cpu);
|
||||||
|
|
||||||
|
while (!cpumask_empty(&nmi_ipi_pending_mask)) {
|
||||||
|
udelay(1);
|
||||||
|
if (delay_us) {
|
||||||
|
delay_us--;
|
||||||
|
if (!delay_us)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nmi_ipi_lock();
|
||||||
|
if (!cpumask_empty(&nmi_ipi_pending_mask)) {
|
||||||
|
/* Could not gather all CPUs */
|
||||||
|
ret = 0;
|
||||||
|
cpumask_clear(&nmi_ipi_pending_mask);
|
||||||
|
}
|
||||||
|
nmi_ipi_busy_count--;
|
||||||
|
nmi_ipi_unlock_end(&flags);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
#endif /* CONFIG_NMI_IPI */
|
||||||
|
|
||||||
#ifdef CONFIG_GENERIC_CLOCKEVENTS_BROADCAST
|
#ifdef CONFIG_GENERIC_CLOCKEVENTS_BROADCAST
|
||||||
void tick_broadcast(const struct cpumask *mask)
|
void tick_broadcast(const struct cpumask *mask)
|
||||||
{
|
{
|
||||||
@ -325,29 +503,22 @@ void tick_broadcast(const struct cpumask *mask)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(CONFIG_DEBUGGER) || defined(CONFIG_KEXEC_CORE)
|
#ifdef CONFIG_DEBUGGER
|
||||||
|
void debugger_ipi_callback(struct pt_regs *regs)
|
||||||
|
{
|
||||||
|
debugger_ipi(regs);
|
||||||
|
}
|
||||||
|
|
||||||
void smp_send_debugger_break(void)
|
void smp_send_debugger_break(void)
|
||||||
{
|
{
|
||||||
int cpu;
|
smp_send_nmi_ipi(NMI_IPI_ALL_OTHERS, debugger_ipi_callback, 1000000);
|
||||||
int me = raw_smp_processor_id();
|
|
||||||
|
|
||||||
if (unlikely(!smp_ops))
|
|
||||||
return;
|
|
||||||
|
|
||||||
for_each_online_cpu(cpu)
|
|
||||||
if (cpu != me)
|
|
||||||
do_message_pass(cpu, PPC_MSG_DEBUGGER_BREAK);
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef CONFIG_KEXEC_CORE
|
#ifdef CONFIG_KEXEC_CORE
|
||||||
void crash_send_ipi(void (*crash_ipi_callback)(struct pt_regs *))
|
void crash_send_ipi(void (*crash_ipi_callback)(struct pt_regs *))
|
||||||
{
|
{
|
||||||
crash_ipi_function_ptr = crash_ipi_callback;
|
smp_send_nmi_ipi(NMI_IPI_ALL_OTHERS, crash_ipi_callback, 1000000);
|
||||||
if (crash_ipi_callback) {
|
|
||||||
mb();
|
|
||||||
smp_send_debugger_break();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -211,7 +211,7 @@ void iic_request_IPIs(void)
|
|||||||
iic_request_ipi(PPC_MSG_CALL_FUNCTION);
|
iic_request_ipi(PPC_MSG_CALL_FUNCTION);
|
||||||
iic_request_ipi(PPC_MSG_RESCHEDULE);
|
iic_request_ipi(PPC_MSG_RESCHEDULE);
|
||||||
iic_request_ipi(PPC_MSG_TICK_BROADCAST);
|
iic_request_ipi(PPC_MSG_TICK_BROADCAST);
|
||||||
iic_request_ipi(PPC_MSG_DEBUGGER_BREAK);
|
iic_request_ipi(PPC_MSG_NMI_IPI);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* CONFIG_SMP */
|
#endif /* CONFIG_SMP */
|
||||||
|
@ -77,7 +77,7 @@ static void __init ps3_smp_probe(void)
|
|||||||
BUILD_BUG_ON(PPC_MSG_CALL_FUNCTION != 0);
|
BUILD_BUG_ON(PPC_MSG_CALL_FUNCTION != 0);
|
||||||
BUILD_BUG_ON(PPC_MSG_RESCHEDULE != 1);
|
BUILD_BUG_ON(PPC_MSG_RESCHEDULE != 1);
|
||||||
BUILD_BUG_ON(PPC_MSG_TICK_BROADCAST != 2);
|
BUILD_BUG_ON(PPC_MSG_TICK_BROADCAST != 2);
|
||||||
BUILD_BUG_ON(PPC_MSG_DEBUGGER_BREAK != 3);
|
BUILD_BUG_ON(PPC_MSG_NMI_IPI != 3);
|
||||||
|
|
||||||
for (i = 0; i < MSG_COUNT; i++) {
|
for (i = 0; i < MSG_COUNT; i++) {
|
||||||
result = ps3_event_receive_port_setup(cpu, &virqs[i]);
|
result = ps3_event_receive_port_setup(cpu, &virqs[i]);
|
||||||
@ -96,7 +96,7 @@ static void __init ps3_smp_probe(void)
|
|||||||
ps3_register_ipi_irq(cpu, virqs[i]);
|
ps3_register_ipi_irq(cpu, virqs[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
ps3_register_ipi_debug_brk(cpu, virqs[PPC_MSG_DEBUGGER_BREAK]);
|
ps3_register_ipi_debug_brk(cpu, virqs[PPC_MSG_NMI_IPI]);
|
||||||
|
|
||||||
DBG(" <- %s:%d: (%d)\n", __func__, __LINE__, cpu);
|
DBG(" <- %s:%d: (%d)\n", __func__, __LINE__, cpu);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user