]>
Commit | Line | Data |
---|---|---|
2874c5fd | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
9f0fd049 AP |
2 | /* |
3 | * This file implements an irqchip for OPAL events. Whenever there is | |
4 | * an interrupt that is handled by OPAL we get passed a list of events | |
5 | * that Linux needs to do something about. These basically look like | |
6 | * interrupts to Linux so we implement an irqchip to handle them. | |
7 | * | |
8 | * Copyright Alistair Popple, IBM Corporation 2014. | |
9f0fd049 AP |
9 | */ |
10 | #include <linux/bitops.h> | |
11 | #include <linux/irq.h> | |
12 | #include <linux/irqchip.h> | |
13 | #include <linux/irqdomain.h> | |
14 | #include <linux/interrupt.h> | |
15 | #include <linux/module.h> | |
16 | #include <linux/of.h> | |
17 | #include <linux/platform_device.h> | |
18 | #include <linux/kthread.h> | |
19 | #include <linux/delay.h> | |
20 | #include <linux/slab.h> | |
77b5f703 | 21 | #include <linux/of_irq.h> |
9f0fd049 AP |
22 | |
23 | #include <asm/machdep.h> | |
24 | #include <asm/opal.h> | |
25 | ||
26 | #include "powernv.h" | |
27 | ||
28 | /* Maximum number of events supported by OPAL firmware */ | |
29 | #define MAX_NUM_EVENTS 64 | |
30 | ||
31 | struct opal_event_irqchip { | |
32 | struct irq_chip irqchip; | |
33 | struct irq_domain *domain; | |
34 | unsigned long mask; | |
35 | }; | |
36 | static struct opal_event_irqchip opal_event_irqchip; | |
56c0b48b | 37 | static u64 last_outstanding_events; |
77b5f703 BH |
38 | static int opal_irq_count; |
39 | static struct resource *opal_irqs; | |
9f0fd049 | 40 | |
56c0b48b | 41 | void opal_handle_events(void) |
25642e14 | 42 | { |
56c0b48b NP |
43 | __be64 events = 0; |
44 | u64 e; | |
45 | ||
46 | e = READ_ONCE(last_outstanding_events) & opal_event_irqchip.mask; | |
47 | again: | |
48 | while (e) { | |
49 | int virq, hwirq; | |
50 | ||
51 | hwirq = fls64(e) - 1; | |
52 | e &= ~BIT_ULL(hwirq); | |
53 | ||
54 | local_irq_disable(); | |
55 | virq = irq_find_mapping(opal_event_irqchip.domain, hwirq); | |
56 | if (virq) { | |
57 | irq_enter(); | |
58 | generic_handle_irq(virq); | |
59 | irq_exit(); | |
60 | } | |
61 | local_irq_enable(); | |
25642e14 | 62 | |
56c0b48b | 63 | cond_resched(); |
25642e14 | 64 | } |
56c0b48b NP |
65 | last_outstanding_events = 0; |
66 | if (opal_poll_events(&events) != OPAL_SUCCESS) | |
67 | return; | |
68 | e = be64_to_cpu(events) & opal_event_irqchip.mask; | |
69 | if (e) | |
70 | goto again; | |
71 | } | |
25642e14 | 72 | |
56c0b48b NP |
73 | bool opal_have_pending_events(void) |
74 | { | |
75 | if (last_outstanding_events & opal_event_irqchip.mask) | |
76 | return true; | |
77 | return false; | |
25642e14 AP |
78 | } |
79 | ||
9f0fd049 AP |
80 | static void opal_event_mask(struct irq_data *d) |
81 | { | |
82 | clear_bit(d->hwirq, &opal_event_irqchip.mask); | |
83 | } | |
84 | ||
85 | static void opal_event_unmask(struct irq_data *d) | |
86 | { | |
87 | set_bit(d->hwirq, &opal_event_irqchip.mask); | |
56c0b48b NP |
88 | if (opal_have_pending_events()) |
89 | opal_wake_poller(); | |
9f0fd049 AP |
90 | } |
91 | ||
92 | static int opal_event_set_type(struct irq_data *d, unsigned int flow_type) | |
93 | { | |
94 | /* | |
95 | * For now we only support level triggered events. The irq | |
96 | * handler will be called continuously until the event has | |
97 | * been cleared in OPAL. | |
98 | */ | |
99 | if (flow_type != IRQ_TYPE_LEVEL_HIGH) | |
100 | return -EINVAL; | |
101 | ||
102 | return 0; | |
103 | } | |
104 | ||
105 | static struct opal_event_irqchip opal_event_irqchip = { | |
106 | .irqchip = { | |
107 | .name = "OPAL EVT", | |
108 | .irq_mask = opal_event_mask, | |
109 | .irq_unmask = opal_event_unmask, | |
110 | .irq_set_type = opal_event_set_type, | |
111 | }, | |
112 | .mask = 0, | |
113 | }; | |
114 | ||
115 | static int opal_event_map(struct irq_domain *d, unsigned int irq, | |
116 | irq_hw_number_t hwirq) | |
117 | { | |
118 | irq_set_chip_data(irq, &opal_event_irqchip); | |
119 | irq_set_chip_and_handler(irq, &opal_event_irqchip.irqchip, | |
120 | handle_level_irq); | |
121 | ||
122 | return 0; | |
123 | } | |
124 | ||
9f0fd049 AP |
125 | static irqreturn_t opal_interrupt(int irq, void *data) |
126 | { | |
127 | __be64 events; | |
128 | ||
129 | opal_handle_interrupt(virq_to_hw(irq), &events); | |
56c0b48b NP |
130 | last_outstanding_events = be64_to_cpu(events); |
131 | if (opal_have_pending_events()) | |
132 | opal_wake_poller(); | |
9f0fd049 AP |
133 | |
134 | return IRQ_HANDLED; | |
135 | } | |
136 | ||
ad3aedfb MZ |
137 | static int opal_event_match(struct irq_domain *h, struct device_node *node, |
138 | enum irq_domain_bus_token bus_token) | |
9f0fd049 | 139 | { |
5d4c9bc7 | 140 | return irq_domain_get_of_node(h) == node; |
9f0fd049 AP |
141 | } |
142 | ||
143 | static int opal_event_xlate(struct irq_domain *h, struct device_node *np, | |
144 | const u32 *intspec, unsigned int intsize, | |
145 | irq_hw_number_t *out_hwirq, unsigned int *out_flags) | |
146 | { | |
147 | *out_hwirq = intspec[0]; | |
148 | *out_flags = IRQ_TYPE_LEVEL_HIGH; | |
149 | ||
150 | return 0; | |
151 | } | |
152 | ||
153 | static const struct irq_domain_ops opal_event_domain_ops = { | |
154 | .match = opal_event_match, | |
155 | .map = opal_event_map, | |
156 | .xlate = opal_event_xlate, | |
157 | }; | |
158 | ||
159 | void opal_event_shutdown(void) | |
160 | { | |
161 | unsigned int i; | |
162 | ||
163 | /* First free interrupts, which will also mask them */ | |
164 | for (i = 0; i < opal_irq_count; i++) { | |
77b5f703 | 165 | if (!opal_irqs || !opal_irqs[i].start) |
c6baa077 ME |
166 | continue; |
167 | ||
c0beffc4 | 168 | if (in_interrupt() || irqs_disabled()) |
77b5f703 | 169 | disable_irq_nosync(opal_irqs[i].start); |
c6baa077 | 170 | else |
77b5f703 | 171 | free_irq(opal_irqs[i].start, NULL); |
c6baa077 | 172 | |
77b5f703 | 173 | opal_irqs[i].start = 0; |
9f0fd049 AP |
174 | } |
175 | } | |
176 | ||
177 | int __init opal_event_init(void) | |
178 | { | |
179 | struct device_node *dn, *opal_node; | |
77b5f703 BH |
180 | bool old_style = false; |
181 | int i, rc = 0; | |
9f0fd049 AP |
182 | |
183 | opal_node = of_find_node_by_path("/ibm,opal"); | |
184 | if (!opal_node) { | |
185 | pr_warn("opal: Node not found\n"); | |
186 | return -ENODEV; | |
187 | } | |
188 | ||
189 | /* If dn is NULL it means the domain won't be linked to a DT | |
190 | * node so therefore irq_of_parse_and_map(...) wont work. But | |
191 | * that shouldn't be problem because if we're running a | |
192 | * version of skiboot that doesn't have the dn then the | |
193 | * devices won't have the correct properties and will have to | |
194 | * fall back to the legacy method (opal_event_request(...)) | |
195 | * anyway. */ | |
196 | dn = of_find_compatible_node(NULL, NULL, "ibm,opal-event"); | |
197 | opal_event_irqchip.domain = irq_domain_add_linear(dn, MAX_NUM_EVENTS, | |
198 | &opal_event_domain_ops, &opal_event_irqchip); | |
199 | of_node_put(dn); | |
200 | if (!opal_event_irqchip.domain) { | |
201 | pr_warn("opal: Unable to create irq domain\n"); | |
202 | rc = -ENOMEM; | |
203 | goto out; | |
204 | } | |
205 | ||
77b5f703 BH |
206 | /* Look for new-style (standard) "interrupts" property */ |
207 | opal_irq_count = of_irq_count(opal_node); | |
2717a33d | 208 | |
77b5f703 BH |
209 | /* Absent ? Look for the old one */ |
210 | if (opal_irq_count < 1) { | |
211 | /* Get opal-interrupts property and names if present */ | |
212 | rc = of_property_count_u32_elems(opal_node, "opal-interrupts"); | |
213 | if (rc > 0) | |
214 | opal_irq_count = rc; | |
215 | old_style = true; | |
216 | } | |
9f0fd049 | 217 | |
77b5f703 BH |
218 | /* No interrupts ? Bail out */ |
219 | if (!opal_irq_count) | |
220 | goto out; | |
2717a33d | 221 | |
77b5f703 BH |
222 | pr_debug("OPAL: Found %d interrupts reserved for OPAL using %s scheme\n", |
223 | opal_irq_count, old_style ? "old" : "new"); | |
2717a33d | 224 | |
77b5f703 BH |
225 | /* Allocate an IRQ resources array */ |
226 | opal_irqs = kcalloc(opal_irq_count, sizeof(struct resource), GFP_KERNEL); | |
227 | if (WARN_ON(!opal_irqs)) { | |
228 | rc = -ENOMEM; | |
229 | goto out; | |
2717a33d BH |
230 | } |
231 | ||
77b5f703 BH |
232 | /* Build the resources array */ |
233 | if (old_style) { | |
234 | /* Old style "opal-interrupts" property */ | |
235 | for (i = 0; i < opal_irq_count; i++) { | |
236 | struct resource *r = &opal_irqs[i]; | |
237 | const char *name = NULL; | |
238 | u32 hw_irq; | |
239 | int virq; | |
240 | ||
241 | rc = of_property_read_u32_index(opal_node, "opal-interrupts", | |
242 | i, &hw_irq); | |
243 | if (WARN_ON(rc < 0)) { | |
244 | opal_irq_count = i; | |
245 | break; | |
246 | } | |
247 | of_property_read_string_index(opal_node, "opal-interrupts-names", | |
248 | i, &name); | |
249 | virq = irq_create_mapping(NULL, hw_irq); | |
250 | if (!virq) { | |
251 | pr_warn("Failed to map OPAL irq 0x%x\n", hw_irq); | |
252 | continue; | |
253 | } | |
254 | r->start = r->end = virq; | |
255 | r->flags = IORESOURCE_IRQ | IRQ_TYPE_LEVEL_LOW; | |
256 | r->name = name; | |
257 | } | |
258 | } else { | |
259 | /* new style standard "interrupts" property */ | |
260 | rc = of_irq_to_resource_table(opal_node, opal_irqs, opal_irq_count); | |
261 | if (WARN_ON(rc < 0)) { | |
262 | opal_irq_count = 0; | |
263 | kfree(opal_irqs); | |
264 | goto out; | |
265 | } | |
266 | if (WARN_ON(rc < opal_irq_count)) | |
267 | opal_irq_count = rc; | |
268 | } | |
2717a33d BH |
269 | |
270 | /* Install interrupt handlers */ | |
271 | for (i = 0; i < opal_irq_count; i++) { | |
77b5f703 BH |
272 | struct resource *r = &opal_irqs[i]; |
273 | const char *name; | |
9f0fd049 | 274 | |
77b5f703 BH |
275 | /* Prefix name */ |
276 | if (r->name && strlen(r->name)) | |
277 | name = kasprintf(GFP_KERNEL, "opal-%s", r->name); | |
2717a33d BH |
278 | else |
279 | name = kasprintf(GFP_KERNEL, "opal"); | |
280 | ||
9f0fd049 | 281 | /* Install interrupt handler */ |
77b5f703 | 282 | rc = request_irq(r->start, opal_interrupt, r->flags & IRQD_TRIGGER_MASK, |
2717a33d | 283 | name, NULL); |
9f0fd049 | 284 | if (rc) { |
77b5f703 | 285 | pr_warn("Error %d requesting OPAL irq %d\n", rc, (int)r->start); |
9f0fd049 AP |
286 | continue; |
287 | } | |
9f0fd049 | 288 | } |
77b5f703 BH |
289 | rc = 0; |
290 | out: | |
9f0fd049 AP |
291 | of_node_put(opal_node); |
292 | return rc; | |
293 | } | |
02b6505c | 294 | machine_arch_initcall(powernv, opal_event_init); |
9f0fd049 AP |
295 | |
296 | /** | |
297 | * opal_event_request(unsigned int opal_event_nr) - Request an event | |
298 | * @opal_event_nr: the opal event number to request | |
299 | * | |
300 | * This routine can be used to find the linux virq number which can | |
301 | * then be passed to request_irq to assign a handler for a particular | |
302 | * opal event. This should only be used by legacy devices which don't | |
303 | * have proper device tree bindings. Most devices should use | |
304 | * irq_of_parse_and_map() instead. | |
305 | */ | |
306 | int opal_event_request(unsigned int opal_event_nr) | |
307 | { | |
02b6505c | 308 | if (WARN_ON_ONCE(!opal_event_irqchip.domain)) |
ef24ba70 | 309 | return 0; |
02b6505c | 310 | |
9f0fd049 AP |
311 | return irq_create_mapping(opal_event_irqchip.domain, opal_event_nr); |
312 | } | |
313 | EXPORT_SYMBOL(opal_event_request); |