blob: 37f866966c5be8f87f463f247ac31a230ceb908a [file] [log] [blame]
/*
* linux/arch/alpha/kernel/irq_smp.c
*
*/
#include <linux/kernel.h>
#include <linux/signal.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/random.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <asm/system.h>
#include <asm/io.h>
/* Who has global_irq_lock. */
int global_irq_holder = NO_PROC_ID;
/* This protects IRQ's. */
spinlock_t global_irq_lock = SPIN_LOCK_UNLOCKED;
/* Global IRQ locking depth. */
static void *previous_irqholder = NULL;
#define MAXCOUNT 100000000
static void
show(char * str, void *where)
{
#if 0
int i;
unsigned long *stack;
#endif
int cpu = smp_processor_id();
printk("\n%s, CPU %d: %p\n", str, cpu, where);
printk("irq: %d [%d %d]\n",
irqs_running(),
local_irq_count(0),
local_irq_count(1));
printk("bh: %d [%d %d]\n",
spin_is_locked(&global_bh_lock) ? 1 : 0,
local_bh_count(0),
local_bh_count(1));
#if 0
stack = (unsigned long *) &str;
for (i = 40; i ; i--) {
unsigned long x = *++stack;
if (x > (unsigned long) &init_task_union &&
x < (unsigned long) &vsprintf) {
printk("<[%08lx]> ", x);
}
}
#endif
}
static inline void
wait_on_irq(int cpu, void *where)
{
int count = MAXCOUNT;
for (;;) {
/*
* Wait until all interrupts are gone. Wait
* for bottom half handlers unless we're
* already executing in one..
*/
if (!irqs_running()) {
if (local_bh_count(cpu)
|| !spin_is_locked(&global_bh_lock))
break;
}
/* Duh, we have to loop. Release the lock to avoid deadlocks */
spin_unlock(&global_irq_lock);
for (;;) {
if (!--count) {
show("wait_on_irq", where);
count = MAXCOUNT;
}
__sti();
udelay(1); /* make sure to run pending irqs */
__cli();
if (irqs_running())
continue;
if (spin_is_locked(&global_irq_lock))
continue;
if (!local_bh_count(cpu)
&& spin_is_locked(&global_bh_lock))
continue;
if (spin_trylock(&global_irq_lock))
break;
}
}
}
static inline void
get_irqlock(int cpu, void* where)
{
if (!spin_trylock(&global_irq_lock)) {
/* Do we already hold the lock? */
if (cpu == global_irq_holder)
return;
/* Uhhuh.. Somebody else got it. Wait. */
spin_lock(&global_irq_lock);
}
/*
* Ok, we got the lock bit.
* But that's actually just the easy part.. Now
* we need to make sure that nobody else is running
* in an interrupt context.
*/
wait_on_irq(cpu, where);
/*
* Finally.
*/
#ifdef CONFIG_DEBUG_SPINLOCK
global_irq_lock.task = current;
global_irq_lock.previous = where;
#endif
global_irq_holder = cpu;
previous_irqholder = where;
}
void
__global_cli(void)
{
int cpu = smp_processor_id();
void *where = __builtin_return_address(0);
/*
* Maximize ipl. If ipl was previously 0 and if this thread
* is not in an irq, then take global_irq_lock.
*/
if (swpipl(IPL_MAX) == IPL_MIN && !local_irq_count(cpu))
get_irqlock(cpu, where);
}
void
__global_sti(void)
{
int cpu = smp_processor_id();
if (!local_irq_count(cpu))
release_irqlock(cpu);
__sti();
}
/*
* SMP flags value to restore to:
* 0 - global cli
* 1 - global sti
* 2 - local cli
* 3 - local sti
*/
unsigned long
__global_save_flags(void)
{
int retval;
int local_enabled;
unsigned long flags;
int cpu = smp_processor_id();
__save_flags(flags);
local_enabled = (!(flags & 7));
/* default to local */
retval = 2 + local_enabled;
/* Check for global flags if we're not in an interrupt. */
if (!local_irq_count(cpu)) {
if (local_enabled)
retval = 1;
if (global_irq_holder == cpu)
retval = 0;
}
return retval;
}
void
__global_restore_flags(unsigned long flags)
{
switch (flags) {
case 0:
__global_cli();
break;
case 1:
__global_sti();
break;
case 2:
__cli();
break;
case 3:
__sti();
break;
default:
printk(KERN_ERR "global_restore_flags: %08lx (%p)\n",
flags, __builtin_return_address(0));
}
}
/*
* From its use, I infer that synchronize_irq() stalls a thread until
* the effects of a command to an external device are known to have
* taken hold. Typically, the command is to stop sending interrupts.
* The strategy here is wait until there is at most one processor
* (this one) in an irq. The memory barrier serializes the write to
* the device and the subsequent accesses of global_irq_count.
* --jmartin
*/
#define DEBUG_SYNCHRONIZE_IRQ 0
void
synchronize_irq(void)
{
#if 0
/* Joe's version. */
int cpu = smp_processor_id();
int local_count;
int global_count;
int countdown = 1<<24;
void *where = __builtin_return_address(0);
mb();
do {
local_count = local_irq_count(cpu);
global_count = atomic_read(&global_irq_count);
if (DEBUG_SYNCHRONIZE_IRQ && (--countdown == 0)) {
printk("%d:%d/%d\n", cpu, local_count, global_count);
show("synchronize_irq", where);
break;
}
} while (global_count != local_count);
#else
/* Jay's version. */
if (irqs_running()) {
cli();
sti();
}
#endif
}