/* * Copyright 2016 IBM Corporation. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include static void icp_opal_teardown_cpu(void) { int hw_cpu = hard_smp_processor_id(); /* Clear any pending IPI */ opal_int_set_mfrr(hw_cpu, 0xff); } static void icp_opal_flush_ipi(void) { /* * We take the ipi irq but and never return so we need to EOI the IPI, * but want to leave our priority 0. * * Should we check all the other interrupts too? * Should we be flagging idle loop instead? * Or creating some task to be scheduled? */ if (opal_int_eoi((0x00 << 24) | XICS_IPI) > 0) force_external_irq_replay(); } static unsigned int icp_opal_get_xirr(void) { unsigned int kvm_xirr; __be32 hw_xirr; int64_t rc; /* Handle an interrupt latched by KVM first */ kvm_xirr = kvmppc_get_xics_latch(); if (kvm_xirr) return kvm_xirr; /* Then ask OPAL */ rc = opal_int_get_xirr(&hw_xirr, false); if (rc < 0) return 0; return be32_to_cpu(hw_xirr); } static unsigned int icp_opal_get_irq(void) { unsigned int xirr; unsigned int vec; unsigned int irq; xirr = icp_opal_get_xirr(); vec = xirr & 0x00ffffff; if (vec == XICS_IRQ_SPURIOUS) return 0; irq = irq_find_mapping(xics_host, vec); if (likely(irq)) { xics_push_cppr(vec); return irq; } /* We don't have a linux mapping, so have rtas mask it. */ xics_mask_unknown_vec(vec); /* We might learn about it later, so EOI it */ if (opal_int_eoi(xirr) > 0) force_external_irq_replay(); return 0; } static void icp_opal_set_cpu_priority(unsigned char cppr) { /* * Here be dragons. The caller has asked to allow only IPI's and not * external interrupts. But OPAL XIVE doesn't support that. So instead * of allowing no interrupts allow all. That's still not right, but * currently the only caller who does this is xics_migrate_irqs_away() * and it works in that case. */ if (cppr >= DEFAULT_PRIORITY) cppr = LOWEST_PRIORITY; xics_set_base_cppr(cppr); opal_int_set_cppr(cppr); iosync(); } static void icp_opal_eoi(struct irq_data *d) { unsigned int hw_irq = (unsigned int)irqd_to_hwirq(d); int64_t rc; iosync(); rc = opal_int_eoi((xics_pop_cppr() << 24) | hw_irq); /* * EOI tells us whether there are more interrupts to fetch. * * Some HW implementations might not be able to send us another * external interrupt in that case, so we force a replay. */ if (rc > 0) force_external_irq_replay(); } #ifdef CONFIG_SMP static void icp_opal_cause_ipi(int cpu) { int hw_cpu = get_hard_smp_processor_id(cpu); kvmppc_set_host_ipi(cpu); opal_int_set_mfrr(hw_cpu, IPI_PRIORITY); } static irqreturn_t icp_opal_ipi_action(int irq, void *dev_id) { int cpu = smp_processor_id(); kvmppc_clear_host_ipi(cpu); opal_int_set_mfrr(get_hard_smp_processor_id(cpu), 0xff); return smp_ipi_demux(); } /* * Called when an interrupt is received on an off-line CPU to * clear the interrupt, so that the CPU can go back to nap mode. */ void icp_opal_flush_interrupt(void) { unsigned int xirr; unsigned int vec; do { xirr = icp_opal_get_xirr(); vec = xirr & 0x00ffffff; if (vec == XICS_IRQ_SPURIOUS) break; if (vec == XICS_IPI) { /* Clear pending IPI */ int cpu = smp_processor_id(); kvmppc_clear_host_ipi(cpu); opal_int_set_mfrr(get_hard_smp_processor_id(cpu), 0xff); } else { pr_err("XICS: hw interrupt 0x%x to offline cpu, " "disabling\n", vec); xics_mask_unknown_vec(vec); } /* EOI the interrupt */ } while (opal_int_eoi(xirr) > 0); } #endif /* CONFIG_SMP */ static const struct icp_ops icp_opal_ops = { .get_irq = icp_opal_get_irq, .eoi = icp_opal_eoi, .set_priority = icp_opal_set_cpu_priority, .teardown_cpu = icp_opal_teardown_cpu, .flush_ipi = icp_opal_flush_ipi, #ifdef CONFIG_SMP .ipi_action = icp_opal_ipi_action, .cause_ipi = icp_opal_cause_ipi, #endif }; int icp_opal_init(void) { struct device_node *np; np = of_find_compatible_node(NULL, NULL, "ibm,opal-intc"); if (!np) return -ENODEV; icp_ops = &icp_opal_ops; printk("XICS: Using OPAL ICP fallbacks\n"); return 0; }