GNU Linux-libre 5.4.274-gnu1
[releases.git] / arch / powerpc / sysdev / xics / icp-opal.c
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Copyright 2016 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/opal.h>
19 #include <asm/kvm_ppc.h>
20
21 static void icp_opal_teardown_cpu(void)
22 {
23         int hw_cpu = hard_smp_processor_id();
24
25         /* Clear any pending IPI */
26         opal_int_set_mfrr(hw_cpu, 0xff);
27 }
28
29 static void icp_opal_flush_ipi(void)
30 {
31         /*
32          * We take the ipi irq but and never return so we need to EOI the IPI,
33          * but want to leave our priority 0.
34          *
35          * Should we check all the other interrupts too?
36          * Should we be flagging idle loop instead?
37          * Or creating some task to be scheduled?
38          */
39         if (opal_int_eoi((0x00 << 24) | XICS_IPI) > 0)
40                 force_external_irq_replay();
41 }
42
43 static unsigned int icp_opal_get_xirr(void)
44 {
45         unsigned int kvm_xirr;
46         __be32 hw_xirr;
47         int64_t rc;
48
49         /* Handle an interrupt latched by KVM first */
50         kvm_xirr = kvmppc_get_xics_latch();
51         if (kvm_xirr)
52                 return kvm_xirr;
53
54         /* Then ask OPAL */
55         rc = opal_int_get_xirr(&hw_xirr, false);
56         if (rc < 0)
57                 return 0;
58         return be32_to_cpu(hw_xirr);
59 }
60
61 static unsigned int icp_opal_get_irq(void)
62 {
63         unsigned int xirr;
64         unsigned int vec;
65         unsigned int irq;
66
67         xirr = icp_opal_get_xirr();
68         vec = xirr & 0x00ffffff;
69         if (vec == XICS_IRQ_SPURIOUS)
70                 return 0;
71
72         irq = irq_find_mapping(xics_host, vec);
73         if (likely(irq)) {
74                 xics_push_cppr(vec);
75                 return irq;
76         }
77
78         /* We don't have a linux mapping, so have rtas mask it. */
79         xics_mask_unknown_vec(vec);
80
81         /* We might learn about it later, so EOI it */
82         if (opal_int_eoi(xirr) > 0)
83                 force_external_irq_replay();
84
85         return 0;
86 }
87
88 static void icp_opal_set_cpu_priority(unsigned char cppr)
89 {
90         /*
91          * Here be dragons. The caller has asked to allow only IPI's and not
92          * external interrupts. But OPAL XIVE doesn't support that. So instead
93          * of allowing no interrupts allow all. That's still not right, but
94          * currently the only caller who does this is xics_migrate_irqs_away()
95          * and it works in that case.
96          */
97         if (cppr >= DEFAULT_PRIORITY)
98                 cppr = LOWEST_PRIORITY;
99
100         xics_set_base_cppr(cppr);
101         opal_int_set_cppr(cppr);
102         iosync();
103 }
104
105 static void icp_opal_eoi(struct irq_data *d)
106 {
107         unsigned int hw_irq = (unsigned int)irqd_to_hwirq(d);
108         int64_t rc;
109
110         iosync();
111         rc = opal_int_eoi((xics_pop_cppr() << 24) | hw_irq);
112
113         /*
114          * EOI tells us whether there are more interrupts to fetch.
115          *
116          * Some HW implementations might not be able to send us another
117          * external interrupt in that case, so we force a replay.
118          */
119         if (rc > 0)
120                 force_external_irq_replay();
121 }
122
123 #ifdef CONFIG_SMP
124
125 static void icp_opal_cause_ipi(int cpu)
126 {
127         int hw_cpu = get_hard_smp_processor_id(cpu);
128
129         kvmppc_set_host_ipi(cpu);
130         opal_int_set_mfrr(hw_cpu, IPI_PRIORITY);
131 }
132
133 static irqreturn_t icp_opal_ipi_action(int irq, void *dev_id)
134 {
135         int cpu = smp_processor_id();
136
137         kvmppc_clear_host_ipi(cpu);
138         opal_int_set_mfrr(get_hard_smp_processor_id(cpu), 0xff);
139
140         return smp_ipi_demux();
141 }
142
143 /*
144  * Called when an interrupt is received on an off-line CPU to
145  * clear the interrupt, so that the CPU can go back to nap mode.
146  */
147 void icp_opal_flush_interrupt(void)
148 {
149         unsigned int xirr;
150         unsigned int vec;
151
152         do {
153                 xirr = icp_opal_get_xirr();
154                 vec = xirr & 0x00ffffff;
155                 if (vec == XICS_IRQ_SPURIOUS)
156                         break;
157                 if (vec == XICS_IPI) {
158                         /* Clear pending IPI */
159                         int cpu = smp_processor_id();
160                         kvmppc_clear_host_ipi(cpu);
161                         opal_int_set_mfrr(get_hard_smp_processor_id(cpu), 0xff);
162                 } else {
163                         pr_err("XICS: hw interrupt 0x%x to offline cpu, "
164                                "disabling\n", vec);
165                         xics_mask_unknown_vec(vec);
166                 }
167
168                 /* EOI the interrupt */
169         } while (opal_int_eoi(xirr) > 0);
170 }
171
172 #endif /* CONFIG_SMP */
173
174 static const struct icp_ops icp_opal_ops = {
175         .get_irq        = icp_opal_get_irq,
176         .eoi            = icp_opal_eoi,
177         .set_priority   = icp_opal_set_cpu_priority,
178         .teardown_cpu   = icp_opal_teardown_cpu,
179         .flush_ipi      = icp_opal_flush_ipi,
180 #ifdef CONFIG_SMP
181         .ipi_action     = icp_opal_ipi_action,
182         .cause_ipi      = icp_opal_cause_ipi,
183 #endif
184 };
185
186 int icp_opal_init(void)
187 {
188         struct device_node *np;
189
190         np = of_find_compatible_node(NULL, NULL, "ibm,opal-intc");
191         if (!np)
192                 return -ENODEV;
193
194         icp_ops = &icp_opal_ops;
195
196         printk("XICS: Using OPAL ICP fallbacks\n");
197
198         of_node_put(np);
199         return 0;
200 }
201