958d24df82
The mpic interrupt controller driver (used on G5 and early pSeries among others) has a bug where it doesn't get the right virtual address for the timer registers. It causes the driver to poke at the MMIO space of whatever has been mapped just next to it (ouch !) when initializing and causes boot failures on some IBM machines. Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
889 lines
23 KiB
C
889 lines
23 KiB
C
/*
|
|
* arch/ppc64/kernel/mpic.c
|
|
*
|
|
* Driver for interrupt controllers following the OpenPIC standard, the
|
|
* common implementation beeing IBM's MPIC. This driver also can deal
|
|
* with various broken implementations of this HW.
|
|
*
|
|
* Copyright (C) 2004 Benjamin Herrenschmidt, IBM Corp.
|
|
*
|
|
* This file is subject to the terms and conditions of the GNU General Public
|
|
* License. See the file COPYING in the main directory of this archive
|
|
* for more details.
|
|
*/
|
|
|
|
#undef DEBUG
|
|
|
|
#include <linux/config.h>
|
|
#include <linux/types.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/init.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/smp.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/bootmem.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/pci.h>
|
|
|
|
#include <asm/ptrace.h>
|
|
#include <asm/signal.h>
|
|
#include <asm/io.h>
|
|
#include <asm/pgtable.h>
|
|
#include <asm/irq.h>
|
|
#include <asm/machdep.h>
|
|
|
|
#include "mpic.h"
|
|
|
|
#ifdef DEBUG
|
|
#define DBG(fmt...) printk(fmt)
|
|
#else
|
|
#define DBG(fmt...)
|
|
#endif
|
|
|
|
static struct mpic *mpics;
|
|
static struct mpic *mpic_primary;
|
|
static DEFINE_SPINLOCK(mpic_lock);
|
|
|
|
|
|
/*
|
|
* Register accessor functions
|
|
*/
|
|
|
|
|
|
static inline u32 _mpic_read(unsigned int be, volatile u32 __iomem *base,
|
|
unsigned int reg)
|
|
{
|
|
if (be)
|
|
return in_be32(base + (reg >> 2));
|
|
else
|
|
return in_le32(base + (reg >> 2));
|
|
}
|
|
|
|
static inline void _mpic_write(unsigned int be, volatile u32 __iomem *base,
|
|
unsigned int reg, u32 value)
|
|
{
|
|
if (be)
|
|
out_be32(base + (reg >> 2), value);
|
|
else
|
|
out_le32(base + (reg >> 2), value);
|
|
}
|
|
|
|
static inline u32 _mpic_ipi_read(struct mpic *mpic, unsigned int ipi)
|
|
{
|
|
unsigned int be = (mpic->flags & MPIC_BIG_ENDIAN) != 0;
|
|
unsigned int offset = MPIC_GREG_IPI_VECTOR_PRI_0 + (ipi * 0x10);
|
|
|
|
if (mpic->flags & MPIC_BROKEN_IPI)
|
|
be = !be;
|
|
return _mpic_read(be, mpic->gregs, offset);
|
|
}
|
|
|
|
static inline void _mpic_ipi_write(struct mpic *mpic, unsigned int ipi, u32 value)
|
|
{
|
|
unsigned int offset = MPIC_GREG_IPI_VECTOR_PRI_0 + (ipi * 0x10);
|
|
|
|
_mpic_write(mpic->flags & MPIC_BIG_ENDIAN, mpic->gregs, offset, value);
|
|
}
|
|
|
|
static inline u32 _mpic_cpu_read(struct mpic *mpic, unsigned int reg)
|
|
{
|
|
unsigned int cpu = 0;
|
|
|
|
if (mpic->flags & MPIC_PRIMARY)
|
|
cpu = hard_smp_processor_id();
|
|
|
|
return _mpic_read(mpic->flags & MPIC_BIG_ENDIAN, mpic->cpuregs[cpu], reg);
|
|
}
|
|
|
|
static inline void _mpic_cpu_write(struct mpic *mpic, unsigned int reg, u32 value)
|
|
{
|
|
unsigned int cpu = 0;
|
|
|
|
if (mpic->flags & MPIC_PRIMARY)
|
|
cpu = hard_smp_processor_id();
|
|
|
|
_mpic_write(mpic->flags & MPIC_BIG_ENDIAN, mpic->cpuregs[cpu], reg, value);
|
|
}
|
|
|
|
static inline u32 _mpic_irq_read(struct mpic *mpic, unsigned int src_no, unsigned int reg)
|
|
{
|
|
unsigned int isu = src_no >> mpic->isu_shift;
|
|
unsigned int idx = src_no & mpic->isu_mask;
|
|
|
|
return _mpic_read(mpic->flags & MPIC_BIG_ENDIAN, mpic->isus[isu],
|
|
reg + (idx * MPIC_IRQ_STRIDE));
|
|
}
|
|
|
|
static inline void _mpic_irq_write(struct mpic *mpic, unsigned int src_no,
|
|
unsigned int reg, u32 value)
|
|
{
|
|
unsigned int isu = src_no >> mpic->isu_shift;
|
|
unsigned int idx = src_no & mpic->isu_mask;
|
|
|
|
_mpic_write(mpic->flags & MPIC_BIG_ENDIAN, mpic->isus[isu],
|
|
reg + (idx * MPIC_IRQ_STRIDE), value);
|
|
}
|
|
|
|
#define mpic_read(b,r) _mpic_read(mpic->flags & MPIC_BIG_ENDIAN,(b),(r))
|
|
#define mpic_write(b,r,v) _mpic_write(mpic->flags & MPIC_BIG_ENDIAN,(b),(r),(v))
|
|
#define mpic_ipi_read(i) _mpic_ipi_read(mpic,(i))
|
|
#define mpic_ipi_write(i,v) _mpic_ipi_write(mpic,(i),(v))
|
|
#define mpic_cpu_read(i) _mpic_cpu_read(mpic,(i))
|
|
#define mpic_cpu_write(i,v) _mpic_cpu_write(mpic,(i),(v))
|
|
#define mpic_irq_read(s,r) _mpic_irq_read(mpic,(s),(r))
|
|
#define mpic_irq_write(s,r,v) _mpic_irq_write(mpic,(s),(r),(v))
|
|
|
|
|
|
/*
|
|
* Low level utility functions
|
|
*/
|
|
|
|
|
|
|
|
/* Check if we have one of those nice broken MPICs with a flipped endian on
|
|
* reads from IPI registers
|
|
*/
|
|
static void __init mpic_test_broken_ipi(struct mpic *mpic)
|
|
{
|
|
u32 r;
|
|
|
|
mpic_write(mpic->gregs, MPIC_GREG_IPI_VECTOR_PRI_0, MPIC_VECPRI_MASK);
|
|
r = mpic_read(mpic->gregs, MPIC_GREG_IPI_VECTOR_PRI_0);
|
|
|
|
if (r == le32_to_cpu(MPIC_VECPRI_MASK)) {
|
|
printk(KERN_INFO "mpic: Detected reversed IPI registers\n");
|
|
mpic->flags |= MPIC_BROKEN_IPI;
|
|
}
|
|
}
|
|
|
|
#ifdef CONFIG_MPIC_BROKEN_U3
|
|
|
|
/* Test if an interrupt is sourced from HyperTransport (used on broken U3s)
|
|
* to force the edge setting on the MPIC and do the ack workaround.
|
|
*/
|
|
static inline int mpic_is_ht_interrupt(struct mpic *mpic, unsigned int source_no)
|
|
{
|
|
if (source_no >= 128 || !mpic->fixups)
|
|
return 0;
|
|
return mpic->fixups[source_no].base != NULL;
|
|
}
|
|
|
|
static inline void mpic_apic_end_irq(struct mpic *mpic, unsigned int source_no)
|
|
{
|
|
struct mpic_irq_fixup *fixup = &mpic->fixups[source_no];
|
|
u32 tmp;
|
|
|
|
spin_lock(&mpic->fixup_lock);
|
|
writeb(0x11 + 2 * fixup->irq, fixup->base);
|
|
tmp = readl(fixup->base + 2);
|
|
writel(tmp | 0x80000000ul, fixup->base + 2);
|
|
/* config writes shouldn't be posted but let's be safe ... */
|
|
(void)readl(fixup->base + 2);
|
|
spin_unlock(&mpic->fixup_lock);
|
|
}
|
|
|
|
|
|
static void __init mpic_amd8111_read_irq(struct mpic *mpic, u8 __iomem *devbase)
|
|
{
|
|
int i, irq;
|
|
u32 tmp;
|
|
|
|
printk(KERN_INFO "mpic: - Workarounds on AMD 8111 @ %p\n", devbase);
|
|
|
|
for (i=0; i < 24; i++) {
|
|
writeb(0x10 + 2*i, devbase + 0xf2);
|
|
tmp = readl(devbase + 0xf4);
|
|
if ((tmp & 0x1) || !(tmp & 0x20))
|
|
continue;
|
|
irq = (tmp >> 16) & 0xff;
|
|
mpic->fixups[irq].irq = i;
|
|
mpic->fixups[irq].base = devbase + 0xf2;
|
|
}
|
|
}
|
|
|
|
static void __init mpic_amd8131_read_irq(struct mpic *mpic, u8 __iomem *devbase)
|
|
{
|
|
int i, irq;
|
|
u32 tmp;
|
|
|
|
printk(KERN_INFO "mpic: - Workarounds on AMD 8131 @ %p\n", devbase);
|
|
|
|
for (i=0; i < 4; i++) {
|
|
writeb(0x10 + 2*i, devbase + 0xba);
|
|
tmp = readl(devbase + 0xbc);
|
|
if ((tmp & 0x1) || !(tmp & 0x20))
|
|
continue;
|
|
irq = (tmp >> 16) & 0xff;
|
|
mpic->fixups[irq].irq = i;
|
|
mpic->fixups[irq].base = devbase + 0xba;
|
|
}
|
|
}
|
|
|
|
static void __init mpic_scan_ioapics(struct mpic *mpic)
|
|
{
|
|
unsigned int devfn;
|
|
u8 __iomem *cfgspace;
|
|
|
|
printk(KERN_INFO "mpic: Setting up IO-APICs workarounds for U3\n");
|
|
|
|
/* Allocate fixups array */
|
|
mpic->fixups = alloc_bootmem(128 * sizeof(struct mpic_irq_fixup));
|
|
BUG_ON(mpic->fixups == NULL);
|
|
memset(mpic->fixups, 0, 128 * sizeof(struct mpic_irq_fixup));
|
|
|
|
/* Init spinlock */
|
|
spin_lock_init(&mpic->fixup_lock);
|
|
|
|
/* Map u3 config space. We assume all IO-APICs are on the primary bus
|
|
* and slot will never be above "0xf" so we only need to map 32k
|
|
*/
|
|
cfgspace = (unsigned char __iomem *)ioremap(0xf2000000, 0x8000);
|
|
BUG_ON(cfgspace == NULL);
|
|
|
|
/* Now we scan all slots. We do a very quick scan, we read the header type,
|
|
* vendor ID and device ID only, that's plenty enough
|
|
*/
|
|
for (devfn = 0; devfn < PCI_DEVFN(0x10,0); devfn ++) {
|
|
u8 __iomem *devbase = cfgspace + (devfn << 8);
|
|
u8 hdr_type = readb(devbase + PCI_HEADER_TYPE);
|
|
u32 l = readl(devbase + PCI_VENDOR_ID);
|
|
u16 vendor_id, device_id;
|
|
int multifunc = 0;
|
|
|
|
DBG("devfn %x, l: %x\n", devfn, l);
|
|
|
|
/* If no device, skip */
|
|
if (l == 0xffffffff || l == 0x00000000 ||
|
|
l == 0x0000ffff || l == 0xffff0000)
|
|
goto next;
|
|
|
|
/* Check if it's a multifunction device (only really used
|
|
* to function 0 though
|
|
*/
|
|
multifunc = !!(hdr_type & 0x80);
|
|
vendor_id = l & 0xffff;
|
|
device_id = (l >> 16) & 0xffff;
|
|
|
|
/* If a known device, go to fixup setup code */
|
|
if (vendor_id == PCI_VENDOR_ID_AMD && device_id == 0x7460)
|
|
mpic_amd8111_read_irq(mpic, devbase);
|
|
if (vendor_id == PCI_VENDOR_ID_AMD && device_id == 0x7450)
|
|
mpic_amd8131_read_irq(mpic, devbase);
|
|
next:
|
|
/* next device, if function 0 */
|
|
if ((PCI_FUNC(devfn) == 0) && !multifunc)
|
|
devfn += 7;
|
|
}
|
|
}
|
|
|
|
#endif /* CONFIG_MPIC_BROKEN_U3 */
|
|
|
|
|
|
/* Find an mpic associated with a given linux interrupt */
|
|
static struct mpic *mpic_find(unsigned int irq, unsigned int *is_ipi)
|
|
{
|
|
struct mpic *mpic = mpics;
|
|
|
|
while(mpic) {
|
|
/* search IPIs first since they may override the main interrupts */
|
|
if (irq >= mpic->ipi_offset && irq < (mpic->ipi_offset + 4)) {
|
|
if (is_ipi)
|
|
*is_ipi = 1;
|
|
return mpic;
|
|
}
|
|
if (irq >= mpic->irq_offset &&
|
|
irq < (mpic->irq_offset + mpic->irq_count)) {
|
|
if (is_ipi)
|
|
*is_ipi = 0;
|
|
return mpic;
|
|
}
|
|
mpic = mpic -> next;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* Convert a cpu mask from logical to physical cpu numbers. */
|
|
static inline u32 mpic_physmask(u32 cpumask)
|
|
{
|
|
int i;
|
|
u32 mask = 0;
|
|
|
|
for (i = 0; i < NR_CPUS; ++i, cpumask >>= 1)
|
|
mask |= (cpumask & 1) << get_hard_smp_processor_id(i);
|
|
return mask;
|
|
}
|
|
|
|
#ifdef CONFIG_SMP
|
|
/* Get the mpic structure from the IPI number */
|
|
static inline struct mpic * mpic_from_ipi(unsigned int ipi)
|
|
{
|
|
return container_of(irq_desc[ipi].handler, struct mpic, hc_ipi);
|
|
}
|
|
#endif
|
|
|
|
/* Get the mpic structure from the irq number */
|
|
static inline struct mpic * mpic_from_irq(unsigned int irq)
|
|
{
|
|
return container_of(irq_desc[irq].handler, struct mpic, hc_irq);
|
|
}
|
|
|
|
/* Send an EOI */
|
|
static inline void mpic_eoi(struct mpic *mpic)
|
|
{
|
|
mpic_cpu_write(MPIC_CPU_EOI, 0);
|
|
(void)mpic_cpu_read(MPIC_CPU_WHOAMI);
|
|
}
|
|
|
|
#ifdef CONFIG_SMP
|
|
static irqreturn_t mpic_ipi_action(int irq, void *dev_id, struct pt_regs *regs)
|
|
{
|
|
struct mpic *mpic = dev_id;
|
|
|
|
smp_message_recv(irq - mpic->ipi_offset, regs);
|
|
return IRQ_HANDLED;
|
|
}
|
|
#endif /* CONFIG_SMP */
|
|
|
|
/*
|
|
* Linux descriptor level callbacks
|
|
*/
|
|
|
|
|
|
static void mpic_enable_irq(unsigned int irq)
|
|
{
|
|
unsigned int loops = 100000;
|
|
struct mpic *mpic = mpic_from_irq(irq);
|
|
unsigned int src = irq - mpic->irq_offset;
|
|
|
|
DBG("%s: enable_irq: %d (src %d)\n", mpic->name, irq, src);
|
|
|
|
mpic_irq_write(src, MPIC_IRQ_VECTOR_PRI,
|
|
mpic_irq_read(src, MPIC_IRQ_VECTOR_PRI) & ~MPIC_VECPRI_MASK);
|
|
|
|
/* make sure mask gets to controller before we return to user */
|
|
do {
|
|
if (!loops--) {
|
|
printk(KERN_ERR "mpic_enable_irq timeout\n");
|
|
break;
|
|
}
|
|
} while(mpic_irq_read(src, MPIC_IRQ_VECTOR_PRI) & MPIC_VECPRI_MASK);
|
|
}
|
|
|
|
static void mpic_disable_irq(unsigned int irq)
|
|
{
|
|
unsigned int loops = 100000;
|
|
struct mpic *mpic = mpic_from_irq(irq);
|
|
unsigned int src = irq - mpic->irq_offset;
|
|
|
|
DBG("%s: disable_irq: %d (src %d)\n", mpic->name, irq, src);
|
|
|
|
mpic_irq_write(src, MPIC_IRQ_VECTOR_PRI,
|
|
mpic_irq_read(src, MPIC_IRQ_VECTOR_PRI) | MPIC_VECPRI_MASK);
|
|
|
|
/* make sure mask gets to controller before we return to user */
|
|
do {
|
|
if (!loops--) {
|
|
printk(KERN_ERR "mpic_enable_irq timeout\n");
|
|
break;
|
|
}
|
|
} while(!(mpic_irq_read(src, MPIC_IRQ_VECTOR_PRI) & MPIC_VECPRI_MASK));
|
|
}
|
|
|
|
static void mpic_end_irq(unsigned int irq)
|
|
{
|
|
struct mpic *mpic = mpic_from_irq(irq);
|
|
|
|
DBG("%s: end_irq: %d\n", mpic->name, irq);
|
|
|
|
/* We always EOI on end_irq() even for edge interrupts since that
|
|
* should only lower the priority, the MPIC should have properly
|
|
* latched another edge interrupt coming in anyway
|
|
*/
|
|
|
|
#ifdef CONFIG_MPIC_BROKEN_U3
|
|
if (mpic->flags & MPIC_BROKEN_U3) {
|
|
unsigned int src = irq - mpic->irq_offset;
|
|
if (mpic_is_ht_interrupt(mpic, src))
|
|
mpic_apic_end_irq(mpic, src);
|
|
}
|
|
#endif /* CONFIG_MPIC_BROKEN_U3 */
|
|
|
|
mpic_eoi(mpic);
|
|
}
|
|
|
|
#ifdef CONFIG_SMP
|
|
|
|
static void mpic_enable_ipi(unsigned int irq)
|
|
{
|
|
struct mpic *mpic = mpic_from_ipi(irq);
|
|
unsigned int src = irq - mpic->ipi_offset;
|
|
|
|
DBG("%s: enable_ipi: %d (ipi %d)\n", mpic->name, irq, src);
|
|
mpic_ipi_write(src, mpic_ipi_read(src) & ~MPIC_VECPRI_MASK);
|
|
}
|
|
|
|
static void mpic_disable_ipi(unsigned int irq)
|
|
{
|
|
/* NEVER disable an IPI... that's just plain wrong! */
|
|
}
|
|
|
|
static void mpic_end_ipi(unsigned int irq)
|
|
{
|
|
struct mpic *mpic = mpic_from_ipi(irq);
|
|
|
|
/*
|
|
* IPIs are marked IRQ_PER_CPU. This has the side effect of
|
|
* preventing the IRQ_PENDING/IRQ_INPROGRESS logic from
|
|
* applying to them. We EOI them late to avoid re-entering.
|
|
* We mark IPI's with SA_INTERRUPT as they must run with
|
|
* irqs disabled.
|
|
*/
|
|
mpic_eoi(mpic);
|
|
}
|
|
|
|
#endif /* CONFIG_SMP */
|
|
|
|
static void mpic_set_affinity(unsigned int irq, cpumask_t cpumask)
|
|
{
|
|
struct mpic *mpic = mpic_from_irq(irq);
|
|
|
|
cpumask_t tmp;
|
|
|
|
cpus_and(tmp, cpumask, cpu_online_map);
|
|
|
|
mpic_irq_write(irq - mpic->irq_offset, MPIC_IRQ_DESTINATION,
|
|
mpic_physmask(cpus_addr(tmp)[0]));
|
|
}
|
|
|
|
|
|
/*
|
|
* Exported functions
|
|
*/
|
|
|
|
|
|
struct mpic * __init mpic_alloc(unsigned long phys_addr,
|
|
unsigned int flags,
|
|
unsigned int isu_size,
|
|
unsigned int irq_offset,
|
|
unsigned int irq_count,
|
|
unsigned int ipi_offset,
|
|
unsigned char *senses,
|
|
unsigned int senses_count,
|
|
const char *name)
|
|
{
|
|
struct mpic *mpic;
|
|
u32 reg;
|
|
const char *vers;
|
|
int i;
|
|
|
|
mpic = alloc_bootmem(sizeof(struct mpic));
|
|
if (mpic == NULL)
|
|
return NULL;
|
|
|
|
memset(mpic, 0, sizeof(struct mpic));
|
|
mpic->name = name;
|
|
|
|
mpic->hc_irq.typename = name;
|
|
mpic->hc_irq.enable = mpic_enable_irq;
|
|
mpic->hc_irq.disable = mpic_disable_irq;
|
|
mpic->hc_irq.end = mpic_end_irq;
|
|
if (flags & MPIC_PRIMARY)
|
|
mpic->hc_irq.set_affinity = mpic_set_affinity;
|
|
#ifdef CONFIG_SMP
|
|
mpic->hc_ipi.typename = name;
|
|
mpic->hc_ipi.enable = mpic_enable_ipi;
|
|
mpic->hc_ipi.disable = mpic_disable_ipi;
|
|
mpic->hc_ipi.end = mpic_end_ipi;
|
|
#endif /* CONFIG_SMP */
|
|
|
|
mpic->flags = flags;
|
|
mpic->isu_size = isu_size;
|
|
mpic->irq_offset = irq_offset;
|
|
mpic->irq_count = irq_count;
|
|
mpic->ipi_offset = ipi_offset;
|
|
mpic->num_sources = 0; /* so far */
|
|
mpic->senses = senses;
|
|
mpic->senses_count = senses_count;
|
|
|
|
/* Map the global registers */
|
|
mpic->gregs = ioremap(phys_addr + MPIC_GREG_BASE, 0x2000);
|
|
mpic->tmregs = mpic->gregs + ((MPIC_TIMER_BASE - MPIC_GREG_BASE) >> 2);
|
|
BUG_ON(mpic->gregs == NULL);
|
|
|
|
/* Reset */
|
|
if (flags & MPIC_WANTS_RESET) {
|
|
mpic_write(mpic->gregs, MPIC_GREG_GLOBAL_CONF_0,
|
|
mpic_read(mpic->gregs, MPIC_GREG_GLOBAL_CONF_0)
|
|
| MPIC_GREG_GCONF_RESET);
|
|
while( mpic_read(mpic->gregs, MPIC_GREG_GLOBAL_CONF_0)
|
|
& MPIC_GREG_GCONF_RESET)
|
|
mb();
|
|
}
|
|
|
|
/* Read feature register, calculate num CPUs and, for non-ISU
|
|
* MPICs, num sources as well. On ISU MPICs, sources are counted
|
|
* as ISUs are added
|
|
*/
|
|
reg = mpic_read(mpic->gregs, MPIC_GREG_FEATURE_0);
|
|
mpic->num_cpus = ((reg & MPIC_GREG_FEATURE_LAST_CPU_MASK)
|
|
>> MPIC_GREG_FEATURE_LAST_CPU_SHIFT) + 1;
|
|
if (isu_size == 0)
|
|
mpic->num_sources = ((reg & MPIC_GREG_FEATURE_LAST_SRC_MASK)
|
|
>> MPIC_GREG_FEATURE_LAST_SRC_SHIFT) + 1;
|
|
|
|
/* Map the per-CPU registers */
|
|
for (i = 0; i < mpic->num_cpus; i++) {
|
|
mpic->cpuregs[i] = ioremap(phys_addr + MPIC_CPU_BASE +
|
|
i * MPIC_CPU_STRIDE, 0x1000);
|
|
BUG_ON(mpic->cpuregs[i] == NULL);
|
|
}
|
|
|
|
/* Initialize main ISU if none provided */
|
|
if (mpic->isu_size == 0) {
|
|
mpic->isu_size = mpic->num_sources;
|
|
mpic->isus[0] = ioremap(phys_addr + MPIC_IRQ_BASE,
|
|
MPIC_IRQ_STRIDE * mpic->isu_size);
|
|
BUG_ON(mpic->isus[0] == NULL);
|
|
}
|
|
mpic->isu_shift = 1 + __ilog2(mpic->isu_size - 1);
|
|
mpic->isu_mask = (1 << mpic->isu_shift) - 1;
|
|
|
|
/* Display version */
|
|
switch (reg & MPIC_GREG_FEATURE_VERSION_MASK) {
|
|
case 1:
|
|
vers = "1.0";
|
|
break;
|
|
case 2:
|
|
vers = "1.2";
|
|
break;
|
|
case 3:
|
|
vers = "1.3";
|
|
break;
|
|
default:
|
|
vers = "<unknown>";
|
|
break;
|
|
}
|
|
printk(KERN_INFO "mpic: Setting up MPIC \"%s\" version %s at %lx, max %d CPUs\n",
|
|
name, vers, phys_addr, mpic->num_cpus);
|
|
printk(KERN_INFO "mpic: ISU size: %d, shift: %d, mask: %x\n", mpic->isu_size,
|
|
mpic->isu_shift, mpic->isu_mask);
|
|
|
|
mpic->next = mpics;
|
|
mpics = mpic;
|
|
|
|
if (flags & MPIC_PRIMARY)
|
|
mpic_primary = mpic;
|
|
|
|
return mpic;
|
|
}
|
|
|
|
void __init mpic_assign_isu(struct mpic *mpic, unsigned int isu_num,
|
|
unsigned long phys_addr)
|
|
{
|
|
unsigned int isu_first = isu_num * mpic->isu_size;
|
|
|
|
BUG_ON(isu_num >= MPIC_MAX_ISU);
|
|
|
|
mpic->isus[isu_num] = ioremap(phys_addr, MPIC_IRQ_STRIDE * mpic->isu_size);
|
|
if ((isu_first + mpic->isu_size) > mpic->num_sources)
|
|
mpic->num_sources = isu_first + mpic->isu_size;
|
|
}
|
|
|
|
void __init mpic_setup_cascade(unsigned int irq, mpic_cascade_t handler,
|
|
void *data)
|
|
{
|
|
struct mpic *mpic = mpic_find(irq, NULL);
|
|
unsigned long flags;
|
|
|
|
/* Synchronization here is a bit dodgy, so don't try to replace cascade
|
|
* interrupts on the fly too often ... but normally it's set up at boot.
|
|
*/
|
|
spin_lock_irqsave(&mpic_lock, flags);
|
|
if (mpic->cascade)
|
|
mpic_disable_irq(mpic->cascade_vec + mpic->irq_offset);
|
|
mpic->cascade = NULL;
|
|
wmb();
|
|
mpic->cascade_vec = irq - mpic->irq_offset;
|
|
mpic->cascade_data = data;
|
|
wmb();
|
|
mpic->cascade = handler;
|
|
mpic_enable_irq(irq);
|
|
spin_unlock_irqrestore(&mpic_lock, flags);
|
|
}
|
|
|
|
void __init mpic_init(struct mpic *mpic)
|
|
{
|
|
int i;
|
|
|
|
BUG_ON(mpic->num_sources == 0);
|
|
|
|
printk(KERN_INFO "mpic: Initializing for %d sources\n", mpic->num_sources);
|
|
|
|
/* Set current processor priority to max */
|
|
mpic_cpu_write(MPIC_CPU_CURRENT_TASK_PRI, 0xf);
|
|
|
|
/* Initialize timers: just disable them all */
|
|
for (i = 0; i < 4; i++) {
|
|
mpic_write(mpic->tmregs,
|
|
i * MPIC_TIMER_STRIDE + MPIC_TIMER_DESTINATION, 0);
|
|
mpic_write(mpic->tmregs,
|
|
i * MPIC_TIMER_STRIDE + MPIC_TIMER_VECTOR_PRI,
|
|
MPIC_VECPRI_MASK |
|
|
(MPIC_VEC_TIMER_0 + i));
|
|
}
|
|
|
|
/* Initialize IPIs to our reserved vectors and mark them disabled for now */
|
|
mpic_test_broken_ipi(mpic);
|
|
for (i = 0; i < 4; i++) {
|
|
mpic_ipi_write(i,
|
|
MPIC_VECPRI_MASK |
|
|
(10 << MPIC_VECPRI_PRIORITY_SHIFT) |
|
|
(MPIC_VEC_IPI_0 + i));
|
|
#ifdef CONFIG_SMP
|
|
if (!(mpic->flags & MPIC_PRIMARY))
|
|
continue;
|
|
irq_desc[mpic->ipi_offset+i].status |= IRQ_PER_CPU;
|
|
irq_desc[mpic->ipi_offset+i].handler = &mpic->hc_ipi;
|
|
|
|
#endif /* CONFIG_SMP */
|
|
}
|
|
|
|
/* Initialize interrupt sources */
|
|
if (mpic->irq_count == 0)
|
|
mpic->irq_count = mpic->num_sources;
|
|
|
|
#ifdef CONFIG_MPIC_BROKEN_U3
|
|
/* Do the ioapic fixups on U3 broken mpic */
|
|
DBG("MPIC flags: %x\n", mpic->flags);
|
|
if ((mpic->flags & MPIC_BROKEN_U3) && (mpic->flags & MPIC_PRIMARY))
|
|
mpic_scan_ioapics(mpic);
|
|
#endif /* CONFIG_MPIC_BROKEN_U3 */
|
|
|
|
for (i = 0; i < mpic->num_sources; i++) {
|
|
/* start with vector = source number, and masked */
|
|
u32 vecpri = MPIC_VECPRI_MASK | i | (8 << MPIC_VECPRI_PRIORITY_SHIFT);
|
|
int level = 0;
|
|
|
|
/* if it's an IPI, we skip it */
|
|
if ((mpic->irq_offset + i) >= (mpic->ipi_offset + i) &&
|
|
(mpic->irq_offset + i) < (mpic->ipi_offset + i + 4))
|
|
continue;
|
|
|
|
/* do senses munging */
|
|
if (mpic->senses && i < mpic->senses_count) {
|
|
if (mpic->senses[i] & IRQ_SENSE_LEVEL)
|
|
vecpri |= MPIC_VECPRI_SENSE_LEVEL;
|
|
if (mpic->senses[i] & IRQ_POLARITY_POSITIVE)
|
|
vecpri |= MPIC_VECPRI_POLARITY_POSITIVE;
|
|
} else
|
|
vecpri |= MPIC_VECPRI_SENSE_LEVEL;
|
|
|
|
/* remember if it was a level interrupts */
|
|
level = (vecpri & MPIC_VECPRI_SENSE_LEVEL);
|
|
|
|
/* deal with broken U3 */
|
|
if (mpic->flags & MPIC_BROKEN_U3) {
|
|
#ifdef CONFIG_MPIC_BROKEN_U3
|
|
if (mpic_is_ht_interrupt(mpic, i)) {
|
|
vecpri &= ~(MPIC_VECPRI_SENSE_MASK |
|
|
MPIC_VECPRI_POLARITY_MASK);
|
|
vecpri |= MPIC_VECPRI_POLARITY_POSITIVE;
|
|
}
|
|
#else
|
|
printk(KERN_ERR "mpic: BROKEN_U3 set, but CONFIG doesn't match\n");
|
|
#endif
|
|
}
|
|
|
|
DBG("setup source %d, vecpri: %08x, level: %d\n", i, vecpri,
|
|
(level != 0));
|
|
|
|
/* init hw */
|
|
mpic_irq_write(i, MPIC_IRQ_VECTOR_PRI, vecpri);
|
|
mpic_irq_write(i, MPIC_IRQ_DESTINATION,
|
|
1 << get_hard_smp_processor_id(boot_cpuid));
|
|
|
|
/* init linux descriptors */
|
|
if (i < mpic->irq_count) {
|
|
irq_desc[mpic->irq_offset+i].status = level ? IRQ_LEVEL : 0;
|
|
irq_desc[mpic->irq_offset+i].handler = &mpic->hc_irq;
|
|
}
|
|
}
|
|
|
|
/* Init spurrious vector */
|
|
mpic_write(mpic->gregs, MPIC_GREG_SPURIOUS, MPIC_VEC_SPURRIOUS);
|
|
|
|
/* Disable 8259 passthrough */
|
|
mpic_write(mpic->gregs, MPIC_GREG_GLOBAL_CONF_0,
|
|
mpic_read(mpic->gregs, MPIC_GREG_GLOBAL_CONF_0)
|
|
| MPIC_GREG_GCONF_8259_PTHROU_DIS);
|
|
|
|
/* Set current processor priority to 0 */
|
|
mpic_cpu_write(MPIC_CPU_CURRENT_TASK_PRI, 0);
|
|
}
|
|
|
|
|
|
|
|
void mpic_irq_set_priority(unsigned int irq, unsigned int pri)
|
|
{
|
|
int is_ipi;
|
|
struct mpic *mpic = mpic_find(irq, &is_ipi);
|
|
unsigned long flags;
|
|
u32 reg;
|
|
|
|
spin_lock_irqsave(&mpic_lock, flags);
|
|
if (is_ipi) {
|
|
reg = mpic_ipi_read(irq - mpic->ipi_offset) & MPIC_VECPRI_PRIORITY_MASK;
|
|
mpic_ipi_write(irq - mpic->ipi_offset,
|
|
reg | (pri << MPIC_VECPRI_PRIORITY_SHIFT));
|
|
} else {
|
|
reg = mpic_irq_read(irq - mpic->irq_offset, MPIC_IRQ_VECTOR_PRI)
|
|
& MPIC_VECPRI_PRIORITY_MASK;
|
|
mpic_irq_write(irq - mpic->irq_offset, MPIC_IRQ_VECTOR_PRI,
|
|
reg | (pri << MPIC_VECPRI_PRIORITY_SHIFT));
|
|
}
|
|
spin_unlock_irqrestore(&mpic_lock, flags);
|
|
}
|
|
|
|
unsigned int mpic_irq_get_priority(unsigned int irq)
|
|
{
|
|
int is_ipi;
|
|
struct mpic *mpic = mpic_find(irq, &is_ipi);
|
|
unsigned long flags;
|
|
u32 reg;
|
|
|
|
spin_lock_irqsave(&mpic_lock, flags);
|
|
if (is_ipi)
|
|
reg = mpic_ipi_read(irq - mpic->ipi_offset);
|
|
else
|
|
reg = mpic_irq_read(irq - mpic->irq_offset, MPIC_IRQ_VECTOR_PRI);
|
|
spin_unlock_irqrestore(&mpic_lock, flags);
|
|
return (reg & MPIC_VECPRI_PRIORITY_MASK) >> MPIC_VECPRI_PRIORITY_SHIFT;
|
|
}
|
|
|
|
void mpic_setup_this_cpu(void)
|
|
{
|
|
#ifdef CONFIG_SMP
|
|
struct mpic *mpic = mpic_primary;
|
|
unsigned long flags;
|
|
u32 msk = 1 << hard_smp_processor_id();
|
|
unsigned int i;
|
|
|
|
BUG_ON(mpic == NULL);
|
|
|
|
DBG("%s: setup_this_cpu(%d)\n", mpic->name, hard_smp_processor_id());
|
|
|
|
spin_lock_irqsave(&mpic_lock, flags);
|
|
|
|
/* let the mpic know we want intrs. default affinity is 0xffffffff
|
|
* until changed via /proc. That's how it's done on x86. If we want
|
|
* it differently, then we should make sure we also change the default
|
|
* values of irq_affinity in irq.c.
|
|
*/
|
|
if (distribute_irqs) {
|
|
for (i = 0; i < mpic->num_sources ; i++)
|
|
mpic_irq_write(i, MPIC_IRQ_DESTINATION,
|
|
mpic_irq_read(i, MPIC_IRQ_DESTINATION) | msk);
|
|
}
|
|
|
|
/* Set current processor priority to 0 */
|
|
mpic_cpu_write(MPIC_CPU_CURRENT_TASK_PRI, 0);
|
|
|
|
spin_unlock_irqrestore(&mpic_lock, flags);
|
|
#endif /* CONFIG_SMP */
|
|
}
|
|
|
|
/*
|
|
* XXX: someone who knows mpic should check this.
|
|
* do we need to eoi the ipi including for kexec cpu here (see xics comments)?
|
|
* or can we reset the mpic in the new kernel?
|
|
*/
|
|
void mpic_teardown_this_cpu(int secondary)
|
|
{
|
|
struct mpic *mpic = mpic_primary;
|
|
unsigned long flags;
|
|
u32 msk = 1 << hard_smp_processor_id();
|
|
unsigned int i;
|
|
|
|
BUG_ON(mpic == NULL);
|
|
|
|
DBG("%s: teardown_this_cpu(%d)\n", mpic->name, hard_smp_processor_id());
|
|
spin_lock_irqsave(&mpic_lock, flags);
|
|
|
|
/* let the mpic know we don't want intrs. */
|
|
for (i = 0; i < mpic->num_sources ; i++)
|
|
mpic_irq_write(i, MPIC_IRQ_DESTINATION,
|
|
mpic_irq_read(i, MPIC_IRQ_DESTINATION) & ~msk);
|
|
|
|
/* Set current processor priority to max */
|
|
mpic_cpu_write(MPIC_CPU_CURRENT_TASK_PRI, 0xf);
|
|
|
|
spin_unlock_irqrestore(&mpic_lock, flags);
|
|
}
|
|
|
|
|
|
void mpic_send_ipi(unsigned int ipi_no, unsigned int cpu_mask)
|
|
{
|
|
struct mpic *mpic = mpic_primary;
|
|
|
|
BUG_ON(mpic == NULL);
|
|
|
|
DBG("%s: send_ipi(ipi_no: %d)\n", mpic->name, ipi_no);
|
|
|
|
mpic_cpu_write(MPIC_CPU_IPI_DISPATCH_0 + ipi_no * 0x10,
|
|
mpic_physmask(cpu_mask & cpus_addr(cpu_online_map)[0]));
|
|
}
|
|
|
|
int mpic_get_one_irq(struct mpic *mpic, struct pt_regs *regs)
|
|
{
|
|
u32 irq;
|
|
|
|
irq = mpic_cpu_read(MPIC_CPU_INTACK) & MPIC_VECPRI_VECTOR_MASK;
|
|
DBG("%s: get_one_irq(): %d\n", mpic->name, irq);
|
|
|
|
if (mpic->cascade && irq == mpic->cascade_vec) {
|
|
DBG("%s: cascading ...\n", mpic->name);
|
|
irq = mpic->cascade(regs, mpic->cascade_data);
|
|
mpic_eoi(mpic);
|
|
return irq;
|
|
}
|
|
if (unlikely(irq == MPIC_VEC_SPURRIOUS))
|
|
return -1;
|
|
if (irq < MPIC_VEC_IPI_0)
|
|
return irq + mpic->irq_offset;
|
|
DBG("%s: ipi %d !\n", mpic->name, irq - MPIC_VEC_IPI_0);
|
|
return irq - MPIC_VEC_IPI_0 + mpic->ipi_offset;
|
|
}
|
|
|
|
int mpic_get_irq(struct pt_regs *regs)
|
|
{
|
|
struct mpic *mpic = mpic_primary;
|
|
|
|
BUG_ON(mpic == NULL);
|
|
|
|
return mpic_get_one_irq(mpic, regs);
|
|
}
|
|
|
|
|
|
#ifdef CONFIG_SMP
|
|
void mpic_request_ipis(void)
|
|
{
|
|
struct mpic *mpic = mpic_primary;
|
|
|
|
BUG_ON(mpic == NULL);
|
|
|
|
printk("requesting IPIs ... \n");
|
|
|
|
/* IPIs are marked SA_INTERRUPT as they must run with irqs disabled */
|
|
request_irq(mpic->ipi_offset+0, mpic_ipi_action, SA_INTERRUPT,
|
|
"IPI0 (call function)", mpic);
|
|
request_irq(mpic->ipi_offset+1, mpic_ipi_action, SA_INTERRUPT,
|
|
"IPI1 (reschedule)", mpic);
|
|
request_irq(mpic->ipi_offset+2, mpic_ipi_action, SA_INTERRUPT,
|
|
"IPI2 (unused)", mpic);
|
|
request_irq(mpic->ipi_offset+3, mpic_ipi_action, SA_INTERRUPT,
|
|
"IPI3 (debugger break)", mpic);
|
|
|
|
printk("IPIs requested... \n");
|
|
}
|
|
#endif /* CONFIG_SMP */
|