The introduction of {map/swizzle}_irq() hooks in the struct pci_host_bridge
allowed to replace the pci_fixup_irqs() PCI IRQ allocation in alpha arch
PCI code with per-bridge map/swizzle functions with commit 0e4c2eeb75
("alpha/PCI: Replace pci_fixup_irqs() call with host bridge IRQ mapping
hooks").
As a side effect of converting PCI IRQ allocation to the struct
pci_host_bridge {map/swizzle}_irq() hooks mechanism, the actual PCI IRQ
allocation function (ie pci_assign_irq()) is carried out per-device in
pci_device_probe() that is called when a PCI device driver is about to be
probed.
This means that, for drivers compiled as loadable modules, the actual PCI
device IRQ allocation can now happen after the system has booted so the
struct pci_host_bridge {map/swizzle}_irq() hooks pci_assign_irq() relies on
must stay valid after the system has booted so that PCI core can carry out
PCI IRQ allocation correctly.
Most of the alpha board structures pci_map_irq() and pci_swizzle() hooks
(that are used to initialize their struct pci_host_bridge equivalent
through the alpha_mv global variable - that represents the struct
alpha_machine_vector of the running kernel) are marked as
__init/__initdata; this causes freed memory dereferences when PCI IRQ
allocation is carried out after the kernel has booted (ie when loading PCI
drivers as loadable module) because when the kernel tries to bind the PCI
device to its (module) driver, the function pci_assign_irq() is called,
that in turn retrieves the struct pci_host_bridge {map/swizzle}_irq() hooks
to carry out PCI IRQ allocation; if those hooks are marked as __init
code/__initdata they point at freed/invalid memory.
Fix the issue by removing the __init/__initdata markers from all subarch
struct alpha_machine_vector.pci_map_irq()/pci_swizzle() functions (and
data).
Fixes: 0e4c2eeb75 ("alpha/PCI: Replace pci_fixup_irqs() call with host bridge IRQ mapping hooks")
Link: http://lkml.kernel.org/r/alpine.LRH.2.21.1710251043170.7098@math.ut.ee
Reported-by: Meelis Roos <mroos@linux.ee>
Signed-off-by: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com>
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
Cc: Richard Henderson <rth@twiddle.net>
Cc: Ivan Kokshaysky <ink@jurassic.park.msu.ru>
Cc: Meelis Roos <mroos@linux.ee>
Cc: Matt Turner <mattst88@gmail.com>
350 lines
8.6 KiB
C
350 lines
8.6 KiB
C
/*
|
|
* linux/arch/alpha/kernel/sys_wildfire.c
|
|
*
|
|
* Wildfire support.
|
|
*
|
|
* Copyright (C) 2000 Andrea Arcangeli <andrea@suse.de> SuSE
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/types.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/init.h>
|
|
#include <linux/bitops.h>
|
|
|
|
#include <asm/ptrace.h>
|
|
#include <asm/dma.h>
|
|
#include <asm/irq.h>
|
|
#include <asm/mmu_context.h>
|
|
#include <asm/io.h>
|
|
#include <asm/pgtable.h>
|
|
#include <asm/core_wildfire.h>
|
|
#include <asm/hwrpb.h>
|
|
#include <asm/tlbflush.h>
|
|
|
|
#include "proto.h"
|
|
#include "irq_impl.h"
|
|
#include "pci_impl.h"
|
|
#include "machvec_impl.h"
|
|
|
|
static unsigned long cached_irq_mask[WILDFIRE_NR_IRQS/(sizeof(long)*8)];
|
|
|
|
DEFINE_SPINLOCK(wildfire_irq_lock);
|
|
|
|
static int doing_init_irq_hw = 0;
|
|
|
|
static void
|
|
wildfire_update_irq_hw(unsigned int irq)
|
|
{
|
|
int qbbno = (irq >> 8) & (WILDFIRE_MAX_QBB - 1);
|
|
int pcano = (irq >> 6) & (WILDFIRE_PCA_PER_QBB - 1);
|
|
wildfire_pca *pca;
|
|
volatile unsigned long * enable0;
|
|
|
|
if (!WILDFIRE_PCA_EXISTS(qbbno, pcano)) {
|
|
if (!doing_init_irq_hw) {
|
|
printk(KERN_ERR "wildfire_update_irq_hw:"
|
|
" got irq %d for non-existent PCA %d"
|
|
" on QBB %d.\n",
|
|
irq, pcano, qbbno);
|
|
}
|
|
return;
|
|
}
|
|
|
|
pca = WILDFIRE_pca(qbbno, pcano);
|
|
enable0 = (unsigned long *) &pca->pca_int[0].enable; /* ??? */
|
|
|
|
*enable0 = cached_irq_mask[qbbno * WILDFIRE_PCA_PER_QBB + pcano];
|
|
mb();
|
|
*enable0;
|
|
}
|
|
|
|
static void __init
|
|
wildfire_init_irq_hw(void)
|
|
{
|
|
#if 0
|
|
register wildfire_pca * pca = WILDFIRE_pca(0, 0);
|
|
volatile unsigned long * enable0, * enable1, * enable2, *enable3;
|
|
volatile unsigned long * target0, * target1, * target2, *target3;
|
|
|
|
enable0 = (unsigned long *) &pca->pca_int[0].enable;
|
|
enable1 = (unsigned long *) &pca->pca_int[1].enable;
|
|
enable2 = (unsigned long *) &pca->pca_int[2].enable;
|
|
enable3 = (unsigned long *) &pca->pca_int[3].enable;
|
|
|
|
target0 = (unsigned long *) &pca->pca_int[0].target;
|
|
target1 = (unsigned long *) &pca->pca_int[1].target;
|
|
target2 = (unsigned long *) &pca->pca_int[2].target;
|
|
target3 = (unsigned long *) &pca->pca_int[3].target;
|
|
|
|
*enable0 = *enable1 = *enable2 = *enable3 = 0;
|
|
|
|
*target0 = (1UL<<8) | WILDFIRE_QBB(0);
|
|
*target1 = *target2 = *target3 = 0;
|
|
|
|
mb();
|
|
|
|
*enable0; *enable1; *enable2; *enable3;
|
|
*target0; *target1; *target2; *target3;
|
|
|
|
#else
|
|
int i;
|
|
|
|
doing_init_irq_hw = 1;
|
|
|
|
/* Need to update only once for every possible PCA. */
|
|
for (i = 0; i < WILDFIRE_NR_IRQS; i+=WILDFIRE_IRQ_PER_PCA)
|
|
wildfire_update_irq_hw(i);
|
|
|
|
doing_init_irq_hw = 0;
|
|
#endif
|
|
}
|
|
|
|
static void
|
|
wildfire_enable_irq(struct irq_data *d)
|
|
{
|
|
unsigned int irq = d->irq;
|
|
|
|
if (irq < 16)
|
|
i8259a_enable_irq(d);
|
|
|
|
spin_lock(&wildfire_irq_lock);
|
|
set_bit(irq, &cached_irq_mask);
|
|
wildfire_update_irq_hw(irq);
|
|
spin_unlock(&wildfire_irq_lock);
|
|
}
|
|
|
|
static void
|
|
wildfire_disable_irq(struct irq_data *d)
|
|
{
|
|
unsigned int irq = d->irq;
|
|
|
|
if (irq < 16)
|
|
i8259a_disable_irq(d);
|
|
|
|
spin_lock(&wildfire_irq_lock);
|
|
clear_bit(irq, &cached_irq_mask);
|
|
wildfire_update_irq_hw(irq);
|
|
spin_unlock(&wildfire_irq_lock);
|
|
}
|
|
|
|
static void
|
|
wildfire_mask_and_ack_irq(struct irq_data *d)
|
|
{
|
|
unsigned int irq = d->irq;
|
|
|
|
if (irq < 16)
|
|
i8259a_mask_and_ack_irq(d);
|
|
|
|
spin_lock(&wildfire_irq_lock);
|
|
clear_bit(irq, &cached_irq_mask);
|
|
wildfire_update_irq_hw(irq);
|
|
spin_unlock(&wildfire_irq_lock);
|
|
}
|
|
|
|
static struct irq_chip wildfire_irq_type = {
|
|
.name = "WILDFIRE",
|
|
.irq_unmask = wildfire_enable_irq,
|
|
.irq_mask = wildfire_disable_irq,
|
|
.irq_mask_ack = wildfire_mask_and_ack_irq,
|
|
};
|
|
|
|
static void __init
|
|
wildfire_init_irq_per_pca(int qbbno, int pcano)
|
|
{
|
|
int i, irq_bias;
|
|
static struct irqaction isa_enable = {
|
|
.handler = no_action,
|
|
.name = "isa_enable",
|
|
};
|
|
|
|
irq_bias = qbbno * (WILDFIRE_PCA_PER_QBB * WILDFIRE_IRQ_PER_PCA)
|
|
+ pcano * WILDFIRE_IRQ_PER_PCA;
|
|
|
|
#if 0
|
|
unsigned long io_bias;
|
|
|
|
/* Only need the following for first PCI bus per PCA. */
|
|
io_bias = WILDFIRE_IO(qbbno, pcano<<1) - WILDFIRE_IO_BIAS;
|
|
|
|
outb(0, DMA1_RESET_REG + io_bias);
|
|
outb(0, DMA2_RESET_REG + io_bias);
|
|
outb(DMA_MODE_CASCADE, DMA2_MODE_REG + io_bias);
|
|
outb(0, DMA2_MASK_REG + io_bias);
|
|
#endif
|
|
|
|
#if 0
|
|
/* ??? Not sure how to do this, yet... */
|
|
init_i8259a_irqs(); /* ??? */
|
|
#endif
|
|
|
|
for (i = 0; i < 16; ++i) {
|
|
if (i == 2)
|
|
continue;
|
|
irq_set_chip_and_handler(i + irq_bias, &wildfire_irq_type,
|
|
handle_level_irq);
|
|
irq_set_status_flags(i + irq_bias, IRQ_LEVEL);
|
|
}
|
|
|
|
irq_set_chip_and_handler(36 + irq_bias, &wildfire_irq_type,
|
|
handle_level_irq);
|
|
irq_set_status_flags(36 + irq_bias, IRQ_LEVEL);
|
|
for (i = 40; i < 64; ++i) {
|
|
irq_set_chip_and_handler(i + irq_bias, &wildfire_irq_type,
|
|
handle_level_irq);
|
|
irq_set_status_flags(i + irq_bias, IRQ_LEVEL);
|
|
}
|
|
|
|
setup_irq(32+irq_bias, &isa_enable);
|
|
}
|
|
|
|
static void __init
|
|
wildfire_init_irq(void)
|
|
{
|
|
int qbbno, pcano;
|
|
|
|
#if 1
|
|
wildfire_init_irq_hw();
|
|
init_i8259a_irqs();
|
|
#endif
|
|
|
|
for (qbbno = 0; qbbno < WILDFIRE_MAX_QBB; qbbno++) {
|
|
if (WILDFIRE_QBB_EXISTS(qbbno)) {
|
|
for (pcano = 0; pcano < WILDFIRE_PCA_PER_QBB; pcano++) {
|
|
if (WILDFIRE_PCA_EXISTS(qbbno, pcano)) {
|
|
wildfire_init_irq_per_pca(qbbno, pcano);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
wildfire_device_interrupt(unsigned long vector)
|
|
{
|
|
int irq;
|
|
|
|
irq = (vector - 0x800) >> 4;
|
|
|
|
/*
|
|
* bits 10-8: source QBB ID
|
|
* bits 7-6: PCA
|
|
* bits 5-0: irq in PCA
|
|
*/
|
|
|
|
handle_irq(irq);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* PCI Fixup configuration.
|
|
*
|
|
* Summary per PCA (2 PCI or HIPPI buses):
|
|
*
|
|
* Bit Meaning
|
|
* 0-15 ISA
|
|
*
|
|
*32 ISA summary
|
|
*33 SMI
|
|
*34 NMI
|
|
*36 builtin QLogic SCSI (or slot 0 if no IO module)
|
|
*40 Interrupt Line A from slot 2 PCI0
|
|
*41 Interrupt Line B from slot 2 PCI0
|
|
*42 Interrupt Line C from slot 2 PCI0
|
|
*43 Interrupt Line D from slot 2 PCI0
|
|
*44 Interrupt Line A from slot 3 PCI0
|
|
*45 Interrupt Line B from slot 3 PCI0
|
|
*46 Interrupt Line C from slot 3 PCI0
|
|
*47 Interrupt Line D from slot 3 PCI0
|
|
*
|
|
*48 Interrupt Line A from slot 4 PCI1
|
|
*49 Interrupt Line B from slot 4 PCI1
|
|
*50 Interrupt Line C from slot 4 PCI1
|
|
*51 Interrupt Line D from slot 4 PCI1
|
|
*52 Interrupt Line A from slot 5 PCI1
|
|
*53 Interrupt Line B from slot 5 PCI1
|
|
*54 Interrupt Line C from slot 5 PCI1
|
|
*55 Interrupt Line D from slot 5 PCI1
|
|
*56 Interrupt Line A from slot 6 PCI1
|
|
*57 Interrupt Line B from slot 6 PCI1
|
|
*58 Interrupt Line C from slot 6 PCI1
|
|
*50 Interrupt Line D from slot 6 PCI1
|
|
*60 Interrupt Line A from slot 7 PCI1
|
|
*61 Interrupt Line B from slot 7 PCI1
|
|
*62 Interrupt Line C from slot 7 PCI1
|
|
*63 Interrupt Line D from slot 7 PCI1
|
|
*
|
|
*
|
|
* IdSel
|
|
* 0 Cypress Bridge I/O (ISA summary interrupt)
|
|
* 1 64 bit PCI 0 option slot 1 (SCSI QLogic builtin)
|
|
* 2 64 bit PCI 0 option slot 2
|
|
* 3 64 bit PCI 0 option slot 3
|
|
* 4 64 bit PCI 1 option slot 4
|
|
* 5 64 bit PCI 1 option slot 5
|
|
* 6 64 bit PCI 1 option slot 6
|
|
* 7 64 bit PCI 1 option slot 7
|
|
*/
|
|
|
|
static int
|
|
wildfire_map_irq(const struct pci_dev *dev, u8 slot, u8 pin)
|
|
{
|
|
static char irq_tab[8][5] = {
|
|
/*INT INTA INTB INTC INTD */
|
|
{ -1, -1, -1, -1, -1}, /* IdSel 0 ISA Bridge */
|
|
{ 36, 36, 36+1, 36+2, 36+3}, /* IdSel 1 SCSI builtin */
|
|
{ 40, 40, 40+1, 40+2, 40+3}, /* IdSel 2 PCI 0 slot 2 */
|
|
{ 44, 44, 44+1, 44+2, 44+3}, /* IdSel 3 PCI 0 slot 3 */
|
|
{ 48, 48, 48+1, 48+2, 48+3}, /* IdSel 4 PCI 1 slot 4 */
|
|
{ 52, 52, 52+1, 52+2, 52+3}, /* IdSel 5 PCI 1 slot 5 */
|
|
{ 56, 56, 56+1, 56+2, 56+3}, /* IdSel 6 PCI 1 slot 6 */
|
|
{ 60, 60, 60+1, 60+2, 60+3}, /* IdSel 7 PCI 1 slot 7 */
|
|
};
|
|
long min_idsel = 0, max_idsel = 7, irqs_per_slot = 5;
|
|
|
|
struct pci_controller *hose = dev->sysdata;
|
|
int irq = COMMON_TABLE_LOOKUP;
|
|
|
|
if (irq > 0) {
|
|
int qbbno = hose->index >> 3;
|
|
int pcano = (hose->index >> 1) & 3;
|
|
irq += (qbbno << 8) + (pcano << 6);
|
|
}
|
|
return irq;
|
|
}
|
|
|
|
|
|
/*
|
|
* The System Vectors
|
|
*/
|
|
|
|
struct alpha_machine_vector wildfire_mv __initmv = {
|
|
.vector_name = "WILDFIRE",
|
|
DO_EV6_MMU,
|
|
DO_DEFAULT_RTC,
|
|
DO_WILDFIRE_IO,
|
|
.machine_check = wildfire_machine_check,
|
|
.max_isa_dma_address = ALPHA_MAX_ISA_DMA_ADDRESS,
|
|
.min_io_address = DEFAULT_IO_BASE,
|
|
.min_mem_address = DEFAULT_MEM_BASE,
|
|
|
|
.nr_irqs = WILDFIRE_NR_IRQS,
|
|
.device_interrupt = wildfire_device_interrupt,
|
|
|
|
.init_arch = wildfire_init_arch,
|
|
.init_irq = wildfire_init_irq,
|
|
.init_rtc = common_init_rtc,
|
|
.init_pci = common_init_pci,
|
|
.kill_arch = wildfire_kill_arch,
|
|
.pci_map_irq = wildfire_map_irq,
|
|
.pci_swizzle = common_swizzle,
|
|
|
|
.pa_to_nid = wildfire_pa_to_nid,
|
|
.cpuid_to_nid = wildfire_cpuid_to_nid,
|
|
.node_mem_start = wildfire_node_mem_start,
|
|
.node_mem_size = wildfire_node_mem_size,
|
|
};
|
|
ALIAS_MV(wildfire)
|