|
@@ -1,39 +1,37 @@
|
|
/*
|
|
/*
|
|
* eeh.c
|
|
* eeh.c
|
|
* Copyright (C) 2001 Dave Engebretsen & Todd Inglett IBM Corporation
|
|
* Copyright (C) 2001 Dave Engebretsen & Todd Inglett IBM Corporation
|
|
- *
|
|
|
|
|
|
+ *
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
* (at your option) any later version.
|
|
- *
|
|
|
|
|
|
+ *
|
|
* This program is distributed in the hope that it will be useful,
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
* GNU General Public License for more details.
|
|
- *
|
|
|
|
|
|
+ *
|
|
* You should have received a copy of the GNU General Public License
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
*/
|
|
|
|
|
|
-#include <linux/bootmem.h>
|
|
|
|
|
|
+#include <linux/delay.h>
|
|
#include <linux/init.h>
|
|
#include <linux/init.h>
|
|
#include <linux/list.h>
|
|
#include <linux/list.h>
|
|
-#include <linux/mm.h>
|
|
|
|
-#include <linux/notifier.h>
|
|
|
|
#include <linux/pci.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/proc_fs.h>
|
|
#include <linux/proc_fs.h>
|
|
#include <linux/rbtree.h>
|
|
#include <linux/rbtree.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/spinlock.h>
|
|
|
|
+#include <asm/atomic.h>
|
|
#include <asm/eeh.h>
|
|
#include <asm/eeh.h>
|
|
|
|
+#include <asm/eeh_event.h>
|
|
#include <asm/io.h>
|
|
#include <asm/io.h>
|
|
#include <asm/machdep.h>
|
|
#include <asm/machdep.h>
|
|
-#include <asm/rtas.h>
|
|
|
|
-#include <asm/atomic.h>
|
|
|
|
-#include <asm/systemcfg.h>
|
|
|
|
#include <asm/ppc-pci.h>
|
|
#include <asm/ppc-pci.h>
|
|
|
|
+#include <asm/rtas.h>
|
|
|
|
|
|
#undef DEBUG
|
|
#undef DEBUG
|
|
|
|
|
|
@@ -49,8 +47,8 @@
|
|
* were "empty": all reads return 0xff's and all writes are silently
|
|
* were "empty": all reads return 0xff's and all writes are silently
|
|
* ignored. EEH slot isolation events can be triggered by parity
|
|
* ignored. EEH slot isolation events can be triggered by parity
|
|
* errors on the address or data busses (e.g. during posted writes),
|
|
* errors on the address or data busses (e.g. during posted writes),
|
|
- * which in turn might be caused by dust, vibration, humidity,
|
|
|
|
- * radioactivity or plain-old failed hardware.
|
|
|
|
|
|
+ * which in turn might be caused by low voltage on the bus, dust,
|
|
|
|
+ * vibration, humidity, radioactivity or plain-old failed hardware.
|
|
*
|
|
*
|
|
* Note, however, that one of the leading causes of EEH slot
|
|
* Note, however, that one of the leading causes of EEH slot
|
|
* freeze events are buggy device drivers, buggy device microcode,
|
|
* freeze events are buggy device drivers, buggy device microcode,
|
|
@@ -71,26 +69,15 @@
|
|
* and sent out for processing.
|
|
* and sent out for processing.
|
|
*/
|
|
*/
|
|
|
|
|
|
-/** Bus Unit ID macros; get low and hi 32-bits of the 64-bit BUID */
|
|
|
|
-#define BUID_HI(buid) ((buid) >> 32)
|
|
|
|
-#define BUID_LO(buid) ((buid) & 0xffffffff)
|
|
|
|
-
|
|
|
|
-/* EEH event workqueue setup. */
|
|
|
|
-static DEFINE_SPINLOCK(eeh_eventlist_lock);
|
|
|
|
-LIST_HEAD(eeh_eventlist);
|
|
|
|
-static void eeh_event_handler(void *);
|
|
|
|
-DECLARE_WORK(eeh_event_wq, eeh_event_handler, NULL);
|
|
|
|
-
|
|
|
|
-static struct notifier_block *eeh_notifier_chain;
|
|
|
|
-
|
|
|
|
-/*
|
|
|
|
- * If a device driver keeps reading an MMIO register in an interrupt
|
|
|
|
|
|
+/* If a device driver keeps reading an MMIO register in an interrupt
|
|
* handler after a slot isolation event has occurred, we assume it
|
|
* handler after a slot isolation event has occurred, we assume it
|
|
* is broken and panic. This sets the threshold for how many read
|
|
* is broken and panic. This sets the threshold for how many read
|
|
* attempts we allow before panicking.
|
|
* attempts we allow before panicking.
|
|
*/
|
|
*/
|
|
-#define EEH_MAX_FAILS 1000
|
|
|
|
-static atomic_t eeh_fail_count;
|
|
|
|
|
|
+#define EEH_MAX_FAILS 100000
|
|
|
|
+
|
|
|
|
+/* Misc forward declaraions */
|
|
|
|
+static void eeh_save_bars(struct pci_dev * pdev, struct pci_dn *pdn);
|
|
|
|
|
|
/* RTAS tokens */
|
|
/* RTAS tokens */
|
|
static int ibm_set_eeh_option;
|
|
static int ibm_set_eeh_option;
|
|
@@ -101,12 +88,19 @@ static int ibm_slot_error_detail;
|
|
|
|
|
|
static int eeh_subsystem_enabled;
|
|
static int eeh_subsystem_enabled;
|
|
|
|
|
|
|
|
+/* Lock to avoid races due to multiple reports of an error */
|
|
|
|
+static DEFINE_SPINLOCK(confirm_error_lock);
|
|
|
|
+
|
|
/* Buffer for reporting slot-error-detail rtas calls */
|
|
/* Buffer for reporting slot-error-detail rtas calls */
|
|
static unsigned char slot_errbuf[RTAS_ERROR_LOG_MAX];
|
|
static unsigned char slot_errbuf[RTAS_ERROR_LOG_MAX];
|
|
static DEFINE_SPINLOCK(slot_errbuf_lock);
|
|
static DEFINE_SPINLOCK(slot_errbuf_lock);
|
|
static int eeh_error_buf_size;
|
|
static int eeh_error_buf_size;
|
|
|
|
|
|
/* System monitoring statistics */
|
|
/* System monitoring statistics */
|
|
|
|
+static DEFINE_PER_CPU(unsigned long, no_device);
|
|
|
|
+static DEFINE_PER_CPU(unsigned long, no_dn);
|
|
|
|
+static DEFINE_PER_CPU(unsigned long, no_cfg_addr);
|
|
|
|
+static DEFINE_PER_CPU(unsigned long, ignored_check);
|
|
static DEFINE_PER_CPU(unsigned long, total_mmio_ffs);
|
|
static DEFINE_PER_CPU(unsigned long, total_mmio_ffs);
|
|
static DEFINE_PER_CPU(unsigned long, false_positives);
|
|
static DEFINE_PER_CPU(unsigned long, false_positives);
|
|
static DEFINE_PER_CPU(unsigned long, ignored_failures);
|
|
static DEFINE_PER_CPU(unsigned long, ignored_failures);
|
|
@@ -224,9 +218,9 @@ pci_addr_cache_insert(struct pci_dev *dev, unsigned long alo,
|
|
while (*p) {
|
|
while (*p) {
|
|
parent = *p;
|
|
parent = *p;
|
|
piar = rb_entry(parent, struct pci_io_addr_range, rb_node);
|
|
piar = rb_entry(parent, struct pci_io_addr_range, rb_node);
|
|
- if (alo < piar->addr_lo) {
|
|
|
|
|
|
+ if (ahi < piar->addr_lo) {
|
|
p = &parent->rb_left;
|
|
p = &parent->rb_left;
|
|
- } else if (ahi > piar->addr_hi) {
|
|
|
|
|
|
+ } else if (alo > piar->addr_hi) {
|
|
p = &parent->rb_right;
|
|
p = &parent->rb_right;
|
|
} else {
|
|
} else {
|
|
if (dev != piar->pcidev ||
|
|
if (dev != piar->pcidev ||
|
|
@@ -245,6 +239,11 @@ pci_addr_cache_insert(struct pci_dev *dev, unsigned long alo,
|
|
piar->pcidev = dev;
|
|
piar->pcidev = dev;
|
|
piar->flags = flags;
|
|
piar->flags = flags;
|
|
|
|
|
|
|
|
+#ifdef DEBUG
|
|
|
|
+ printk(KERN_DEBUG "PIAR: insert range=[%lx:%lx] dev=%s\n",
|
|
|
|
+ alo, ahi, pci_name (dev));
|
|
|
|
+#endif
|
|
|
|
+
|
|
rb_link_node(&piar->rb_node, parent, p);
|
|
rb_link_node(&piar->rb_node, parent, p);
|
|
rb_insert_color(&piar->rb_node, &pci_io_addr_cache_root.rb_root);
|
|
rb_insert_color(&piar->rb_node, &pci_io_addr_cache_root.rb_root);
|
|
|
|
|
|
@@ -260,18 +259,17 @@ static void __pci_addr_cache_insert_device(struct pci_dev *dev)
|
|
|
|
|
|
dn = pci_device_to_OF_node(dev);
|
|
dn = pci_device_to_OF_node(dev);
|
|
if (!dn) {
|
|
if (!dn) {
|
|
- printk(KERN_WARNING "PCI: no pci dn found for dev=%s\n",
|
|
|
|
- pci_name(dev));
|
|
|
|
|
|
+ printk(KERN_WARNING "PCI: no pci dn found for dev=%s\n", pci_name(dev));
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
/* Skip any devices for which EEH is not enabled. */
|
|
/* Skip any devices for which EEH is not enabled. */
|
|
- pdn = dn->data;
|
|
|
|
|
|
+ pdn = PCI_DN(dn);
|
|
if (!(pdn->eeh_mode & EEH_MODE_SUPPORTED) ||
|
|
if (!(pdn->eeh_mode & EEH_MODE_SUPPORTED) ||
|
|
pdn->eeh_mode & EEH_MODE_NOCHECK) {
|
|
pdn->eeh_mode & EEH_MODE_NOCHECK) {
|
|
#ifdef DEBUG
|
|
#ifdef DEBUG
|
|
- printk(KERN_INFO "PCI: skip building address cache for=%s\n",
|
|
|
|
- pci_name(dev));
|
|
|
|
|
|
+ printk(KERN_INFO "PCI: skip building address cache for=%s - %s\n",
|
|
|
|
+ pci_name(dev), pdn->node->full_name);
|
|
#endif
|
|
#endif
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
@@ -307,7 +305,7 @@ static void __pci_addr_cache_insert_device(struct pci_dev *dev)
|
|
* we maintain a cache of devices that can be quickly searched.
|
|
* we maintain a cache of devices that can be quickly searched.
|
|
* This routine adds a device to that cache.
|
|
* This routine adds a device to that cache.
|
|
*/
|
|
*/
|
|
-void pci_addr_cache_insert_device(struct pci_dev *dev)
|
|
|
|
|
|
+static void pci_addr_cache_insert_device(struct pci_dev *dev)
|
|
{
|
|
{
|
|
unsigned long flags;
|
|
unsigned long flags;
|
|
|
|
|
|
@@ -350,7 +348,7 @@ restart:
|
|
* the tree multiple times (once per resource).
|
|
* the tree multiple times (once per resource).
|
|
* But so what; device removal doesn't need to be that fast.
|
|
* But so what; device removal doesn't need to be that fast.
|
|
*/
|
|
*/
|
|
-void pci_addr_cache_remove_device(struct pci_dev *dev)
|
|
|
|
|
|
+static void pci_addr_cache_remove_device(struct pci_dev *dev)
|
|
{
|
|
{
|
|
unsigned long flags;
|
|
unsigned long flags;
|
|
|
|
|
|
@@ -370,8 +368,12 @@ void pci_addr_cache_remove_device(struct pci_dev *dev)
|
|
*/
|
|
*/
|
|
void __init pci_addr_cache_build(void)
|
|
void __init pci_addr_cache_build(void)
|
|
{
|
|
{
|
|
|
|
+ struct device_node *dn;
|
|
struct pci_dev *dev = NULL;
|
|
struct pci_dev *dev = NULL;
|
|
|
|
|
|
|
|
+ if (!eeh_subsystem_enabled)
|
|
|
|
+ return;
|
|
|
|
+
|
|
spin_lock_init(&pci_io_addr_cache_root.piar_lock);
|
|
spin_lock_init(&pci_io_addr_cache_root.piar_lock);
|
|
|
|
|
|
while ((dev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, dev)) != NULL) {
|
|
while ((dev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, dev)) != NULL) {
|
|
@@ -380,6 +382,10 @@ void __init pci_addr_cache_build(void)
|
|
continue;
|
|
continue;
|
|
}
|
|
}
|
|
pci_addr_cache_insert_device(dev);
|
|
pci_addr_cache_insert_device(dev);
|
|
|
|
+
|
|
|
|
+ /* Save the BAR's; firmware doesn't restore these after EEH reset */
|
|
|
|
+ dn = pci_device_to_OF_node(dev);
|
|
|
|
+ eeh_save_bars(dev, PCI_DN(dn));
|
|
}
|
|
}
|
|
|
|
|
|
#ifdef DEBUG
|
|
#ifdef DEBUG
|
|
@@ -391,22 +397,26 @@ void __init pci_addr_cache_build(void)
|
|
/* --------------------------------------------------------------- */
|
|
/* --------------------------------------------------------------- */
|
|
/* Above lies the PCI Address Cache. Below lies the EEH event infrastructure */
|
|
/* Above lies the PCI Address Cache. Below lies the EEH event infrastructure */
|
|
|
|
|
|
-/**
|
|
|
|
- * eeh_register_notifier - Register to find out about EEH events.
|
|
|
|
- * @nb: notifier block to callback on events
|
|
|
|
- */
|
|
|
|
-int eeh_register_notifier(struct notifier_block *nb)
|
|
|
|
|
|
+void eeh_slot_error_detail (struct pci_dn *pdn, int severity)
|
|
{
|
|
{
|
|
- return notifier_chain_register(&eeh_notifier_chain, nb);
|
|
|
|
-}
|
|
|
|
|
|
+ unsigned long flags;
|
|
|
|
+ int rc;
|
|
|
|
|
|
-/**
|
|
|
|
- * eeh_unregister_notifier - Unregister to an EEH event notifier.
|
|
|
|
- * @nb: notifier block to callback on events
|
|
|
|
- */
|
|
|
|
-int eeh_unregister_notifier(struct notifier_block *nb)
|
|
|
|
-{
|
|
|
|
- return notifier_chain_unregister(&eeh_notifier_chain, nb);
|
|
|
|
|
|
+ /* Log the error with the rtas logger */
|
|
|
|
+ spin_lock_irqsave(&slot_errbuf_lock, flags);
|
|
|
|
+ memset(slot_errbuf, 0, eeh_error_buf_size);
|
|
|
|
+
|
|
|
|
+ rc = rtas_call(ibm_slot_error_detail,
|
|
|
|
+ 8, 1, NULL, pdn->eeh_config_addr,
|
|
|
|
+ BUID_HI(pdn->phb->buid),
|
|
|
|
+ BUID_LO(pdn->phb->buid), NULL, 0,
|
|
|
|
+ virt_to_phys(slot_errbuf),
|
|
|
|
+ eeh_error_buf_size,
|
|
|
|
+ severity);
|
|
|
|
+
|
|
|
|
+ if (rc == 0)
|
|
|
|
+ log_error(slot_errbuf, ERR_TYPE_RTAS_LOG, 0);
|
|
|
|
+ spin_unlock_irqrestore(&slot_errbuf_lock, flags);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -414,16 +424,16 @@ int eeh_unregister_notifier(struct notifier_block *nb)
|
|
* @dn: device node to read
|
|
* @dn: device node to read
|
|
* @rets: array to return results in
|
|
* @rets: array to return results in
|
|
*/
|
|
*/
|
|
-static int read_slot_reset_state(struct device_node *dn, int rets[])
|
|
|
|
|
|
+static int read_slot_reset_state(struct pci_dn *pdn, int rets[])
|
|
{
|
|
{
|
|
int token, outputs;
|
|
int token, outputs;
|
|
- struct pci_dn *pdn = dn->data;
|
|
|
|
|
|
|
|
if (ibm_read_slot_reset_state2 != RTAS_UNKNOWN_SERVICE) {
|
|
if (ibm_read_slot_reset_state2 != RTAS_UNKNOWN_SERVICE) {
|
|
token = ibm_read_slot_reset_state2;
|
|
token = ibm_read_slot_reset_state2;
|
|
outputs = 4;
|
|
outputs = 4;
|
|
} else {
|
|
} else {
|
|
token = ibm_read_slot_reset_state;
|
|
token = ibm_read_slot_reset_state;
|
|
|
|
+ rets[2] = 0; /* fake PE Unavailable info */
|
|
outputs = 3;
|
|
outputs = 3;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -432,87 +442,84 @@ static int read_slot_reset_state(struct device_node *dn, int rets[])
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
/**
|
|
- * eeh_panic - call panic() for an eeh event that cannot be handled.
|
|
|
|
- * The philosophy of this routine is that it is better to panic and
|
|
|
|
- * halt the OS than it is to risk possible data corruption by
|
|
|
|
- * oblivious device drivers that don't know better.
|
|
|
|
- *
|
|
|
|
- * @dev pci device that had an eeh event
|
|
|
|
- * @reset_state current reset state of the device slot
|
|
|
|
|
|
+ * eeh_token_to_phys - convert EEH address token to phys address
|
|
|
|
+ * @token i/o token, should be address in the form 0xA....
|
|
*/
|
|
*/
|
|
-static void eeh_panic(struct pci_dev *dev, int reset_state)
|
|
|
|
|
|
+static inline unsigned long eeh_token_to_phys(unsigned long token)
|
|
{
|
|
{
|
|
- /*
|
|
|
|
- * XXX We should create a separate sysctl for this.
|
|
|
|
- *
|
|
|
|
- * Since the panic_on_oops sysctl is used to halt the system
|
|
|
|
- * in light of potential corruption, we can use it here.
|
|
|
|
- */
|
|
|
|
- if (panic_on_oops)
|
|
|
|
- panic("EEH: MMIO failure (%d) on device:%s\n", reset_state,
|
|
|
|
- pci_name(dev));
|
|
|
|
- else {
|
|
|
|
- __get_cpu_var(ignored_failures)++;
|
|
|
|
- printk(KERN_INFO "EEH: Ignored MMIO failure (%d) on device:%s\n",
|
|
|
|
- reset_state, pci_name(dev));
|
|
|
|
- }
|
|
|
|
|
|
+ pte_t *ptep;
|
|
|
|
+ unsigned long pa;
|
|
|
|
+
|
|
|
|
+ ptep = find_linux_pte(init_mm.pgd, token);
|
|
|
|
+ if (!ptep)
|
|
|
|
+ return token;
|
|
|
|
+ pa = pte_pfn(*ptep) << PAGE_SHIFT;
|
|
|
|
+
|
|
|
|
+ return pa | (token & (PAGE_SIZE-1));
|
|
}
|
|
}
|
|
|
|
|
|
-/**
|
|
|
|
- * eeh_event_handler - dispatch EEH events. The detection of a frozen
|
|
|
|
- * slot can occur inside an interrupt, where it can be hard to do
|
|
|
|
- * anything about it. The goal of this routine is to pull these
|
|
|
|
- * detection events out of the context of the interrupt handler, and
|
|
|
|
- * re-dispatch them for processing at a later time in a normal context.
|
|
|
|
- *
|
|
|
|
- * @dummy - unused
|
|
|
|
|
|
+/**
|
|
|
|
+ * Return the "partitionable endpoint" (pe) under which this device lies
|
|
*/
|
|
*/
|
|
-static void eeh_event_handler(void *dummy)
|
|
|
|
|
|
+static struct device_node * find_device_pe(struct device_node *dn)
|
|
{
|
|
{
|
|
- unsigned long flags;
|
|
|
|
- struct eeh_event *event;
|
|
|
|
-
|
|
|
|
- while (1) {
|
|
|
|
- spin_lock_irqsave(&eeh_eventlist_lock, flags);
|
|
|
|
- event = NULL;
|
|
|
|
- if (!list_empty(&eeh_eventlist)) {
|
|
|
|
- event = list_entry(eeh_eventlist.next, struct eeh_event, list);
|
|
|
|
- list_del(&event->list);
|
|
|
|
- }
|
|
|
|
- spin_unlock_irqrestore(&eeh_eventlist_lock, flags);
|
|
|
|
- if (event == NULL)
|
|
|
|
- break;
|
|
|
|
-
|
|
|
|
- printk(KERN_INFO "EEH: MMIO failure (%d), notifiying device "
|
|
|
|
- "%s\n", event->reset_state,
|
|
|
|
- pci_name(event->dev));
|
|
|
|
|
|
+ while ((dn->parent) && PCI_DN(dn->parent) &&
|
|
|
|
+ (PCI_DN(dn->parent)->eeh_mode & EEH_MODE_SUPPORTED)) {
|
|
|
|
+ dn = dn->parent;
|
|
|
|
+ }
|
|
|
|
+ return dn;
|
|
|
|
+}
|
|
|
|
|
|
- atomic_set(&eeh_fail_count, 0);
|
|
|
|
- notifier_call_chain (&eeh_notifier_chain,
|
|
|
|
- EEH_NOTIFY_FREEZE, event);
|
|
|
|
|
|
+/** Mark all devices that are peers of this device as failed.
|
|
|
|
+ * Mark the device driver too, so that it can see the failure
|
|
|
|
+ * immediately; this is critical, since some drivers poll
|
|
|
|
+ * status registers in interrupts ... If a driver is polling,
|
|
|
|
+ * and the slot is frozen, then the driver can deadlock in
|
|
|
|
+ * an interrupt context, which is bad.
|
|
|
|
+ */
|
|
|
|
|
|
- __get_cpu_var(slot_resets)++;
|
|
|
|
|
|
+static void __eeh_mark_slot (struct device_node *dn, int mode_flag)
|
|
|
|
+{
|
|
|
|
+ while (dn) {
|
|
|
|
+ if (PCI_DN(dn)) {
|
|
|
|
+ PCI_DN(dn)->eeh_mode |= mode_flag;
|
|
|
|
|
|
- pci_dev_put(event->dev);
|
|
|
|
- kfree(event);
|
|
|
|
|
|
+ if (dn->child)
|
|
|
|
+ __eeh_mark_slot (dn->child, mode_flag);
|
|
|
|
+ }
|
|
|
|
+ dn = dn->sibling;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
-/**
|
|
|
|
- * eeh_token_to_phys - convert EEH address token to phys address
|
|
|
|
- * @token i/o token, should be address in the form 0xE....
|
|
|
|
- */
|
|
|
|
-static inline unsigned long eeh_token_to_phys(unsigned long token)
|
|
|
|
|
|
+void eeh_mark_slot (struct device_node *dn, int mode_flag)
|
|
{
|
|
{
|
|
- pte_t *ptep;
|
|
|
|
- unsigned long pa;
|
|
|
|
|
|
+ dn = find_device_pe (dn);
|
|
|
|
+ PCI_DN(dn)->eeh_mode |= mode_flag;
|
|
|
|
+ __eeh_mark_slot (dn->child, mode_flag);
|
|
|
|
+}
|
|
|
|
|
|
- ptep = find_linux_pte(init_mm.pgd, token);
|
|
|
|
- if (!ptep)
|
|
|
|
- return token;
|
|
|
|
- pa = pte_pfn(*ptep) << PAGE_SHIFT;
|
|
|
|
|
|
+static void __eeh_clear_slot (struct device_node *dn, int mode_flag)
|
|
|
|
+{
|
|
|
|
+ while (dn) {
|
|
|
|
+ if (PCI_DN(dn)) {
|
|
|
|
+ PCI_DN(dn)->eeh_mode &= ~mode_flag;
|
|
|
|
+ PCI_DN(dn)->eeh_check_count = 0;
|
|
|
|
+ if (dn->child)
|
|
|
|
+ __eeh_clear_slot (dn->child, mode_flag);
|
|
|
|
+ }
|
|
|
|
+ dn = dn->sibling;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
|
|
- return pa | (token & (PAGE_SIZE-1));
|
|
|
|
|
|
+void eeh_clear_slot (struct device_node *dn, int mode_flag)
|
|
|
|
+{
|
|
|
|
+ unsigned long flags;
|
|
|
|
+ spin_lock_irqsave(&confirm_error_lock, flags);
|
|
|
|
+ dn = find_device_pe (dn);
|
|
|
|
+ PCI_DN(dn)->eeh_mode &= ~mode_flag;
|
|
|
|
+ PCI_DN(dn)->eeh_check_count = 0;
|
|
|
|
+ __eeh_clear_slot (dn->child, mode_flag);
|
|
|
|
+ spin_unlock_irqrestore(&confirm_error_lock, flags);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -526,7 +533,7 @@ static inline unsigned long eeh_token_to_phys(unsigned long token)
|
|
* will query firmware for the EEH status.
|
|
* will query firmware for the EEH status.
|
|
*
|
|
*
|
|
* Returns 0 if there has not been an EEH error; otherwise returns
|
|
* Returns 0 if there has not been an EEH error; otherwise returns
|
|
- * a non-zero value and queues up a solt isolation event notification.
|
|
|
|
|
|
+ * a non-zero value and queues up a slot isolation event notification.
|
|
*
|
|
*
|
|
* It is safe to call this routine in an interrupt context.
|
|
* It is safe to call this routine in an interrupt context.
|
|
*/
|
|
*/
|
|
@@ -535,42 +542,59 @@ int eeh_dn_check_failure(struct device_node *dn, struct pci_dev *dev)
|
|
int ret;
|
|
int ret;
|
|
int rets[3];
|
|
int rets[3];
|
|
unsigned long flags;
|
|
unsigned long flags;
|
|
- int rc, reset_state;
|
|
|
|
- struct eeh_event *event;
|
|
|
|
struct pci_dn *pdn;
|
|
struct pci_dn *pdn;
|
|
|
|
+ int rc = 0;
|
|
|
|
|
|
__get_cpu_var(total_mmio_ffs)++;
|
|
__get_cpu_var(total_mmio_ffs)++;
|
|
|
|
|
|
if (!eeh_subsystem_enabled)
|
|
if (!eeh_subsystem_enabled)
|
|
return 0;
|
|
return 0;
|
|
|
|
|
|
- if (!dn)
|
|
|
|
|
|
+ if (!dn) {
|
|
|
|
+ __get_cpu_var(no_dn)++;
|
|
return 0;
|
|
return 0;
|
|
- pdn = dn->data;
|
|
|
|
|
|
+ }
|
|
|
|
+ pdn = PCI_DN(dn);
|
|
|
|
|
|
/* Access to IO BARs might get this far and still not want checking. */
|
|
/* Access to IO BARs might get this far and still not want checking. */
|
|
- if (!pdn->eeh_capable || !(pdn->eeh_mode & EEH_MODE_SUPPORTED) ||
|
|
|
|
|
|
+ if (!(pdn->eeh_mode & EEH_MODE_SUPPORTED) ||
|
|
pdn->eeh_mode & EEH_MODE_NOCHECK) {
|
|
pdn->eeh_mode & EEH_MODE_NOCHECK) {
|
|
|
|
+ __get_cpu_var(ignored_check)++;
|
|
|
|
+#ifdef DEBUG
|
|
|
|
+ printk ("EEH:ignored check (%x) for %s %s\n",
|
|
|
|
+ pdn->eeh_mode, pci_name (dev), dn->full_name);
|
|
|
|
+#endif
|
|
return 0;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
if (!pdn->eeh_config_addr) {
|
|
if (!pdn->eeh_config_addr) {
|
|
|
|
+ __get_cpu_var(no_cfg_addr)++;
|
|
return 0;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
- /*
|
|
|
|
- * If we already have a pending isolation event for this
|
|
|
|
- * slot, we know it's bad already, we don't need to check...
|
|
|
|
|
|
+ /* If we already have a pending isolation event for this
|
|
|
|
+ * slot, we know it's bad already, we don't need to check.
|
|
|
|
+ * Do this checking under a lock; as multiple PCI devices
|
|
|
|
+ * in one slot might report errors simultaneously, and we
|
|
|
|
+ * only want one error recovery routine running.
|
|
*/
|
|
*/
|
|
|
|
+ spin_lock_irqsave(&confirm_error_lock, flags);
|
|
|
|
+ rc = 1;
|
|
if (pdn->eeh_mode & EEH_MODE_ISOLATED) {
|
|
if (pdn->eeh_mode & EEH_MODE_ISOLATED) {
|
|
- atomic_inc(&eeh_fail_count);
|
|
|
|
- if (atomic_read(&eeh_fail_count) >= EEH_MAX_FAILS) {
|
|
|
|
|
|
+ pdn->eeh_check_count ++;
|
|
|
|
+ if (pdn->eeh_check_count >= EEH_MAX_FAILS) {
|
|
|
|
+ printk (KERN_ERR "EEH: Device driver ignored %d bad reads, panicing\n",
|
|
|
|
+ pdn->eeh_check_count);
|
|
|
|
+ dump_stack();
|
|
|
|
+
|
|
/* re-read the slot reset state */
|
|
/* re-read the slot reset state */
|
|
- if (read_slot_reset_state(dn, rets) != 0)
|
|
|
|
|
|
+ if (read_slot_reset_state(pdn, rets) != 0)
|
|
rets[0] = -1; /* reset state unknown */
|
|
rets[0] = -1; /* reset state unknown */
|
|
- eeh_panic(dev, rets[0]);
|
|
|
|
|
|
+
|
|
|
|
+ /* If we are here, then we hit an infinite loop. Stop. */
|
|
|
|
+ panic("EEH: MMIO halt (%d) on device:%s\n", rets[0], pci_name(dev));
|
|
}
|
|
}
|
|
- return 0;
|
|
|
|
|
|
+ goto dn_unlock;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
/*
|
|
@@ -580,66 +604,69 @@ int eeh_dn_check_failure(struct device_node *dn, struct pci_dev *dev)
|
|
* function zero of a multi-function device.
|
|
* function zero of a multi-function device.
|
|
* In any case they must share a common PHB.
|
|
* In any case they must share a common PHB.
|
|
*/
|
|
*/
|
|
- ret = read_slot_reset_state(dn, rets);
|
|
|
|
- if (!(ret == 0 && rets[1] == 1 && (rets[0] == 2 || rets[0] == 4))) {
|
|
|
|
|
|
+ ret = read_slot_reset_state(pdn, rets);
|
|
|
|
+
|
|
|
|
+ /* If the call to firmware failed, punt */
|
|
|
|
+ if (ret != 0) {
|
|
|
|
+ printk(KERN_WARNING "EEH: read_slot_reset_state() failed; rc=%d dn=%s\n",
|
|
|
|
+ ret, dn->full_name);
|
|
__get_cpu_var(false_positives)++;
|
|
__get_cpu_var(false_positives)++;
|
|
- return 0;
|
|
|
|
|
|
+ rc = 0;
|
|
|
|
+ goto dn_unlock;
|
|
}
|
|
}
|
|
|
|
|
|
- /* prevent repeated reports of this failure */
|
|
|
|
- pdn->eeh_mode |= EEH_MODE_ISOLATED;
|
|
|
|
-
|
|
|
|
- reset_state = rets[0];
|
|
|
|
-
|
|
|
|
- spin_lock_irqsave(&slot_errbuf_lock, flags);
|
|
|
|
- memset(slot_errbuf, 0, eeh_error_buf_size);
|
|
|
|
-
|
|
|
|
- rc = rtas_call(ibm_slot_error_detail,
|
|
|
|
- 8, 1, NULL, pdn->eeh_config_addr,
|
|
|
|
- BUID_HI(pdn->phb->buid),
|
|
|
|
- BUID_LO(pdn->phb->buid), NULL, 0,
|
|
|
|
- virt_to_phys(slot_errbuf),
|
|
|
|
- eeh_error_buf_size,
|
|
|
|
- 1 /* Temporary Error */);
|
|
|
|
-
|
|
|
|
- if (rc == 0)
|
|
|
|
- log_error(slot_errbuf, ERR_TYPE_RTAS_LOG, 0);
|
|
|
|
- spin_unlock_irqrestore(&slot_errbuf_lock, flags);
|
|
|
|
|
|
+ /* If EEH is not supported on this device, punt. */
|
|
|
|
+ if (rets[1] != 1) {
|
|
|
|
+ printk(KERN_WARNING "EEH: event on unsupported device, rc=%d dn=%s\n",
|
|
|
|
+ ret, dn->full_name);
|
|
|
|
+ __get_cpu_var(false_positives)++;
|
|
|
|
+ rc = 0;
|
|
|
|
+ goto dn_unlock;
|
|
|
|
+ }
|
|
|
|
|
|
- printk(KERN_INFO "EEH: MMIO failure (%d) on device: %s %s\n",
|
|
|
|
- rets[0], dn->name, dn->full_name);
|
|
|
|
- event = kmalloc(sizeof(*event), GFP_ATOMIC);
|
|
|
|
- if (event == NULL) {
|
|
|
|
- eeh_panic(dev, reset_state);
|
|
|
|
- return 1;
|
|
|
|
- }
|
|
|
|
|
|
+ /* If not the kind of error we know about, punt. */
|
|
|
|
+ if (rets[0] != 2 && rets[0] != 4 && rets[0] != 5) {
|
|
|
|
+ __get_cpu_var(false_positives)++;
|
|
|
|
+ rc = 0;
|
|
|
|
+ goto dn_unlock;
|
|
|
|
+ }
|
|
|
|
|
|
- event->dev = dev;
|
|
|
|
- event->dn = dn;
|
|
|
|
- event->reset_state = reset_state;
|
|
|
|
|
|
+ /* Note that config-io to empty slots may fail;
|
|
|
|
+ * we recognize empty because they don't have children. */
|
|
|
|
+ if ((rets[0] == 5) && (dn->child == NULL)) {
|
|
|
|
+ __get_cpu_var(false_positives)++;
|
|
|
|
+ rc = 0;
|
|
|
|
+ goto dn_unlock;
|
|
|
|
+ }
|
|
|
|
|
|
- /* We may or may not be called in an interrupt context */
|
|
|
|
- spin_lock_irqsave(&eeh_eventlist_lock, flags);
|
|
|
|
- list_add(&event->list, &eeh_eventlist);
|
|
|
|
- spin_unlock_irqrestore(&eeh_eventlist_lock, flags);
|
|
|
|
|
|
+ __get_cpu_var(slot_resets)++;
|
|
|
|
+
|
|
|
|
+ /* Avoid repeated reports of this failure, including problems
|
|
|
|
+ * with other functions on this device, and functions under
|
|
|
|
+ * bridges. */
|
|
|
|
+ eeh_mark_slot (dn, EEH_MODE_ISOLATED);
|
|
|
|
+ spin_unlock_irqrestore(&confirm_error_lock, flags);
|
|
|
|
|
|
|
|
+ eeh_send_failure_event (dn, dev, rets[0], rets[2]);
|
|
|
|
+
|
|
/* Most EEH events are due to device driver bugs. Having
|
|
/* Most EEH events are due to device driver bugs. Having
|
|
* a stack trace will help the device-driver authors figure
|
|
* a stack trace will help the device-driver authors figure
|
|
* out what happened. So print that out. */
|
|
* out what happened. So print that out. */
|
|
- dump_stack();
|
|
|
|
- schedule_work(&eeh_event_wq);
|
|
|
|
|
|
+ if (rets[0] != 5) dump_stack();
|
|
|
|
+ return 1;
|
|
|
|
|
|
- return 0;
|
|
|
|
|
|
+dn_unlock:
|
|
|
|
+ spin_unlock_irqrestore(&confirm_error_lock, flags);
|
|
|
|
+ return rc;
|
|
}
|
|
}
|
|
|
|
|
|
-EXPORT_SYMBOL(eeh_dn_check_failure);
|
|
|
|
|
|
+EXPORT_SYMBOL_GPL(eeh_dn_check_failure);
|
|
|
|
|
|
/**
|
|
/**
|
|
* eeh_check_failure - check if all 1's data is due to EEH slot freeze
|
|
* eeh_check_failure - check if all 1's data is due to EEH slot freeze
|
|
* @token i/o token, should be address in the form 0xA....
|
|
* @token i/o token, should be address in the form 0xA....
|
|
* @val value, should be all 1's (XXX why do we need this arg??)
|
|
* @val value, should be all 1's (XXX why do we need this arg??)
|
|
*
|
|
*
|
|
- * Check for an eeh failure at the given token address.
|
|
|
|
* Check for an EEH failure at the given token address. Call this
|
|
* Check for an EEH failure at the given token address. Call this
|
|
* routine if the result of a read was all 0xff's and you want to
|
|
* routine if the result of a read was all 0xff's and you want to
|
|
* find out if this is due to an EEH slot freeze event. This routine
|
|
* find out if this is due to an EEH slot freeze event. This routine
|
|
@@ -656,8 +683,10 @@ unsigned long eeh_check_failure(const volatile void __iomem *token, unsigned lon
|
|
/* Finding the phys addr + pci device; this is pretty quick. */
|
|
/* Finding the phys addr + pci device; this is pretty quick. */
|
|
addr = eeh_token_to_phys((unsigned long __force) token);
|
|
addr = eeh_token_to_phys((unsigned long __force) token);
|
|
dev = pci_get_device_by_addr(addr);
|
|
dev = pci_get_device_by_addr(addr);
|
|
- if (!dev)
|
|
|
|
|
|
+ if (!dev) {
|
|
|
|
+ __get_cpu_var(no_device)++;
|
|
return val;
|
|
return val;
|
|
|
|
+ }
|
|
|
|
|
|
dn = pci_device_to_OF_node(dev);
|
|
dn = pci_device_to_OF_node(dev);
|
|
eeh_dn_check_failure (dn, dev);
|
|
eeh_dn_check_failure (dn, dev);
|
|
@@ -668,6 +697,217 @@ unsigned long eeh_check_failure(const volatile void __iomem *token, unsigned lon
|
|
|
|
|
|
EXPORT_SYMBOL(eeh_check_failure);
|
|
EXPORT_SYMBOL(eeh_check_failure);
|
|
|
|
|
|
|
|
+/* ------------------------------------------------------------- */
|
|
|
|
+/* The code below deals with error recovery */
|
|
|
|
+
|
|
|
|
+/** Return negative value if a permanent error, else return
|
|
|
|
+ * a number of milliseconds to wait until the PCI slot is
|
|
|
|
+ * ready to be used.
|
|
|
|
+ */
|
|
|
|
+static int
|
|
|
|
+eeh_slot_availability(struct pci_dn *pdn)
|
|
|
|
+{
|
|
|
|
+ int rc;
|
|
|
|
+ int rets[3];
|
|
|
|
+
|
|
|
|
+ rc = read_slot_reset_state(pdn, rets);
|
|
|
|
+
|
|
|
|
+ if (rc) return rc;
|
|
|
|
+
|
|
|
|
+ if (rets[1] == 0) return -1; /* EEH is not supported */
|
|
|
|
+ if (rets[0] == 0) return 0; /* Oll Korrect */
|
|
|
|
+ if (rets[0] == 5) {
|
|
|
|
+ if (rets[2] == 0) return -1; /* permanently unavailable */
|
|
|
|
+ return rets[2]; /* number of millisecs to wait */
|
|
|
|
+ }
|
|
|
|
+ return -1;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/** rtas_pci_slot_reset raises/lowers the pci #RST line
|
|
|
|
+ * state: 1/0 to raise/lower the #RST
|
|
|
|
+ *
|
|
|
|
+ * Clear the EEH-frozen condition on a slot. This routine
|
|
|
|
+ * asserts the PCI #RST line if the 'state' argument is '1',
|
|
|
|
+ * and drops the #RST line if 'state is '0'. This routine is
|
|
|
|
+ * safe to call in an interrupt context.
|
|
|
|
+ *
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+static void
|
|
|
|
+rtas_pci_slot_reset(struct pci_dn *pdn, int state)
|
|
|
|
+{
|
|
|
|
+ int rc;
|
|
|
|
+
|
|
|
|
+ BUG_ON (pdn==NULL);
|
|
|
|
+
|
|
|
|
+ if (!pdn->phb) {
|
|
|
|
+ printk (KERN_WARNING "EEH: in slot reset, device node %s has no phb\n",
|
|
|
|
+ pdn->node->full_name);
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ rc = rtas_call(ibm_set_slot_reset,4,1, NULL,
|
|
|
|
+ pdn->eeh_config_addr,
|
|
|
|
+ BUID_HI(pdn->phb->buid),
|
|
|
|
+ BUID_LO(pdn->phb->buid),
|
|
|
|
+ state);
|
|
|
|
+ if (rc) {
|
|
|
|
+ printk (KERN_WARNING "EEH: Unable to reset the failed slot, (%d) #RST=%d dn=%s\n",
|
|
|
|
+ rc, state, pdn->node->full_name);
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/** rtas_set_slot_reset -- assert the pci #RST line for 1/4 second
|
|
|
|
+ * dn -- device node to be reset.
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+void
|
|
|
|
+rtas_set_slot_reset(struct pci_dn *pdn)
|
|
|
|
+{
|
|
|
|
+ int i, rc;
|
|
|
|
+
|
|
|
|
+ rtas_pci_slot_reset (pdn, 1);
|
|
|
|
+
|
|
|
|
+ /* The PCI bus requires that the reset be held high for at least
|
|
|
|
+ * a 100 milliseconds. We wait a bit longer 'just in case'. */
|
|
|
|
+
|
|
|
|
+#define PCI_BUS_RST_HOLD_TIME_MSEC 250
|
|
|
|
+ msleep (PCI_BUS_RST_HOLD_TIME_MSEC);
|
|
|
|
+
|
|
|
|
+ /* We might get hit with another EEH freeze as soon as the
|
|
|
|
+ * pci slot reset line is dropped. Make sure we don't miss
|
|
|
|
+ * these, and clear the flag now. */
|
|
|
|
+ eeh_clear_slot (pdn->node, EEH_MODE_ISOLATED);
|
|
|
|
+
|
|
|
|
+ rtas_pci_slot_reset (pdn, 0);
|
|
|
|
+
|
|
|
|
+ /* After a PCI slot has been reset, the PCI Express spec requires
|
|
|
|
+ * a 1.5 second idle time for the bus to stabilize, before starting
|
|
|
|
+ * up traffic. */
|
|
|
|
+#define PCI_BUS_SETTLE_TIME_MSEC 1800
|
|
|
|
+ msleep (PCI_BUS_SETTLE_TIME_MSEC);
|
|
|
|
+
|
|
|
|
+ /* Now double check with the firmware to make sure the device is
|
|
|
|
+ * ready to be used; if not, wait for recovery. */
|
|
|
|
+ for (i=0; i<10; i++) {
|
|
|
|
+ rc = eeh_slot_availability (pdn);
|
|
|
|
+ if (rc <= 0) break;
|
|
|
|
+
|
|
|
|
+ msleep (rc+100);
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* ------------------------------------------------------- */
|
|
|
|
+/** Save and restore of PCI BARs
|
|
|
|
+ *
|
|
|
|
+ * Although firmware will set up BARs during boot, it doesn't
|
|
|
|
+ * set up device BAR's after a device reset, although it will,
|
|
|
|
+ * if requested, set up bridge configuration. Thus, we need to
|
|
|
|
+ * configure the PCI devices ourselves.
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * __restore_bars - Restore the Base Address Registers
|
|
|
|
+ * Loads the PCI configuration space base address registers,
|
|
|
|
+ * the expansion ROM base address, the latency timer, and etc.
|
|
|
|
+ * from the saved values in the device node.
|
|
|
|
+ */
|
|
|
|
+static inline void __restore_bars (struct pci_dn *pdn)
|
|
|
|
+{
|
|
|
|
+ int i;
|
|
|
|
+
|
|
|
|
+ if (NULL==pdn->phb) return;
|
|
|
|
+ for (i=4; i<10; i++) {
|
|
|
|
+ rtas_write_config(pdn, i*4, 4, pdn->config_space[i]);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* 12 == Expansion ROM Address */
|
|
|
|
+ rtas_write_config(pdn, 12*4, 4, pdn->config_space[12]);
|
|
|
|
+
|
|
|
|
+#define BYTE_SWAP(OFF) (8*((OFF)/4)+3-(OFF))
|
|
|
|
+#define SAVED_BYTE(OFF) (((u8 *)(pdn->config_space))[BYTE_SWAP(OFF)])
|
|
|
|
+
|
|
|
|
+ rtas_write_config (pdn, PCI_CACHE_LINE_SIZE, 1,
|
|
|
|
+ SAVED_BYTE(PCI_CACHE_LINE_SIZE));
|
|
|
|
+
|
|
|
|
+ rtas_write_config (pdn, PCI_LATENCY_TIMER, 1,
|
|
|
|
+ SAVED_BYTE(PCI_LATENCY_TIMER));
|
|
|
|
+
|
|
|
|
+ /* max latency, min grant, interrupt pin and line */
|
|
|
|
+ rtas_write_config(pdn, 15*4, 4, pdn->config_space[15]);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * eeh_restore_bars - restore the PCI config space info
|
|
|
|
+ *
|
|
|
|
+ * This routine performs a recursive walk to the children
|
|
|
|
+ * of this device as well.
|
|
|
|
+ */
|
|
|
|
+void eeh_restore_bars(struct pci_dn *pdn)
|
|
|
|
+{
|
|
|
|
+ struct device_node *dn;
|
|
|
|
+ if (!pdn)
|
|
|
|
+ return;
|
|
|
|
+
|
|
|
|
+ if (! pdn->eeh_is_bridge)
|
|
|
|
+ __restore_bars (pdn);
|
|
|
|
+
|
|
|
|
+ dn = pdn->node->child;
|
|
|
|
+ while (dn) {
|
|
|
|
+ eeh_restore_bars (PCI_DN(dn));
|
|
|
|
+ dn = dn->sibling;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * eeh_save_bars - save device bars
|
|
|
|
+ *
|
|
|
|
+ * Save the values of the device bars. Unlike the restore
|
|
|
|
+ * routine, this routine is *not* recursive. This is because
|
|
|
|
+ * PCI devices are added individuallly; but, for the restore,
|
|
|
|
+ * an entire slot is reset at a time.
|
|
|
|
+ */
|
|
|
|
+static void eeh_save_bars(struct pci_dev * pdev, struct pci_dn *pdn)
|
|
|
|
+{
|
|
|
|
+ int i;
|
|
|
|
+
|
|
|
|
+ if (!pdev || !pdn )
|
|
|
|
+ return;
|
|
|
|
+
|
|
|
|
+ for (i = 0; i < 16; i++)
|
|
|
|
+ pci_read_config_dword(pdev, i * 4, &pdn->config_space[i]);
|
|
|
|
+
|
|
|
|
+ if (pdev->hdr_type == PCI_HEADER_TYPE_BRIDGE)
|
|
|
|
+ pdn->eeh_is_bridge = 1;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+void
|
|
|
|
+rtas_configure_bridge(struct pci_dn *pdn)
|
|
|
|
+{
|
|
|
|
+ int token = rtas_token ("ibm,configure-bridge");
|
|
|
|
+ int rc;
|
|
|
|
+
|
|
|
|
+ if (token == RTAS_UNKNOWN_SERVICE)
|
|
|
|
+ return;
|
|
|
|
+ rc = rtas_call(token,3,1, NULL,
|
|
|
|
+ pdn->eeh_config_addr,
|
|
|
|
+ BUID_HI(pdn->phb->buid),
|
|
|
|
+ BUID_LO(pdn->phb->buid));
|
|
|
|
+ if (rc) {
|
|
|
|
+ printk (KERN_WARNING "EEH: Unable to configure device bridge (%d) for %s\n",
|
|
|
|
+ rc, pdn->node->full_name);
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* ------------------------------------------------------------- */
|
|
|
|
+/* The code below deals with enabling EEH for devices during the
|
|
|
|
+ * early boot sequence. EEH must be enabled before any PCI probing
|
|
|
|
+ * can be done.
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+#define EEH_ENABLE 1
|
|
|
|
+
|
|
struct eeh_early_enable_info {
|
|
struct eeh_early_enable_info {
|
|
unsigned int buid_hi;
|
|
unsigned int buid_hi;
|
|
unsigned int buid_lo;
|
|
unsigned int buid_lo;
|
|
@@ -684,9 +924,11 @@ static void *early_enable_eeh(struct device_node *dn, void *data)
|
|
u32 *device_id = (u32 *)get_property(dn, "device-id", NULL);
|
|
u32 *device_id = (u32 *)get_property(dn, "device-id", NULL);
|
|
u32 *regs;
|
|
u32 *regs;
|
|
int enable;
|
|
int enable;
|
|
- struct pci_dn *pdn = dn->data;
|
|
|
|
|
|
+ struct pci_dn *pdn = PCI_DN(dn);
|
|
|
|
|
|
pdn->eeh_mode = 0;
|
|
pdn->eeh_mode = 0;
|
|
|
|
+ pdn->eeh_check_count = 0;
|
|
|
|
+ pdn->eeh_freeze_count = 0;
|
|
|
|
|
|
if (status && strcmp(status, "ok") != 0)
|
|
if (status && strcmp(status, "ok") != 0)
|
|
return NULL; /* ignore devices with bad status */
|
|
return NULL; /* ignore devices with bad status */
|
|
@@ -723,8 +965,9 @@ static void *early_enable_eeh(struct device_node *dn, void *data)
|
|
/* First register entry is addr (00BBSS00) */
|
|
/* First register entry is addr (00BBSS00) */
|
|
/* Try to enable eeh */
|
|
/* Try to enable eeh */
|
|
ret = rtas_call(ibm_set_eeh_option, 4, 1, NULL,
|
|
ret = rtas_call(ibm_set_eeh_option, 4, 1, NULL,
|
|
- regs[0], info->buid_hi, info->buid_lo,
|
|
|
|
- EEH_ENABLE);
|
|
|
|
|
|
+ regs[0], info->buid_hi, info->buid_lo,
|
|
|
|
+ EEH_ENABLE);
|
|
|
|
+
|
|
if (ret == 0) {
|
|
if (ret == 0) {
|
|
eeh_subsystem_enabled = 1;
|
|
eeh_subsystem_enabled = 1;
|
|
pdn->eeh_mode |= EEH_MODE_SUPPORTED;
|
|
pdn->eeh_mode |= EEH_MODE_SUPPORTED;
|
|
@@ -736,7 +979,7 @@ static void *early_enable_eeh(struct device_node *dn, void *data)
|
|
|
|
|
|
/* This device doesn't support EEH, but it may have an
|
|
/* This device doesn't support EEH, but it may have an
|
|
* EEH parent, in which case we mark it as supported. */
|
|
* EEH parent, in which case we mark it as supported. */
|
|
- if (dn->parent && dn->parent->data
|
|
|
|
|
|
+ if (dn->parent && PCI_DN(dn->parent)
|
|
&& (PCI_DN(dn->parent)->eeh_mode & EEH_MODE_SUPPORTED)) {
|
|
&& (PCI_DN(dn->parent)->eeh_mode & EEH_MODE_SUPPORTED)) {
|
|
/* Parent supports EEH. */
|
|
/* Parent supports EEH. */
|
|
pdn->eeh_mode |= EEH_MODE_SUPPORTED;
|
|
pdn->eeh_mode |= EEH_MODE_SUPPORTED;
|
|
@@ -749,7 +992,7 @@ static void *early_enable_eeh(struct device_node *dn, void *data)
|
|
dn->full_name);
|
|
dn->full_name);
|
|
}
|
|
}
|
|
|
|
|
|
- return NULL;
|
|
|
|
|
|
+ return NULL;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
/*
|
|
@@ -770,6 +1013,9 @@ void __init eeh_init(void)
|
|
struct device_node *phb, *np;
|
|
struct device_node *phb, *np;
|
|
struct eeh_early_enable_info info;
|
|
struct eeh_early_enable_info info;
|
|
|
|
|
|
|
|
+ spin_lock_init(&confirm_error_lock);
|
|
|
|
+ spin_lock_init(&slot_errbuf_lock);
|
|
|
|
+
|
|
np = of_find_node_by_path("/rtas");
|
|
np = of_find_node_by_path("/rtas");
|
|
if (np == NULL)
|
|
if (np == NULL)
|
|
return;
|
|
return;
|
|
@@ -797,13 +1043,11 @@ void __init eeh_init(void)
|
|
for (phb = of_find_node_by_name(NULL, "pci"); phb;
|
|
for (phb = of_find_node_by_name(NULL, "pci"); phb;
|
|
phb = of_find_node_by_name(phb, "pci")) {
|
|
phb = of_find_node_by_name(phb, "pci")) {
|
|
unsigned long buid;
|
|
unsigned long buid;
|
|
- struct pci_dn *pci;
|
|
|
|
|
|
|
|
buid = get_phb_buid(phb);
|
|
buid = get_phb_buid(phb);
|
|
- if (buid == 0 || phb->data == NULL)
|
|
|
|
|
|
+ if (buid == 0 || PCI_DN(phb) == NULL)
|
|
continue;
|
|
continue;
|
|
|
|
|
|
- pci = phb->data;
|
|
|
|
info.buid_lo = BUID_LO(buid);
|
|
info.buid_lo = BUID_LO(buid);
|
|
info.buid_hi = BUID_HI(buid);
|
|
info.buid_hi = BUID_HI(buid);
|
|
traverse_pci_devices(phb, early_enable_eeh, &info);
|
|
traverse_pci_devices(phb, early_enable_eeh, &info);
|
|
@@ -832,11 +1076,13 @@ void eeh_add_device_early(struct device_node *dn)
|
|
struct pci_controller *phb;
|
|
struct pci_controller *phb;
|
|
struct eeh_early_enable_info info;
|
|
struct eeh_early_enable_info info;
|
|
|
|
|
|
- if (!dn || !dn->data)
|
|
|
|
|
|
+ if (!dn || !PCI_DN(dn))
|
|
return;
|
|
return;
|
|
phb = PCI_DN(dn)->phb;
|
|
phb = PCI_DN(dn)->phb;
|
|
if (NULL == phb || 0 == phb->buid) {
|
|
if (NULL == phb || 0 == phb->buid) {
|
|
- printk(KERN_WARNING "EEH: Expected buid but found none\n");
|
|
|
|
|
|
+ printk(KERN_WARNING "EEH: Expected buid but found none for %s\n",
|
|
|
|
+ dn->full_name);
|
|
|
|
+ dump_stack();
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -844,7 +1090,7 @@ void eeh_add_device_early(struct device_node *dn)
|
|
info.buid_lo = BUID_LO(phb->buid);
|
|
info.buid_lo = BUID_LO(phb->buid);
|
|
early_enable_eeh(dn, &info);
|
|
early_enable_eeh(dn, &info);
|
|
}
|
|
}
|
|
-EXPORT_SYMBOL(eeh_add_device_early);
|
|
|
|
|
|
+EXPORT_SYMBOL_GPL(eeh_add_device_early);
|
|
|
|
|
|
/**
|
|
/**
|
|
* eeh_add_device_late - perform EEH initialization for the indicated pci device
|
|
* eeh_add_device_late - perform EEH initialization for the indicated pci device
|
|
@@ -855,6 +1101,9 @@ EXPORT_SYMBOL(eeh_add_device_early);
|
|
*/
|
|
*/
|
|
void eeh_add_device_late(struct pci_dev *dev)
|
|
void eeh_add_device_late(struct pci_dev *dev)
|
|
{
|
|
{
|
|
|
|
+ struct device_node *dn;
|
|
|
|
+ struct pci_dn *pdn;
|
|
|
|
+
|
|
if (!dev || !eeh_subsystem_enabled)
|
|
if (!dev || !eeh_subsystem_enabled)
|
|
return;
|
|
return;
|
|
|
|
|
|
@@ -862,9 +1111,15 @@ void eeh_add_device_late(struct pci_dev *dev)
|
|
printk(KERN_DEBUG "EEH: adding device %s\n", pci_name(dev));
|
|
printk(KERN_DEBUG "EEH: adding device %s\n", pci_name(dev));
|
|
#endif
|
|
#endif
|
|
|
|
|
|
|
|
+ pci_dev_get (dev);
|
|
|
|
+ dn = pci_device_to_OF_node(dev);
|
|
|
|
+ pdn = PCI_DN(dn);
|
|
|
|
+ pdn->pcidev = dev;
|
|
|
|
+
|
|
pci_addr_cache_insert_device (dev);
|
|
pci_addr_cache_insert_device (dev);
|
|
|
|
+ eeh_save_bars(dev, pdn);
|
|
}
|
|
}
|
|
-EXPORT_SYMBOL(eeh_add_device_late);
|
|
|
|
|
|
+EXPORT_SYMBOL_GPL(eeh_add_device_late);
|
|
|
|
|
|
/**
|
|
/**
|
|
* eeh_remove_device - undo EEH setup for the indicated pci device
|
|
* eeh_remove_device - undo EEH setup for the indicated pci device
|
|
@@ -875,6 +1130,7 @@ EXPORT_SYMBOL(eeh_add_device_late);
|
|
*/
|
|
*/
|
|
void eeh_remove_device(struct pci_dev *dev)
|
|
void eeh_remove_device(struct pci_dev *dev)
|
|
{
|
|
{
|
|
|
|
+ struct device_node *dn;
|
|
if (!dev || !eeh_subsystem_enabled)
|
|
if (!dev || !eeh_subsystem_enabled)
|
|
return;
|
|
return;
|
|
|
|
|
|
@@ -883,20 +1139,29 @@ void eeh_remove_device(struct pci_dev *dev)
|
|
printk(KERN_DEBUG "EEH: remove device %s\n", pci_name(dev));
|
|
printk(KERN_DEBUG "EEH: remove device %s\n", pci_name(dev));
|
|
#endif
|
|
#endif
|
|
pci_addr_cache_remove_device(dev);
|
|
pci_addr_cache_remove_device(dev);
|
|
|
|
+
|
|
|
|
+ dn = pci_device_to_OF_node(dev);
|
|
|
|
+ PCI_DN(dn)->pcidev = NULL;
|
|
|
|
+ pci_dev_put (dev);
|
|
}
|
|
}
|
|
-EXPORT_SYMBOL(eeh_remove_device);
|
|
|
|
|
|
+EXPORT_SYMBOL_GPL(eeh_remove_device);
|
|
|
|
|
|
static int proc_eeh_show(struct seq_file *m, void *v)
|
|
static int proc_eeh_show(struct seq_file *m, void *v)
|
|
{
|
|
{
|
|
unsigned int cpu;
|
|
unsigned int cpu;
|
|
unsigned long ffs = 0, positives = 0, failures = 0;
|
|
unsigned long ffs = 0, positives = 0, failures = 0;
|
|
unsigned long resets = 0;
|
|
unsigned long resets = 0;
|
|
|
|
+ unsigned long no_dev = 0, no_dn = 0, no_cfg = 0, no_check = 0;
|
|
|
|
|
|
for_each_cpu(cpu) {
|
|
for_each_cpu(cpu) {
|
|
ffs += per_cpu(total_mmio_ffs, cpu);
|
|
ffs += per_cpu(total_mmio_ffs, cpu);
|
|
positives += per_cpu(false_positives, cpu);
|
|
positives += per_cpu(false_positives, cpu);
|
|
failures += per_cpu(ignored_failures, cpu);
|
|
failures += per_cpu(ignored_failures, cpu);
|
|
resets += per_cpu(slot_resets, cpu);
|
|
resets += per_cpu(slot_resets, cpu);
|
|
|
|
+ no_dev += per_cpu(no_device, cpu);
|
|
|
|
+ no_dn += per_cpu(no_dn, cpu);
|
|
|
|
+ no_cfg += per_cpu(no_cfg_addr, cpu);
|
|
|
|
+ no_check += per_cpu(ignored_check, cpu);
|
|
}
|
|
}
|
|
|
|
|
|
if (0 == eeh_subsystem_enabled) {
|
|
if (0 == eeh_subsystem_enabled) {
|
|
@@ -904,13 +1169,17 @@ static int proc_eeh_show(struct seq_file *m, void *v)
|
|
seq_printf(m, "eeh_total_mmio_ffs=%ld\n", ffs);
|
|
seq_printf(m, "eeh_total_mmio_ffs=%ld\n", ffs);
|
|
} else {
|
|
} else {
|
|
seq_printf(m, "EEH Subsystem is enabled\n");
|
|
seq_printf(m, "EEH Subsystem is enabled\n");
|
|
- seq_printf(m, "eeh_total_mmio_ffs=%ld\n"
|
|
|
|
- "eeh_false_positives=%ld\n"
|
|
|
|
- "eeh_ignored_failures=%ld\n"
|
|
|
|
- "eeh_slot_resets=%ld\n"
|
|
|
|
- "eeh_fail_count=%d\n",
|
|
|
|
- ffs, positives, failures, resets,
|
|
|
|
- eeh_fail_count.counter);
|
|
|
|
|
|
+ seq_printf(m,
|
|
|
|
+ "no device=%ld\n"
|
|
|
|
+ "no device node=%ld\n"
|
|
|
|
+ "no config address=%ld\n"
|
|
|
|
+ "check not wanted=%ld\n"
|
|
|
|
+ "eeh_total_mmio_ffs=%ld\n"
|
|
|
|
+ "eeh_false_positives=%ld\n"
|
|
|
|
+ "eeh_ignored_failures=%ld\n"
|
|
|
|
+ "eeh_slot_resets=%ld\n",
|
|
|
|
+ no_dev, no_dn, no_cfg, no_check,
|
|
|
|
+ ffs, positives, failures, resets);
|
|
}
|
|
}
|
|
|
|
|
|
return 0;
|
|
return 0;
|
|
@@ -932,7 +1201,7 @@ static int __init eeh_init_proc(void)
|
|
{
|
|
{
|
|
struct proc_dir_entry *e;
|
|
struct proc_dir_entry *e;
|
|
|
|
|
|
- if (systemcfg->platform & PLATFORM_PSERIES) {
|
|
|
|
|
|
+ if (platform_is_pseries()) {
|
|
e = create_proc_entry("ppc64/eeh", 0, NULL);
|
|
e = create_proc_entry("ppc64/eeh", 0, NULL);
|
|
if (e)
|
|
if (e)
|
|
e->proc_fops = &proc_eeh_operations;
|
|
e->proc_fops = &proc_eeh_operations;
|