diff options
Diffstat (limited to 'arch/powerpc/kernel/eeh.c')
-rw-r--r-- | arch/powerpc/kernel/eeh.c | 1070 |
1 files changed, 1070 insertions, 0 deletions
diff --git a/arch/powerpc/kernel/eeh.c b/arch/powerpc/kernel/eeh.c new file mode 100644 index 000000000000..39954fe941b8 --- /dev/null +++ b/arch/powerpc/kernel/eeh.c | |||
@@ -0,0 +1,1070 @@ | |||
1 | /* | ||
2 | * Copyright IBM Corporation 2001, 2005, 2006 | ||
3 | * Copyright Dave Engebretsen & Todd Inglett 2001 | ||
4 | * Copyright Linas Vepstas 2005, 2006 | ||
5 | * Copyright 2001-2012 IBM Corporation. | ||
6 | * | ||
7 | * This program is free software; you can redistribute it and/or modify | ||
8 | * it under the terms of the GNU General Public License as published by | ||
9 | * the Free Software Foundation; either version 2 of the License, or | ||
10 | * (at your option) any later version. | ||
11 | * | ||
12 | * This program is distributed in the hope that it will be useful, | ||
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
15 | * GNU General Public License for more details. | ||
16 | * | ||
17 | * You should have received a copy of the GNU General Public License | ||
18 | * along with this program; if not, write to the Free Software | ||
19 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
20 | * | ||
21 | * Please address comments and feedback to Linas Vepstas <linas@austin.ibm.com> | ||
22 | */ | ||
23 | |||
24 | #include <linux/delay.h> | ||
25 | #include <linux/sched.h> | ||
26 | #include <linux/init.h> | ||
27 | #include <linux/list.h> | ||
28 | #include <linux/pci.h> | ||
29 | #include <linux/proc_fs.h> | ||
30 | #include <linux/rbtree.h> | ||
31 | #include <linux/seq_file.h> | ||
32 | #include <linux/spinlock.h> | ||
33 | #include <linux/export.h> | ||
34 | #include <linux/of.h> | ||
35 | |||
36 | #include <linux/atomic.h> | ||
37 | #include <asm/eeh.h> | ||
38 | #include <asm/eeh_event.h> | ||
39 | #include <asm/io.h> | ||
40 | #include <asm/machdep.h> | ||
41 | #include <asm/ppc-pci.h> | ||
42 | #include <asm/rtas.h> | ||
43 | |||
44 | |||
45 | /** Overview: | ||
46 | * EEH, or "Extended Error Handling" is a PCI bridge technology for | ||
47 | * dealing with PCI bus errors that can't be dealt with within the | ||
48 | * usual PCI framework, except by check-stopping the CPU. Systems | ||
49 | * that are designed for high-availability/reliability cannot afford | ||
50 | * to crash due to a "mere" PCI error, thus the need for EEH. | ||
51 | * An EEH-capable bridge operates by converting a detected error | ||
52 | * into a "slot freeze", taking the PCI adapter off-line, making | ||
53 | * the slot behave, from the OS'es point of view, as if the slot | ||
54 | * were "empty": all reads return 0xff's and all writes are silently | ||
55 | * ignored. EEH slot isolation events can be triggered by parity | ||
56 | * errors on the address or data busses (e.g. during posted writes), | ||
57 | * which in turn might be caused by low voltage on the bus, dust, | ||
58 | * vibration, humidity, radioactivity or plain-old failed hardware. | ||
59 | * | ||
60 | * Note, however, that one of the leading causes of EEH slot | ||
61 | * freeze events are buggy device drivers, buggy device microcode, | ||
62 | * or buggy device hardware. This is because any attempt by the | ||
63 | * device to bus-master data to a memory address that is not | ||
64 | * assigned to the device will trigger a slot freeze. (The idea | ||
65 | * is to prevent devices-gone-wild from corrupting system memory). | ||
66 | * Buggy hardware/drivers will have a miserable time co-existing | ||
67 | * with EEH. | ||
68 | * | ||
69 | * Ideally, a PCI device driver, when suspecting that an isolation | ||
70 | * event has occurred (e.g. by reading 0xff's), will then ask EEH | ||
71 | * whether this is the case, and then take appropriate steps to | ||
72 | * reset the PCI slot, the PCI device, and then resume operations. | ||
73 | * However, until that day, the checking is done here, with the | ||
74 | * eeh_check_failure() routine embedded in the MMIO macros. If | ||
75 | * the slot is found to be isolated, an "EEH Event" is synthesized | ||
76 | * and sent out for processing. | ||
77 | */ | ||
78 | |||
79 | /* If a device driver keeps reading an MMIO register in an interrupt | ||
80 | * handler after a slot isolation event, it might be broken. | ||
81 | * This sets the threshold for how many read attempts we allow | ||
82 | * before printing an error message. | ||
83 | */ | ||
84 | #define EEH_MAX_FAILS 2100000 | ||
85 | |||
86 | /* Time to wait for a PCI slot to report status, in milliseconds */ | ||
87 | #define PCI_BUS_RESET_WAIT_MSEC (60*1000) | ||
88 | |||
89 | /* Platform dependent EEH operations */ | ||
90 | struct eeh_ops *eeh_ops = NULL; | ||
91 | |||
92 | int eeh_subsystem_enabled; | ||
93 | EXPORT_SYMBOL(eeh_subsystem_enabled); | ||
94 | |||
95 | /* | ||
96 | * EEH probe mode support. The intention is to support multiple | ||
97 | * platforms for EEH. Some platforms like pSeries do PCI emunation | ||
98 | * based on device tree. However, other platforms like powernv probe | ||
99 | * PCI devices from hardware. The flag is used to distinguish that. | ||
100 | * In addition, struct eeh_ops::probe would be invoked for particular | ||
101 | * OF node or PCI device so that the corresponding PE would be created | ||
102 | * there. | ||
103 | */ | ||
104 | int eeh_probe_mode; | ||
105 | |||
106 | /* Lock to avoid races due to multiple reports of an error */ | ||
107 | DEFINE_RAW_SPINLOCK(confirm_error_lock); | ||
108 | |||
109 | /* Buffer for reporting pci register dumps. Its here in BSS, and | ||
110 | * not dynamically alloced, so that it ends up in RMO where RTAS | ||
111 | * can access it. | ||
112 | */ | ||
113 | #define EEH_PCI_REGS_LOG_LEN 4096 | ||
114 | static unsigned char pci_regs_buf[EEH_PCI_REGS_LOG_LEN]; | ||
115 | |||
116 | /* | ||
117 | * The struct is used to maintain the EEH global statistic | ||
118 | * information. Besides, the EEH global statistics will be | ||
119 | * exported to user space through procfs | ||
120 | */ | ||
121 | struct eeh_stats { | ||
122 | u64 no_device; /* PCI device not found */ | ||
123 | u64 no_dn; /* OF node not found */ | ||
124 | u64 no_cfg_addr; /* Config address not found */ | ||
125 | u64 ignored_check; /* EEH check skipped */ | ||
126 | u64 total_mmio_ffs; /* Total EEH checks */ | ||
127 | u64 false_positives; /* Unnecessary EEH checks */ | ||
128 | u64 slot_resets; /* PE reset */ | ||
129 | }; | ||
130 | |||
131 | static struct eeh_stats eeh_stats; | ||
132 | |||
133 | #define IS_BRIDGE(class_code) (((class_code)<<16) == PCI_BASE_CLASS_BRIDGE) | ||
134 | |||
135 | /** | ||
136 | * eeh_gather_pci_data - Copy assorted PCI config space registers to buff | ||
137 | * @edev: device to report data for | ||
138 | * @buf: point to buffer in which to log | ||
139 | * @len: amount of room in buffer | ||
140 | * | ||
141 | * This routine captures assorted PCI configuration space data, | ||
142 | * and puts them into a buffer for RTAS error logging. | ||
143 | */ | ||
144 | static size_t eeh_gather_pci_data(struct eeh_dev *edev, char * buf, size_t len) | ||
145 | { | ||
146 | struct device_node *dn = eeh_dev_to_of_node(edev); | ||
147 | struct pci_dev *dev = eeh_dev_to_pci_dev(edev); | ||
148 | u32 cfg; | ||
149 | int cap, i; | ||
150 | int n = 0; | ||
151 | |||
152 | n += scnprintf(buf+n, len-n, "%s\n", dn->full_name); | ||
153 | printk(KERN_WARNING "EEH: of node=%s\n", dn->full_name); | ||
154 | |||
155 | eeh_ops->read_config(dn, PCI_VENDOR_ID, 4, &cfg); | ||
156 | n += scnprintf(buf+n, len-n, "dev/vend:%08x\n", cfg); | ||
157 | printk(KERN_WARNING "EEH: PCI device/vendor: %08x\n", cfg); | ||
158 | |||
159 | eeh_ops->read_config(dn, PCI_COMMAND, 4, &cfg); | ||
160 | n += scnprintf(buf+n, len-n, "cmd/stat:%x\n", cfg); | ||
161 | printk(KERN_WARNING "EEH: PCI cmd/status register: %08x\n", cfg); | ||
162 | |||
163 | if (!dev) { | ||
164 | printk(KERN_WARNING "EEH: no PCI device for this of node\n"); | ||
165 | return n; | ||
166 | } | ||
167 | |||
168 | /* Gather bridge-specific registers */ | ||
169 | if (dev->class >> 16 == PCI_BASE_CLASS_BRIDGE) { | ||
170 | eeh_ops->read_config(dn, PCI_SEC_STATUS, 2, &cfg); | ||
171 | n += scnprintf(buf+n, len-n, "sec stat:%x\n", cfg); | ||
172 | printk(KERN_WARNING "EEH: Bridge secondary status: %04x\n", cfg); | ||
173 | |||
174 | eeh_ops->read_config(dn, PCI_BRIDGE_CONTROL, 2, &cfg); | ||
175 | n += scnprintf(buf+n, len-n, "brdg ctl:%x\n", cfg); | ||
176 | printk(KERN_WARNING "EEH: Bridge control: %04x\n", cfg); | ||
177 | } | ||
178 | |||
179 | /* Dump out the PCI-X command and status regs */ | ||
180 | cap = pci_find_capability(dev, PCI_CAP_ID_PCIX); | ||
181 | if (cap) { | ||
182 | eeh_ops->read_config(dn, cap, 4, &cfg); | ||
183 | n += scnprintf(buf+n, len-n, "pcix-cmd:%x\n", cfg); | ||
184 | printk(KERN_WARNING "EEH: PCI-X cmd: %08x\n", cfg); | ||
185 | |||
186 | eeh_ops->read_config(dn, cap+4, 4, &cfg); | ||
187 | n += scnprintf(buf+n, len-n, "pcix-stat:%x\n", cfg); | ||
188 | printk(KERN_WARNING "EEH: PCI-X status: %08x\n", cfg); | ||
189 | } | ||
190 | |||
191 | /* If PCI-E capable, dump PCI-E cap 10, and the AER */ | ||
192 | cap = pci_find_capability(dev, PCI_CAP_ID_EXP); | ||
193 | if (cap) { | ||
194 | n += scnprintf(buf+n, len-n, "pci-e cap10:\n"); | ||
195 | printk(KERN_WARNING | ||
196 | "EEH: PCI-E capabilities and status follow:\n"); | ||
197 | |||
198 | for (i=0; i<=8; i++) { | ||
199 | eeh_ops->read_config(dn, cap+4*i, 4, &cfg); | ||
200 | n += scnprintf(buf+n, len-n, "%02x:%x\n", 4*i, cfg); | ||
201 | printk(KERN_WARNING "EEH: PCI-E %02x: %08x\n", i, cfg); | ||
202 | } | ||
203 | |||
204 | cap = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ERR); | ||
205 | if (cap) { | ||
206 | n += scnprintf(buf+n, len-n, "pci-e AER:\n"); | ||
207 | printk(KERN_WARNING | ||
208 | "EEH: PCI-E AER capability register set follows:\n"); | ||
209 | |||
210 | for (i=0; i<14; i++) { | ||
211 | eeh_ops->read_config(dn, cap+4*i, 4, &cfg); | ||
212 | n += scnprintf(buf+n, len-n, "%02x:%x\n", 4*i, cfg); | ||
213 | printk(KERN_WARNING "EEH: PCI-E AER %02x: %08x\n", i, cfg); | ||
214 | } | ||
215 | } | ||
216 | } | ||
217 | |||
218 | return n; | ||
219 | } | ||
220 | |||
221 | /** | ||
222 | * eeh_slot_error_detail - Generate combined log including driver log and error log | ||
223 | * @pe: EEH PE | ||
224 | * @severity: temporary or permanent error log | ||
225 | * | ||
226 | * This routine should be called to generate the combined log, which | ||
227 | * is comprised of driver log and error log. The driver log is figured | ||
228 | * out from the config space of the corresponding PCI device, while | ||
229 | * the error log is fetched through platform dependent function call. | ||
230 | */ | ||
231 | void eeh_slot_error_detail(struct eeh_pe *pe, int severity) | ||
232 | { | ||
233 | size_t loglen = 0; | ||
234 | struct eeh_dev *edev; | ||
235 | bool valid_cfg_log = true; | ||
236 | |||
237 | /* | ||
238 | * When the PHB is fenced or dead, it's pointless to collect | ||
239 | * the data from PCI config space because it should return | ||
240 | * 0xFF's. For ER, we still retrieve the data from the PCI | ||
241 | * config space. | ||
242 | */ | ||
243 | if (eeh_probe_mode_dev() && | ||
244 | (pe->type & EEH_PE_PHB) && | ||
245 | (pe->state & (EEH_PE_ISOLATED | EEH_PE_PHB_DEAD))) | ||
246 | valid_cfg_log = false; | ||
247 | |||
248 | if (valid_cfg_log) { | ||
249 | eeh_pci_enable(pe, EEH_OPT_THAW_MMIO); | ||
250 | eeh_ops->configure_bridge(pe); | ||
251 | eeh_pe_restore_bars(pe); | ||
252 | |||
253 | pci_regs_buf[0] = 0; | ||
254 | eeh_pe_for_each_dev(pe, edev) { | ||
255 | loglen += eeh_gather_pci_data(edev, pci_regs_buf + loglen, | ||
256 | EEH_PCI_REGS_LOG_LEN - loglen); | ||
257 | } | ||
258 | } | ||
259 | |||
260 | eeh_ops->get_log(pe, severity, pci_regs_buf, loglen); | ||
261 | } | ||
262 | |||
263 | /** | ||
264 | * eeh_token_to_phys - Convert EEH address token to phys address | ||
265 | * @token: I/O token, should be address in the form 0xA.... | ||
266 | * | ||
267 | * This routine should be called to convert virtual I/O address | ||
268 | * to physical one. | ||
269 | */ | ||
270 | static inline unsigned long eeh_token_to_phys(unsigned long token) | ||
271 | { | ||
272 | pte_t *ptep; | ||
273 | unsigned long pa; | ||
274 | int hugepage_shift; | ||
275 | |||
276 | /* | ||
277 | * We won't find hugepages here, iomem | ||
278 | */ | ||
279 | ptep = find_linux_pte_or_hugepte(init_mm.pgd, token, &hugepage_shift); | ||
280 | if (!ptep) | ||
281 | return token; | ||
282 | WARN_ON(hugepage_shift); | ||
283 | pa = pte_pfn(*ptep) << PAGE_SHIFT; | ||
284 | |||
285 | return pa | (token & (PAGE_SIZE-1)); | ||
286 | } | ||
287 | |||
288 | /* | ||
289 | * On PowerNV platform, we might already have fenced PHB there. | ||
290 | * For that case, it's meaningless to recover frozen PE. Intead, | ||
291 | * We have to handle fenced PHB firstly. | ||
292 | */ | ||
293 | static int eeh_phb_check_failure(struct eeh_pe *pe) | ||
294 | { | ||
295 | struct eeh_pe *phb_pe; | ||
296 | unsigned long flags; | ||
297 | int ret; | ||
298 | |||
299 | if (!eeh_probe_mode_dev()) | ||
300 | return -EPERM; | ||
301 | |||
302 | /* Find the PHB PE */ | ||
303 | phb_pe = eeh_phb_pe_get(pe->phb); | ||
304 | if (!phb_pe) { | ||
305 | pr_warning("%s Can't find PE for PHB#%d\n", | ||
306 | __func__, pe->phb->global_number); | ||
307 | return -EEXIST; | ||
308 | } | ||
309 | |||
310 | /* If the PHB has been in problematic state */ | ||
311 | eeh_serialize_lock(&flags); | ||
312 | if (phb_pe->state & (EEH_PE_ISOLATED | EEH_PE_PHB_DEAD)) { | ||
313 | ret = 0; | ||
314 | goto out; | ||
315 | } | ||
316 | |||
317 | /* Check PHB state */ | ||
318 | ret = eeh_ops->get_state(phb_pe, NULL); | ||
319 | if ((ret < 0) || | ||
320 | (ret == EEH_STATE_NOT_SUPPORT) || | ||
321 | (ret & (EEH_STATE_MMIO_ACTIVE | EEH_STATE_DMA_ACTIVE)) == | ||
322 | (EEH_STATE_MMIO_ACTIVE | EEH_STATE_DMA_ACTIVE)) { | ||
323 | ret = 0; | ||
324 | goto out; | ||
325 | } | ||
326 | |||
327 | /* Isolate the PHB and send event */ | ||
328 | eeh_pe_state_mark(phb_pe, EEH_PE_ISOLATED); | ||
329 | eeh_serialize_unlock(flags); | ||
330 | eeh_send_failure_event(phb_pe); | ||
331 | |||
332 | pr_err("EEH: PHB#%x failure detected\n", | ||
333 | phb_pe->phb->global_number); | ||
334 | dump_stack(); | ||
335 | |||
336 | return 1; | ||
337 | out: | ||
338 | eeh_serialize_unlock(flags); | ||
339 | return ret; | ||
340 | } | ||
341 | |||
342 | /** | ||
343 | * eeh_dev_check_failure - Check if all 1's data is due to EEH slot freeze | ||
344 | * @edev: eeh device | ||
345 | * | ||
346 | * Check for an EEH failure for the given device node. Call this | ||
347 | * routine if the result of a read was all 0xff's and you want to | ||
348 | * find out if this is due to an EEH slot freeze. This routine | ||
349 | * will query firmware for the EEH status. | ||
350 | * | ||
351 | * Returns 0 if there has not been an EEH error; otherwise returns | ||
352 | * a non-zero value and queues up a slot isolation event notification. | ||
353 | * | ||
354 | * It is safe to call this routine in an interrupt context. | ||
355 | */ | ||
356 | int eeh_dev_check_failure(struct eeh_dev *edev) | ||
357 | { | ||
358 | int ret; | ||
359 | unsigned long flags; | ||
360 | struct device_node *dn; | ||
361 | struct pci_dev *dev; | ||
362 | struct eeh_pe *pe; | ||
363 | int rc = 0; | ||
364 | const char *location; | ||
365 | |||
366 | eeh_stats.total_mmio_ffs++; | ||
367 | |||
368 | if (!eeh_subsystem_enabled) | ||
369 | return 0; | ||
370 | |||
371 | if (!edev) { | ||
372 | eeh_stats.no_dn++; | ||
373 | return 0; | ||
374 | } | ||
375 | dn = eeh_dev_to_of_node(edev); | ||
376 | dev = eeh_dev_to_pci_dev(edev); | ||
377 | pe = edev->pe; | ||
378 | |||
379 | /* Access to IO BARs might get this far and still not want checking. */ | ||
380 | if (!pe) { | ||
381 | eeh_stats.ignored_check++; | ||
382 | pr_debug("EEH: Ignored check for %s %s\n", | ||
383 | eeh_pci_name(dev), dn->full_name); | ||
384 | return 0; | ||
385 | } | ||
386 | |||
387 | if (!pe->addr && !pe->config_addr) { | ||
388 | eeh_stats.no_cfg_addr++; | ||
389 | return 0; | ||
390 | } | ||
391 | |||
392 | /* | ||
393 | * On PowerNV platform, we might already have fenced PHB | ||
394 | * there and we need take care of that firstly. | ||
395 | */ | ||
396 | ret = eeh_phb_check_failure(pe); | ||
397 | if (ret > 0) | ||
398 | return ret; | ||
399 | |||
400 | /* If we already have a pending isolation event for this | ||
401 | * slot, we know it's bad already, we don't need to check. | ||
402 | * Do this checking under a lock; as multiple PCI devices | ||
403 | * in one slot might report errors simultaneously, and we | ||
404 | * only want one error recovery routine running. | ||
405 | */ | ||
406 | eeh_serialize_lock(&flags); | ||
407 | rc = 1; | ||
408 | if (pe->state & EEH_PE_ISOLATED) { | ||
409 | pe->check_count++; | ||
410 | if (pe->check_count % EEH_MAX_FAILS == 0) { | ||
411 | location = of_get_property(dn, "ibm,loc-code", NULL); | ||
412 | printk(KERN_ERR "EEH: %d reads ignored for recovering device at " | ||
413 | "location=%s driver=%s pci addr=%s\n", | ||
414 | pe->check_count, location, | ||
415 | eeh_driver_name(dev), eeh_pci_name(dev)); | ||
416 | printk(KERN_ERR "EEH: Might be infinite loop in %s driver\n", | ||
417 | eeh_driver_name(dev)); | ||
418 | dump_stack(); | ||
419 | } | ||
420 | goto dn_unlock; | ||
421 | } | ||
422 | |||
423 | /* | ||
424 | * Now test for an EEH failure. This is VERY expensive. | ||
425 | * Note that the eeh_config_addr may be a parent device | ||
426 | * in the case of a device behind a bridge, or it may be | ||
427 | * function zero of a multi-function device. | ||
428 | * In any case they must share a common PHB. | ||
429 | */ | ||
430 | ret = eeh_ops->get_state(pe, NULL); | ||
431 | |||
432 | /* Note that config-io to empty slots may fail; | ||
433 | * they are empty when they don't have children. | ||
434 | * We will punt with the following conditions: Failure to get | ||
435 | * PE's state, EEH not support and Permanently unavailable | ||
436 | * state, PE is in good state. | ||
437 | */ | ||
438 | if ((ret < 0) || | ||
439 | (ret == EEH_STATE_NOT_SUPPORT) || | ||
440 | (ret & (EEH_STATE_MMIO_ACTIVE | EEH_STATE_DMA_ACTIVE)) == | ||
441 | (EEH_STATE_MMIO_ACTIVE | EEH_STATE_DMA_ACTIVE)) { | ||
442 | eeh_stats.false_positives++; | ||
443 | pe->false_positives++; | ||
444 | rc = 0; | ||
445 | goto dn_unlock; | ||
446 | } | ||
447 | |||
448 | eeh_stats.slot_resets++; | ||
449 | |||
450 | /* Avoid repeated reports of this failure, including problems | ||
451 | * with other functions on this device, and functions under | ||
452 | * bridges. | ||
453 | */ | ||
454 | eeh_pe_state_mark(pe, EEH_PE_ISOLATED); | ||
455 | eeh_serialize_unlock(flags); | ||
456 | |||
457 | eeh_send_failure_event(pe); | ||
458 | |||
459 | /* Most EEH events are due to device driver bugs. Having | ||
460 | * a stack trace will help the device-driver authors figure | ||
461 | * out what happened. So print that out. | ||
462 | */ | ||
463 | pr_err("EEH: Frozen PE#%x detected on PHB#%x\n", | ||
464 | pe->addr, pe->phb->global_number); | ||
465 | dump_stack(); | ||
466 | |||
467 | return 1; | ||
468 | |||
469 | dn_unlock: | ||
470 | eeh_serialize_unlock(flags); | ||
471 | return rc; | ||
472 | } | ||
473 | |||
474 | EXPORT_SYMBOL_GPL(eeh_dev_check_failure); | ||
475 | |||
476 | /** | ||
477 | * eeh_check_failure - Check if all 1's data is due to EEH slot freeze | ||
478 | * @token: I/O token, should be address in the form 0xA.... | ||
479 | * @val: value, should be all 1's (XXX why do we need this arg??) | ||
480 | * | ||
481 | * Check for an EEH failure at the given token address. Call this | ||
482 | * routine if the result of a read was all 0xff's and you want to | ||
483 | * find out if this is due to an EEH slot freeze event. This routine | ||
484 | * will query firmware for the EEH status. | ||
485 | * | ||
486 | * Note this routine is safe to call in an interrupt context. | ||
487 | */ | ||
488 | unsigned long eeh_check_failure(const volatile void __iomem *token, unsigned long val) | ||
489 | { | ||
490 | unsigned long addr; | ||
491 | struct eeh_dev *edev; | ||
492 | |||
493 | /* Finding the phys addr + pci device; this is pretty quick. */ | ||
494 | addr = eeh_token_to_phys((unsigned long __force) token); | ||
495 | edev = eeh_addr_cache_get_dev(addr); | ||
496 | if (!edev) { | ||
497 | eeh_stats.no_device++; | ||
498 | return val; | ||
499 | } | ||
500 | |||
501 | eeh_dev_check_failure(edev); | ||
502 | |||
503 | pci_dev_put(eeh_dev_to_pci_dev(edev)); | ||
504 | return val; | ||
505 | } | ||
506 | |||
507 | EXPORT_SYMBOL(eeh_check_failure); | ||
508 | |||
509 | |||
510 | /** | ||
511 | * eeh_pci_enable - Enable MMIO or DMA transfers for this slot | ||
512 | * @pe: EEH PE | ||
513 | * | ||
514 | * This routine should be called to reenable frozen MMIO or DMA | ||
515 | * so that it would work correctly again. It's useful while doing | ||
516 | * recovery or log collection on the indicated device. | ||
517 | */ | ||
518 | int eeh_pci_enable(struct eeh_pe *pe, int function) | ||
519 | { | ||
520 | int rc; | ||
521 | |||
522 | rc = eeh_ops->set_option(pe, function); | ||
523 | if (rc) | ||
524 | pr_warning("%s: Unexpected state change %d on PHB#%d-PE#%x, err=%d\n", | ||
525 | __func__, function, pe->phb->global_number, pe->addr, rc); | ||
526 | |||
527 | rc = eeh_ops->wait_state(pe, PCI_BUS_RESET_WAIT_MSEC); | ||
528 | if (rc > 0 && (rc & EEH_STATE_MMIO_ENABLED) && | ||
529 | (function == EEH_OPT_THAW_MMIO)) | ||
530 | return 0; | ||
531 | |||
532 | return rc; | ||
533 | } | ||
534 | |||
535 | /** | ||
536 | * pcibios_set_pcie_slot_reset - Set PCI-E reset state | ||
537 | * @dev: pci device struct | ||
538 | * @state: reset state to enter | ||
539 | * | ||
540 | * Return value: | ||
541 | * 0 if success | ||
542 | */ | ||
543 | int pcibios_set_pcie_reset_state(struct pci_dev *dev, enum pcie_reset_state state) | ||
544 | { | ||
545 | struct eeh_dev *edev = pci_dev_to_eeh_dev(dev); | ||
546 | struct eeh_pe *pe = edev->pe; | ||
547 | |||
548 | if (!pe) { | ||
549 | pr_err("%s: No PE found on PCI device %s\n", | ||
550 | __func__, pci_name(dev)); | ||
551 | return -EINVAL; | ||
552 | } | ||
553 | |||
554 | switch (state) { | ||
555 | case pcie_deassert_reset: | ||
556 | eeh_ops->reset(pe, EEH_RESET_DEACTIVATE); | ||
557 | break; | ||
558 | case pcie_hot_reset: | ||
559 | eeh_ops->reset(pe, EEH_RESET_HOT); | ||
560 | break; | ||
561 | case pcie_warm_reset: | ||
562 | eeh_ops->reset(pe, EEH_RESET_FUNDAMENTAL); | ||
563 | break; | ||
564 | default: | ||
565 | return -EINVAL; | ||
566 | }; | ||
567 | |||
568 | return 0; | ||
569 | } | ||
570 | |||
571 | /** | ||
572 | * eeh_set_pe_freset - Check the required reset for the indicated device | ||
573 | * @data: EEH device | ||
574 | * @flag: return value | ||
575 | * | ||
576 | * Each device might have its preferred reset type: fundamental or | ||
577 | * hot reset. The routine is used to collected the information for | ||
578 | * the indicated device and its children so that the bunch of the | ||
579 | * devices could be reset properly. | ||
580 | */ | ||
581 | static void *eeh_set_dev_freset(void *data, void *flag) | ||
582 | { | ||
583 | struct pci_dev *dev; | ||
584 | unsigned int *freset = (unsigned int *)flag; | ||
585 | struct eeh_dev *edev = (struct eeh_dev *)data; | ||
586 | |||
587 | dev = eeh_dev_to_pci_dev(edev); | ||
588 | if (dev) | ||
589 | *freset |= dev->needs_freset; | ||
590 | |||
591 | return NULL; | ||
592 | } | ||
593 | |||
594 | /** | ||
595 | * eeh_reset_pe_once - Assert the pci #RST line for 1/4 second | ||
596 | * @pe: EEH PE | ||
597 | * | ||
598 | * Assert the PCI #RST line for 1/4 second. | ||
599 | */ | ||
600 | static void eeh_reset_pe_once(struct eeh_pe *pe) | ||
601 | { | ||
602 | unsigned int freset = 0; | ||
603 | |||
604 | /* Determine type of EEH reset required for | ||
605 | * Partitionable Endpoint, a hot-reset (1) | ||
606 | * or a fundamental reset (3). | ||
607 | * A fundamental reset required by any device under | ||
608 | * Partitionable Endpoint trumps hot-reset. | ||
609 | */ | ||
610 | eeh_pe_dev_traverse(pe, eeh_set_dev_freset, &freset); | ||
611 | |||
612 | if (freset) | ||
613 | eeh_ops->reset(pe, EEH_RESET_FUNDAMENTAL); | ||
614 | else | ||
615 | eeh_ops->reset(pe, EEH_RESET_HOT); | ||
616 | |||
617 | /* The PCI bus requires that the reset be held high for at least | ||
618 | * a 100 milliseconds. We wait a bit longer 'just in case'. | ||
619 | */ | ||
620 | #define PCI_BUS_RST_HOLD_TIME_MSEC 250 | ||
621 | msleep(PCI_BUS_RST_HOLD_TIME_MSEC); | ||
622 | |||
623 | /* We might get hit with another EEH freeze as soon as the | ||
624 | * pci slot reset line is dropped. Make sure we don't miss | ||
625 | * these, and clear the flag now. | ||
626 | */ | ||
627 | eeh_pe_state_clear(pe, EEH_PE_ISOLATED); | ||
628 | |||
629 | eeh_ops->reset(pe, EEH_RESET_DEACTIVATE); | ||
630 | |||
631 | /* After a PCI slot has been reset, the PCI Express spec requires | ||
632 | * a 1.5 second idle time for the bus to stabilize, before starting | ||
633 | * up traffic. | ||
634 | */ | ||
635 | #define PCI_BUS_SETTLE_TIME_MSEC 1800 | ||
636 | msleep(PCI_BUS_SETTLE_TIME_MSEC); | ||
637 | } | ||
638 | |||
639 | /** | ||
640 | * eeh_reset_pe - Reset the indicated PE | ||
641 | * @pe: EEH PE | ||
642 | * | ||
643 | * This routine should be called to reset indicated device, including | ||
644 | * PE. A PE might include multiple PCI devices and sometimes PCI bridges | ||
645 | * might be involved as well. | ||
646 | */ | ||
647 | int eeh_reset_pe(struct eeh_pe *pe) | ||
648 | { | ||
649 | int flags = (EEH_STATE_MMIO_ACTIVE | EEH_STATE_DMA_ACTIVE); | ||
650 | int i, rc; | ||
651 | |||
652 | /* Take three shots at resetting the bus */ | ||
653 | for (i=0; i<3; i++) { | ||
654 | eeh_reset_pe_once(pe); | ||
655 | |||
656 | rc = eeh_ops->wait_state(pe, PCI_BUS_RESET_WAIT_MSEC); | ||
657 | if ((rc & flags) == flags) | ||
658 | return 0; | ||
659 | |||
660 | if (rc < 0) { | ||
661 | pr_err("%s: Unrecoverable slot failure on PHB#%d-PE#%x", | ||
662 | __func__, pe->phb->global_number, pe->addr); | ||
663 | return -1; | ||
664 | } | ||
665 | pr_err("EEH: bus reset %d failed on PHB#%d-PE#%x, rc=%d\n", | ||
666 | i+1, pe->phb->global_number, pe->addr, rc); | ||
667 | } | ||
668 | |||
669 | return -1; | ||
670 | } | ||
671 | |||
672 | /** | ||
673 | * eeh_save_bars - Save device bars | ||
674 | * @edev: PCI device associated EEH device | ||
675 | * | ||
676 | * Save the values of the device bars. Unlike the restore | ||
677 | * routine, this routine is *not* recursive. This is because | ||
678 | * PCI devices are added individually; but, for the restore, | ||
679 | * an entire slot is reset at a time. | ||
680 | */ | ||
681 | void eeh_save_bars(struct eeh_dev *edev) | ||
682 | { | ||
683 | int i; | ||
684 | struct device_node *dn; | ||
685 | |||
686 | if (!edev) | ||
687 | return; | ||
688 | dn = eeh_dev_to_of_node(edev); | ||
689 | |||
690 | for (i = 0; i < 16; i++) | ||
691 | eeh_ops->read_config(dn, i * 4, 4, &edev->config_space[i]); | ||
692 | } | ||
693 | |||
694 | /** | ||
695 | * eeh_ops_register - Register platform dependent EEH operations | ||
696 | * @ops: platform dependent EEH operations | ||
697 | * | ||
698 | * Register the platform dependent EEH operation callback | ||
699 | * functions. The platform should call this function before | ||
700 | * any other EEH operations. | ||
701 | */ | ||
702 | int __init eeh_ops_register(struct eeh_ops *ops) | ||
703 | { | ||
704 | if (!ops->name) { | ||
705 | pr_warning("%s: Invalid EEH ops name for %p\n", | ||
706 | __func__, ops); | ||
707 | return -EINVAL; | ||
708 | } | ||
709 | |||
710 | if (eeh_ops && eeh_ops != ops) { | ||
711 | pr_warning("%s: EEH ops of platform %s already existing (%s)\n", | ||
712 | __func__, eeh_ops->name, ops->name); | ||
713 | return -EEXIST; | ||
714 | } | ||
715 | |||
716 | eeh_ops = ops; | ||
717 | |||
718 | return 0; | ||
719 | } | ||
720 | |||
721 | /** | ||
722 | * eeh_ops_unregister - Unreigster platform dependent EEH operations | ||
723 | * @name: name of EEH platform operations | ||
724 | * | ||
725 | * Unregister the platform dependent EEH operation callback | ||
726 | * functions. | ||
727 | */ | ||
728 | int __exit eeh_ops_unregister(const char *name) | ||
729 | { | ||
730 | if (!name || !strlen(name)) { | ||
731 | pr_warning("%s: Invalid EEH ops name\n", | ||
732 | __func__); | ||
733 | return -EINVAL; | ||
734 | } | ||
735 | |||
736 | if (eeh_ops && !strcmp(eeh_ops->name, name)) { | ||
737 | eeh_ops = NULL; | ||
738 | return 0; | ||
739 | } | ||
740 | |||
741 | return -EEXIST; | ||
742 | } | ||
743 | |||
744 | /** | ||
745 | * eeh_init - EEH initialization | ||
746 | * | ||
747 | * Initialize EEH by trying to enable it for all of the adapters in the system. | ||
748 | * As a side effect we can determine here if eeh is supported at all. | ||
749 | * Note that we leave EEH on so failed config cycles won't cause a machine | ||
750 | * check. If a user turns off EEH for a particular adapter they are really | ||
751 | * telling Linux to ignore errors. Some hardware (e.g. POWER5) won't | ||
752 | * grant access to a slot if EEH isn't enabled, and so we always enable | ||
753 | * EEH for all slots/all devices. | ||
754 | * | ||
755 | * The eeh-force-off option disables EEH checking globally, for all slots. | ||
756 | * Even if force-off is set, the EEH hardware is still enabled, so that | ||
757 | * newer systems can boot. | ||
758 | */ | ||
759 | int eeh_init(void) | ||
760 | { | ||
761 | struct pci_controller *hose, *tmp; | ||
762 | struct device_node *phb; | ||
763 | static int cnt = 0; | ||
764 | int ret = 0; | ||
765 | |||
766 | /* | ||
767 | * We have to delay the initialization on PowerNV after | ||
768 | * the PCI hierarchy tree has been built because the PEs | ||
769 | * are figured out based on PCI devices instead of device | ||
770 | * tree nodes | ||
771 | */ | ||
772 | if (machine_is(powernv) && cnt++ <= 0) | ||
773 | return ret; | ||
774 | |||
775 | /* call platform initialization function */ | ||
776 | if (!eeh_ops) { | ||
777 | pr_warning("%s: Platform EEH operation not found\n", | ||
778 | __func__); | ||
779 | return -EEXIST; | ||
780 | } else if ((ret = eeh_ops->init())) { | ||
781 | pr_warning("%s: Failed to call platform init function (%d)\n", | ||
782 | __func__, ret); | ||
783 | return ret; | ||
784 | } | ||
785 | |||
786 | /* Initialize EEH event */ | ||
787 | ret = eeh_event_init(); | ||
788 | if (ret) | ||
789 | return ret; | ||
790 | |||
791 | /* Enable EEH for all adapters */ | ||
792 | if (eeh_probe_mode_devtree()) { | ||
793 | list_for_each_entry_safe(hose, tmp, | ||
794 | &hose_list, list_node) { | ||
795 | phb = hose->dn; | ||
796 | traverse_pci_devices(phb, eeh_ops->of_probe, NULL); | ||
797 | } | ||
798 | } else if (eeh_probe_mode_dev()) { | ||
799 | list_for_each_entry_safe(hose, tmp, | ||
800 | &hose_list, list_node) | ||
801 | pci_walk_bus(hose->bus, eeh_ops->dev_probe, NULL); | ||
802 | } else { | ||
803 | pr_warning("%s: Invalid probe mode %d\n", | ||
804 | __func__, eeh_probe_mode); | ||
805 | return -EINVAL; | ||
806 | } | ||
807 | |||
808 | /* | ||
809 | * Call platform post-initialization. Actually, It's good chance | ||
810 | * to inform platform that EEH is ready to supply service if the | ||
811 | * I/O cache stuff has been built up. | ||
812 | */ | ||
813 | if (eeh_ops->post_init) { | ||
814 | ret = eeh_ops->post_init(); | ||
815 | if (ret) | ||
816 | return ret; | ||
817 | } | ||
818 | |||
819 | if (eeh_subsystem_enabled) | ||
820 | pr_info("EEH: PCI Enhanced I/O Error Handling Enabled\n"); | ||
821 | else | ||
822 | pr_warning("EEH: No capable adapters found\n"); | ||
823 | |||
824 | return ret; | ||
825 | } | ||
826 | |||
827 | core_initcall_sync(eeh_init); | ||
828 | |||
829 | /** | ||
830 | * eeh_add_device_early - Enable EEH for the indicated device_node | ||
831 | * @dn: device node for which to set up EEH | ||
832 | * | ||
833 | * This routine must be used to perform EEH initialization for PCI | ||
834 | * devices that were added after system boot (e.g. hotplug, dlpar). | ||
835 | * This routine must be called before any i/o is performed to the | ||
836 | * adapter (inluding any config-space i/o). | ||
837 | * Whether this actually enables EEH or not for this device depends | ||
838 | * on the CEC architecture, type of the device, on earlier boot | ||
839 | * command-line arguments & etc. | ||
840 | */ | ||
841 | static void eeh_add_device_early(struct device_node *dn) | ||
842 | { | ||
843 | struct pci_controller *phb; | ||
844 | |||
845 | /* | ||
846 | * If we're doing EEH probe based on PCI device, we | ||
847 | * would delay the probe until late stage because | ||
848 | * the PCI device isn't available this moment. | ||
849 | */ | ||
850 | if (!eeh_probe_mode_devtree()) | ||
851 | return; | ||
852 | |||
853 | if (!of_node_to_eeh_dev(dn)) | ||
854 | return; | ||
855 | phb = of_node_to_eeh_dev(dn)->phb; | ||
856 | |||
857 | /* USB Bus children of PCI devices will not have BUID's */ | ||
858 | if (NULL == phb || 0 == phb->buid) | ||
859 | return; | ||
860 | |||
861 | eeh_ops->of_probe(dn, NULL); | ||
862 | } | ||
863 | |||
864 | /** | ||
865 | * eeh_add_device_tree_early - Enable EEH for the indicated device | ||
866 | * @dn: device node | ||
867 | * | ||
868 | * This routine must be used to perform EEH initialization for the | ||
869 | * indicated PCI device that was added after system boot (e.g. | ||
870 | * hotplug, dlpar). | ||
871 | */ | ||
872 | void eeh_add_device_tree_early(struct device_node *dn) | ||
873 | { | ||
874 | struct device_node *sib; | ||
875 | |||
876 | for_each_child_of_node(dn, sib) | ||
877 | eeh_add_device_tree_early(sib); | ||
878 | eeh_add_device_early(dn); | ||
879 | } | ||
880 | EXPORT_SYMBOL_GPL(eeh_add_device_tree_early); | ||
881 | |||
882 | /** | ||
883 | * eeh_add_device_late - Perform EEH initialization for the indicated pci device | ||
884 | * @dev: pci device for which to set up EEH | ||
885 | * | ||
886 | * This routine must be used to complete EEH initialization for PCI | ||
887 | * devices that were added after system boot (e.g. hotplug, dlpar). | ||
888 | */ | ||
889 | static void eeh_add_device_late(struct pci_dev *dev) | ||
890 | { | ||
891 | struct device_node *dn; | ||
892 | struct eeh_dev *edev; | ||
893 | |||
894 | if (!dev || !eeh_subsystem_enabled) | ||
895 | return; | ||
896 | |||
897 | pr_debug("EEH: Adding device %s\n", pci_name(dev)); | ||
898 | |||
899 | dn = pci_device_to_OF_node(dev); | ||
900 | edev = of_node_to_eeh_dev(dn); | ||
901 | if (edev->pdev == dev) { | ||
902 | pr_debug("EEH: Already referenced !\n"); | ||
903 | return; | ||
904 | } | ||
905 | WARN_ON(edev->pdev); | ||
906 | |||
907 | pci_dev_get(dev); | ||
908 | edev->pdev = dev; | ||
909 | dev->dev.archdata.edev = edev; | ||
910 | |||
911 | /* | ||
912 | * We have to do the EEH probe here because the PCI device | ||
913 | * hasn't been created yet in the early stage. | ||
914 | */ | ||
915 | if (eeh_probe_mode_dev()) | ||
916 | eeh_ops->dev_probe(dev, NULL); | ||
917 | |||
918 | eeh_addr_cache_insert_dev(dev); | ||
919 | } | ||
920 | |||
921 | /** | ||
922 | * eeh_add_device_tree_late - Perform EEH initialization for the indicated PCI bus | ||
923 | * @bus: PCI bus | ||
924 | * | ||
925 | * This routine must be used to perform EEH initialization for PCI | ||
926 | * devices which are attached to the indicated PCI bus. The PCI bus | ||
927 | * is added after system boot through hotplug or dlpar. | ||
928 | */ | ||
929 | void eeh_add_device_tree_late(struct pci_bus *bus) | ||
930 | { | ||
931 | struct pci_dev *dev; | ||
932 | |||
933 | list_for_each_entry(dev, &bus->devices, bus_list) { | ||
934 | eeh_add_device_late(dev); | ||
935 | if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE) { | ||
936 | struct pci_bus *subbus = dev->subordinate; | ||
937 | if (subbus) | ||
938 | eeh_add_device_tree_late(subbus); | ||
939 | } | ||
940 | } | ||
941 | } | ||
942 | EXPORT_SYMBOL_GPL(eeh_add_device_tree_late); | ||
943 | |||
944 | /** | ||
945 | * eeh_add_sysfs_files - Add EEH sysfs files for the indicated PCI bus | ||
946 | * @bus: PCI bus | ||
947 | * | ||
948 | * This routine must be used to add EEH sysfs files for PCI | ||
949 | * devices which are attached to the indicated PCI bus. The PCI bus | ||
950 | * is added after system boot through hotplug or dlpar. | ||
951 | */ | ||
952 | void eeh_add_sysfs_files(struct pci_bus *bus) | ||
953 | { | ||
954 | struct pci_dev *dev; | ||
955 | |||
956 | list_for_each_entry(dev, &bus->devices, bus_list) { | ||
957 | eeh_sysfs_add_device(dev); | ||
958 | if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE) { | ||
959 | struct pci_bus *subbus = dev->subordinate; | ||
960 | if (subbus) | ||
961 | eeh_add_sysfs_files(subbus); | ||
962 | } | ||
963 | } | ||
964 | } | ||
965 | EXPORT_SYMBOL_GPL(eeh_add_sysfs_files); | ||
966 | |||
967 | /** | ||
968 | * eeh_remove_device - Undo EEH setup for the indicated pci device | ||
969 | * @dev: pci device to be removed | ||
970 | * @purge_pe: remove the PE or not | ||
971 | * | ||
972 | * This routine should be called when a device is removed from | ||
973 | * a running system (e.g. by hotplug or dlpar). It unregisters | ||
974 | * the PCI device from the EEH subsystem. I/O errors affecting | ||
975 | * this device will no longer be detected after this call; thus, | ||
976 | * i/o errors affecting this slot may leave this device unusable. | ||
977 | */ | ||
978 | static void eeh_remove_device(struct pci_dev *dev, int purge_pe) | ||
979 | { | ||
980 | struct eeh_dev *edev; | ||
981 | |||
982 | if (!dev || !eeh_subsystem_enabled) | ||
983 | return; | ||
984 | edev = pci_dev_to_eeh_dev(dev); | ||
985 | |||
986 | /* Unregister the device with the EEH/PCI address search system */ | ||
987 | pr_debug("EEH: Removing device %s\n", pci_name(dev)); | ||
988 | |||
989 | if (!edev || !edev->pdev) { | ||
990 | pr_debug("EEH: Not referenced !\n"); | ||
991 | return; | ||
992 | } | ||
993 | edev->pdev = NULL; | ||
994 | dev->dev.archdata.edev = NULL; | ||
995 | pci_dev_put(dev); | ||
996 | |||
997 | eeh_rmv_from_parent_pe(edev, purge_pe); | ||
998 | eeh_addr_cache_rmv_dev(dev); | ||
999 | eeh_sysfs_remove_device(dev); | ||
1000 | } | ||
1001 | |||
1002 | /** | ||
1003 | * eeh_remove_bus_device - Undo EEH setup for the indicated PCI device | ||
1004 | * @dev: PCI device | ||
1005 | * @purge_pe: remove the corresponding PE or not | ||
1006 | * | ||
1007 | * This routine must be called when a device is removed from the | ||
1008 | * running system through hotplug or dlpar. The corresponding | ||
1009 | * PCI address cache will be removed. | ||
1010 | */ | ||
1011 | void eeh_remove_bus_device(struct pci_dev *dev, int purge_pe) | ||
1012 | { | ||
1013 | struct pci_bus *bus = dev->subordinate; | ||
1014 | struct pci_dev *child, *tmp; | ||
1015 | |||
1016 | eeh_remove_device(dev, purge_pe); | ||
1017 | |||
1018 | if (bus && dev->hdr_type == PCI_HEADER_TYPE_BRIDGE) { | ||
1019 | list_for_each_entry_safe(child, tmp, &bus->devices, bus_list) | ||
1020 | eeh_remove_bus_device(child, purge_pe); | ||
1021 | } | ||
1022 | } | ||
1023 | EXPORT_SYMBOL_GPL(eeh_remove_bus_device); | ||
1024 | |||
1025 | static int proc_eeh_show(struct seq_file *m, void *v) | ||
1026 | { | ||
1027 | if (0 == eeh_subsystem_enabled) { | ||
1028 | seq_printf(m, "EEH Subsystem is globally disabled\n"); | ||
1029 | seq_printf(m, "eeh_total_mmio_ffs=%llu\n", eeh_stats.total_mmio_ffs); | ||
1030 | } else { | ||
1031 | seq_printf(m, "EEH Subsystem is enabled\n"); | ||
1032 | seq_printf(m, | ||
1033 | "no device=%llu\n" | ||
1034 | "no device node=%llu\n" | ||
1035 | "no config address=%llu\n" | ||
1036 | "check not wanted=%llu\n" | ||
1037 | "eeh_total_mmio_ffs=%llu\n" | ||
1038 | "eeh_false_positives=%llu\n" | ||
1039 | "eeh_slot_resets=%llu\n", | ||
1040 | eeh_stats.no_device, | ||
1041 | eeh_stats.no_dn, | ||
1042 | eeh_stats.no_cfg_addr, | ||
1043 | eeh_stats.ignored_check, | ||
1044 | eeh_stats.total_mmio_ffs, | ||
1045 | eeh_stats.false_positives, | ||
1046 | eeh_stats.slot_resets); | ||
1047 | } | ||
1048 | |||
1049 | return 0; | ||
1050 | } | ||
1051 | |||
1052 | static int proc_eeh_open(struct inode *inode, struct file *file) | ||
1053 | { | ||
1054 | return single_open(file, proc_eeh_show, NULL); | ||
1055 | } | ||
1056 | |||
1057 | static const struct file_operations proc_eeh_operations = { | ||
1058 | .open = proc_eeh_open, | ||
1059 | .read = seq_read, | ||
1060 | .llseek = seq_lseek, | ||
1061 | .release = single_release, | ||
1062 | }; | ||
1063 | |||
1064 | static int __init eeh_init_proc(void) | ||
1065 | { | ||
1066 | if (machine_is(pseries)) | ||
1067 | proc_create("powerpc/eeh", 0, NULL, &proc_eeh_operations); | ||
1068 | return 0; | ||
1069 | } | ||
1070 | __initcall(eeh_init_proc); | ||