diff options
author | Gavin Shan <shangw@linux.vnet.ibm.com> | 2013-06-20 01:21:13 -0400 |
---|---|---|
committer | Benjamin Herrenschmidt <benh@kernel.crashing.org> | 2013-06-20 03:06:43 -0400 |
commit | 29310e5e860697955b9c10ddb448d61267fab0dc (patch) | |
tree | ee98dbbc3d35fa619f3e11db5a9bacc46e0e932c /arch/powerpc/platforms/powernv | |
parent | 70f942db4669c4417b7bb4f3353b3eddf1179aae (diff) |
powerpc/eeh: PowerNV EEH backends
The patch adds EEH backends for PowerNV platform. It's notable that
part of those EEH backends call to the I/O chip dependent backends.
[Removed pointless change to eeh_pseries.c -- BenH]
Signed-off-by: Gavin Shan <shangw@linux.vnet.ibm.com>
Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Diffstat (limited to 'arch/powerpc/platforms/powernv')
-rw-r--r-- | arch/powerpc/platforms/powernv/Makefile | 2 | ||||
-rw-r--r-- | arch/powerpc/platforms/powernv/eeh-powernv.c | 419 |
2 files changed, 420 insertions, 1 deletions
diff --git a/arch/powerpc/platforms/powernv/Makefile b/arch/powerpc/platforms/powernv/Makefile index 09bd0cbb7c4f..7fe595152478 100644 --- a/arch/powerpc/platforms/powernv/Makefile +++ b/arch/powerpc/platforms/powernv/Makefile | |||
@@ -3,4 +3,4 @@ obj-y += opal-rtc.o opal-nvram.o | |||
3 | 3 | ||
4 | obj-$(CONFIG_SMP) += smp.o | 4 | obj-$(CONFIG_SMP) += smp.o |
5 | obj-$(CONFIG_PCI) += pci.o pci-p5ioc2.o pci-ioda.o | 5 | obj-$(CONFIG_PCI) += pci.o pci-p5ioc2.o pci-ioda.o |
6 | obj-$(CONFIG_EEH) += eeh-ioda.o | 6 | obj-$(CONFIG_EEH) += eeh-ioda.o eeh-powernv.o |
diff --git a/arch/powerpc/platforms/powernv/eeh-powernv.c b/arch/powerpc/platforms/powernv/eeh-powernv.c new file mode 100644 index 000000000000..9559115424d6 --- /dev/null +++ b/arch/powerpc/platforms/powernv/eeh-powernv.c | |||
@@ -0,0 +1,419 @@ | |||
1 | /* | ||
2 | * The file intends to implement the platform dependent EEH operations on | ||
3 | * powernv platform. Actually, the powernv was created in order to fully | ||
4 | * hypervisor support. | ||
5 | * | ||
6 | * Copyright Benjamin Herrenschmidt & Gavin Shan, IBM Corporation 2013. | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify | ||
9 | * it under the terms of the GNU General Public License as published by | ||
10 | * the Free Software Foundation; either version 2 of the License, or | ||
11 | * (at your option) any later version. | ||
12 | */ | ||
13 | |||
14 | #include <linux/atomic.h> | ||
15 | #include <linux/delay.h> | ||
16 | #include <linux/export.h> | ||
17 | #include <linux/init.h> | ||
18 | #include <linux/list.h> | ||
19 | #include <linux/msi.h> | ||
20 | #include <linux/of.h> | ||
21 | #include <linux/pci.h> | ||
22 | #include <linux/proc_fs.h> | ||
23 | #include <linux/rbtree.h> | ||
24 | #include <linux/sched.h> | ||
25 | #include <linux/seq_file.h> | ||
26 | #include <linux/spinlock.h> | ||
27 | |||
28 | #include <asm/eeh.h> | ||
29 | #include <asm/eeh_event.h> | ||
30 | #include <asm/firmware.h> | ||
31 | #include <asm/io.h> | ||
32 | #include <asm/iommu.h> | ||
33 | #include <asm/machdep.h> | ||
34 | #include <asm/msi_bitmap.h> | ||
35 | #include <asm/opal.h> | ||
36 | #include <asm/ppc-pci.h> | ||
37 | |||
38 | #include "powernv.h" | ||
39 | #include "pci.h" | ||
40 | |||
41 | /** | ||
42 | * powernv_eeh_init - EEH platform dependent initialization | ||
43 | * | ||
44 | * EEH platform dependent initialization on powernv | ||
45 | */ | ||
46 | static int powernv_eeh_init(void) | ||
47 | { | ||
48 | /* We require OPALv3 */ | ||
49 | if (!firmware_has_feature(FW_FEATURE_OPALv3)) { | ||
50 | pr_warning("%s: OPALv3 is required !\n", __func__); | ||
51 | return -EINVAL; | ||
52 | } | ||
53 | |||
54 | /* Set EEH probe mode */ | ||
55 | eeh_probe_mode_set(EEH_PROBE_MODE_DEV); | ||
56 | |||
57 | return 0; | ||
58 | } | ||
59 | |||
60 | /** | ||
61 | * powernv_eeh_post_init - EEH platform dependent post initialization | ||
62 | * | ||
63 | * EEH platform dependent post initialization on powernv. When | ||
64 | * the function is called, the EEH PEs and devices should have | ||
65 | * been built. If the I/O cache staff has been built, EEH is | ||
66 | * ready to supply service. | ||
67 | */ | ||
68 | static int powernv_eeh_post_init(void) | ||
69 | { | ||
70 | struct pci_controller *hose; | ||
71 | struct pnv_phb *phb; | ||
72 | int ret = 0; | ||
73 | |||
74 | list_for_each_entry(hose, &hose_list, list_node) { | ||
75 | phb = hose->private_data; | ||
76 | |||
77 | if (phb->eeh_ops && phb->eeh_ops->post_init) { | ||
78 | ret = phb->eeh_ops->post_init(hose); | ||
79 | if (ret) | ||
80 | break; | ||
81 | } | ||
82 | } | ||
83 | |||
84 | return ret; | ||
85 | } | ||
86 | |||
87 | /** | ||
88 | * powernv_eeh_dev_probe - Do probe on PCI device | ||
89 | * @dev: PCI device | ||
90 | * @flag: unused | ||
91 | * | ||
92 | * When EEH module is installed during system boot, all PCI devices | ||
93 | * are checked one by one to see if it supports EEH. The function | ||
94 | * is introduced for the purpose. By default, EEH has been enabled | ||
95 | * on all PCI devices. That's to say, we only need do necessary | ||
96 | * initialization on the corresponding eeh device and create PE | ||
97 | * accordingly. | ||
98 | * | ||
99 | * It's notable that's unsafe to retrieve the EEH device through | ||
100 | * the corresponding PCI device. During the PCI device hotplug, which | ||
101 | * was possiblly triggered by EEH core, the binding between EEH device | ||
102 | * and the PCI device isn't built yet. | ||
103 | */ | ||
104 | static int powernv_eeh_dev_probe(struct pci_dev *dev, void *flag) | ||
105 | { | ||
106 | struct pci_controller *hose = pci_bus_to_host(dev->bus); | ||
107 | struct pnv_phb *phb = hose->private_data; | ||
108 | struct device_node *dn = pci_device_to_OF_node(dev); | ||
109 | struct eeh_dev *edev = of_node_to_eeh_dev(dn); | ||
110 | |||
111 | /* | ||
112 | * When probing the root bridge, which doesn't have any | ||
113 | * subordinate PCI devices. We don't have OF node for | ||
114 | * the root bridge. So it's not reasonable to continue | ||
115 | * the probing. | ||
116 | */ | ||
117 | if (!dn || !edev) | ||
118 | return 0; | ||
119 | |||
120 | /* Skip for PCI-ISA bridge */ | ||
121 | if ((dev->class >> 8) == PCI_CLASS_BRIDGE_ISA) | ||
122 | return 0; | ||
123 | |||
124 | /* Initialize eeh device */ | ||
125 | edev->class_code = dev->class; | ||
126 | edev->mode = 0; | ||
127 | edev->config_addr = ((dev->bus->number << 8) | dev->devfn); | ||
128 | edev->pe_config_addr = phb->bdfn_to_pe(phb, dev->bus, dev->devfn & 0xff); | ||
129 | |||
130 | /* Create PE */ | ||
131 | eeh_add_to_parent_pe(edev); | ||
132 | |||
133 | /* | ||
134 | * Enable EEH explicitly so that we will do EEH check | ||
135 | * while accessing I/O stuff | ||
136 | * | ||
137 | * FIXME: Enable that for PHB3 later | ||
138 | */ | ||
139 | if (phb->type == PNV_PHB_IODA1) | ||
140 | eeh_subsystem_enabled = 1; | ||
141 | |||
142 | /* Save memory bars */ | ||
143 | eeh_save_bars(edev); | ||
144 | |||
145 | return 0; | ||
146 | } | ||
147 | |||
148 | /** | ||
149 | * powernv_eeh_set_option - Initialize EEH or MMIO/DMA reenable | ||
150 | * @pe: EEH PE | ||
151 | * @option: operation to be issued | ||
152 | * | ||
153 | * The function is used to control the EEH functionality globally. | ||
154 | * Currently, following options are support according to PAPR: | ||
155 | * Enable EEH, Disable EEH, Enable MMIO and Enable DMA | ||
156 | */ | ||
157 | static int powernv_eeh_set_option(struct eeh_pe *pe, int option) | ||
158 | { | ||
159 | struct pci_controller *hose = pe->phb; | ||
160 | struct pnv_phb *phb = hose->private_data; | ||
161 | int ret = -EEXIST; | ||
162 | |||
163 | /* | ||
164 | * What we need do is pass it down for hardware | ||
165 | * implementation to handle it. | ||
166 | */ | ||
167 | if (phb->eeh_ops && phb->eeh_ops->set_option) | ||
168 | ret = phb->eeh_ops->set_option(pe, option); | ||
169 | |||
170 | return ret; | ||
171 | } | ||
172 | |||
173 | /** | ||
174 | * powernv_eeh_get_pe_addr - Retrieve PE address | ||
175 | * @pe: EEH PE | ||
176 | * | ||
177 | * Retrieve the PE address according to the given tranditional | ||
178 | * PCI BDF (Bus/Device/Function) address. | ||
179 | */ | ||
180 | static int powernv_eeh_get_pe_addr(struct eeh_pe *pe) | ||
181 | { | ||
182 | return pe->addr; | ||
183 | } | ||
184 | |||
185 | /** | ||
186 | * powernv_eeh_get_state - Retrieve PE state | ||
187 | * @pe: EEH PE | ||
188 | * @delay: delay while PE state is temporarily unavailable | ||
189 | * | ||
190 | * Retrieve the state of the specified PE. For IODA-compitable | ||
191 | * platform, it should be retrieved from IODA table. Therefore, | ||
192 | * we prefer passing down to hardware implementation to handle | ||
193 | * it. | ||
194 | */ | ||
195 | static int powernv_eeh_get_state(struct eeh_pe *pe, int *delay) | ||
196 | { | ||
197 | struct pci_controller *hose = pe->phb; | ||
198 | struct pnv_phb *phb = hose->private_data; | ||
199 | int ret = EEH_STATE_NOT_SUPPORT; | ||
200 | |||
201 | if (phb->eeh_ops && phb->eeh_ops->get_state) { | ||
202 | ret = phb->eeh_ops->get_state(pe); | ||
203 | |||
204 | /* | ||
205 | * If the PE state is temporarily unavailable, | ||
206 | * to inform the EEH core delay for default | ||
207 | * period (1 second) | ||
208 | */ | ||
209 | if (delay) { | ||
210 | *delay = 0; | ||
211 | if (ret & EEH_STATE_UNAVAILABLE) | ||
212 | *delay = 1000; | ||
213 | } | ||
214 | } | ||
215 | |||
216 | return ret; | ||
217 | } | ||
218 | |||
219 | /** | ||
220 | * powernv_eeh_reset - Reset the specified PE | ||
221 | * @pe: EEH PE | ||
222 | * @option: reset option | ||
223 | * | ||
224 | * Reset the specified PE | ||
225 | */ | ||
226 | static int powernv_eeh_reset(struct eeh_pe *pe, int option) | ||
227 | { | ||
228 | struct pci_controller *hose = pe->phb; | ||
229 | struct pnv_phb *phb = hose->private_data; | ||
230 | int ret = -EEXIST; | ||
231 | |||
232 | if (phb->eeh_ops && phb->eeh_ops->reset) | ||
233 | ret = phb->eeh_ops->reset(pe, option); | ||
234 | |||
235 | return ret; | ||
236 | } | ||
237 | |||
238 | /** | ||
239 | * powernv_eeh_wait_state - Wait for PE state | ||
240 | * @pe: EEH PE | ||
241 | * @max_wait: maximal period in microsecond | ||
242 | * | ||
243 | * Wait for the state of associated PE. It might take some time | ||
244 | * to retrieve the PE's state. | ||
245 | */ | ||
246 | static int powernv_eeh_wait_state(struct eeh_pe *pe, int max_wait) | ||
247 | { | ||
248 | int ret; | ||
249 | int mwait; | ||
250 | |||
251 | while (1) { | ||
252 | ret = powernv_eeh_get_state(pe, &mwait); | ||
253 | |||
254 | /* | ||
255 | * If the PE's state is temporarily unavailable, | ||
256 | * we have to wait for the specified time. Otherwise, | ||
257 | * the PE's state will be returned immediately. | ||
258 | */ | ||
259 | if (ret != EEH_STATE_UNAVAILABLE) | ||
260 | return ret; | ||
261 | |||
262 | max_wait -= mwait; | ||
263 | if (max_wait <= 0) { | ||
264 | pr_warning("%s: Timeout getting PE#%x's state (%d)\n", | ||
265 | __func__, pe->addr, max_wait); | ||
266 | return EEH_STATE_NOT_SUPPORT; | ||
267 | } | ||
268 | |||
269 | msleep(mwait); | ||
270 | } | ||
271 | |||
272 | return EEH_STATE_NOT_SUPPORT; | ||
273 | } | ||
274 | |||
275 | /** | ||
276 | * powernv_eeh_get_log - Retrieve error log | ||
277 | * @pe: EEH PE | ||
278 | * @severity: temporary or permanent error log | ||
279 | * @drv_log: driver log to be combined with retrieved error log | ||
280 | * @len: length of driver log | ||
281 | * | ||
282 | * Retrieve the temporary or permanent error from the PE. | ||
283 | */ | ||
284 | static int powernv_eeh_get_log(struct eeh_pe *pe, int severity, | ||
285 | char *drv_log, unsigned long len) | ||
286 | { | ||
287 | struct pci_controller *hose = pe->phb; | ||
288 | struct pnv_phb *phb = hose->private_data; | ||
289 | int ret = -EEXIST; | ||
290 | |||
291 | if (phb->eeh_ops && phb->eeh_ops->get_log) | ||
292 | ret = phb->eeh_ops->get_log(pe, severity, drv_log, len); | ||
293 | |||
294 | return ret; | ||
295 | } | ||
296 | |||
297 | /** | ||
298 | * powernv_eeh_configure_bridge - Configure PCI bridges in the indicated PE | ||
299 | * @pe: EEH PE | ||
300 | * | ||
301 | * The function will be called to reconfigure the bridges included | ||
302 | * in the specified PE so that the mulfunctional PE would be recovered | ||
303 | * again. | ||
304 | */ | ||
305 | static int powernv_eeh_configure_bridge(struct eeh_pe *pe) | ||
306 | { | ||
307 | struct pci_controller *hose = pe->phb; | ||
308 | struct pnv_phb *phb = hose->private_data; | ||
309 | int ret = 0; | ||
310 | |||
311 | if (phb->eeh_ops && phb->eeh_ops->configure_bridge) | ||
312 | ret = phb->eeh_ops->configure_bridge(pe); | ||
313 | |||
314 | return ret; | ||
315 | } | ||
316 | |||
317 | /** | ||
318 | * powernv_eeh_read_config - Read PCI config space | ||
319 | * @dn: device node | ||
320 | * @where: PCI address | ||
321 | * @size: size to read | ||
322 | * @val: return value | ||
323 | * | ||
324 | * Read config space from the speicifed device | ||
325 | */ | ||
326 | static int powernv_eeh_read_config(struct device_node *dn, int where, | ||
327 | int size, u32 *val) | ||
328 | { | ||
329 | struct eeh_dev *edev = of_node_to_eeh_dev(dn); | ||
330 | struct pci_dev *dev = eeh_dev_to_pci_dev(edev); | ||
331 | struct pci_controller *hose = edev->phb; | ||
332 | |||
333 | return hose->ops->read(dev->bus, dev->devfn, where, size, val); | ||
334 | } | ||
335 | |||
336 | /** | ||
337 | * powernv_eeh_write_config - Write PCI config space | ||
338 | * @dn: device node | ||
339 | * @where: PCI address | ||
340 | * @size: size to write | ||
341 | * @val: value to be written | ||
342 | * | ||
343 | * Write config space to the specified device | ||
344 | */ | ||
345 | static int powernv_eeh_write_config(struct device_node *dn, int where, | ||
346 | int size, u32 val) | ||
347 | { | ||
348 | struct eeh_dev *edev = of_node_to_eeh_dev(dn); | ||
349 | struct pci_dev *dev = eeh_dev_to_pci_dev(edev); | ||
350 | struct pci_controller *hose = edev->phb; | ||
351 | |||
352 | hose = pci_bus_to_host(dev->bus); | ||
353 | |||
354 | return hose->ops->write(dev->bus, dev->devfn, where, size, val); | ||
355 | } | ||
356 | |||
357 | /** | ||
358 | * powernv_eeh_next_error - Retrieve next EEH error to handle | ||
359 | * @pe: Affected PE | ||
360 | * | ||
361 | * Using OPAL API, to retrieve next EEH error for EEH core to handle | ||
362 | */ | ||
363 | static int powernv_eeh_next_error(struct eeh_pe **pe) | ||
364 | { | ||
365 | struct pci_controller *hose; | ||
366 | struct pnv_phb *phb = NULL; | ||
367 | |||
368 | list_for_each_entry(hose, &hose_list, list_node) { | ||
369 | phb = hose->private_data; | ||
370 | break; | ||
371 | } | ||
372 | |||
373 | if (phb && phb->eeh_ops->next_error) | ||
374 | return phb->eeh_ops->next_error(pe); | ||
375 | |||
376 | return -EEXIST; | ||
377 | } | ||
378 | |||
379 | static struct eeh_ops powernv_eeh_ops = { | ||
380 | .name = "powernv", | ||
381 | .init = powernv_eeh_init, | ||
382 | .post_init = powernv_eeh_post_init, | ||
383 | .of_probe = NULL, | ||
384 | .dev_probe = powernv_eeh_dev_probe, | ||
385 | .set_option = powernv_eeh_set_option, | ||
386 | .get_pe_addr = powernv_eeh_get_pe_addr, | ||
387 | .get_state = powernv_eeh_get_state, | ||
388 | .reset = powernv_eeh_reset, | ||
389 | .wait_state = powernv_eeh_wait_state, | ||
390 | .get_log = powernv_eeh_get_log, | ||
391 | .configure_bridge = powernv_eeh_configure_bridge, | ||
392 | .read_config = powernv_eeh_read_config, | ||
393 | .write_config = powernv_eeh_write_config, | ||
394 | .next_error = powernv_eeh_next_error | ||
395 | }; | ||
396 | |||
397 | /** | ||
398 | * eeh_powernv_init - Register platform dependent EEH operations | ||
399 | * | ||
400 | * EEH initialization on powernv platform. This function should be | ||
401 | * called before any EEH related functions. | ||
402 | */ | ||
403 | static int __init eeh_powernv_init(void) | ||
404 | { | ||
405 | int ret = -EINVAL; | ||
406 | |||
407 | if (!machine_is(powernv)) | ||
408 | return ret; | ||
409 | |||
410 | ret = eeh_ops_register(&powernv_eeh_ops); | ||
411 | if (!ret) | ||
412 | pr_info("EEH: PowerNV platform initialized\n"); | ||
413 | else | ||
414 | pr_info("EEH: Failed to initialize PowerNV platform (%d)\n", ret); | ||
415 | |||
416 | return ret; | ||
417 | } | ||
418 | |||
419 | early_initcall(eeh_powernv_init); | ||