diff options
Diffstat (limited to 'arch/ia64/sn/kernel/irq.c')
-rw-r--r-- | arch/ia64/sn/kernel/irq.c | 431 |
1 files changed, 431 insertions, 0 deletions
diff --git a/arch/ia64/sn/kernel/irq.c b/arch/ia64/sn/kernel/irq.c new file mode 100644 index 0000000..3be4472 --- /dev/null +++ b/arch/ia64/sn/kernel/irq.c @@ -0,0 +1,431 @@ +/* + * Platform dependent support for SGI SN + * + * 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. + * + * Copyright (c) 2000-2004 Silicon Graphics, Inc. All Rights Reserved. + */ + +#include <linux/irq.h> +#include <asm/sn/intr.h> +#include <asm/sn/addrs.h> +#include <asm/sn/arch.h> +#include "xtalk/xwidgetdev.h" +#include "pci/pcibus_provider_defs.h" +#include "pci/pcidev.h" +#include "pci/pcibr_provider.h" +#include <asm/sn/shub_mmr.h> +#include <asm/sn/sn_sal.h> + +static void force_interrupt(int irq); +static void register_intr_pda(struct sn_irq_info *sn_irq_info); +static void unregister_intr_pda(struct sn_irq_info *sn_irq_info); + +extern int sn_force_interrupt_flag; +extern int sn_ioif_inited; +struct sn_irq_info **sn_irq; + +static inline uint64_t sn_intr_alloc(nasid_t local_nasid, int local_widget, + u64 sn_irq_info, + int req_irq, nasid_t req_nasid, + int req_slice) +{ + struct ia64_sal_retval ret_stuff; + ret_stuff.status = 0; + ret_stuff.v0 = 0; + + SAL_CALL_NOLOCK(ret_stuff, (u64) SN_SAL_IOIF_INTERRUPT, + (u64) SAL_INTR_ALLOC, (u64) local_nasid, + (u64) local_widget, (u64) sn_irq_info, (u64) req_irq, + (u64) req_nasid, (u64) req_slice); + return ret_stuff.status; +} + +static inline void sn_intr_free(nasid_t local_nasid, int local_widget, + struct sn_irq_info *sn_irq_info) +{ + struct ia64_sal_retval ret_stuff; + ret_stuff.status = 0; + ret_stuff.v0 = 0; + + SAL_CALL_NOLOCK(ret_stuff, (u64) SN_SAL_IOIF_INTERRUPT, + (u64) SAL_INTR_FREE, (u64) local_nasid, + (u64) local_widget, (u64) sn_irq_info->irq_irq, + (u64) sn_irq_info->irq_cookie, 0, 0); +} + +static unsigned int sn_startup_irq(unsigned int irq) +{ + return 0; +} + +static void sn_shutdown_irq(unsigned int irq) +{ +} + +static void sn_disable_irq(unsigned int irq) +{ +} + +static void sn_enable_irq(unsigned int irq) +{ +} + +static void sn_ack_irq(unsigned int irq) +{ + uint64_t event_occurred, mask = 0; + int nasid; + + irq = irq & 0xff; + nasid = get_nasid(); + event_occurred = + HUB_L((uint64_t *) GLOBAL_MMR_ADDR(nasid, SH_EVENT_OCCURRED)); + if (event_occurred & SH_EVENT_OCCURRED_UART_INT_MASK) { + mask |= (1 << SH_EVENT_OCCURRED_UART_INT_SHFT); + } + if (event_occurred & SH_EVENT_OCCURRED_IPI_INT_MASK) { + mask |= (1 << SH_EVENT_OCCURRED_IPI_INT_SHFT); + } + if (event_occurred & SH_EVENT_OCCURRED_II_INT0_MASK) { + mask |= (1 << SH_EVENT_OCCURRED_II_INT0_SHFT); + } + if (event_occurred & SH_EVENT_OCCURRED_II_INT1_MASK) { + mask |= (1 << SH_EVENT_OCCURRED_II_INT1_SHFT); + } + HUB_S((uint64_t *) GLOBAL_MMR_ADDR(nasid, SH_EVENT_OCCURRED_ALIAS), + mask); + __set_bit(irq, (volatile void *)pda->sn_in_service_ivecs); + + move_irq(irq); +} + +static void sn_end_irq(unsigned int irq) +{ + int nasid; + int ivec; + uint64_t event_occurred; + + ivec = irq & 0xff; + if (ivec == SGI_UART_VECTOR) { + nasid = get_nasid(); + event_occurred = HUB_L((uint64_t *) GLOBAL_MMR_ADDR + (nasid, SH_EVENT_OCCURRED)); + /* If the UART bit is set here, we may have received an + * interrupt from the UART that the driver missed. To + * make sure, we IPI ourselves to force us to look again. + */ + if (event_occurred & SH_EVENT_OCCURRED_UART_INT_MASK) { + platform_send_ipi(smp_processor_id(), SGI_UART_VECTOR, + IA64_IPI_DM_INT, 0); + } + } + __clear_bit(ivec, (volatile void *)pda->sn_in_service_ivecs); + if (sn_force_interrupt_flag) + force_interrupt(irq); +} + +static void sn_set_affinity_irq(unsigned int irq, cpumask_t mask) +{ + struct sn_irq_info *sn_irq_info = sn_irq[irq]; + struct sn_irq_info *tmp_sn_irq_info; + int cpuid, cpuphys; + nasid_t t_nasid; /* nasid to target */ + int t_slice; /* slice to target */ + + /* allocate a temp sn_irq_info struct to get new target info */ + tmp_sn_irq_info = kmalloc(sizeof(*tmp_sn_irq_info), GFP_KERNEL); + if (!tmp_sn_irq_info) + return; + + cpuid = first_cpu(mask); + cpuphys = cpu_physical_id(cpuid); + t_nasid = cpuid_to_nasid(cpuid); + t_slice = cpuid_to_slice(cpuid); + + while (sn_irq_info) { + int status; + int local_widget; + uint64_t bridge = (uint64_t) sn_irq_info->irq_bridge; + nasid_t local_nasid = NASID_GET(bridge); + + if (!bridge) + break; /* irq is not a device interrupt */ + + if (local_nasid & 1) + local_widget = TIO_SWIN_WIDGETNUM(bridge); + else + local_widget = SWIN_WIDGETNUM(bridge); + + /* Free the old PROM sn_irq_info structure */ + sn_intr_free(local_nasid, local_widget, sn_irq_info); + + /* allocate a new PROM sn_irq_info struct */ + status = sn_intr_alloc(local_nasid, local_widget, + __pa(tmp_sn_irq_info), irq, t_nasid, + t_slice); + + if (status == 0) { + /* Update kernels sn_irq_info with new target info */ + unregister_intr_pda(sn_irq_info); + sn_irq_info->irq_cpuid = cpuid; + sn_irq_info->irq_nasid = t_nasid; + sn_irq_info->irq_slice = t_slice; + sn_irq_info->irq_xtalkaddr = + tmp_sn_irq_info->irq_xtalkaddr; + sn_irq_info->irq_cookie = tmp_sn_irq_info->irq_cookie; + register_intr_pda(sn_irq_info); + + if (IS_PCI_BRIDGE_ASIC(sn_irq_info->irq_bridge_type)) { + pcibr_change_devices_irq(sn_irq_info); + } + + sn_irq_info = sn_irq_info->irq_next; + +#ifdef CONFIG_SMP + set_irq_affinity_info((irq & 0xff), cpuphys, 0); +#endif + } else { + break; /* snp_affinity failed the intr_alloc */ + } + } + kfree(tmp_sn_irq_info); +} + +struct hw_interrupt_type irq_type_sn = { + "SN hub", + sn_startup_irq, + sn_shutdown_irq, + sn_enable_irq, + sn_disable_irq, + sn_ack_irq, + sn_end_irq, + sn_set_affinity_irq +}; + +unsigned int sn_local_vector_to_irq(u8 vector) +{ + return (CPU_VECTOR_TO_IRQ(smp_processor_id(), vector)); +} + +void sn_irq_init(void) +{ + int i; + irq_desc_t *base_desc = irq_desc; + + for (i = 0; i < NR_IRQS; i++) { + if (base_desc[i].handler == &no_irq_type) { + base_desc[i].handler = &irq_type_sn; + } + } +} + +static void register_intr_pda(struct sn_irq_info *sn_irq_info) +{ + int irq = sn_irq_info->irq_irq; + int cpu = sn_irq_info->irq_cpuid; + + if (pdacpu(cpu)->sn_last_irq < irq) { + pdacpu(cpu)->sn_last_irq = irq; + } + + if (pdacpu(cpu)->sn_first_irq == 0 || pdacpu(cpu)->sn_first_irq > irq) { + pdacpu(cpu)->sn_first_irq = irq; + } +} + +static void unregister_intr_pda(struct sn_irq_info *sn_irq_info) +{ + int irq = sn_irq_info->irq_irq; + int cpu = sn_irq_info->irq_cpuid; + struct sn_irq_info *tmp_irq_info; + int i, foundmatch; + + if (pdacpu(cpu)->sn_last_irq == irq) { + foundmatch = 0; + for (i = pdacpu(cpu)->sn_last_irq - 1; i; i--) { + tmp_irq_info = sn_irq[i]; + while (tmp_irq_info) { + if (tmp_irq_info->irq_cpuid == cpu) { + foundmatch++; + break; + } + tmp_irq_info = tmp_irq_info->irq_next; + } + if (foundmatch) { + break; + } + } + pdacpu(cpu)->sn_last_irq = i; + } + + if (pdacpu(cpu)->sn_first_irq == irq) { + foundmatch = 0; + for (i = pdacpu(cpu)->sn_first_irq + 1; i < NR_IRQS; i++) { + tmp_irq_info = sn_irq[i]; + while (tmp_irq_info) { + if (tmp_irq_info->irq_cpuid == cpu) { + foundmatch++; + break; + } + tmp_irq_info = tmp_irq_info->irq_next; + } + if (foundmatch) { + break; + } + } + pdacpu(cpu)->sn_first_irq = ((i == NR_IRQS) ? 0 : i); + } +} + +struct sn_irq_info *sn_irq_alloc(nasid_t local_nasid, int local_widget, int irq, + nasid_t nasid, int slice) +{ + struct sn_irq_info *sn_irq_info; + int status; + + sn_irq_info = kmalloc(sizeof(*sn_irq_info), GFP_KERNEL); + if (sn_irq_info == NULL) + return NULL; + + memset(sn_irq_info, 0x0, sizeof(*sn_irq_info)); + + status = + sn_intr_alloc(local_nasid, local_widget, __pa(sn_irq_info), irq, + nasid, slice); + + if (status) { + kfree(sn_irq_info); + return NULL; + } else { + return sn_irq_info; + } +} + +void sn_irq_free(struct sn_irq_info *sn_irq_info) +{ + uint64_t bridge = (uint64_t) sn_irq_info->irq_bridge; + nasid_t local_nasid = NASID_GET(bridge); + int local_widget; + + if (local_nasid & 1) /* tio check */ + local_widget = TIO_SWIN_WIDGETNUM(bridge); + else + local_widget = SWIN_WIDGETNUM(bridge); + + sn_intr_free(local_nasid, local_widget, sn_irq_info); + + kfree(sn_irq_info); +} + +void sn_irq_fixup(struct pci_dev *pci_dev, struct sn_irq_info *sn_irq_info) +{ + nasid_t nasid = sn_irq_info->irq_nasid; + int slice = sn_irq_info->irq_slice; + int cpu = nasid_slice_to_cpuid(nasid, slice); + + sn_irq_info->irq_cpuid = cpu; + sn_irq_info->irq_pciioinfo = SN_PCIDEV_INFO(pci_dev); + + /* link it into the sn_irq[irq] list */ + sn_irq_info->irq_next = sn_irq[sn_irq_info->irq_irq]; + sn_irq[sn_irq_info->irq_irq] = sn_irq_info; + + (void)register_intr_pda(sn_irq_info); +} + +static void force_interrupt(int irq) +{ + struct sn_irq_info *sn_irq_info; + + if (!sn_ioif_inited) + return; + sn_irq_info = sn_irq[irq]; + while (sn_irq_info) { + if (IS_PCI_BRIDGE_ASIC(sn_irq_info->irq_bridge_type) && + (sn_irq_info->irq_bridge != NULL)) { + pcibr_force_interrupt(sn_irq_info); + } + sn_irq_info = sn_irq_info->irq_next; + } +} + +/* + * Check for lost interrupts. If the PIC int_status reg. says that + * an interrupt has been sent, but not handled, and the interrupt + * is not pending in either the cpu irr regs or in the soft irr regs, + * and the interrupt is not in service, then the interrupt may have + * been lost. Force an interrupt on that pin. It is possible that + * the interrupt is in flight, so we may generate a spurious interrupt, + * but we should never miss a real lost interrupt. + */ +static void sn_check_intr(int irq, struct sn_irq_info *sn_irq_info) +{ + uint64_t regval; + int irr_reg_num; + int irr_bit; + uint64_t irr_reg; + struct pcidev_info *pcidev_info; + struct pcibus_info *pcibus_info; + + pcidev_info = (struct pcidev_info *)sn_irq_info->irq_pciioinfo; + if (!pcidev_info) + return; + + pcibus_info = + (struct pcibus_info *)pcidev_info->pdi_host_pcidev_info-> + pdi_pcibus_info; + regval = pcireg_intr_status_get(pcibus_info); + + irr_reg_num = irq_to_vector(irq) / 64; + irr_bit = irq_to_vector(irq) % 64; + switch (irr_reg_num) { + case 0: + irr_reg = ia64_getreg(_IA64_REG_CR_IRR0); + break; + case 1: + irr_reg = ia64_getreg(_IA64_REG_CR_IRR1); + break; + case 2: + irr_reg = ia64_getreg(_IA64_REG_CR_IRR2); + break; + case 3: + irr_reg = ia64_getreg(_IA64_REG_CR_IRR3); + break; + } + if (!test_bit(irr_bit, &irr_reg)) { + if (!test_bit(irq, pda->sn_soft_irr)) { + if (!test_bit(irq, pda->sn_in_service_ivecs)) { + regval &= 0xff; + if (sn_irq_info->irq_int_bit & regval & + sn_irq_info->irq_last_intr) { + regval &= + ~(sn_irq_info-> + irq_int_bit & regval); + pcibr_force_interrupt(sn_irq_info); + } + } + } + } + sn_irq_info->irq_last_intr = regval; +} + +void sn_lb_int_war_check(void) +{ + int i; + + if (!sn_ioif_inited || pda->sn_first_irq == 0) + return; + for (i = pda->sn_first_irq; i <= pda->sn_last_irq; i++) { + struct sn_irq_info *sn_irq_info = sn_irq[i]; + while (sn_irq_info) { + /* Only call for PCI bridges that are fully initialized. */ + if (IS_PCI_BRIDGE_ASIC(sn_irq_info->irq_bridge_type) && + (sn_irq_info->irq_bridge != NULL)) { + sn_check_intr(i, sn_irq_info); + } + sn_irq_info = sn_irq_info->irq_next; + } + } +} |