1// SPDX-License-Identifier: GPL-2.0-or-later 2/* 3 * Copyright 2011 IBM Corporation. 4 */ 5#include <linux/types.h> 6#include <linux/kernel.h> 7#include <linux/irq.h> 8#include <linux/smp.h> 9#include <linux/interrupt.h> 10#include <linux/cpu.h> 11#include <linux/of.h> 12 13#include <asm/smp.h> 14#include <asm/irq.h> 15#include <asm/errno.h> 16#include <asm/xics.h> 17#include <asm/io.h> 18#include <asm/hvcall.h> 19 20static inline unsigned int icp_hv_get_xirr(unsigned char cppr) 21{ 22 unsigned long retbuf[PLPAR_HCALL_BUFSIZE]; 23 long rc; 24 unsigned int ret = XICS_IRQ_SPURIOUS; 25 26 rc = plpar_hcall(H_XIRR, retbuf, cppr); 27 if (rc == H_SUCCESS) { 28 ret = (unsigned int)retbuf[0]; 29 } else { 30 pr_err("%s: bad return code xirr cppr=0x%x returned %ld\n", 31 __func__, cppr, rc); 32 WARN_ON_ONCE(1); 33 } 34 35 return ret; 36} 37 38static inline void icp_hv_set_cppr(u8 value) 39{ 40 long rc = plpar_hcall_norets(H_CPPR, value); 41 if (rc != H_SUCCESS) { 42 pr_err("%s: bad return code cppr cppr=0x%x returned %ld\n", 43 __func__, value, rc); 44 WARN_ON_ONCE(1); 45 } 46} 47 48static inline void icp_hv_set_xirr(unsigned int value) 49{ 50 long rc = plpar_hcall_norets(H_EOI, value); 51 if (rc != H_SUCCESS) { 52 pr_err("%s: bad return code eoi xirr=0x%x returned %ld\n", 53 __func__, value, rc); 54 WARN_ON_ONCE(1); 55 icp_hv_set_cppr(value >> 24); 56 } 57} 58 59static inline void icp_hv_set_qirr(int n_cpu , u8 value) 60{ 61 int hw_cpu = get_hard_smp_processor_id(n_cpu); 62 long rc; 63 64 /* Make sure all previous accesses are ordered before IPI sending */ 65 mb(); 66 rc = plpar_hcall_norets(H_IPI, hw_cpu, value); 67 if (rc != H_SUCCESS) { 68 pr_err("%s: bad return code qirr cpu=%d hw_cpu=%d mfrr=0x%x " 69 "returned %ld\n", __func__, n_cpu, hw_cpu, value, rc); 70 WARN_ON_ONCE(1); 71 } 72} 73 74static void icp_hv_eoi(struct irq_data *d) 75{ 76 unsigned int hw_irq = (unsigned int)irqd_to_hwirq(d); 77 78 iosync(); 79 icp_hv_set_xirr((xics_pop_cppr() << 24) | hw_irq); 80} 81 82static void icp_hv_teardown_cpu(void) 83{ 84 int cpu = smp_processor_id(); 85 86 /* Clear any pending IPI */ 87 icp_hv_set_qirr(cpu, 0xff); 88} 89 90static void icp_hv_flush_ipi(void) 91{ 92 /* We take the ipi irq but and never return so we 93 * need to EOI the IPI, but want to leave our priority 0 94 * 95 * should we check all the other interrupts too? 96 * should we be flagging idle loop instead? 97 * or creating some task to be scheduled? 98 */ 99 100 icp_hv_set_xirr((0x00 << 24) | XICS_IPI); 101} 102 103static unsigned int icp_hv_get_irq(void) 104{ 105 unsigned int xirr = icp_hv_get_xirr(xics_cppr_top()); 106 unsigned int vec = xirr & 0x00ffffff; 107 unsigned int irq; 108 109 if (vec == XICS_IRQ_SPURIOUS) 110 return 0; 111 112 irq = irq_find_mapping(xics_host, vec); 113 if (likely(irq)) { 114 xics_push_cppr(vec); 115 return irq; 116 } 117 118 /* We don't have a linux mapping, so have rtas mask it. */ 119 xics_mask_unknown_vec(vec); 120 121 /* We might learn about it later, so EOI it */ 122 icp_hv_set_xirr(xirr); 123 124 return 0; 125} 126 127static void icp_hv_set_cpu_priority(unsigned char cppr) 128{ 129 xics_set_base_cppr(cppr); 130 icp_hv_set_cppr(cppr); 131 iosync(); 132} 133 134#ifdef CONFIG_SMP 135 136static void icp_hv_cause_ipi(int cpu) 137{ 138 icp_hv_set_qirr(cpu, IPI_PRIORITY); 139} 140 141static irqreturn_t icp_hv_ipi_action(int irq, void *dev_id) 142{ 143 int cpu = smp_processor_id(); 144 145 icp_hv_set_qirr(cpu, 0xff); 146 147 return smp_ipi_demux(); 148} 149 150#endif /* CONFIG_SMP */ 151 152static const struct icp_ops icp_hv_ops = { 153 .get_irq = icp_hv_get_irq, 154 .eoi = icp_hv_eoi, 155 .set_priority = icp_hv_set_cpu_priority, 156 .teardown_cpu = icp_hv_teardown_cpu, 157 .flush_ipi = icp_hv_flush_ipi, 158#ifdef CONFIG_SMP 159 .ipi_action = icp_hv_ipi_action, 160 .cause_ipi = icp_hv_cause_ipi, 161#endif 162}; 163 164int icp_hv_init(void) 165{ 166 struct device_node *np; 167 168 np = of_find_compatible_node(NULL, NULL, "ibm,ppc-xicp"); 169 if (!np) 170 np = of_find_node_by_type(NULL, 171 "PowerPC-External-Interrupt-Presentation"); 172 if (!np) 173 return -ENODEV; 174 175 icp_ops = &icp_hv_ops; 176 177 of_node_put(np); 178 return 0; 179} 180 181