Commit | Line | Data |
---|---|---|
0c3b4f1a SR |
1 | /* |
2 | * This module supports the iSeries PCI bus interrupt handling | |
3 | * Copyright (C) 20yy <Robert L Holtorf> <IBM Corp> | |
89ef68f0 | 4 | * Copyright (C) 2004-2005 IBM Corporation |
0c3b4f1a SR |
5 | * |
6 | * This program is free software; you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License as published by | |
8 | * the Free Software Foundation; either version 2 of the License, or | |
9 | * (at your option) any later version. | |
10 | * | |
11 | * This program is distributed in the hope that it will be useful, | |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | * GNU General Public License for more details. | |
15 | * | |
16 | * You should have received a copy of the GNU General Public License | |
17 | * along with this program; if not, write to the: | |
18 | * Free Software Foundation, Inc., | |
19 | * 59 Temple Place, Suite 330, | |
20 | * Boston, MA 02111-1307 USA | |
21 | * | |
22 | * Change Activity: | |
23 | * Created, December 13, 2000 by Wayne Holm | |
24 | * End Change Activity | |
25 | */ | |
89ef68f0 | 26 | #include <linux/config.h> |
1da177e4 LT |
27 | #include <linux/pci.h> |
28 | #include <linux/init.h> | |
29 | #include <linux/threads.h> | |
30 | #include <linux/smp.h> | |
31 | #include <linux/param.h> | |
32 | #include <linux/string.h> | |
33 | #include <linux/bootmem.h> | |
34 | #include <linux/ide.h> | |
1da177e4 LT |
35 | #include <linux/irq.h> |
36 | #include <linux/spinlock.h> | |
1da177e4 | 37 | |
1ec65d76 | 38 | #include <asm/iseries/hv_types.h> |
e45423ea | 39 | #include <asm/iseries/hv_lp_event.h> |
8021b8a7 | 40 | #include <asm/iseries/hv_call_xm.h> |
b08567cb SR |
41 | |
42 | #include "irq.h" | |
c6d2ea92 | 43 | #include "call_pci.h" |
1da177e4 | 44 | |
60798c6a SR |
45 | enum pci_event_type { |
46 | pe_bus_created = 0, /* PHB has been created */ | |
47 | pe_bus_error = 1, /* PHB has failed */ | |
48 | pe_bus_failed = 2, /* Msg to Secondary, Primary failed bus */ | |
49 | pe_node_failed = 4, /* Multi-adapter bridge has failed */ | |
50 | pe_node_recovered = 5, /* Multi-adapter bridge has recovered */ | |
51 | pe_bus_recovered = 12, /* PHB has been recovered */ | |
52 | pe_unquiese_bus = 18, /* Secondary bus unqiescing */ | |
53 | pe_bridge_error = 21, /* Bridge Error */ | |
54 | pe_slot_interrupt = 22 /* Slot interrupt */ | |
89ef68f0 SR |
55 | }; |
56 | ||
60798c6a SR |
57 | struct pci_event { |
58 | struct HvLpEvent event; | |
89ef68f0 | 59 | union { |
60798c6a | 60 | u64 __align; /* Align on an 8-byte boundary */ |
89ef68f0 SR |
61 | struct { |
62 | u32 fisr; | |
60798c6a SR |
63 | HvBusNumber bus_number; |
64 | HvSubBusNumber sub_bus_number; | |
65 | HvAgentId dev_id; | |
66 | } slot; | |
67 | struct { | |
68 | HvBusNumber bus_number; | |
69 | HvSubBusNumber sub_bus_number; | |
70 | } bus; | |
71 | struct { | |
72 | HvBusNumber bus_number; | |
73 | HvSubBusNumber sub_bus_number; | |
74 | HvAgentId dev_id; | |
75 | } node; | |
76 | } data; | |
89ef68f0 SR |
77 | }; |
78 | ||
60798c6a | 79 | static void int_received(struct pci_event *event, struct pt_regs *regs) |
89ef68f0 SR |
80 | { |
81 | int irq; | |
5a7b3ff4 SR |
82 | #ifdef CONFIG_IRQSTACKS |
83 | struct thread_info *curtp, *irqtp; | |
84 | #endif | |
89ef68f0 | 85 | |
60798c6a SR |
86 | switch (event->event.xSubtype) { |
87 | case pe_slot_interrupt: | |
88 | irq = event->event.xCorrelationToken; | |
89ef68f0 | 89 | /* Dispatch the interrupt handlers for this irq */ |
5a7b3ff4 SR |
90 | #ifdef CONFIG_IRQSTACKS |
91 | /* Switch to the irq stack to handle this */ | |
92 | curtp = current_thread_info(); | |
93 | irqtp = hardirq_ctx[smp_processor_id()]; | |
94 | if (curtp != irqtp) { | |
95 | irqtp->task = curtp->task; | |
96 | irqtp->flags = 0; | |
60798c6a | 97 | call___do_IRQ(irq, regs, irqtp); |
5a7b3ff4 SR |
98 | irqtp->task = NULL; |
99 | if (irqtp->flags) | |
100 | set_bits(irqtp->flags, &curtp->flags); | |
101 | } else | |
102 | #endif | |
60798c6a | 103 | __do_IRQ(irq, regs); |
89ef68f0 SR |
104 | break; |
105 | /* Ignore error recovery events for now */ | |
60798c6a SR |
106 | case pe_bus_created: |
107 | printk(KERN_INFO "int_received: system bus %d created\n", | |
108 | event->data.bus.bus_number); | |
89ef68f0 | 109 | break; |
60798c6a SR |
110 | case pe_bus_error: |
111 | case pe_bus_failed: | |
112 | printk(KERN_INFO "int_received: system bus %d failed\n", | |
113 | event->data.bus.bus_number); | |
89ef68f0 | 114 | break; |
60798c6a SR |
115 | case pe_bus_recovered: |
116 | case pe_unquiese_bus: | |
117 | printk(KERN_INFO "int_received: system bus %d recovered\n", | |
118 | event->data.bus.bus_number); | |
89ef68f0 | 119 | break; |
60798c6a SR |
120 | case pe_node_failed: |
121 | case pe_bridge_error: | |
89ef68f0 | 122 | printk(KERN_INFO |
60798c6a SR |
123 | "int_received: multi-adapter bridge %d/%d/%d failed\n", |
124 | event->data.node.bus_number, | |
125 | event->data.node.sub_bus_number, | |
126 | event->data.node.dev_id); | |
89ef68f0 | 127 | break; |
60798c6a | 128 | case pe_node_recovered: |
89ef68f0 | 129 | printk(KERN_INFO |
60798c6a SR |
130 | "int_received: multi-adapter bridge %d/%d/%d recovered\n", |
131 | event->data.node.bus_number, | |
132 | event->data.node.sub_bus_number, | |
133 | event->data.node.dev_id); | |
89ef68f0 SR |
134 | break; |
135 | default: | |
136 | printk(KERN_ERR | |
60798c6a SR |
137 | "int_received: unrecognized event subtype 0x%x\n", |
138 | event->event.xSubtype); | |
89ef68f0 SR |
139 | break; |
140 | } | |
141 | } | |
142 | ||
60798c6a | 143 | static void pci_event_handler(struct HvLpEvent *event, struct pt_regs *regs) |
89ef68f0 | 144 | { |
60798c6a SR |
145 | if (event && (event->xType == HvLpEvent_Type_PciIo)) { |
146 | switch (event->xFlags.xFunction) { | |
89ef68f0 | 147 | case HvLpEvent_Function_Int: |
60798c6a | 148 | int_received((struct pci_event *)event, regs); |
89ef68f0 SR |
149 | break; |
150 | case HvLpEvent_Function_Ack: | |
151 | printk(KERN_ERR | |
60798c6a | 152 | "pci_event_handler: unexpected ack received\n"); |
89ef68f0 SR |
153 | break; |
154 | default: | |
155 | printk(KERN_ERR | |
60798c6a SR |
156 | "pci_event_handler: unexpected event function %d\n", |
157 | (int)event->xFlags.xFunction); | |
89ef68f0 SR |
158 | break; |
159 | } | |
60798c6a | 160 | } else if (event) |
89ef68f0 | 161 | printk(KERN_ERR |
60798c6a SR |
162 | "pci_event_handler: Unrecognized PCI event type 0x%x\n", |
163 | (int)event->xType); | |
89ef68f0 | 164 | else |
60798c6a | 165 | printk(KERN_ERR "pci_event_handler: NULL event received\n"); |
89ef68f0 SR |
166 | } |
167 | ||
7f74e79f SR |
168 | /* |
169 | * This is called by init_IRQ. set in ppc_md.init_IRQ by iSeries_setup.c | |
170 | * It must be called before the bus walk. | |
171 | */ | |
172 | void __init iSeries_init_IRQ(void) | |
89ef68f0 | 173 | { |
7f74e79f | 174 | /* Register PCI event handler and open an event path */ |
60798c6a SR |
175 | int ret; |
176 | ||
177 | ret = HvLpEvent_registerHandler(HvLpEvent_Type_PciIo, | |
178 | &pci_event_handler); | |
179 | if (ret == 0) { | |
180 | ret = HvLpEvent_openPath(HvLpEvent_Type_PciIo, 0); | |
181 | if (ret != 0) | |
182 | printk(KERN_ERR "iseries_init_IRQ: open event path " | |
183 | "failed with rc 0x%x\n", ret); | |
89ef68f0 | 184 | } else |
60798c6a SR |
185 | printk(KERN_ERR "iseries_init_IRQ: register handler " |
186 | "failed with rc 0x%x\n", ret); | |
1da177e4 LT |
187 | } |
188 | ||
853f828c | 189 | #define REAL_IRQ_TO_SUBBUS(irq) (((irq) >> 14) & 0xff) |
0c3b4f1a SR |
190 | #define REAL_IRQ_TO_BUS(irq) ((((irq) >> 6) & 0xff) + 1) |
191 | #define REAL_IRQ_TO_IDSEL(irq) ((((irq) >> 3) & 7) + 1) | |
192 | #define REAL_IRQ_TO_FUNC(irq) ((irq) & 7) | |
193 | ||
1da177e4 | 194 | /* |
0c3b4f1a SR |
195 | * This will be called by device drivers (via enable_IRQ) |
196 | * to enable INTA in the bridge interrupt status register. | |
1da177e4 | 197 | */ |
60798c6a | 198 | static void iseries_enable_IRQ(unsigned int irq) |
1da177e4 | 199 | { |
60798c6a SR |
200 | u32 bus, dev_id, function, mask; |
201 | const u32 sub_bus = 0; | |
0c3b4f1a | 202 | unsigned int rirq = virt_irq_to_real_map[irq]; |
1da177e4 | 203 | |
0c3b4f1a SR |
204 | /* The IRQ has already been locked by the caller */ |
205 | bus = REAL_IRQ_TO_BUS(rirq); | |
206 | function = REAL_IRQ_TO_FUNC(rirq); | |
60798c6a | 207 | dev_id = (REAL_IRQ_TO_IDSEL(rirq) << 4) + function; |
1da177e4 | 208 | |
0c3b4f1a SR |
209 | /* Unmask secondary INTA */ |
210 | mask = 0x80000000; | |
60798c6a | 211 | HvCallPci_unmaskInterrupts(bus, sub_bus, dev_id, mask); |
1da177e4 LT |
212 | } |
213 | ||
60798c6a SR |
214 | /* This is called by iseries_activate_IRQs */ |
215 | static unsigned int iseries_startup_IRQ(unsigned int irq) | |
1da177e4 | 216 | { |
60798c6a SR |
217 | u32 bus, dev_id, function, mask; |
218 | const u32 sub_bus = 0; | |
1da177e4 LT |
219 | unsigned int rirq = virt_irq_to_real_map[irq]; |
220 | ||
221 | bus = REAL_IRQ_TO_BUS(rirq); | |
222 | function = REAL_IRQ_TO_FUNC(rirq); | |
60798c6a | 223 | dev_id = (REAL_IRQ_TO_IDSEL(rirq) << 4) + function; |
1da177e4 LT |
224 | |
225 | /* Link the IRQ number to the bridge */ | |
60798c6a | 226 | HvCallXm_connectBusUnit(bus, sub_bus, dev_id, irq); |
1da177e4 LT |
227 | |
228 | /* Unmask bridge interrupts in the FISR */ | |
229 | mask = 0x01010000 << function; | |
60798c6a SR |
230 | HvCallPci_unmaskFisr(bus, sub_bus, dev_id, mask); |
231 | iseries_enable_IRQ(irq); | |
1da177e4 LT |
232 | return 0; |
233 | } | |
234 | ||
235 | /* | |
236 | * This is called out of iSeries_fixup to activate interrupt | |
237 | * generation for usable slots | |
238 | */ | |
239 | void __init iSeries_activate_IRQs() | |
240 | { | |
241 | int irq; | |
242 | unsigned long flags; | |
243 | ||
244 | for_each_irq (irq) { | |
245 | irq_desc_t *desc = get_irq_desc(irq); | |
246 | ||
247 | if (desc && desc->handler && desc->handler->startup) { | |
248 | spin_lock_irqsave(&desc->lock, flags); | |
249 | desc->handler->startup(irq); | |
250 | spin_unlock_irqrestore(&desc->lock, flags); | |
251 | } | |
0c3b4f1a | 252 | } |
1da177e4 LT |
253 | } |
254 | ||
255 | /* this is not called anywhere currently */ | |
60798c6a | 256 | static void iseries_shutdown_IRQ(unsigned int irq) |
1da177e4 | 257 | { |
60798c6a SR |
258 | u32 bus, dev_id, function, mask; |
259 | const u32 sub_bus = 0; | |
1da177e4 LT |
260 | unsigned int rirq = virt_irq_to_real_map[irq]; |
261 | ||
262 | /* irq should be locked by the caller */ | |
263 | bus = REAL_IRQ_TO_BUS(rirq); | |
264 | function = REAL_IRQ_TO_FUNC(rirq); | |
60798c6a | 265 | dev_id = (REAL_IRQ_TO_IDSEL(rirq) << 4) + function; |
1da177e4 LT |
266 | |
267 | /* Invalidate the IRQ number in the bridge */ | |
60798c6a | 268 | HvCallXm_connectBusUnit(bus, sub_bus, dev_id, 0); |
1da177e4 LT |
269 | |
270 | /* Mask bridge interrupts in the FISR */ | |
271 | mask = 0x01010000 << function; | |
60798c6a | 272 | HvCallPci_maskFisr(bus, sub_bus, dev_id, mask); |
1da177e4 LT |
273 | } |
274 | ||
275 | /* | |
276 | * This will be called by device drivers (via disable_IRQ) | |
277 | * to disable INTA in the bridge interrupt status register. | |
278 | */ | |
60798c6a | 279 | static void iseries_disable_IRQ(unsigned int irq) |
1da177e4 | 280 | { |
60798c6a SR |
281 | u32 bus, dev_id, function, mask; |
282 | const u32 sub_bus = 0; | |
1da177e4 LT |
283 | unsigned int rirq = virt_irq_to_real_map[irq]; |
284 | ||
285 | /* The IRQ has already been locked by the caller */ | |
286 | bus = REAL_IRQ_TO_BUS(rirq); | |
287 | function = REAL_IRQ_TO_FUNC(rirq); | |
60798c6a | 288 | dev_id = (REAL_IRQ_TO_IDSEL(rirq) << 4) + function; |
1da177e4 LT |
289 | |
290 | /* Mask secondary INTA */ | |
291 | mask = 0x80000000; | |
60798c6a | 292 | HvCallPci_maskInterrupts(bus, sub_bus, dev_id, mask); |
1da177e4 LT |
293 | } |
294 | ||
60798c6a | 295 | static void iseries_end_IRQ(unsigned int irq) |
1da177e4 | 296 | { |
853f828c SR |
297 | unsigned int rirq = virt_irq_to_real_map[irq]; |
298 | ||
299 | HvCallPci_eoi(REAL_IRQ_TO_BUS(rirq), REAL_IRQ_TO_SUBBUS(rirq), | |
300 | (REAL_IRQ_TO_IDSEL(rirq) << 4) + REAL_IRQ_TO_FUNC(rirq)); | |
1da177e4 | 301 | } |
0c3b4f1a SR |
302 | |
303 | static hw_irq_controller iSeries_IRQ_handler = { | |
304 | .typename = "iSeries irq controller", | |
60798c6a SR |
305 | .startup = iseries_startup_IRQ, |
306 | .shutdown = iseries_shutdown_IRQ, | |
307 | .enable = iseries_enable_IRQ, | |
308 | .disable = iseries_disable_IRQ, | |
309 | .end = iseries_end_IRQ | |
0c3b4f1a SR |
310 | }; |
311 | ||
312 | /* | |
313 | * This is called out of iSeries_scan_slot to allocate an IRQ for an EADS slot | |
314 | * It calculates the irq value for the slot. | |
853f828c | 315 | * Note that sub_bus is always 0 (at the moment at least). |
0c3b4f1a | 316 | */ |
853f828c SR |
317 | int __init iSeries_allocate_IRQ(HvBusNumber bus, |
318 | HvSubBusNumber sub_bus, HvAgentId dev_id) | |
0c3b4f1a | 319 | { |
d9ae2bad SR |
320 | int virtirq; |
321 | unsigned int realirq; | |
60798c6a SR |
322 | u8 idsel = (dev_id >> 4); |
323 | u8 function = dev_id & 7; | |
0c3b4f1a | 324 | |
853f828c SR |
325 | realirq = (((((sub_bus << 8) + (bus - 1)) << 3) + (idsel - 1)) << 3) |
326 | + function; | |
d9ae2bad | 327 | virtirq = virt_irq_create_mapping(realirq); |
0c3b4f1a SR |
328 | |
329 | irq_desc[virtirq].handler = &iSeries_IRQ_handler; | |
330 | return virtirq; | |
331 | } |