summaryrefslogtreecommitdiffstats
path: root/arch/powerpc/sysdev/xics/icp-hv.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/powerpc/sysdev/xics/icp-hv.c')
-rw-r--r--arch/powerpc/sysdev/xics/icp-hv.c181
1 files changed, 181 insertions, 0 deletions
diff --git a/arch/powerpc/sysdev/xics/icp-hv.c b/arch/powerpc/sysdev/xics/icp-hv.c
new file mode 100644
index 000000000..cf8db19a4
--- /dev/null
+++ b/arch/powerpc/sysdev/xics/icp-hv.c
@@ -0,0 +1,181 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2011 IBM Corporation.
+ */
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/irq.h>
+#include <linux/smp.h>
+#include <linux/interrupt.h>
+#include <linux/irqdomain.h>
+#include <linux/cpu.h>
+#include <linux/of.h>
+
+#include <asm/smp.h>
+#include <asm/irq.h>
+#include <asm/errno.h>
+#include <asm/xics.h>
+#include <asm/io.h>
+#include <asm/hvcall.h>
+
+static inline unsigned int icp_hv_get_xirr(unsigned char cppr)
+{
+ unsigned long retbuf[PLPAR_HCALL_BUFSIZE];
+ long rc;
+ unsigned int ret = XICS_IRQ_SPURIOUS;
+
+ rc = plpar_hcall(H_XIRR, retbuf, cppr);
+ if (rc == H_SUCCESS) {
+ ret = (unsigned int)retbuf[0];
+ } else {
+ pr_err("%s: bad return code xirr cppr=0x%x returned %ld\n",
+ __func__, cppr, rc);
+ WARN_ON_ONCE(1);
+ }
+
+ return ret;
+}
+
+static inline void icp_hv_set_cppr(u8 value)
+{
+ long rc = plpar_hcall_norets(H_CPPR, value);
+ if (rc != H_SUCCESS) {
+ pr_err("%s: bad return code cppr cppr=0x%x returned %ld\n",
+ __func__, value, rc);
+ WARN_ON_ONCE(1);
+ }
+}
+
+static inline void icp_hv_set_xirr(unsigned int value)
+{
+ long rc = plpar_hcall_norets(H_EOI, value);
+ if (rc != H_SUCCESS) {
+ pr_err("%s: bad return code eoi xirr=0x%x returned %ld\n",
+ __func__, value, rc);
+ WARN_ON_ONCE(1);
+ icp_hv_set_cppr(value >> 24);
+ }
+}
+
+static inline void icp_hv_set_qirr(int n_cpu , u8 value)
+{
+ int hw_cpu = get_hard_smp_processor_id(n_cpu);
+ long rc;
+
+ /* Make sure all previous accesses are ordered before IPI sending */
+ mb();
+ rc = plpar_hcall_norets(H_IPI, hw_cpu, value);
+ if (rc != H_SUCCESS) {
+ pr_err("%s: bad return code qirr cpu=%d hw_cpu=%d mfrr=0x%x "
+ "returned %ld\n", __func__, n_cpu, hw_cpu, value, rc);
+ WARN_ON_ONCE(1);
+ }
+}
+
+static void icp_hv_eoi(struct irq_data *d)
+{
+ unsigned int hw_irq = (unsigned int)irqd_to_hwirq(d);
+
+ iosync();
+ icp_hv_set_xirr((xics_pop_cppr() << 24) | hw_irq);
+}
+
+static void icp_hv_teardown_cpu(void)
+{
+ int cpu = smp_processor_id();
+
+ /* Clear any pending IPI */
+ icp_hv_set_qirr(cpu, 0xff);
+}
+
+static void icp_hv_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?
+ */
+
+ icp_hv_set_xirr((0x00 << 24) | XICS_IPI);
+}
+
+static unsigned int icp_hv_get_irq(void)
+{
+ unsigned int xirr = icp_hv_get_xirr(xics_cppr_top());
+ unsigned int vec = xirr & 0x00ffffff;
+ unsigned int irq;
+
+ 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 */
+ icp_hv_set_xirr(xirr);
+
+ return 0;
+}
+
+static void icp_hv_set_cpu_priority(unsigned char cppr)
+{
+ xics_set_base_cppr(cppr);
+ icp_hv_set_cppr(cppr);
+ iosync();
+}
+
+#ifdef CONFIG_SMP
+
+static void icp_hv_cause_ipi(int cpu)
+{
+ icp_hv_set_qirr(cpu, IPI_PRIORITY);
+}
+
+static irqreturn_t icp_hv_ipi_action(int irq, void *dev_id)
+{
+ int cpu = smp_processor_id();
+
+ icp_hv_set_qirr(cpu, 0xff);
+
+ return smp_ipi_demux();
+}
+
+#endif /* CONFIG_SMP */
+
+static const struct icp_ops icp_hv_ops = {
+ .get_irq = icp_hv_get_irq,
+ .eoi = icp_hv_eoi,
+ .set_priority = icp_hv_set_cpu_priority,
+ .teardown_cpu = icp_hv_teardown_cpu,
+ .flush_ipi = icp_hv_flush_ipi,
+#ifdef CONFIG_SMP
+ .ipi_action = icp_hv_ipi_action,
+ .cause_ipi = icp_hv_cause_ipi,
+#endif
+};
+
+int __init icp_hv_init(void)
+{
+ struct device_node *np;
+
+ np = of_find_compatible_node(NULL, NULL, "ibm,ppc-xicp");
+ if (!np)
+ np = of_find_node_by_type(NULL,
+ "PowerPC-External-Interrupt-Presentation");
+ if (!np)
+ return -ENODEV;
+
+ icp_ops = &icp_hv_ops;
+
+ of_node_put(np);
+ return 0;
+}
+