Message ID | 1371544435-4943-2-git-send-email-shangw@linux.vnet.ibm.com (mailing list archive) |
---|---|
State | Superseded |
Headers | show |
Bunch of whitespace issues here: % git am ~/Mail/linuxppc/31202 Applying: powerpc/eeh: Move common part to kernel directory /home/mikey/src/powerpc-test/.git/rebase-apply/patch:437: trailing whitespace. /home/mikey/src/powerpc-test/.git/rebase-apply/patch:594: space before tab in indent. */ /home/mikey/src/powerpc-test/.git/rebase-apply/patch:607: trailing whitespace. /home/mikey/src/powerpc-test/.git/rebase-apply/patch:608: trailing whitespace. /* We might get hit with another EEH freeze as soon as the /home/mikey/src/powerpc-test/.git/rebase-apply/patch:673: trailing whitespace. error: patch failed: arch/powerpc/platforms/pseries/eeh_pe.c:1 error: arch/powerpc/platforms/pseries/eeh_pe.c: patch does not apply Patch failed at 0001 powerpc/eeh: Move common part to kernel directory When you have resolved this problem run "git am --resolved". If you would prefer to skip this patch, instead run "git am --skip". To restore the original branch and stop patching run "git am --abort". Mikey Gavin Shan <shangw@linux.vnet.ibm.com> wrote: > The patch moves the common part of EEH core into arch/powerpc/kernel > directory so that we needn't PPC_PSERIES while compiling POWERNV > platform: > > * Move the EEH common part into arch/powerpc/kernel > * Move the functions for PCI hotplug from pSeries platform to > arch/powerpc/kernel/pci_hotplug.c > * Move CONFIG_EEH from arch/powerpc/platforms/pseries/Kconfig to > arch/powerpc/platforms/Kconfig > * Adjust makefile accordingly > > Signed-off-by: Gavin Shan <shangw@linux.vnet.ibm.com> > --- > arch/powerpc/kernel/Makefile | 4 +- > arch/powerpc/kernel/eeh.c | 942 +++++++++++++++++++++++++++ > arch/powerpc/kernel/eeh_cache.c | 319 +++++++++ > arch/powerpc/kernel/eeh_dev.c | 112 ++++ > arch/powerpc/kernel/eeh_driver.c | 552 ++++++++++++++++ > arch/powerpc/kernel/eeh_event.c | 142 ++++ > arch/powerpc/kernel/eeh_pe.c | 653 +++++++++++++++++++ > arch/powerpc/kernel/eeh_sysfs.c | 75 +++ > arch/powerpc/kernel/pci_hotplug.c | 111 ++++ > arch/powerpc/platforms/Kconfig | 5 + > arch/powerpc/platforms/pseries/Kconfig | 5 - > arch/powerpc/platforms/pseries/Makefile | 4 +- > arch/powerpc/platforms/pseries/eeh.c | 942 --------------------------- > arch/powerpc/platforms/pseries/eeh_cache.c | 319 --------- > arch/powerpc/platforms/pseries/eeh_dev.c | 112 ---- > arch/powerpc/platforms/pseries/eeh_driver.c | 552 ---------------- > arch/powerpc/platforms/pseries/eeh_event.c | 142 ---- > arch/powerpc/platforms/pseries/eeh_pe.c | 653 ------------------- > arch/powerpc/platforms/pseries/eeh_sysfs.c | 75 --- > arch/powerpc/platforms/pseries/pci_dlpar.c | 85 --- > 20 files changed, 2915 insertions(+), 2889 deletions(-) > create mode 100644 arch/powerpc/kernel/eeh.c > create mode 100644 arch/powerpc/kernel/eeh_cache.c > create mode 100644 arch/powerpc/kernel/eeh_dev.c > create mode 100644 arch/powerpc/kernel/eeh_driver.c > create mode 100644 arch/powerpc/kernel/eeh_event.c > create mode 100644 arch/powerpc/kernel/eeh_pe.c > create mode 100644 arch/powerpc/kernel/eeh_sysfs.c > create mode 100644 arch/powerpc/kernel/pci_hotplug.c > delete mode 100644 arch/powerpc/platforms/pseries/eeh.c > delete mode 100644 arch/powerpc/platforms/pseries/eeh_cache.c > delete mode 100644 arch/powerpc/platforms/pseries/eeh_dev.c > delete mode 100644 arch/powerpc/platforms/pseries/eeh_driver.c > delete mode 100644 arch/powerpc/platforms/pseries/eeh_event.c > delete mode 100644 arch/powerpc/platforms/pseries/eeh_pe.c > delete mode 100644 arch/powerpc/platforms/pseries/eeh_sysfs.c > > diff --git a/arch/powerpc/kernel/Makefile b/arch/powerpc/kernel/Makefile > index f960a79..5826906 100644 > --- a/arch/powerpc/kernel/Makefile > +++ b/arch/powerpc/kernel/Makefile > @@ -58,6 +58,8 @@ obj-$(CONFIG_RTAS_PROC) += rtas-proc.o > obj-$(CONFIG_LPARCFG) += lparcfg.o > obj-$(CONFIG_IBMVIO) += vio.o > obj-$(CONFIG_IBMEBUS) += ibmebus.o > +obj-$(CONFIG_EEH) += eeh.o eeh_pe.o eeh_dev.o eeh_cache.o \ > + eeh_driver.o eeh_event.o eeh_sysfs.o > obj-$(CONFIG_GENERIC_TBSYNC) += smp-tbsync.o > obj-$(CONFIG_CRASH_DUMP) += crash_dump.o > obj-$(CONFIG_FA_DUMP) += fadump.o > @@ -100,7 +102,7 @@ obj-$(CONFIG_PPC_UDBG_16550) += legacy_serial.o udbg_16550.o > obj-$(CONFIG_STACKTRACE) += stacktrace.o > obj-$(CONFIG_SWIOTLB) += dma-swiotlb.o > > -pci64-$(CONFIG_PPC64) += pci_dn.o isa-bridge.o > +pci64-$(CONFIG_PPC64) += pci_hotplug.o pci_dn.o isa-bridge.o > obj-$(CONFIG_PCI) += pci_$(CONFIG_WORD_SIZE).o $(pci64-y) \ > pci-common.o pci_of_scan.o > obj-$(CONFIG_PCI_MSI) += msi.o > diff --git a/arch/powerpc/kernel/eeh.c b/arch/powerpc/kernel/eeh.c > new file mode 100644 > index 0000000..6b73d6c > --- /dev/null > +++ b/arch/powerpc/kernel/eeh.c > @@ -0,0 +1,942 @@ > +/* > + * Copyright IBM Corporation 2001, 2005, 2006 > + * Copyright Dave Engebretsen & Todd Inglett 2001 > + * Copyright Linas Vepstas 2005, 2006 > + * Copyright 2001-2012 IBM Corporation. > + * > + * 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 > + * the Free Software Foundation; either version 2 of the License, or > + * (at your option) any later version. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, write to the Free Software > + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA > + * > + * Please address comments and feedback to Linas Vepstas <linas@austin.ibm.com> > + */ > + > +#include <linux/delay.h> > +#include <linux/sched.h> > +#include <linux/init.h> > +#include <linux/list.h> > +#include <linux/pci.h> > +#include <linux/proc_fs.h> > +#include <linux/rbtree.h> > +#include <linux/seq_file.h> > +#include <linux/spinlock.h> > +#include <linux/export.h> > +#include <linux/of.h> > + > +#include <linux/atomic.h> > +#include <asm/eeh.h> > +#include <asm/eeh_event.h> > +#include <asm/io.h> > +#include <asm/machdep.h> > +#include <asm/ppc-pci.h> > +#include <asm/rtas.h> > + > + > +/** Overview: > + * EEH, or "Extended Error Handling" is a PCI bridge technology for > + * dealing with PCI bus errors that can't be dealt with within the > + * usual PCI framework, except by check-stopping the CPU. Systems > + * that are designed for high-availability/reliability cannot afford > + * to crash due to a "mere" PCI error, thus the need for EEH. > + * An EEH-capable bridge operates by converting a detected error > + * into a "slot freeze", taking the PCI adapter off-line, making > + * the slot behave, from the OS'es point of view, as if the slot > + * were "empty": all reads return 0xff's and all writes are silently > + * ignored. EEH slot isolation events can be triggered by parity > + * errors on the address or data busses (e.g. during posted writes), > + * 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 > + * freeze events are buggy device drivers, buggy device microcode, > + * or buggy device hardware. This is because any attempt by the > + * device to bus-master data to a memory address that is not > + * assigned to the device will trigger a slot freeze. (The idea > + * is to prevent devices-gone-wild from corrupting system memory). > + * Buggy hardware/drivers will have a miserable time co-existing > + * with EEH. > + * > + * Ideally, a PCI device driver, when suspecting that an isolation > + * event has occurred (e.g. by reading 0xff's), will then ask EEH > + * whether this is the case, and then take appropriate steps to > + * reset the PCI slot, the PCI device, and then resume operations. > + * However, until that day, the checking is done here, with the > + * eeh_check_failure() routine embedded in the MMIO macros. If > + * the slot is found to be isolated, an "EEH Event" is synthesized > + * and sent out for processing. > + */ > + > +/* If a device driver keeps reading an MMIO register in an interrupt > + * handler after a slot isolation event, it might be broken. > + * This sets the threshold for how many read attempts we allow > + * before printing an error message. > + */ > +#define EEH_MAX_FAILS 2100000 > + > +/* Time to wait for a PCI slot to report status, in milliseconds */ > +#define PCI_BUS_RESET_WAIT_MSEC (60*1000) > + > +/* Platform dependent EEH operations */ > +struct eeh_ops *eeh_ops = NULL; > + > +int eeh_subsystem_enabled; > +EXPORT_SYMBOL(eeh_subsystem_enabled); > + > +/* > + * EEH probe mode support. The intention is to support multiple > + * platforms for EEH. Some platforms like pSeries do PCI emunation > + * based on device tree. However, other platforms like powernv probe > + * PCI devices from hardware. The flag is used to distinguish that. > + * In addition, struct eeh_ops::probe would be invoked for particular > + * OF node or PCI device so that the corresponding PE would be created > + * there. > + */ > +int eeh_probe_mode; > + > +/* Global EEH mutex */ > +DEFINE_MUTEX(eeh_mutex); > + > +/* Lock to avoid races due to multiple reports of an error */ > +static DEFINE_RAW_SPINLOCK(confirm_error_lock); > + > +/* Buffer for reporting pci register dumps. Its here in BSS, and > + * not dynamically alloced, so that it ends up in RMO where RTAS > + * can access it. > + */ > +#define EEH_PCI_REGS_LOG_LEN 4096 > +static unsigned char pci_regs_buf[EEH_PCI_REGS_LOG_LEN]; > + > +/* > + * The struct is used to maintain the EEH global statistic > + * information. Besides, the EEH global statistics will be > + * exported to user space through procfs > + */ > +struct eeh_stats { > + u64 no_device; /* PCI device not found */ > + u64 no_dn; /* OF node not found */ > + u64 no_cfg_addr; /* Config address not found */ > + u64 ignored_check; /* EEH check skipped */ > + u64 total_mmio_ffs; /* Total EEH checks */ > + u64 false_positives; /* Unnecessary EEH checks */ > + u64 slot_resets; /* PE reset */ > +}; > + > +static struct eeh_stats eeh_stats; > + > +#define IS_BRIDGE(class_code) (((class_code)<<16) == PCI_BASE_CLASS_BRIDGE) > + > +/** > + * eeh_gather_pci_data - Copy assorted PCI config space registers to buff > + * @edev: device to report data for > + * @buf: point to buffer in which to log > + * @len: amount of room in buffer > + * > + * This routine captures assorted PCI configuration space data, > + * and puts them into a buffer for RTAS error logging. > + */ > +static size_t eeh_gather_pci_data(struct eeh_dev *edev, char * buf, size_t len) > +{ > + struct device_node *dn = eeh_dev_to_of_node(edev); > + struct pci_dev *dev = eeh_dev_to_pci_dev(edev); > + u32 cfg; > + int cap, i; > + int n = 0; > + > + n += scnprintf(buf+n, len-n, "%s\n", dn->full_name); > + printk(KERN_WARNING "EEH: of node=%s\n", dn->full_name); > + > + eeh_ops->read_config(dn, PCI_VENDOR_ID, 4, &cfg); > + n += scnprintf(buf+n, len-n, "dev/vend:%08x\n", cfg); > + printk(KERN_WARNING "EEH: PCI device/vendor: %08x\n", cfg); > + > + eeh_ops->read_config(dn, PCI_COMMAND, 4, &cfg); > + n += scnprintf(buf+n, len-n, "cmd/stat:%x\n", cfg); > + printk(KERN_WARNING "EEH: PCI cmd/status register: %08x\n", cfg); > + > + if (!dev) { > + printk(KERN_WARNING "EEH: no PCI device for this of node\n"); > + return n; > + } > + > + /* Gather bridge-specific registers */ > + if (dev->class >> 16 == PCI_BASE_CLASS_BRIDGE) { > + eeh_ops->read_config(dn, PCI_SEC_STATUS, 2, &cfg); > + n += scnprintf(buf+n, len-n, "sec stat:%x\n", cfg); > + printk(KERN_WARNING "EEH: Bridge secondary status: %04x\n", cfg); > + > + eeh_ops->read_config(dn, PCI_BRIDGE_CONTROL, 2, &cfg); > + n += scnprintf(buf+n, len-n, "brdg ctl:%x\n", cfg); > + printk(KERN_WARNING "EEH: Bridge control: %04x\n", cfg); > + } > + > + /* Dump out the PCI-X command and status regs */ > + cap = pci_find_capability(dev, PCI_CAP_ID_PCIX); > + if (cap) { > + eeh_ops->read_config(dn, cap, 4, &cfg); > + n += scnprintf(buf+n, len-n, "pcix-cmd:%x\n", cfg); > + printk(KERN_WARNING "EEH: PCI-X cmd: %08x\n", cfg); > + > + eeh_ops->read_config(dn, cap+4, 4, &cfg); > + n += scnprintf(buf+n, len-n, "pcix-stat:%x\n", cfg); > + printk(KERN_WARNING "EEH: PCI-X status: %08x\n", cfg); > + } > + > + /* If PCI-E capable, dump PCI-E cap 10, and the AER */ > + cap = pci_find_capability(dev, PCI_CAP_ID_EXP); > + if (cap) { > + n += scnprintf(buf+n, len-n, "pci-e cap10:\n"); > + printk(KERN_WARNING > + "EEH: PCI-E capabilities and status follow:\n"); > + > + for (i=0; i<=8; i++) { > + eeh_ops->read_config(dn, cap+4*i, 4, &cfg); > + n += scnprintf(buf+n, len-n, "%02x:%x\n", 4*i, cfg); > + printk(KERN_WARNING "EEH: PCI-E %02x: %08x\n", i, cfg); > + } > + > + cap = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ERR); > + if (cap) { > + n += scnprintf(buf+n, len-n, "pci-e AER:\n"); > + printk(KERN_WARNING > + "EEH: PCI-E AER capability register set follows:\n"); > + > + for (i=0; i<14; i++) { > + eeh_ops->read_config(dn, cap+4*i, 4, &cfg); > + n += scnprintf(buf+n, len-n, "%02x:%x\n", 4*i, cfg); > + printk(KERN_WARNING "EEH: PCI-E AER %02x: %08x\n", i, cfg); > + } > + } > + } > + > + return n; > +} > + > +/** > + * eeh_slot_error_detail - Generate combined log including driver log and error log > + * @pe: EEH PE > + * @severity: temporary or permanent error log > + * > + * This routine should be called to generate the combined log, which > + * is comprised of driver log and error log. The driver log is figured > + * out from the config space of the corresponding PCI device, while > + * the error log is fetched through platform dependent function call. > + */ > +void eeh_slot_error_detail(struct eeh_pe *pe, int severity) > +{ > + size_t loglen = 0; > + struct eeh_dev *edev; > + > + eeh_pci_enable(pe, EEH_OPT_THAW_MMIO); > + eeh_ops->configure_bridge(pe); > + eeh_pe_restore_bars(pe); > + > + pci_regs_buf[0] = 0; > + eeh_pe_for_each_dev(pe, edev) { > + loglen += eeh_gather_pci_data(edev, pci_regs_buf, > + EEH_PCI_REGS_LOG_LEN); > + } > + > + eeh_ops->get_log(pe, severity, pci_regs_buf, loglen); > +} > + > +/** > + * eeh_token_to_phys - Convert EEH address token to phys address > + * @token: I/O token, should be address in the form 0xA.... > + * > + * This routine should be called to convert virtual I/O address > + * to physical one. > + */ > +static inline unsigned long eeh_token_to_phys(unsigned long token) > +{ > + 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_dev_check_failure - Check if all 1's data is due to EEH slot freeze > + * @edev: eeh device > + * > + * Check for an EEH failure for the given device node. Call this > + * 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. This routine > + * will query firmware for the EEH status. > + * > + * Returns 0 if there has not been an EEH error; otherwise returns > + * a non-zero value and queues up a slot isolation event notification. > + * > + * It is safe to call this routine in an interrupt context. > + */ > +int eeh_dev_check_failure(struct eeh_dev *edev) > +{ > + int ret; > + unsigned long flags; > + struct device_node *dn; > + struct pci_dev *dev; > + struct eeh_pe *pe; > + int rc = 0; > + const char *location; > + > + eeh_stats.total_mmio_ffs++; > + > + if (!eeh_subsystem_enabled) > + return 0; > + > + if (!edev) { > + eeh_stats.no_dn++; > + return 0; > + } > + dn = eeh_dev_to_of_node(edev); > + dev = eeh_dev_to_pci_dev(edev); > + pe = edev->pe; > + > + /* Access to IO BARs might get this far and still not want checking. */ > + if (!pe) { > + eeh_stats.ignored_check++; > + pr_debug("EEH: Ignored check for %s %s\n", > + eeh_pci_name(dev), dn->full_name); > + return 0; > + } > + > + if (!pe->addr && !pe->config_addr) { > + eeh_stats.no_cfg_addr++; > + 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. > + * 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. > + */ > + raw_spin_lock_irqsave(&confirm_error_lock, flags); > + rc = 1; > + if (pe->state & EEH_PE_ISOLATED) { > + pe->check_count++; > + if (pe->check_count % EEH_MAX_FAILS == 0) { > + location = of_get_property(dn, "ibm,loc-code", NULL); > + printk(KERN_ERR "EEH: %d reads ignored for recovering device at " > + "location=%s driver=%s pci addr=%s\n", > + pe->check_count, location, > + eeh_driver_name(dev), eeh_pci_name(dev)); > + printk(KERN_ERR "EEH: Might be infinite loop in %s driver\n", > + eeh_driver_name(dev)); > + dump_stack(); > + } > + goto dn_unlock; > + } > + > + /* > + * Now test for an EEH failure. This is VERY expensive. > + * Note that the eeh_config_addr may be a parent device > + * in the case of a device behind a bridge, or it may be > + * function zero of a multi-function device. > + * In any case they must share a common PHB. > + */ > + ret = eeh_ops->get_state(pe, NULL); > + > + /* Note that config-io to empty slots may fail; > + * they are empty when they don't have children. > + * We will punt with the following conditions: Failure to get > + * PE's state, EEH not support and Permanently unavailable > + * state, PE is in good state. > + */ > + if ((ret < 0) || > + (ret == EEH_STATE_NOT_SUPPORT) || > + (ret & (EEH_STATE_MMIO_ACTIVE | EEH_STATE_DMA_ACTIVE)) == > + (EEH_STATE_MMIO_ACTIVE | EEH_STATE_DMA_ACTIVE)) { > + eeh_stats.false_positives++; > + pe->false_positives++; > + rc = 0; > + goto dn_unlock; > + } > + > + eeh_stats.slot_resets++; > + > + /* Avoid repeated reports of this failure, including problems > + * with other functions on this device, and functions under > + * bridges. > + */ > + eeh_pe_state_mark(pe, EEH_PE_ISOLATED); > + raw_spin_unlock_irqrestore(&confirm_error_lock, flags); > + > + eeh_send_failure_event(pe); > + > + /* Most EEH events are due to device driver bugs. Having > + * a stack trace will help the device-driver authors figure > + * out what happened. So print that out. > + */ > + WARN(1, "EEH: failure detected\n"); > + return 1; > + > +dn_unlock: > + raw_spin_unlock_irqrestore(&confirm_error_lock, flags); > + return rc; > +} > + > +EXPORT_SYMBOL_GPL(eeh_dev_check_failure); > + > +/** > + * 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.... > + * @val: value, should be all 1's (XXX why do we need this arg??) > + * > + * 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 > + * find out if this is due to an EEH slot freeze event. This routine > + * will query firmware for the EEH status. > + * > + * Note this routine is safe to call in an interrupt context. > + */ > +unsigned long eeh_check_failure(const volatile void __iomem *token, unsigned long val) > +{ > + unsigned long addr; > + struct eeh_dev *edev; > + > + /* Finding the phys addr + pci device; this is pretty quick. */ > + addr = eeh_token_to_phys((unsigned long __force) token); > + edev = eeh_addr_cache_get_dev(addr); > + if (!edev) { > + eeh_stats.no_device++; > + return val; > + } > + > + eeh_dev_check_failure(edev); > + > + pci_dev_put(eeh_dev_to_pci_dev(edev)); > + return val; > +} > + > +EXPORT_SYMBOL(eeh_check_failure); > + > + > +/** > + * eeh_pci_enable - Enable MMIO or DMA transfers for this slot > + * @pe: EEH PE > + * > + * This routine should be called to reenable frozen MMIO or DMA > + * so that it would work correctly again. It's useful while doing > + * recovery or log collection on the indicated device. > + */ > +int eeh_pci_enable(struct eeh_pe *pe, int function) > +{ > + int rc; > + > + rc = eeh_ops->set_option(pe, function); > + if (rc) > + pr_warning("%s: Unexpected state change %d on PHB#%d-PE#%x, err=%d\n", > + __func__, function, pe->phb->global_number, pe->addr, rc); > + > + rc = eeh_ops->wait_state(pe, PCI_BUS_RESET_WAIT_MSEC); > + if (rc > 0 && (rc & EEH_STATE_MMIO_ENABLED) && > + (function == EEH_OPT_THAW_MMIO)) > + return 0; > + > + return rc; > +} > + > +/** > + * pcibios_set_pcie_slot_reset - Set PCI-E reset state > + * @dev: pci device struct > + * @state: reset state to enter > + * > + * Return value: > + * 0 if success > + */ > +int pcibios_set_pcie_reset_state(struct pci_dev *dev, enum pcie_reset_state state) > +{ > + struct eeh_dev *edev = pci_dev_to_eeh_dev(dev); > + struct eeh_pe *pe = edev->pe; > + > + if (!pe) { > + pr_err("%s: No PE found on PCI device %s\n", > + __func__, pci_name(dev)); > + return -EINVAL; > + } > + > + switch (state) { > + case pcie_deassert_reset: > + eeh_ops->reset(pe, EEH_RESET_DEACTIVATE); > + break; > + case pcie_hot_reset: > + eeh_ops->reset(pe, EEH_RESET_HOT); > + break; > + case pcie_warm_reset: > + eeh_ops->reset(pe, EEH_RESET_FUNDAMENTAL); > + break; > + default: > + return -EINVAL; > + }; > + > + return 0; > +} > + > +/** > + * eeh_set_pe_freset - Check the required reset for the indicated device > + * @data: EEH device > + * @flag: return value > + * > + * Each device might have its preferred reset type: fundamental or > + * hot reset. The routine is used to collected the information for > + * the indicated device and its children so that the bunch of the > + * devices could be reset properly. > + */ > +static void *eeh_set_dev_freset(void *data, void *flag) > +{ > + struct pci_dev *dev; > + unsigned int *freset = (unsigned int *)flag; > + struct eeh_dev *edev = (struct eeh_dev *)data; > + > + dev = eeh_dev_to_pci_dev(edev); > + if (dev) > + *freset |= dev->needs_freset; > + > + return NULL; > +} > + > +/** > + * eeh_reset_pe_once - Assert the pci #RST line for 1/4 second > + * @pe: EEH PE > + * > + * Assert the PCI #RST line for 1/4 second. > + */ > +static void eeh_reset_pe_once(struct eeh_pe *pe) > +{ > + unsigned int freset = 0; > + > + /* Determine type of EEH reset required for > + * Partitionable Endpoint, a hot-reset (1) > + * or a fundamental reset (3). > + * A fundamental reset required by any device under > + * Partitionable Endpoint trumps hot-reset. > + */ > + eeh_pe_dev_traverse(pe, eeh_set_dev_freset, &freset); > + > + if (freset) > + eeh_ops->reset(pe, EEH_RESET_FUNDAMENTAL); > + else > + eeh_ops->reset(pe, EEH_RESET_HOT); > + > + /* 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_pe_state_clear(pe, EEH_PE_ISOLATED); > + > + eeh_ops->reset(pe, EEH_RESET_DEACTIVATE); > + > + /* 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); > +} > + > +/** > + * eeh_reset_pe - Reset the indicated PE > + * @pe: EEH PE > + * > + * This routine should be called to reset indicated device, including > + * PE. A PE might include multiple PCI devices and sometimes PCI bridges > + * might be involved as well. > + */ > +int eeh_reset_pe(struct eeh_pe *pe) > +{ > + int i, rc; > + > + /* Take three shots at resetting the bus */ > + for (i=0; i<3; i++) { > + eeh_reset_pe_once(pe); > + > + rc = eeh_ops->wait_state(pe, PCI_BUS_RESET_WAIT_MSEC); > + if (rc == (EEH_STATE_MMIO_ACTIVE | EEH_STATE_DMA_ACTIVE)) > + return 0; > + > + if (rc < 0) { > + pr_err("%s: Unrecoverable slot failure on PHB#%d-PE#%x", > + __func__, pe->phb->global_number, pe->addr); > + return -1; > + } > + pr_err("EEH: bus reset %d failed on PHB#%d-PE#%x, rc=%d\n", > + i+1, pe->phb->global_number, pe->addr, rc); > + } > + > + return -1; > +} > + > +/** > + * eeh_save_bars - Save device bars > + * @edev: PCI device associated EEH device > + * > + * Save the values of the device bars. Unlike the restore > + * routine, this routine is *not* recursive. This is because > + * PCI devices are added individually; but, for the restore, > + * an entire slot is reset at a time. > + */ > +void eeh_save_bars(struct eeh_dev *edev) > +{ > + int i; > + struct device_node *dn; > + > + if (!edev) > + return; > + dn = eeh_dev_to_of_node(edev); > + > + for (i = 0; i < 16; i++) > + eeh_ops->read_config(dn, i * 4, 4, &edev->config_space[i]); > +} > + > +/** > + * eeh_ops_register - Register platform dependent EEH operations > + * @ops: platform dependent EEH operations > + * > + * Register the platform dependent EEH operation callback > + * functions. The platform should call this function before > + * any other EEH operations. > + */ > +int __init eeh_ops_register(struct eeh_ops *ops) > +{ > + if (!ops->name) { > + pr_warning("%s: Invalid EEH ops name for %p\n", > + __func__, ops); > + return -EINVAL; > + } > + > + if (eeh_ops && eeh_ops != ops) { > + pr_warning("%s: EEH ops of platform %s already existing (%s)\n", > + __func__, eeh_ops->name, ops->name); > + return -EEXIST; > + } > + > + eeh_ops = ops; > + > + return 0; > +} > + > +/** > + * eeh_ops_unregister - Unreigster platform dependent EEH operations > + * @name: name of EEH platform operations > + * > + * Unregister the platform dependent EEH operation callback > + * functions. > + */ > +int __exit eeh_ops_unregister(const char *name) > +{ > + if (!name || !strlen(name)) { > + pr_warning("%s: Invalid EEH ops name\n", > + __func__); > + return -EINVAL; > + } > + > + if (eeh_ops && !strcmp(eeh_ops->name, name)) { > + eeh_ops = NULL; > + return 0; > + } > + > + return -EEXIST; > +} > + > +/** > + * eeh_init - EEH initialization > + * > + * Initialize EEH by trying to enable it for all of the adapters in the system. > + * As a side effect we can determine here if eeh is supported at all. > + * Note that we leave EEH on so failed config cycles won't cause a machine > + * check. If a user turns off EEH for a particular adapter they are really > + * telling Linux to ignore errors. Some hardware (e.g. POWER5) won't > + * grant access to a slot if EEH isn't enabled, and so we always enable > + * EEH for all slots/all devices. > + * > + * The eeh-force-off option disables EEH checking globally, for all slots. > + * Even if force-off is set, the EEH hardware is still enabled, so that > + * newer systems can boot. > + */ > +static int __init eeh_init(void) > +{ > + struct pci_controller *hose, *tmp; > + struct device_node *phb; > + int ret; > + > + /* call platform initialization function */ > + if (!eeh_ops) { > + pr_warning("%s: Platform EEH operation not found\n", > + __func__); > + return -EEXIST; > + } else if ((ret = eeh_ops->init())) { > + pr_warning("%s: Failed to call platform init function (%d)\n", > + __func__, ret); > + return ret; > + } > + > + raw_spin_lock_init(&confirm_error_lock); > + > + /* Enable EEH for all adapters */ > + if (eeh_probe_mode_devtree()) { > + list_for_each_entry_safe(hose, tmp, > + &hose_list, list_node) { > + phb = hose->dn; > + traverse_pci_devices(phb, eeh_ops->of_probe, NULL); > + } > + } > + > + if (eeh_subsystem_enabled) > + pr_info("EEH: PCI Enhanced I/O Error Handling Enabled\n"); > + else > + pr_warning("EEH: No capable adapters found\n"); > + > + return ret; > +} > + > +core_initcall_sync(eeh_init); > + > +/** > + * eeh_add_device_early - Enable EEH for the indicated device_node > + * @dn: device node for which to set up EEH > + * > + * This routine must be used to perform EEH initialization for PCI > + * devices that were added after system boot (e.g. hotplug, dlpar). > + * This routine must be called before any i/o is performed to the > + * adapter (inluding any config-space i/o). > + * Whether this actually enables EEH or not for this device depends > + * on the CEC architecture, type of the device, on earlier boot > + * command-line arguments & etc. > + */ > +static void eeh_add_device_early(struct device_node *dn) > +{ > + struct pci_controller *phb; > + > + if (!of_node_to_eeh_dev(dn)) > + return; > + phb = of_node_to_eeh_dev(dn)->phb; > + > + /* USB Bus children of PCI devices will not have BUID's */ > + if (NULL == phb || 0 == phb->buid) > + return; > + > + /* FIXME: hotplug support on POWERNV */ > + eeh_ops->of_probe(dn, NULL); > +} > + > +/** > + * eeh_add_device_tree_early - Enable EEH for the indicated device > + * @dn: device node > + * > + * This routine must be used to perform EEH initialization for the > + * indicated PCI device that was added after system boot (e.g. > + * hotplug, dlpar). > + */ > +void eeh_add_device_tree_early(struct device_node *dn) > +{ > + struct device_node *sib; > + > + for_each_child_of_node(dn, sib) > + eeh_add_device_tree_early(sib); > + eeh_add_device_early(dn); > +} > +EXPORT_SYMBOL_GPL(eeh_add_device_tree_early); > + > +/** > + * eeh_add_device_late - Perform EEH initialization for the indicated pci device > + * @dev: pci device for which to set up EEH > + * > + * This routine must be used to complete EEH initialization for PCI > + * devices that were added after system boot (e.g. hotplug, dlpar). > + */ > +static void eeh_add_device_late(struct pci_dev *dev) > +{ > + struct device_node *dn; > + struct eeh_dev *edev; > + > + if (!dev || !eeh_subsystem_enabled) > + return; > + > + pr_debug("EEH: Adding device %s\n", pci_name(dev)); > + > + dn = pci_device_to_OF_node(dev); > + edev = of_node_to_eeh_dev(dn); > + if (edev->pdev == dev) { > + pr_debug("EEH: Already referenced !\n"); > + return; > + } > + WARN_ON(edev->pdev); > + > + pci_dev_get(dev); > + edev->pdev = dev; > + dev->dev.archdata.edev = edev; > + > + eeh_addr_cache_insert_dev(dev); > +} > + > +/** > + * eeh_add_device_tree_late - Perform EEH initialization for the indicated PCI bus > + * @bus: PCI bus > + * > + * This routine must be used to perform EEH initialization for PCI > + * devices which are attached to the indicated PCI bus. The PCI bus > + * is added after system boot through hotplug or dlpar. > + */ > +void eeh_add_device_tree_late(struct pci_bus *bus) > +{ > + struct pci_dev *dev; > + > + list_for_each_entry(dev, &bus->devices, bus_list) { > + eeh_add_device_late(dev); > + if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE) { > + struct pci_bus *subbus = dev->subordinate; > + if (subbus) > + eeh_add_device_tree_late(subbus); > + } > + } > +} > +EXPORT_SYMBOL_GPL(eeh_add_device_tree_late); > + > +/** > + * eeh_add_sysfs_files - Add EEH sysfs files for the indicated PCI bus > + * @bus: PCI bus > + * > + * This routine must be used to add EEH sysfs files for PCI > + * devices which are attached to the indicated PCI bus. The PCI bus > + * is added after system boot through hotplug or dlpar. > + */ > +void eeh_add_sysfs_files(struct pci_bus *bus) > +{ > + struct pci_dev *dev; > + > + list_for_each_entry(dev, &bus->devices, bus_list) { > + eeh_sysfs_add_device(dev); > + if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE) { > + struct pci_bus *subbus = dev->subordinate; > + if (subbus) > + eeh_add_sysfs_files(subbus); > + } > + } > +} > +EXPORT_SYMBOL_GPL(eeh_add_sysfs_files); > + > +/** > + * eeh_remove_device - Undo EEH setup for the indicated pci device > + * @dev: pci device to be removed > + * @purge_pe: remove the PE or not > + * > + * This routine should be called when a device is removed from > + * a running system (e.g. by hotplug or dlpar). It unregisters > + * the PCI device from the EEH subsystem. I/O errors affecting > + * this device will no longer be detected after this call; thus, > + * i/o errors affecting this slot may leave this device unusable. > + */ > +static void eeh_remove_device(struct pci_dev *dev, int purge_pe) > +{ > + struct eeh_dev *edev; > + > + if (!dev || !eeh_subsystem_enabled) > + return; > + edev = pci_dev_to_eeh_dev(dev); > + > + /* Unregister the device with the EEH/PCI address search system */ > + pr_debug("EEH: Removing device %s\n", pci_name(dev)); > + > + if (!edev || !edev->pdev) { > + pr_debug("EEH: Not referenced !\n"); > + return; > + } > + edev->pdev = NULL; > + dev->dev.archdata.edev = NULL; > + pci_dev_put(dev); > + > + eeh_rmv_from_parent_pe(edev, purge_pe); > + eeh_addr_cache_rmv_dev(dev); > + eeh_sysfs_remove_device(dev); > +} > + > +/** > + * eeh_remove_bus_device - Undo EEH setup for the indicated PCI device > + * @dev: PCI device > + * @purge_pe: remove the corresponding PE or not > + * > + * This routine must be called when a device is removed from the > + * running system through hotplug or dlpar. The corresponding > + * PCI address cache will be removed. > + */ > +void eeh_remove_bus_device(struct pci_dev *dev, int purge_pe) > +{ > + struct pci_bus *bus = dev->subordinate; > + struct pci_dev *child, *tmp; > + > + eeh_remove_device(dev, purge_pe); > + > + if (bus && dev->hdr_type == PCI_HEADER_TYPE_BRIDGE) { > + list_for_each_entry_safe(child, tmp, &bus->devices, bus_list) > + eeh_remove_bus_device(child, purge_pe); > + } > +} > +EXPORT_SYMBOL_GPL(eeh_remove_bus_device); > + > +static int proc_eeh_show(struct seq_file *m, void *v) > +{ > + if (0 == eeh_subsystem_enabled) { > + seq_printf(m, "EEH Subsystem is globally disabled\n"); > + seq_printf(m, "eeh_total_mmio_ffs=%llu\n", eeh_stats.total_mmio_ffs); > + } else { > + seq_printf(m, "EEH Subsystem is enabled\n"); > + seq_printf(m, > + "no device=%llu\n" > + "no device node=%llu\n" > + "no config address=%llu\n" > + "check not wanted=%llu\n" > + "eeh_total_mmio_ffs=%llu\n" > + "eeh_false_positives=%llu\n" > + "eeh_slot_resets=%llu\n", > + eeh_stats.no_device, > + eeh_stats.no_dn, > + eeh_stats.no_cfg_addr, > + eeh_stats.ignored_check, > + eeh_stats.total_mmio_ffs, > + eeh_stats.false_positives, > + eeh_stats.slot_resets); > + } > + > + return 0; > +} > + > +static int proc_eeh_open(struct inode *inode, struct file *file) > +{ > + return single_open(file, proc_eeh_show, NULL); > +} > + > +static const struct file_operations proc_eeh_operations = { > + .open = proc_eeh_open, > + .read = seq_read, > + .llseek = seq_lseek, > + .release = single_release, > +}; > + > +static int __init eeh_init_proc(void) > +{ > + if (machine_is(pseries)) > + proc_create("powerpc/eeh", 0, NULL, &proc_eeh_operations); > + return 0; > +} > +__initcall(eeh_init_proc); > diff --git a/arch/powerpc/kernel/eeh_cache.c b/arch/powerpc/kernel/eeh_cache.c > new file mode 100644 > index 0000000..5a4c879 > --- /dev/null > +++ b/arch/powerpc/kernel/eeh_cache.c > @@ -0,0 +1,319 @@ > +/* > + * PCI address cache; allows the lookup of PCI devices based on I/O address > + * > + * Copyright IBM Corporation 2004 > + * Copyright Linas Vepstas <linas@austin.ibm.com> 2004 > + * > + * 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 > + * the Free Software Foundation; either version 2 of the License, or > + * (at your option) any later version. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, write to the Free Software > + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA > + */ > + > +#include <linux/list.h> > +#include <linux/pci.h> > +#include <linux/rbtree.h> > +#include <linux/slab.h> > +#include <linux/spinlock.h> > +#include <linux/atomic.h> > +#include <asm/pci-bridge.h> > +#include <asm/ppc-pci.h> > + > + > +/** > + * The pci address cache subsystem. This subsystem places > + * PCI device address resources into a red-black tree, sorted > + * according to the address range, so that given only an i/o > + * address, the corresponding PCI device can be **quickly** > + * found. It is safe to perform an address lookup in an interrupt > + * context; this ability is an important feature. > + * > + * Currently, the only customer of this code is the EEH subsystem; > + * thus, this code has been somewhat tailored to suit EEH better. > + * In particular, the cache does *not* hold the addresses of devices > + * for which EEH is not enabled. > + * > + * (Implementation Note: The RB tree seems to be better/faster > + * than any hash algo I could think of for this problem, even > + * with the penalty of slow pointer chases for d-cache misses). > + */ > +struct pci_io_addr_range { > + struct rb_node rb_node; > + unsigned long addr_lo; > + unsigned long addr_hi; > + struct eeh_dev *edev; > + struct pci_dev *pcidev; > + unsigned int flags; > +}; > + > +static struct pci_io_addr_cache { > + struct rb_root rb_root; > + spinlock_t piar_lock; > +} pci_io_addr_cache_root; > + > +static inline struct eeh_dev *__eeh_addr_cache_get_device(unsigned long addr) > +{ > + struct rb_node *n = pci_io_addr_cache_root.rb_root.rb_node; > + > + while (n) { > + struct pci_io_addr_range *piar; > + piar = rb_entry(n, struct pci_io_addr_range, rb_node); > + > + if (addr < piar->addr_lo) { > + n = n->rb_left; > + } else { > + if (addr > piar->addr_hi) { > + n = n->rb_right; > + } else { > + pci_dev_get(piar->pcidev); > + return piar->edev; > + } > + } > + } > + > + return NULL; > +} > + > +/** > + * eeh_addr_cache_get_dev - Get device, given only address > + * @addr: mmio (PIO) phys address or i/o port number > + * > + * Given an mmio phys address, or a port number, find a pci device > + * that implements this address. Be sure to pci_dev_put the device > + * when finished. I/O port numbers are assumed to be offset > + * from zero (that is, they do *not* have pci_io_addr added in). > + * It is safe to call this function within an interrupt. > + */ > +struct eeh_dev *eeh_addr_cache_get_dev(unsigned long addr) > +{ > + struct eeh_dev *edev; > + unsigned long flags; > + > + spin_lock_irqsave(&pci_io_addr_cache_root.piar_lock, flags); > + edev = __eeh_addr_cache_get_device(addr); > + spin_unlock_irqrestore(&pci_io_addr_cache_root.piar_lock, flags); > + return edev; > +} > + > +#ifdef DEBUG > +/* > + * Handy-dandy debug print routine, does nothing more > + * than print out the contents of our addr cache. > + */ > +static void eeh_addr_cache_print(struct pci_io_addr_cache *cache) > +{ > + struct rb_node *n; > + int cnt = 0; > + > + n = rb_first(&cache->rb_root); > + while (n) { > + struct pci_io_addr_range *piar; > + piar = rb_entry(n, struct pci_io_addr_range, rb_node); > + pr_debug("PCI: %s addr range %d [%lx-%lx]: %s\n", > + (piar->flags & IORESOURCE_IO) ? "i/o" : "mem", cnt, > + piar->addr_lo, piar->addr_hi, pci_name(piar->pcidev)); > + cnt++; > + n = rb_next(n); > + } > +} > +#endif > + > +/* Insert address range into the rb tree. */ > +static struct pci_io_addr_range * > +eeh_addr_cache_insert(struct pci_dev *dev, unsigned long alo, > + unsigned long ahi, unsigned int flags) > +{ > + struct rb_node **p = &pci_io_addr_cache_root.rb_root.rb_node; > + struct rb_node *parent = NULL; > + struct pci_io_addr_range *piar; > + > + /* Walk tree, find a place to insert into tree */ > + while (*p) { > + parent = *p; > + piar = rb_entry(parent, struct pci_io_addr_range, rb_node); > + if (ahi < piar->addr_lo) { > + p = &parent->rb_left; > + } else if (alo > piar->addr_hi) { > + p = &parent->rb_right; > + } else { > + if (dev != piar->pcidev || > + alo != piar->addr_lo || ahi != piar->addr_hi) { > + pr_warning("PIAR: overlapping address range\n"); > + } > + return piar; > + } > + } > + piar = kzalloc(sizeof(struct pci_io_addr_range), GFP_ATOMIC); > + if (!piar) > + return NULL; > + > + pci_dev_get(dev); > + piar->addr_lo = alo; > + piar->addr_hi = ahi; > + piar->edev = pci_dev_to_eeh_dev(dev); > + piar->pcidev = dev; > + piar->flags = flags; > + > +#ifdef DEBUG > + pr_debug("PIAR: insert range=[%lx:%lx] dev=%s\n", > + alo, ahi, pci_name(dev)); > +#endif > + > + rb_link_node(&piar->rb_node, parent, p); > + rb_insert_color(&piar->rb_node, &pci_io_addr_cache_root.rb_root); > + > + return piar; > +} > + > +static void __eeh_addr_cache_insert_dev(struct pci_dev *dev) > +{ > + struct device_node *dn; > + struct eeh_dev *edev; > + int i; > + > + dn = pci_device_to_OF_node(dev); > + if (!dn) { > + pr_warning("PCI: no pci dn found for dev=%s\n", pci_name(dev)); > + return; > + } > + > + edev = of_node_to_eeh_dev(dn); > + if (!edev) { > + pr_warning("PCI: no EEH dev found for dn=%s\n", > + dn->full_name); > + return; > + } > + > + /* Skip any devices for which EEH is not enabled. */ > + if (!edev->pe) { > +#ifdef DEBUG > + pr_info("PCI: skip building address cache for=%s - %s\n", > + pci_name(dev), dn->full_name); > +#endif > + return; > + } > + > + /* Walk resources on this device, poke them into the tree */ > + for (i = 0; i < DEVICE_COUNT_RESOURCE; i++) { > + unsigned long start = pci_resource_start(dev,i); > + unsigned long end = pci_resource_end(dev,i); > + unsigned int flags = pci_resource_flags(dev,i); > + > + /* We are interested only bus addresses, not dma or other stuff */ > + if (0 == (flags & (IORESOURCE_IO | IORESOURCE_MEM))) > + continue; > + if (start == 0 || ~start == 0 || end == 0 || ~end == 0) > + continue; > + eeh_addr_cache_insert(dev, start, end, flags); > + } > +} > + > +/** > + * eeh_addr_cache_insert_dev - Add a device to the address cache > + * @dev: PCI device whose I/O addresses we are interested in. > + * > + * In order to support the fast lookup of devices based on addresses, > + * we maintain a cache of devices that can be quickly searched. > + * This routine adds a device to that cache. > + */ > +void eeh_addr_cache_insert_dev(struct pci_dev *dev) > +{ > + unsigned long flags; > + > + /* Ignore PCI bridges */ > + if ((dev->class >> 16) == PCI_BASE_CLASS_BRIDGE) > + return; > + > + spin_lock_irqsave(&pci_io_addr_cache_root.piar_lock, flags); > + __eeh_addr_cache_insert_dev(dev); > + spin_unlock_irqrestore(&pci_io_addr_cache_root.piar_lock, flags); > +} > + > +static inline void __eeh_addr_cache_rmv_dev(struct pci_dev *dev) > +{ > + struct rb_node *n; > + > +restart: > + n = rb_first(&pci_io_addr_cache_root.rb_root); > + while (n) { > + struct pci_io_addr_range *piar; > + piar = rb_entry(n, struct pci_io_addr_range, rb_node); > + > + if (piar->pcidev == dev) { > + rb_erase(n, &pci_io_addr_cache_root.rb_root); > + pci_dev_put(piar->pcidev); > + kfree(piar); > + goto restart; > + } > + n = rb_next(n); > + } > +} > + > +/** > + * eeh_addr_cache_rmv_dev - remove pci device from addr cache > + * @dev: device to remove > + * > + * Remove a device from the addr-cache tree. > + * This is potentially expensive, since it will walk > + * the tree multiple times (once per resource). > + * But so what; device removal doesn't need to be that fast. > + */ > +void eeh_addr_cache_rmv_dev(struct pci_dev *dev) > +{ > + unsigned long flags; > + > + spin_lock_irqsave(&pci_io_addr_cache_root.piar_lock, flags); > + __eeh_addr_cache_rmv_dev(dev); > + spin_unlock_irqrestore(&pci_io_addr_cache_root.piar_lock, flags); > +} > + > +/** > + * eeh_addr_cache_build - Build a cache of I/O addresses > + * > + * Build a cache of pci i/o addresses. This cache will be used to > + * find the pci device that corresponds to a given address. > + * This routine scans all pci busses to build the cache. > + * Must be run late in boot process, after the pci controllers > + * have been scanned for devices (after all device resources are known). > + */ > +void __init eeh_addr_cache_build(void) > +{ > + struct device_node *dn; > + struct eeh_dev *edev; > + struct pci_dev *dev = NULL; > + > + spin_lock_init(&pci_io_addr_cache_root.piar_lock); > + > + for_each_pci_dev(dev) { > + eeh_addr_cache_insert_dev(dev); > + > + dn = pci_device_to_OF_node(dev); > + if (!dn) > + continue; > + > + edev = of_node_to_eeh_dev(dn); > + if (!edev) > + continue; > + > + pci_dev_get(dev); /* matching put is in eeh_remove_device() */ > + dev->dev.archdata.edev = edev; > + edev->pdev = dev; > + > + eeh_sysfs_add_device(dev); > + } > + > +#ifdef DEBUG > + /* Verify tree built up above, echo back the list of addrs. */ > + eeh_addr_cache_print(&pci_io_addr_cache_root); > +#endif > +} > + > diff --git a/arch/powerpc/kernel/eeh_dev.c b/arch/powerpc/kernel/eeh_dev.c > new file mode 100644 > index 0000000..1efa28f > --- /dev/null > +++ b/arch/powerpc/kernel/eeh_dev.c > @@ -0,0 +1,112 @@ > +/* > + * The file intends to implement dynamic creation of EEH device, which will > + * be bound with OF node and PCI device simutaneously. The EEH devices would > + * be foundamental information for EEH core components to work proerly. Besides, > + * We have to support multiple situations where dynamic creation of EEH device > + * is required: > + * > + * 1) Before PCI emunation starts, we need create EEH devices according to the > + * PCI sensitive OF nodes. > + * 2) When PCI emunation is done, we need do the binding between PCI device and > + * the associated EEH device. > + * 3) DR (Dynamic Reconfiguration) would create PCI sensitive OF node. EEH device > + * will be created while PCI sensitive OF node is detected from DR. > + * 4) PCI hotplug needs redoing the binding between PCI device and EEH device. If > + * PHB is newly inserted, we also need create EEH devices accordingly. > + * > + * Copyright Benjamin Herrenschmidt & Gavin Shan, IBM Corporation 2012. > + * > + * 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 > + * the Free Software Foundation; either version 2 of the License, or > + * (at your option) any later version. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, write to the Free Software > + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA > + */ > + > +#include <linux/export.h> > +#include <linux/gfp.h> > +#include <linux/init.h> > +#include <linux/kernel.h> > +#include <linux/pci.h> > +#include <linux/string.h> > + > +#include <asm/pci-bridge.h> > +#include <asm/ppc-pci.h> > + > +/** > + * eeh_dev_init - Create EEH device according to OF node > + * @dn: device node > + * @data: PHB > + * > + * It will create EEH device according to the given OF node. The function > + * might be called by PCI emunation, DR, PHB hotplug. > + */ > +void *eeh_dev_init(struct device_node *dn, void *data) > +{ > + struct pci_controller *phb = data; > + struct eeh_dev *edev; > + > + /* Allocate EEH device */ > + edev = kzalloc(sizeof(*edev), GFP_KERNEL); > + if (!edev) { > + pr_warning("%s: out of memory\n", __func__); > + return NULL; > + } > + > + /* Associate EEH device with OF node */ > + PCI_DN(dn)->edev = edev; > + edev->dn = dn; > + edev->phb = phb; > + INIT_LIST_HEAD(&edev->list); > + > + return NULL; > +} > + > +/** > + * eeh_dev_phb_init_dynamic - Create EEH devices for devices included in PHB > + * @phb: PHB > + * > + * Scan the PHB OF node and its child association, then create the > + * EEH devices accordingly > + */ > +void eeh_dev_phb_init_dynamic(struct pci_controller *phb) > +{ > + struct device_node *dn = phb->dn; > + > + /* EEH PE for PHB */ > + eeh_phb_pe_create(phb); > + > + /* EEH device for PHB */ > + eeh_dev_init(dn, phb); > + > + /* EEH devices for children OF nodes */ > + traverse_pci_devices(dn, eeh_dev_init, phb); > +} > + > +/** > + * eeh_dev_phb_init - Create EEH devices for devices included in existing PHBs > + * > + * Scan all the existing PHBs and create EEH devices for their OF > + * nodes and their children OF nodes > + */ > +static int __init eeh_dev_phb_init(void) > +{ > + struct pci_controller *phb, *tmp; > + > + list_for_each_entry_safe(phb, tmp, &hose_list, list_node) > + eeh_dev_phb_init_dynamic(phb); > + > + pr_info("EEH: devices created\n"); > + > + return 0; > +} > + > +core_initcall(eeh_dev_phb_init); > diff --git a/arch/powerpc/kernel/eeh_driver.c b/arch/powerpc/kernel/eeh_driver.c > new file mode 100644 > index 0000000..a3fefb6 > --- /dev/null > +++ b/arch/powerpc/kernel/eeh_driver.c > @@ -0,0 +1,552 @@ > +/* > + * PCI Error Recovery Driver for RPA-compliant PPC64 platform. > + * Copyright IBM Corp. 2004 2005 > + * Copyright Linas Vepstas <linas@linas.org> 2004, 2005 > + * > + * All rights reserved. > + * > + * 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 > + * the Free Software Foundation; either version 2 of the License, or (at > + * your option) any later version. > + * > + * This program is distributed in the hope that it will be useful, but > + * WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or > + * NON INFRINGEMENT. See the GNU General Public License for more > + * details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, write to the Free Software > + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. > + * > + * Send comments and feedback to Linas Vepstas <linas@austin.ibm.com> > + */ > +#include <linux/delay.h> > +#include <linux/interrupt.h> > +#include <linux/irq.h> > +#include <linux/module.h> > +#include <linux/pci.h> > +#include <asm/eeh.h> > +#include <asm/eeh_event.h> > +#include <asm/ppc-pci.h> > +#include <asm/pci-bridge.h> > +#include <asm/prom.h> > +#include <asm/rtas.h> > + > +/** > + * eeh_pcid_name - Retrieve name of PCI device driver > + * @pdev: PCI device > + * > + * This routine is used to retrieve the name of PCI device driver > + * if that's valid. > + */ > +static inline const char *eeh_pcid_name(struct pci_dev *pdev) > +{ > + if (pdev && pdev->dev.driver) > + return pdev->dev.driver->name; > + return ""; > +} > + > +/** > + * eeh_pcid_get - Get the PCI device driver > + * @pdev: PCI device > + * > + * The function is used to retrieve the PCI device driver for > + * the indicated PCI device. Besides, we will increase the reference > + * of the PCI device driver to prevent that being unloaded on > + * the fly. Otherwise, kernel crash would be seen. > + */ > +static inline struct pci_driver *eeh_pcid_get(struct pci_dev *pdev) > +{ > + if (!pdev || !pdev->driver) > + return NULL; > + > + if (!try_module_get(pdev->driver->driver.owner)) > + return NULL; > + > + return pdev->driver; > +} > + > +/** > + * eeh_pcid_put - Dereference on the PCI device driver > + * @pdev: PCI device > + * > + * The function is called to do dereference on the PCI device > + * driver of the indicated PCI device. > + */ > +static inline void eeh_pcid_put(struct pci_dev *pdev) > +{ > + if (!pdev || !pdev->driver) > + return; > + > + module_put(pdev->driver->driver.owner); > +} > + > +#if 0 > +static void print_device_node_tree(struct pci_dn *pdn, int dent) > +{ > + int i; > + struct device_node *pc; > + > + if (!pdn) > + return; > + for (i = 0; i < dent; i++) > + printk(" "); > + printk("dn=%s mode=%x \tcfg_addr=%x pe_addr=%x \tfull=%s\n", > + pdn->node->name, pdn->eeh_mode, pdn->eeh_config_addr, > + pdn->eeh_pe_config_addr, pdn->node->full_name); > + dent += 3; > + pc = pdn->node->child; > + while (pc) { > + print_device_node_tree(PCI_DN(pc), dent); > + pc = pc->sibling; > + } > +} > +#endif > + > +/** > + * eeh_disable_irq - Disable interrupt for the recovering device > + * @dev: PCI device > + * > + * This routine must be called when reporting temporary or permanent > + * error to the particular PCI device to disable interrupt of that > + * device. If the device has enabled MSI or MSI-X interrupt, we needn't > + * do real work because EEH should freeze DMA transfers for those PCI > + * devices encountering EEH errors, which includes MSI or MSI-X. > + */ > +static void eeh_disable_irq(struct pci_dev *dev) > +{ > + struct eeh_dev *edev = pci_dev_to_eeh_dev(dev); > + > + /* Don't disable MSI and MSI-X interrupts. They are > + * effectively disabled by the DMA Stopped state > + * when an EEH error occurs. > + */ > + if (dev->msi_enabled || dev->msix_enabled) > + return; > + > + if (!irq_has_action(dev->irq)) > + return; > + > + edev->mode |= EEH_DEV_IRQ_DISABLED; > + disable_irq_nosync(dev->irq); > +} > + > +/** > + * eeh_enable_irq - Enable interrupt for the recovering device > + * @dev: PCI device > + * > + * This routine must be called to enable interrupt while failed > + * device could be resumed. > + */ > +static void eeh_enable_irq(struct pci_dev *dev) > +{ > + struct eeh_dev *edev = pci_dev_to_eeh_dev(dev); > + > + if ((edev->mode) & EEH_DEV_IRQ_DISABLED) { > + edev->mode &= ~EEH_DEV_IRQ_DISABLED; > + enable_irq(dev->irq); > + } > +} > + > +/** > + * eeh_report_error - Report pci error to each device driver > + * @data: eeh device > + * @userdata: return value > + * > + * Report an EEH error to each device driver, collect up and > + * merge the device driver responses. Cumulative response > + * passed back in "userdata". > + */ > +static void *eeh_report_error(void *data, void *userdata) > +{ > + struct eeh_dev *edev = (struct eeh_dev *)data; > + struct pci_dev *dev = eeh_dev_to_pci_dev(edev); > + enum pci_ers_result rc, *res = userdata; > + struct pci_driver *driver; > + > + /* We might not have the associated PCI device, > + * then we should continue for next one. > + */ > + if (!dev) return NULL; > + dev->error_state = pci_channel_io_frozen; > + > + driver = eeh_pcid_get(dev); > + if (!driver) return NULL; > + > + eeh_disable_irq(dev); > + > + if (!driver->err_handler || > + !driver->err_handler->error_detected) { > + eeh_pcid_put(dev); > + return NULL; > + } > + > + rc = driver->err_handler->error_detected(dev, pci_channel_io_frozen); > + > + /* A driver that needs a reset trumps all others */ > + if (rc == PCI_ERS_RESULT_NEED_RESET) *res = rc; > + if (*res == PCI_ERS_RESULT_NONE) *res = rc; > + > + eeh_pcid_put(dev); > + return NULL; > +} > + > +/** > + * eeh_report_mmio_enabled - Tell drivers that MMIO has been enabled > + * @data: eeh device > + * @userdata: return value > + * > + * Tells each device driver that IO ports, MMIO and config space I/O > + * are now enabled. Collects up and merges the device driver responses. > + * Cumulative response passed back in "userdata". > + */ > +static void *eeh_report_mmio_enabled(void *data, void *userdata) > +{ > + struct eeh_dev *edev = (struct eeh_dev *)data; > + struct pci_dev *dev = eeh_dev_to_pci_dev(edev); > + enum pci_ers_result rc, *res = userdata; > + struct pci_driver *driver; > + > + driver = eeh_pcid_get(dev); > + if (!driver) return NULL; > + > + if (!driver->err_handler || > + !driver->err_handler->mmio_enabled) { > + eeh_pcid_put(dev); > + return NULL; > + } > + > + rc = driver->err_handler->mmio_enabled(dev); > + > + /* A driver that needs a reset trumps all others */ > + if (rc == PCI_ERS_RESULT_NEED_RESET) *res = rc; > + if (*res == PCI_ERS_RESULT_NONE) *res = rc; > + > + eeh_pcid_put(dev); > + return NULL; > +} > + > +/** > + * eeh_report_reset - Tell device that slot has been reset > + * @data: eeh device > + * @userdata: return value > + * > + * This routine must be called while EEH tries to reset particular > + * PCI device so that the associated PCI device driver could take > + * some actions, usually to save data the driver needs so that the > + * driver can work again while the device is recovered. > + */ > +static void *eeh_report_reset(void *data, void *userdata) > +{ > + struct eeh_dev *edev = (struct eeh_dev *)data; > + struct pci_dev *dev = eeh_dev_to_pci_dev(edev); > + enum pci_ers_result rc, *res = userdata; > + struct pci_driver *driver; > + > + if (!dev) return NULL; > + dev->error_state = pci_channel_io_normal; > + > + driver = eeh_pcid_get(dev); > + if (!driver) return NULL; > + > + eeh_enable_irq(dev); > + > + if (!driver->err_handler || > + !driver->err_handler->slot_reset) { > + eeh_pcid_put(dev); > + return NULL; > + } > + > + rc = driver->err_handler->slot_reset(dev); > + if ((*res == PCI_ERS_RESULT_NONE) || > + (*res == PCI_ERS_RESULT_RECOVERED)) *res = rc; > + if (*res == PCI_ERS_RESULT_DISCONNECT && > + rc == PCI_ERS_RESULT_NEED_RESET) *res = rc; > + > + eeh_pcid_put(dev); > + return NULL; > +} > + > +/** > + * eeh_report_resume - Tell device to resume normal operations > + * @data: eeh device > + * @userdata: return value > + * > + * This routine must be called to notify the device driver that it > + * could resume so that the device driver can do some initialization > + * to make the recovered device work again. > + */ > +static void *eeh_report_resume(void *data, void *userdata) > +{ > + struct eeh_dev *edev = (struct eeh_dev *)data; > + struct pci_dev *dev = eeh_dev_to_pci_dev(edev); > + struct pci_driver *driver; > + > + if (!dev) return NULL; > + dev->error_state = pci_channel_io_normal; > + > + driver = eeh_pcid_get(dev); > + if (!driver) return NULL; > + > + eeh_enable_irq(dev); > + > + if (!driver->err_handler || > + !driver->err_handler->resume) { > + eeh_pcid_put(dev); > + return NULL; > + } > + > + driver->err_handler->resume(dev); > + > + eeh_pcid_put(dev); > + return NULL; > +} > + > +/** > + * eeh_report_failure - Tell device driver that device is dead. > + * @data: eeh device > + * @userdata: return value > + * > + * This informs the device driver that the device is permanently > + * dead, and that no further recovery attempts will be made on it. > + */ > +static void *eeh_report_failure(void *data, void *userdata) > +{ > + struct eeh_dev *edev = (struct eeh_dev *)data; > + struct pci_dev *dev = eeh_dev_to_pci_dev(edev); > + struct pci_driver *driver; > + > + if (!dev) return NULL; > + dev->error_state = pci_channel_io_perm_failure; > + > + driver = eeh_pcid_get(dev); > + if (!driver) return NULL; > + > + eeh_disable_irq(dev); > + > + if (!driver->err_handler || > + !driver->err_handler->error_detected) { > + eeh_pcid_put(dev); > + return NULL; > + } > + > + driver->err_handler->error_detected(dev, pci_channel_io_perm_failure); > + > + eeh_pcid_put(dev); > + return NULL; > +} > + > +/** > + * eeh_reset_device - Perform actual reset of a pci slot > + * @pe: EEH PE > + * @bus: PCI bus corresponding to the isolcated slot > + * > + * This routine must be called to do reset on the indicated PE. > + * During the reset, udev might be invoked because those affected > + * PCI devices will be removed and then added. > + */ > +static int eeh_reset_device(struct eeh_pe *pe, struct pci_bus *bus) > +{ > + int cnt, rc; > + > + /* pcibios will clear the counter; save the value */ > + cnt = pe->freeze_count; > + > + /* > + * We don't remove the corresponding PE instances because > + * we need the information afterwords. The attached EEH > + * devices are expected to be attached soon when calling > + * into pcibios_add_pci_devices(). > + */ > + if (bus) > + __pcibios_remove_pci_devices(bus, 0); > + > + /* Reset the pci controller. (Asserts RST#; resets config space). > + * Reconfigure bridges and devices. Don't try to bring the system > + * up if the reset failed for some reason. > + */ > + rc = eeh_reset_pe(pe); > + if (rc) > + return rc; > + > + /* Restore PE */ > + eeh_ops->configure_bridge(pe); > + eeh_pe_restore_bars(pe); > + > + /* Give the system 5 seconds to finish running the user-space > + * hotplug shutdown scripts, e.g. ifdown for ethernet. Yes, > + * this is a hack, but if we don't do this, and try to bring > + * the device up before the scripts have taken it down, > + * potentially weird things happen. > + */ > + if (bus) { > + ssleep(5); > + pcibios_add_pci_devices(bus); > + } > + pe->freeze_count = cnt; > + > + return 0; > +} > + > +/* The longest amount of time to wait for a pci device > + * to come back on line, in seconds. > + */ > +#define MAX_WAIT_FOR_RECOVERY 150 > + > +/** > + * eeh_handle_event - Reset a PCI device after hard lockup. > + * @pe: EEH PE > + * > + * While PHB detects address or data parity errors on particular PCI > + * slot, the associated PE will be frozen. Besides, DMA's occurring > + * to wild addresses (which usually happen due to bugs in device > + * drivers or in PCI adapter firmware) can cause EEH error. #SERR, > + * #PERR or other misc PCI-related errors also can trigger EEH errors. > + * > + * Recovery process consists of unplugging the device driver (which > + * generated hotplug events to userspace), then issuing a PCI #RST to > + * the device, then reconfiguring the PCI config space for all bridges > + * & devices under this slot, and then finally restarting the device > + * drivers (which cause a second set of hotplug events to go out to > + * userspace). > + */ > +void eeh_handle_event(struct eeh_pe *pe) > +{ > + struct pci_bus *frozen_bus; > + int rc = 0; > + enum pci_ers_result result = PCI_ERS_RESULT_NONE; > + > + frozen_bus = eeh_pe_bus_get(pe); > + if (!frozen_bus) { > + pr_err("%s: Cannot find PCI bus for PHB#%d-PE#%x\n", > + __func__, pe->phb->global_number, pe->addr); > + return; > + } > + > + pe->freeze_count++; > + if (pe->freeze_count > EEH_MAX_ALLOWED_FREEZES) > + goto excess_failures; > + pr_warning("EEH: This PCI device has failed %d times in the last hour\n", > + pe->freeze_count); > + > + /* Walk the various device drivers attached to this slot through > + * a reset sequence, giving each an opportunity to do what it needs > + * to accomplish the reset. Each child gets a report of the > + * status ... if any child can't handle the reset, then the entire > + * slot is dlpar removed and added. > + */ > + eeh_pe_dev_traverse(pe, eeh_report_error, &result); > + > + /* Get the current PCI slot state. This can take a long time, > + * sometimes over 3 seconds for certain systems. > + */ > + rc = eeh_ops->wait_state(pe, MAX_WAIT_FOR_RECOVERY*1000); > + if (rc < 0 || rc == EEH_STATE_NOT_SUPPORT) { > + printk(KERN_WARNING "EEH: Permanent failure\n"); > + goto hard_fail; > + } > + > + /* Since rtas may enable MMIO when posting the error log, > + * don't post the error log until after all dev drivers > + * have been informed. > + */ > + eeh_slot_error_detail(pe, EEH_LOG_TEMP); > + > + /* If all device drivers were EEH-unaware, then shut > + * down all of the device drivers, and hope they > + * go down willingly, without panicing the system. > + */ > + if (result == PCI_ERS_RESULT_NONE) { > + rc = eeh_reset_device(pe, frozen_bus); > + if (rc) { > + printk(KERN_WARNING "EEH: Unable to reset, rc=%d\n", rc); > + goto hard_fail; > + } > + } > + > + /* If all devices reported they can proceed, then re-enable MMIO */ > + if (result == PCI_ERS_RESULT_CAN_RECOVER) { > + rc = eeh_pci_enable(pe, EEH_OPT_THAW_MMIO); > + > + if (rc < 0) > + goto hard_fail; > + if (rc) { > + result = PCI_ERS_RESULT_NEED_RESET; > + } else { > + result = PCI_ERS_RESULT_NONE; > + eeh_pe_dev_traverse(pe, eeh_report_mmio_enabled, &result); > + } > + } > + > + /* If all devices reported they can proceed, then re-enable DMA */ > + if (result == PCI_ERS_RESULT_CAN_RECOVER) { > + rc = eeh_pci_enable(pe, EEH_OPT_THAW_DMA); > + > + if (rc < 0) > + goto hard_fail; > + if (rc) > + result = PCI_ERS_RESULT_NEED_RESET; > + else > + result = PCI_ERS_RESULT_RECOVERED; > + } > + > + /* If any device has a hard failure, then shut off everything. */ > + if (result == PCI_ERS_RESULT_DISCONNECT) { > + printk(KERN_WARNING "EEH: Device driver gave up\n"); > + goto hard_fail; > + } > + > + /* If any device called out for a reset, then reset the slot */ > + if (result == PCI_ERS_RESULT_NEED_RESET) { > + rc = eeh_reset_device(pe, NULL); > + if (rc) { > + printk(KERN_WARNING "EEH: Cannot reset, rc=%d\n", rc); > + goto hard_fail; > + } > + result = PCI_ERS_RESULT_NONE; > + eeh_pe_dev_traverse(pe, eeh_report_reset, &result); > + } > + > + /* All devices should claim they have recovered by now. */ > + if ((result != PCI_ERS_RESULT_RECOVERED) && > + (result != PCI_ERS_RESULT_NONE)) { > + printk(KERN_WARNING "EEH: Not recovered\n"); > + goto hard_fail; > + } > + > + /* Tell all device drivers that they can resume operations */ > + eeh_pe_dev_traverse(pe, eeh_report_resume, NULL); > + > + return; > + > +excess_failures: > + /* > + * About 90% of all real-life EEH failures in the field > + * are due to poorly seated PCI cards. Only 10% or so are > + * due to actual, failed cards. > + */ > + pr_err("EEH: PHB#%d-PE#%x has failed %d times in the\n" > + "last hour and has been permanently disabled.\n" > + "Please try reseating or replacing it.\n", > + pe->phb->global_number, pe->addr, > + pe->freeze_count); > + goto perm_error; > + > +hard_fail: > + pr_err("EEH: Unable to recover from failure from PHB#%d-PE#%x.\n" > + "Please try reseating or replacing it\n", > + pe->phb->global_number, pe->addr); > + > +perm_error: > + eeh_slot_error_detail(pe, EEH_LOG_PERM); > + > + /* Notify all devices that they're about to go down. */ > + eeh_pe_dev_traverse(pe, eeh_report_failure, NULL); > + > + /* Shut down the device drivers for good. */ > + if (frozen_bus) > + pcibios_remove_pci_devices(frozen_bus); > +} > + > diff --git a/arch/powerpc/kernel/eeh_event.c b/arch/powerpc/kernel/eeh_event.c > new file mode 100644 > index 0000000..185bedd > --- /dev/null > +++ b/arch/powerpc/kernel/eeh_event.c > @@ -0,0 +1,142 @@ > +/* > + * 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 > + * the Free Software Foundation; either version 2 of the License, or > + * (at your option) any later version. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, write to the Free Software > + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA > + * > + * Copyright (c) 2005 Linas Vepstas <linas@linas.org> > + */ > + > +#include <linux/delay.h> > +#include <linux/list.h> > +#include <linux/mutex.h> > +#include <linux/sched.h> > +#include <linux/pci.h> > +#include <linux/slab.h> > +#include <linux/workqueue.h> > +#include <linux/kthread.h> > +#include <asm/eeh_event.h> > +#include <asm/ppc-pci.h> > + > +/** Overview: > + * EEH error states may be detected within exception handlers; > + * however, the recovery processing needs to occur asynchronously > + * in a normal kernel context and not an interrupt context. > + * This pair of routines creates an event and queues it onto a > + * work-queue, where a worker thread can drive recovery. > + */ > + > +/* EEH event workqueue setup. */ > +static DEFINE_SPINLOCK(eeh_eventlist_lock); > +LIST_HEAD(eeh_eventlist); > +static void eeh_thread_launcher(struct work_struct *); > +DECLARE_WORK(eeh_event_wq, eeh_thread_launcher); > + > +/* Serialize reset sequences for a given pci device */ > +DEFINE_MUTEX(eeh_event_mutex); > + > +/** > + * eeh_event_handler - Dispatch EEH events. > + * @dummy - unused > + * > + * 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. > + */ > +static int eeh_event_handler(void * dummy) > +{ > + unsigned long flags; > + struct eeh_event *event; > + struct eeh_pe *pe; > + > + spin_lock_irqsave(&eeh_eventlist_lock, flags); > + event = NULL; > + > + /* Unqueue the event, get ready to process. */ > + 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) > + return 0; > + > + /* Serialize processing of EEH events */ > + mutex_lock(&eeh_event_mutex); > + pe = event->pe; > + eeh_pe_state_mark(pe, EEH_PE_RECOVERING); > + pr_info("EEH: Detected PCI bus error on PHB#%d-PE#%x\n", > + pe->phb->global_number, pe->addr); > + > + set_current_state(TASK_INTERRUPTIBLE); /* Don't add to load average */ > + eeh_handle_event(pe); > + eeh_pe_state_clear(pe, EEH_PE_RECOVERING); > + > + kfree(event); > + mutex_unlock(&eeh_event_mutex); > + > + /* If there are no new errors after an hour, clear the counter. */ > + if (pe && pe->freeze_count > 0) { > + msleep_interruptible(3600*1000); > + if (pe->freeze_count > 0) > + pe->freeze_count--; > + > + } > + > + return 0; > +} > + > +/** > + * eeh_thread_launcher - Start kernel thread to handle EEH events > + * @dummy - unused > + * > + * This routine is called to start the kernel thread for processing > + * EEH event. > + */ > +static void eeh_thread_launcher(struct work_struct *dummy) > +{ > + if (IS_ERR(kthread_run(eeh_event_handler, NULL, "eehd"))) > + printk(KERN_ERR "Failed to start EEH daemon\n"); > +} > + > +/** > + * eeh_send_failure_event - Generate a PCI error event > + * @pe: EEH PE > + * > + * This routine can be called within an interrupt context; > + * the actual event will be delivered in a normal context > + * (from a workqueue). > + */ > +int eeh_send_failure_event(struct eeh_pe *pe) > +{ > + unsigned long flags; > + struct eeh_event *event; > + > + event = kzalloc(sizeof(*event), GFP_ATOMIC); > + if (!event) { > + pr_err("EEH: out of memory, event not handled\n"); > + return -ENOMEM; > + } > + event->pe = pe; > + > + /* 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); > + > + schedule_work(&eeh_event_wq); > + > + return 0; > +} > diff --git a/arch/powerpc/kernel/eeh_pe.c b/arch/powerpc/kernel/eeh_pe.c > new file mode 100644 > index 0000000..9d4a9e8 > --- /dev/null > +++ b/arch/powerpc/kernel/eeh_pe.c > @@ -0,0 +1,653 @@ > +/* > + * The file intends to implement PE based on the information from > + * platforms. Basically, there have 3 types of PEs: PHB/Bus/Device. > + * All the PEs should be organized as hierarchy tree. The first level > + * of the tree will be associated to existing PHBs since the particular > + * PE is only meaningful in one PHB domain. > + * > + * Copyright Benjamin Herrenschmidt & Gavin Shan, IBM Corporation 2012. > + * > + * 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 > + * the Free Software Foundation; either version 2 of the License, or > + * (at your option) any later version. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, write to the Free Software > + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA > + */ > + > +#include <linux/export.h> > +#include <linux/gfp.h> > +#include <linux/init.h> > +#include <linux/kernel.h> > +#include <linux/pci.h> > +#include <linux/string.h> > + > +#include <asm/pci-bridge.h> > +#include <asm/ppc-pci.h> > + > +static LIST_HEAD(eeh_phb_pe); > + > +/** > + * eeh_pe_alloc - Allocate PE > + * @phb: PCI controller > + * @type: PE type > + * > + * Allocate PE instance dynamically. > + */ > +static struct eeh_pe *eeh_pe_alloc(struct pci_controller *phb, int type) > +{ > + struct eeh_pe *pe; > + > + /* Allocate PHB PE */ > + pe = kzalloc(sizeof(struct eeh_pe), GFP_KERNEL); > + if (!pe) return NULL; > + > + /* Initialize PHB PE */ > + pe->type = type; > + pe->phb = phb; > + INIT_LIST_HEAD(&pe->child_list); > + INIT_LIST_HEAD(&pe->child); > + INIT_LIST_HEAD(&pe->edevs); > + > + return pe; > +} > + > +/** > + * eeh_phb_pe_create - Create PHB PE > + * @phb: PCI controller > + * > + * The function should be called while the PHB is detected during > + * system boot or PCI hotplug in order to create PHB PE. > + */ > +int eeh_phb_pe_create(struct pci_controller *phb) > +{ > + struct eeh_pe *pe; > + > + /* Allocate PHB PE */ > + pe = eeh_pe_alloc(phb, EEH_PE_PHB); > + if (!pe) { > + pr_err("%s: out of memory!\n", __func__); > + return -ENOMEM; > + } > + > + /* Put it into the list */ > + eeh_lock(); > + list_add_tail(&pe->child, &eeh_phb_pe); > + eeh_unlock(); > + > + pr_debug("EEH: Add PE for PHB#%d\n", phb->global_number); > + > + return 0; > +} > + > +/** > + * eeh_phb_pe_get - Retrieve PHB PE based on the given PHB > + * @phb: PCI controller > + * > + * The overall PEs form hierarchy tree. The first layer of the > + * hierarchy tree is composed of PHB PEs. The function is used > + * to retrieve the corresponding PHB PE according to the given PHB. > + */ > +static struct eeh_pe *eeh_phb_pe_get(struct pci_controller *phb) > +{ > + struct eeh_pe *pe; > + > + list_for_each_entry(pe, &eeh_phb_pe, child) { > + /* > + * Actually, we needn't check the type since > + * the PE for PHB has been determined when that > + * was created. > + */ > + if ((pe->type & EEH_PE_PHB) && pe->phb == phb) > + return pe; > + } > + > + return NULL; > +} > + > +/** > + * eeh_pe_next - Retrieve the next PE in the tree > + * @pe: current PE > + * @root: root PE > + * > + * The function is used to retrieve the next PE in the > + * hierarchy PE tree. > + */ > +static struct eeh_pe *eeh_pe_next(struct eeh_pe *pe, > + struct eeh_pe *root) > +{ > + struct list_head *next = pe->child_list.next; > + > + if (next == &pe->child_list) { > + while (1) { > + if (pe == root) > + return NULL; > + next = pe->child.next; > + if (next != &pe->parent->child_list) > + break; > + pe = pe->parent; > + } > + } > + > + return list_entry(next, struct eeh_pe, child); > +} > + > +/** > + * eeh_pe_traverse - Traverse PEs in the specified PHB > + * @root: root PE > + * @fn: callback > + * @flag: extra parameter to callback > + * > + * The function is used to traverse the specified PE and its > + * child PEs. The traversing is to be terminated once the > + * callback returns something other than NULL, or no more PEs > + * to be traversed. > + */ > +static void *eeh_pe_traverse(struct eeh_pe *root, > + eeh_traverse_func fn, void *flag) > +{ > + struct eeh_pe *pe; > + void *ret; > + > + for (pe = root; pe; pe = eeh_pe_next(pe, root)) { > + ret = fn(pe, flag); > + if (ret) return ret; > + } > + > + return NULL; > +} > + > +/** > + * eeh_pe_dev_traverse - Traverse the devices from the PE > + * @root: EEH PE > + * @fn: function callback > + * @flag: extra parameter to callback > + * > + * The function is used to traverse the devices of the specified > + * PE and its child PEs. > + */ > +void *eeh_pe_dev_traverse(struct eeh_pe *root, > + eeh_traverse_func fn, void *flag) > +{ > + struct eeh_pe *pe; > + struct eeh_dev *edev; > + void *ret; > + > + if (!root) { > + pr_warning("%s: Invalid PE %p\n", __func__, root); > + return NULL; > + } > + > + eeh_lock(); > + > + /* Traverse root PE */ > + for (pe = root; pe; pe = eeh_pe_next(pe, root)) { > + eeh_pe_for_each_dev(pe, edev) { > + ret = fn(edev, flag); > + if (ret) { > + eeh_unlock(); > + return ret; > + } > + } > + } > + > + eeh_unlock(); > + > + return NULL; > +} > + > +/** > + * __eeh_pe_get - Check the PE address > + * @data: EEH PE > + * @flag: EEH device > + * > + * For one particular PE, it can be identified by PE address > + * or tranditional BDF address. BDF address is composed of > + * Bus/Device/Function number. The extra data referred by flag > + * indicates which type of address should be used. > + */ > +static void *__eeh_pe_get(void *data, void *flag) > +{ > + struct eeh_pe *pe = (struct eeh_pe *)data; > + struct eeh_dev *edev = (struct eeh_dev *)flag; > + > + /* Unexpected PHB PE */ > + if (pe->type & EEH_PE_PHB) > + return NULL; > + > + /* We prefer PE address */ > + if (edev->pe_config_addr && > + (edev->pe_config_addr == pe->addr)) > + return pe; > + > + /* Try BDF address */ > + if (edev->pe_config_addr && > + (edev->config_addr == pe->config_addr)) > + return pe; > + > + return NULL; > +} > + > +/** > + * eeh_pe_get - Search PE based on the given address > + * @edev: EEH device > + * > + * Search the corresponding PE based on the specified address which > + * is included in the eeh device. The function is used to check if > + * the associated PE has been created against the PE address. It's > + * notable that the PE address has 2 format: traditional PE address > + * which is composed of PCI bus/device/function number, or unified > + * PE address. > + */ > +static struct eeh_pe *eeh_pe_get(struct eeh_dev *edev) > +{ > + struct eeh_pe *root = eeh_phb_pe_get(edev->phb); > + struct eeh_pe *pe; > + > + pe = eeh_pe_traverse(root, __eeh_pe_get, edev); > + > + return pe; > +} > + > +/** > + * eeh_pe_get_parent - Retrieve the parent PE > + * @edev: EEH device > + * > + * The whole PEs existing in the system are organized as hierarchy > + * tree. The function is used to retrieve the parent PE according > + * to the parent EEH device. > + */ > +static struct eeh_pe *eeh_pe_get_parent(struct eeh_dev *edev) > +{ > + struct device_node *dn; > + struct eeh_dev *parent; > + > + /* > + * It might have the case for the indirect parent > + * EEH device already having associated PE, but > + * the direct parent EEH device doesn't have yet. > + */ > + dn = edev->dn->parent; > + while (dn) { > + /* We're poking out of PCI territory */ > + if (!PCI_DN(dn)) return NULL; > + > + parent = of_node_to_eeh_dev(dn); > + /* We're poking out of PCI territory */ > + if (!parent) return NULL; > + > + if (parent->pe) > + return parent->pe; > + > + dn = dn->parent; > + } > + > + return NULL; > +} > + > +/** > + * eeh_add_to_parent_pe - Add EEH device to parent PE > + * @edev: EEH device > + * > + * Add EEH device to the parent PE. If the parent PE already > + * exists, the PE type will be changed to EEH_PE_BUS. Otherwise, > + * we have to create new PE to hold the EEH device and the new > + * PE will be linked to its parent PE as well. > + */ > +int eeh_add_to_parent_pe(struct eeh_dev *edev) > +{ > + struct eeh_pe *pe, *parent; > + > + eeh_lock(); > + > + /* > + * Search the PE has been existing or not according > + * to the PE address. If that has been existing, the > + * PE should be composed of PCI bus and its subordinate > + * components. > + */ > + pe = eeh_pe_get(edev); > + if (pe && !(pe->type & EEH_PE_INVALID)) { > + if (!edev->pe_config_addr) { > + eeh_unlock(); > + pr_err("%s: PE with addr 0x%x already exists\n", > + __func__, edev->config_addr); > + return -EEXIST; > + } > + > + /* Mark the PE as type of PCI bus */ > + pe->type = EEH_PE_BUS; > + edev->pe = pe; > + > + /* Put the edev to PE */ > + list_add_tail(&edev->list, &pe->edevs); > + eeh_unlock(); > + pr_debug("EEH: Add %s to Bus PE#%x\n", > + edev->dn->full_name, pe->addr); > + > + return 0; > + } else if (pe && (pe->type & EEH_PE_INVALID)) { > + list_add_tail(&edev->list, &pe->edevs); > + edev->pe = pe; > + /* > + * We're running to here because of PCI hotplug caused by > + * EEH recovery. We need clear EEH_PE_INVALID until the top. > + */ > + parent = pe; > + while (parent) { > + if (!(parent->type & EEH_PE_INVALID)) > + break; > + parent->type &= ~EEH_PE_INVALID; > + parent = parent->parent; > + } > + eeh_unlock(); > + pr_debug("EEH: Add %s to Device PE#%x, Parent PE#%x\n", > + edev->dn->full_name, pe->addr, pe->parent->addr); > + > + return 0; > + } > + > + /* Create a new EEH PE */ > + pe = eeh_pe_alloc(edev->phb, EEH_PE_DEVICE); > + if (!pe) { > + eeh_unlock(); > + pr_err("%s: out of memory!\n", __func__); > + return -ENOMEM; > + } > + pe->addr = edev->pe_config_addr; > + pe->config_addr = edev->config_addr; > + > + /* > + * Put the new EEH PE into hierarchy tree. If the parent > + * can't be found, the newly created PE will be attached > + * to PHB directly. Otherwise, we have to associate the > + * PE with its parent. > + */ > + parent = eeh_pe_get_parent(edev); > + if (!parent) { > + parent = eeh_phb_pe_get(edev->phb); > + if (!parent) { > + eeh_unlock(); > + pr_err("%s: No PHB PE is found (PHB Domain=%d)\n", > + __func__, edev->phb->global_number); > + edev->pe = NULL; > + kfree(pe); > + return -EEXIST; > + } > + } > + pe->parent = parent; > + > + /* > + * Put the newly created PE into the child list and > + * link the EEH device accordingly. > + */ > + list_add_tail(&pe->child, &parent->child_list); > + list_add_tail(&edev->list, &pe->edevs); > + edev->pe = pe; > + eeh_unlock(); > + pr_debug("EEH: Add %s to Device PE#%x, Parent PE#%x\n", > + edev->dn->full_name, pe->addr, pe->parent->addr); > + > + return 0; > +} > + > +/** > + * eeh_rmv_from_parent_pe - Remove one EEH device from the associated PE > + * @edev: EEH device > + * @purge_pe: remove PE or not > + * > + * The PE hierarchy tree might be changed when doing PCI hotplug. > + * Also, the PCI devices or buses could be removed from the system > + * during EEH recovery. So we have to call the function remove the > + * corresponding PE accordingly if necessary. > + */ > +int eeh_rmv_from_parent_pe(struct eeh_dev *edev, int purge_pe) > +{ > + struct eeh_pe *pe, *parent, *child; > + int cnt; > + > + if (!edev->pe) { > + pr_warning("%s: No PE found for EEH device %s\n", > + __func__, edev->dn->full_name); > + return -EEXIST; > + } > + > + eeh_lock(); > + > + /* Remove the EEH device */ > + pe = edev->pe; > + edev->pe = NULL; > + list_del(&edev->list); > + > + /* > + * Check if the parent PE includes any EEH devices. > + * If not, we should delete that. Also, we should > + * delete the parent PE if it doesn't have associated > + * child PEs and EEH devices. > + */ > + while (1) { > + parent = pe->parent; > + if (pe->type & EEH_PE_PHB) > + break; > + > + if (purge_pe) { > + if (list_empty(&pe->edevs) && > + list_empty(&pe->child_list)) { > + list_del(&pe->child); > + kfree(pe); > + } else { > + break; > + } > + } else { > + if (list_empty(&pe->edevs)) { > + cnt = 0; > + list_for_each_entry(child, &pe->child_list, child) { > + if (!(child->type & EEH_PE_INVALID)) { > + cnt++; > + break; > + } > + } > + > + if (!cnt) > + pe->type |= EEH_PE_INVALID; > + else > + break; > + } > + } > + > + pe = parent; > + } > + > + eeh_unlock(); > + > + return 0; > +} > + > +/** > + * __eeh_pe_state_mark - Mark the state for the PE > + * @data: EEH PE > + * @flag: state > + * > + * The function is used to mark the indicated state for the given > + * PE. Also, the associated PCI devices will be put into IO frozen > + * state as well. > + */ > +static void *__eeh_pe_state_mark(void *data, void *flag) > +{ > + struct eeh_pe *pe = (struct eeh_pe *)data; > + int state = *((int *)flag); > + struct eeh_dev *tmp; > + struct pci_dev *pdev; > + > + /* > + * Mark the PE with the indicated state. Also, > + * the associated PCI device will be put into > + * I/O frozen state to avoid I/O accesses from > + * the PCI device driver. > + */ > + pe->state |= state; > + eeh_pe_for_each_dev(pe, tmp) { > + pdev = eeh_dev_to_pci_dev(tmp); > + if (pdev) > + pdev->error_state = pci_channel_io_frozen; > + } > + > + return NULL; > +} > + > +/** > + * eeh_pe_state_mark - Mark specified state for PE and its associated device > + * @pe: EEH PE > + * > + * EEH error affects the current PE and its child PEs. The function > + * is used to mark appropriate state for the affected PEs and the > + * associated devices. > + */ > +void eeh_pe_state_mark(struct eeh_pe *pe, int state) > +{ > + eeh_lock(); > + eeh_pe_traverse(pe, __eeh_pe_state_mark, &state); > + eeh_unlock(); > +} > + > +/** > + * __eeh_pe_state_clear - Clear state for the PE > + * @data: EEH PE > + * @flag: state > + * > + * The function is used to clear the indicated state from the > + * given PE. Besides, we also clear the check count of the PE > + * as well. > + */ > +static void *__eeh_pe_state_clear(void *data, void *flag) > +{ > + struct eeh_pe *pe = (struct eeh_pe *)data; > + int state = *((int *)flag); > + > + pe->state &= ~state; > + pe->check_count = 0; > + > + return NULL; > +} > + > +/** > + * eeh_pe_state_clear - Clear state for the PE and its children > + * @pe: PE > + * @state: state to be cleared > + * > + * When the PE and its children has been recovered from error, > + * we need clear the error state for that. The function is used > + * for the purpose. > + */ > +void eeh_pe_state_clear(struct eeh_pe *pe, int state) > +{ > + eeh_lock(); > + eeh_pe_traverse(pe, __eeh_pe_state_clear, &state); > + eeh_unlock(); > +} > + > +/** > + * eeh_restore_one_device_bars - Restore the Base Address Registers for one device > + * @data: EEH device > + * @flag: Unused > + * > + * 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 void *eeh_restore_one_device_bars(void *data, void *flag) > +{ > + int i; > + u32 cmd; > + struct eeh_dev *edev = (struct eeh_dev *)data; > + struct device_node *dn = eeh_dev_to_of_node(edev); > + > + for (i = 4; i < 10; i++) > + eeh_ops->write_config(dn, i*4, 4, edev->config_space[i]); > + /* 12 == Expansion ROM Address */ > + eeh_ops->write_config(dn, 12*4, 4, edev->config_space[12]); > + > +#define BYTE_SWAP(OFF) (8*((OFF)/4)+3-(OFF)) > +#define SAVED_BYTE(OFF) (((u8 *)(edev->config_space))[BYTE_SWAP(OFF)]) > + > + eeh_ops->write_config(dn, PCI_CACHE_LINE_SIZE, 1, > + SAVED_BYTE(PCI_CACHE_LINE_SIZE)); > + eeh_ops->write_config(dn, PCI_LATENCY_TIMER, 1, > + SAVED_BYTE(PCI_LATENCY_TIMER)); > + > + /* max latency, min grant, interrupt pin and line */ > + eeh_ops->write_config(dn, 15*4, 4, edev->config_space[15]); > + > + /* > + * Restore PERR & SERR bits, some devices require it, > + * don't touch the other command bits > + */ > + eeh_ops->read_config(dn, PCI_COMMAND, 4, &cmd); > + if (edev->config_space[1] & PCI_COMMAND_PARITY) > + cmd |= PCI_COMMAND_PARITY; > + else > + cmd &= ~PCI_COMMAND_PARITY; > + if (edev->config_space[1] & PCI_COMMAND_SERR) > + cmd |= PCI_COMMAND_SERR; > + else > + cmd &= ~PCI_COMMAND_SERR; > + eeh_ops->write_config(dn, PCI_COMMAND, 4, cmd); > + > + return NULL; > +} > + > +/** > + * eeh_pe_restore_bars - Restore the PCI config space info > + * @pe: EEH PE > + * > + * This routine performs a recursive walk to the children > + * of this device as well. > + */ > +void eeh_pe_restore_bars(struct eeh_pe *pe) > +{ > + /* > + * We needn't take the EEH lock since eeh_pe_dev_traverse() > + * will take that. > + */ > + eeh_pe_dev_traverse(pe, eeh_restore_one_device_bars, NULL); > +} > + > +/** > + * eeh_pe_bus_get - Retrieve PCI bus according to the given PE > + * @pe: EEH PE > + * > + * Retrieve the PCI bus according to the given PE. Basically, > + * there're 3 types of PEs: PHB/Bus/Device. For PHB PE, the > + * primary PCI bus will be retrieved. The parent bus will be > + * returned for BUS PE. However, we don't have associated PCI > + * bus for DEVICE PE. > + */ > +struct pci_bus *eeh_pe_bus_get(struct eeh_pe *pe) > +{ > + struct pci_bus *bus = NULL; > + struct eeh_dev *edev; > + struct pci_dev *pdev; > + > + eeh_lock(); > + > + if (pe->type & EEH_PE_PHB) { > + bus = pe->phb->bus; > + } else if (pe->type & EEH_PE_BUS || > + pe->type & EEH_PE_DEVICE) { > + edev = list_first_entry(&pe->edevs, struct eeh_dev, list); > + pdev = eeh_dev_to_pci_dev(edev); > + if (pdev) > + bus = pdev->bus; > + } > + > + eeh_unlock(); > + > + return bus; > +} > diff --git a/arch/powerpc/kernel/eeh_sysfs.c b/arch/powerpc/kernel/eeh_sysfs.c > new file mode 100644 > index 0000000..d377083 > --- /dev/null > +++ b/arch/powerpc/kernel/eeh_sysfs.c > @@ -0,0 +1,75 @@ > +/* > + * Sysfs entries for PCI Error Recovery for PAPR-compliant platform. > + * Copyright IBM Corporation 2007 > + * Copyright Linas Vepstas <linas@austin.ibm.com> 2007 > + * > + * All rights reserved. > + * > + * 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 > + * the Free Software Foundation; either version 2 of the License, or (at > + * your option) any later version. > + * > + * This program is distributed in the hope that it will be useful, but > + * WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or > + * NON INFRINGEMENT. See the GNU General Public License for more > + * details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, write to the Free Software > + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. > + * > + * Send comments and feedback to Linas Vepstas <linas@austin.ibm.com> > + */ > +#include <linux/pci.h> > +#include <linux/stat.h> > +#include <asm/ppc-pci.h> > +#include <asm/pci-bridge.h> > + > +/** > + * EEH_SHOW_ATTR -- Create sysfs entry for eeh statistic > + * @_name: name of file in sysfs directory > + * @_memb: name of member in struct pci_dn to access > + * @_format: printf format for display > + * > + * All of the attributes look very similar, so just > + * auto-gen a cut-n-paste routine to display them. > + */ > +#define EEH_SHOW_ATTR(_name,_memb,_format) \ > +static ssize_t eeh_show_##_name(struct device *dev, \ > + struct device_attribute *attr, char *buf) \ > +{ \ > + struct pci_dev *pdev = to_pci_dev(dev); \ > + struct eeh_dev *edev = pci_dev_to_eeh_dev(pdev); \ > + \ > + if (!edev) \ > + return 0; \ > + \ > + return sprintf(buf, _format "\n", edev->_memb); \ > +} \ > +static DEVICE_ATTR(_name, S_IRUGO, eeh_show_##_name, NULL); > + > +EEH_SHOW_ATTR(eeh_mode, mode, "0x%x"); > +EEH_SHOW_ATTR(eeh_config_addr, config_addr, "0x%x"); > +EEH_SHOW_ATTR(eeh_pe_config_addr, pe_config_addr, "0x%x"); > + > +void eeh_sysfs_add_device(struct pci_dev *pdev) > +{ > + int rc=0; > + > + rc += device_create_file(&pdev->dev, &dev_attr_eeh_mode); > + rc += device_create_file(&pdev->dev, &dev_attr_eeh_config_addr); > + rc += device_create_file(&pdev->dev, &dev_attr_eeh_pe_config_addr); > + > + if (rc) > + printk(KERN_WARNING "EEH: Unable to create sysfs entries\n"); > +} > + > +void eeh_sysfs_remove_device(struct pci_dev *pdev) > +{ > + device_remove_file(&pdev->dev, &dev_attr_eeh_mode); > + device_remove_file(&pdev->dev, &dev_attr_eeh_config_addr); > + device_remove_file(&pdev->dev, &dev_attr_eeh_pe_config_addr); > +} > + > diff --git a/arch/powerpc/kernel/pci_hotplug.c b/arch/powerpc/kernel/pci_hotplug.c > new file mode 100644 > index 0000000..3f60880 > --- /dev/null > +++ b/arch/powerpc/kernel/pci_hotplug.c > @@ -0,0 +1,111 @@ > +/* > + * Derived from "arch/powerpc/platforms/pseries/pci_dlpar.c" > + * > + * Copyright (C) 2003 Linda Xie <lxie@us.ibm.com> > + * Copyright (C) 2005 International Business Machines > + * > + * Updates, 2005, John Rose <johnrose@austin.ibm.com> > + * Updates, 2005, Linas Vepstas <linas@austin.ibm.com> > + * Updates, 2013, Gavin Shan <shangw@linux.vnet.ibm.com> > + * > + * 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 > + * the Free Software Foundation; either version 2 of the License, or > + * (at your option) any later version. > + */ > + > +#include <linux/pci.h> > +#include <linux/export.h> > +#include <asm/pci-bridge.h> > +#include <asm/ppc-pci.h> > +#include <asm/firmware.h> > +#include <asm/eeh.h> > + > +/** > + * __pcibios_remove_pci_devices - remove all devices under this bus > + * @bus: the indicated PCI bus > + * @purge_pe: destroy the PE on removal of PCI devices > + * > + * Remove all of the PCI devices under this bus both from the > + * linux pci device tree, and from the powerpc EEH address cache. > + * By default, the corresponding PE will be destroied during the > + * normal PCI hotplug path. For PCI hotplug during EEH recovery, > + * the corresponding PE won't be destroied and deallocated. > + */ > +void __pcibios_remove_pci_devices(struct pci_bus *bus, int purge_pe) > +{ > + struct pci_dev *dev, *tmp; > + struct pci_bus *child_bus; > + > + /* First go down child busses */ > + list_for_each_entry(child_bus, &bus->children, node) > + __pcibios_remove_pci_devices(child_bus, purge_pe); > + > + pr_debug("PCI: Removing devices on bus %04x:%02x\n", > + pci_domain_nr(bus), bus->number); > + list_for_each_entry_safe(dev, tmp, &bus->devices, bus_list) { > + pr_debug(" * Removing %s...\n", pci_name(dev)); > + eeh_remove_bus_device(dev, purge_pe); > + pci_stop_and_remove_bus_device(dev); > + } > +} > + > +/** > + * pcibios_remove_pci_devices - remove all devices under this bus > + * @bus: the indicated PCI bus > + * > + * Remove all of the PCI devices under this bus both from the > + * linux pci device tree, and from the powerpc EEH address cache. > + */ > +void pcibios_remove_pci_devices(struct pci_bus *bus) > +{ > + __pcibios_remove_pci_devices(bus, 1); > +} > +EXPORT_SYMBOL_GPL(pcibios_remove_pci_devices); > + > +/** > + * pcibios_add_pci_devices - adds new pci devices to bus > + * @bus: the indicated PCI bus > + * > + * This routine will find and fixup new pci devices under > + * the indicated bus. This routine presumes that there > + * might already be some devices under this bridge, so > + * it carefully tries to add only new devices. (And that > + * is how this routine differs from other, similar pcibios > + * routines.) > + */ > +void pcibios_add_pci_devices(struct pci_bus * bus) > +{ > + int slotno, num, mode, pass, max; > + struct pci_dev *dev; > + struct device_node *dn = pci_bus_to_OF_node(bus); > + > + eeh_add_device_tree_early(dn); > + > + mode = PCI_PROBE_NORMAL; > + if (ppc_md.pci_probe_mode) > + mode = ppc_md.pci_probe_mode(bus); > + > + if (mode == PCI_PROBE_DEVTREE) { > + /* use ofdt-based probe */ > + of_rescan_bus(dn, bus); > + } else if (mode == PCI_PROBE_NORMAL) { > + /* use legacy probe */ > + slotno = PCI_SLOT(PCI_DN(dn->child)->devfn); > + num = pci_scan_slot(bus, PCI_DEVFN(slotno, 0)); > + if (!num) > + return; > + pcibios_setup_bus_devices(bus); > + max = bus->busn_res.start; > + for (pass = 0; pass < 2; pass++) { > + list_for_each_entry(dev, &bus->devices, bus_list) { > + if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE || > + dev->hdr_type == PCI_HEADER_TYPE_CARDBUS) > + max = pci_scan_bridge(bus, dev, > + max, pass); > + } > + } > + } > + pcibios_finish_adding_to_bus(bus); > +} > +EXPORT_SYMBOL_GPL(pcibios_add_pci_devices); > diff --git a/arch/powerpc/platforms/Kconfig b/arch/powerpc/platforms/Kconfig > index b62aab3..bed8c60 100644 > --- a/arch/powerpc/platforms/Kconfig > +++ b/arch/powerpc/platforms/Kconfig > @@ -164,6 +164,11 @@ config IBMEBUS > help > Bus device driver for GX bus based adapters. > > +config EEH > + bool > + depends on (PPC_POWERNV || PPC_PSERIES) && PCI > + default y > + > config PPC_MPC106 > bool > default n > diff --git a/arch/powerpc/platforms/pseries/Kconfig b/arch/powerpc/platforms/pseries/Kconfig > index 4459eff..1bd3399 100644 > --- a/arch/powerpc/platforms/pseries/Kconfig > +++ b/arch/powerpc/platforms/pseries/Kconfig > @@ -33,11 +33,6 @@ config PPC_SPLPAR > processors, that is, which share physical processors between > two or more partitions. > > -config EEH > - bool > - depends on PPC_PSERIES && PCI > - default y > - > config PSERIES_MSI > bool > depends on PCI_MSI && EEH > diff --git a/arch/powerpc/platforms/pseries/Makefile b/arch/powerpc/platforms/pseries/Makefile > index 53866e5..8ae0103 100644 > --- a/arch/powerpc/platforms/pseries/Makefile > +++ b/arch/powerpc/platforms/pseries/Makefile > @@ -6,9 +6,7 @@ obj-y := lpar.o hvCall.o nvram.o reconfig.o \ > firmware.o power.o dlpar.o mobility.o > obj-$(CONFIG_SMP) += smp.o > obj-$(CONFIG_SCANLOG) += scanlog.o > -obj-$(CONFIG_EEH) += eeh.o eeh_pe.o eeh_dev.o eeh_cache.o \ > - eeh_driver.o eeh_event.o eeh_sysfs.o \ > - eeh_pseries.o > +obj-$(CONFIG_EEH) += eeh_pseries.o > obj-$(CONFIG_KEXEC) += kexec.o > obj-$(CONFIG_PCI) += pci.o pci_dlpar.o > obj-$(CONFIG_PSERIES_MSI) += msi.o > diff --git a/arch/powerpc/platforms/pseries/eeh.c b/arch/powerpc/platforms/pseries/eeh.c > deleted file mode 100644 > index 6b73d6c..0000000 > --- a/arch/powerpc/platforms/pseries/eeh.c > +++ /dev/null > @@ -1,942 +0,0 @@ > -/* > - * Copyright IBM Corporation 2001, 2005, 2006 > - * Copyright Dave Engebretsen & Todd Inglett 2001 > - * Copyright Linas Vepstas 2005, 2006 > - * Copyright 2001-2012 IBM Corporation. > - * > - * 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 > - * the Free Software Foundation; either version 2 of the License, or > - * (at your option) any later version. > - * > - * This program is distributed in the hope that it will be useful, > - * but WITHOUT ANY WARRANTY; without even the implied warranty of > - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > - * GNU General Public License for more details. > - * > - * You should have received a copy of the GNU General Public License > - * along with this program; if not, write to the Free Software > - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA > - * > - * Please address comments and feedback to Linas Vepstas <linas@austin.ibm.com> > - */ > - > -#include <linux/delay.h> > -#include <linux/sched.h> > -#include <linux/init.h> > -#include <linux/list.h> > -#include <linux/pci.h> > -#include <linux/proc_fs.h> > -#include <linux/rbtree.h> > -#include <linux/seq_file.h> > -#include <linux/spinlock.h> > -#include <linux/export.h> > -#include <linux/of.h> > - > -#include <linux/atomic.h> > -#include <asm/eeh.h> > -#include <asm/eeh_event.h> > -#include <asm/io.h> > -#include <asm/machdep.h> > -#include <asm/ppc-pci.h> > -#include <asm/rtas.h> > - > - > -/** Overview: > - * EEH, or "Extended Error Handling" is a PCI bridge technology for > - * dealing with PCI bus errors that can't be dealt with within the > - * usual PCI framework, except by check-stopping the CPU. Systems > - * that are designed for high-availability/reliability cannot afford > - * to crash due to a "mere" PCI error, thus the need for EEH. > - * An EEH-capable bridge operates by converting a detected error > - * into a "slot freeze", taking the PCI adapter off-line, making > - * the slot behave, from the OS'es point of view, as if the slot > - * were "empty": all reads return 0xff's and all writes are silently > - * ignored. EEH slot isolation events can be triggered by parity > - * errors on the address or data busses (e.g. during posted writes), > - * 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 > - * freeze events are buggy device drivers, buggy device microcode, > - * or buggy device hardware. This is because any attempt by the > - * device to bus-master data to a memory address that is not > - * assigned to the device will trigger a slot freeze. (The idea > - * is to prevent devices-gone-wild from corrupting system memory). > - * Buggy hardware/drivers will have a miserable time co-existing > - * with EEH. > - * > - * Ideally, a PCI device driver, when suspecting that an isolation > - * event has occurred (e.g. by reading 0xff's), will then ask EEH > - * whether this is the case, and then take appropriate steps to > - * reset the PCI slot, the PCI device, and then resume operations. > - * However, until that day, the checking is done here, with the > - * eeh_check_failure() routine embedded in the MMIO macros. If > - * the slot is found to be isolated, an "EEH Event" is synthesized > - * and sent out for processing. > - */ > - > -/* If a device driver keeps reading an MMIO register in an interrupt > - * handler after a slot isolation event, it might be broken. > - * This sets the threshold for how many read attempts we allow > - * before printing an error message. > - */ > -#define EEH_MAX_FAILS 2100000 > - > -/* Time to wait for a PCI slot to report status, in milliseconds */ > -#define PCI_BUS_RESET_WAIT_MSEC (60*1000) > - > -/* Platform dependent EEH operations */ > -struct eeh_ops *eeh_ops = NULL; > - > -int eeh_subsystem_enabled; > -EXPORT_SYMBOL(eeh_subsystem_enabled); > - > -/* > - * EEH probe mode support. The intention is to support multiple > - * platforms for EEH. Some platforms like pSeries do PCI emunation > - * based on device tree. However, other platforms like powernv probe > - * PCI devices from hardware. The flag is used to distinguish that. > - * In addition, struct eeh_ops::probe would be invoked for particular > - * OF node or PCI device so that the corresponding PE would be created > - * there. > - */ > -int eeh_probe_mode; > - > -/* Global EEH mutex */ > -DEFINE_MUTEX(eeh_mutex); > - > -/* Lock to avoid races due to multiple reports of an error */ > -static DEFINE_RAW_SPINLOCK(confirm_error_lock); > - > -/* Buffer for reporting pci register dumps. Its here in BSS, and > - * not dynamically alloced, so that it ends up in RMO where RTAS > - * can access it. > - */ > -#define EEH_PCI_REGS_LOG_LEN 4096 > -static unsigned char pci_regs_buf[EEH_PCI_REGS_LOG_LEN]; > - > -/* > - * The struct is used to maintain the EEH global statistic > - * information. Besides, the EEH global statistics will be > - * exported to user space through procfs > - */ > -struct eeh_stats { > - u64 no_device; /* PCI device not found */ > - u64 no_dn; /* OF node not found */ > - u64 no_cfg_addr; /* Config address not found */ > - u64 ignored_check; /* EEH check skipped */ > - u64 total_mmio_ffs; /* Total EEH checks */ > - u64 false_positives; /* Unnecessary EEH checks */ > - u64 slot_resets; /* PE reset */ > -}; > - > -static struct eeh_stats eeh_stats; > - > -#define IS_BRIDGE(class_code) (((class_code)<<16) == PCI_BASE_CLASS_BRIDGE) > - > -/** > - * eeh_gather_pci_data - Copy assorted PCI config space registers to buff > - * @edev: device to report data for > - * @buf: point to buffer in which to log > - * @len: amount of room in buffer > - * > - * This routine captures assorted PCI configuration space data, > - * and puts them into a buffer for RTAS error logging. > - */ > -static size_t eeh_gather_pci_data(struct eeh_dev *edev, char * buf, size_t len) > -{ > - struct device_node *dn = eeh_dev_to_of_node(edev); > - struct pci_dev *dev = eeh_dev_to_pci_dev(edev); > - u32 cfg; > - int cap, i; > - int n = 0; > - > - n += scnprintf(buf+n, len-n, "%s\n", dn->full_name); > - printk(KERN_WARNING "EEH: of node=%s\n", dn->full_name); > - > - eeh_ops->read_config(dn, PCI_VENDOR_ID, 4, &cfg); > - n += scnprintf(buf+n, len-n, "dev/vend:%08x\n", cfg); > - printk(KERN_WARNING "EEH: PCI device/vendor: %08x\n", cfg); > - > - eeh_ops->read_config(dn, PCI_COMMAND, 4, &cfg); > - n += scnprintf(buf+n, len-n, "cmd/stat:%x\n", cfg); > - printk(KERN_WARNING "EEH: PCI cmd/status register: %08x\n", cfg); > - > - if (!dev) { > - printk(KERN_WARNING "EEH: no PCI device for this of node\n"); > - return n; > - } > - > - /* Gather bridge-specific registers */ > - if (dev->class >> 16 == PCI_BASE_CLASS_BRIDGE) { > - eeh_ops->read_config(dn, PCI_SEC_STATUS, 2, &cfg); > - n += scnprintf(buf+n, len-n, "sec stat:%x\n", cfg); > - printk(KERN_WARNING "EEH: Bridge secondary status: %04x\n", cfg); > - > - eeh_ops->read_config(dn, PCI_BRIDGE_CONTROL, 2, &cfg); > - n += scnprintf(buf+n, len-n, "brdg ctl:%x\n", cfg); > - printk(KERN_WARNING "EEH: Bridge control: %04x\n", cfg); > - } > - > - /* Dump out the PCI-X command and status regs */ > - cap = pci_find_capability(dev, PCI_CAP_ID_PCIX); > - if (cap) { > - eeh_ops->read_config(dn, cap, 4, &cfg); > - n += scnprintf(buf+n, len-n, "pcix-cmd:%x\n", cfg); > - printk(KERN_WARNING "EEH: PCI-X cmd: %08x\n", cfg); > - > - eeh_ops->read_config(dn, cap+4, 4, &cfg); > - n += scnprintf(buf+n, len-n, "pcix-stat:%x\n", cfg); > - printk(KERN_WARNING "EEH: PCI-X status: %08x\n", cfg); > - } > - > - /* If PCI-E capable, dump PCI-E cap 10, and the AER */ > - cap = pci_find_capability(dev, PCI_CAP_ID_EXP); > - if (cap) { > - n += scnprintf(buf+n, len-n, "pci-e cap10:\n"); > - printk(KERN_WARNING > - "EEH: PCI-E capabilities and status follow:\n"); > - > - for (i=0; i<=8; i++) { > - eeh_ops->read_config(dn, cap+4*i, 4, &cfg); > - n += scnprintf(buf+n, len-n, "%02x:%x\n", 4*i, cfg); > - printk(KERN_WARNING "EEH: PCI-E %02x: %08x\n", i, cfg); > - } > - > - cap = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ERR); > - if (cap) { > - n += scnprintf(buf+n, len-n, "pci-e AER:\n"); > - printk(KERN_WARNING > - "EEH: PCI-E AER capability register set follows:\n"); > - > - for (i=0; i<14; i++) { > - eeh_ops->read_config(dn, cap+4*i, 4, &cfg); > - n += scnprintf(buf+n, len-n, "%02x:%x\n", 4*i, cfg); > - printk(KERN_WARNING "EEH: PCI-E AER %02x: %08x\n", i, cfg); > - } > - } > - } > - > - return n; > -} > - > -/** > - * eeh_slot_error_detail - Generate combined log including driver log and error log > - * @pe: EEH PE > - * @severity: temporary or permanent error log > - * > - * This routine should be called to generate the combined log, which > - * is comprised of driver log and error log. The driver log is figured > - * out from the config space of the corresponding PCI device, while > - * the error log is fetched through platform dependent function call. > - */ > -void eeh_slot_error_detail(struct eeh_pe *pe, int severity) > -{ > - size_t loglen = 0; > - struct eeh_dev *edev; > - > - eeh_pci_enable(pe, EEH_OPT_THAW_MMIO); > - eeh_ops->configure_bridge(pe); > - eeh_pe_restore_bars(pe); > - > - pci_regs_buf[0] = 0; > - eeh_pe_for_each_dev(pe, edev) { > - loglen += eeh_gather_pci_data(edev, pci_regs_buf, > - EEH_PCI_REGS_LOG_LEN); > - } > - > - eeh_ops->get_log(pe, severity, pci_regs_buf, loglen); > -} > - > -/** > - * eeh_token_to_phys - Convert EEH address token to phys address > - * @token: I/O token, should be address in the form 0xA.... > - * > - * This routine should be called to convert virtual I/O address > - * to physical one. > - */ > -static inline unsigned long eeh_token_to_phys(unsigned long token) > -{ > - 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_dev_check_failure - Check if all 1's data is due to EEH slot freeze > - * @edev: eeh device > - * > - * Check for an EEH failure for the given device node. Call this > - * 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. This routine > - * will query firmware for the EEH status. > - * > - * Returns 0 if there has not been an EEH error; otherwise returns > - * a non-zero value and queues up a slot isolation event notification. > - * > - * It is safe to call this routine in an interrupt context. > - */ > -int eeh_dev_check_failure(struct eeh_dev *edev) > -{ > - int ret; > - unsigned long flags; > - struct device_node *dn; > - struct pci_dev *dev; > - struct eeh_pe *pe; > - int rc = 0; > - const char *location; > - > - eeh_stats.total_mmio_ffs++; > - > - if (!eeh_subsystem_enabled) > - return 0; > - > - if (!edev) { > - eeh_stats.no_dn++; > - return 0; > - } > - dn = eeh_dev_to_of_node(edev); > - dev = eeh_dev_to_pci_dev(edev); > - pe = edev->pe; > - > - /* Access to IO BARs might get this far and still not want checking. */ > - if (!pe) { > - eeh_stats.ignored_check++; > - pr_debug("EEH: Ignored check for %s %s\n", > - eeh_pci_name(dev), dn->full_name); > - return 0; > - } > - > - if (!pe->addr && !pe->config_addr) { > - eeh_stats.no_cfg_addr++; > - 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. > - * 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. > - */ > - raw_spin_lock_irqsave(&confirm_error_lock, flags); > - rc = 1; > - if (pe->state & EEH_PE_ISOLATED) { > - pe->check_count++; > - if (pe->check_count % EEH_MAX_FAILS == 0) { > - location = of_get_property(dn, "ibm,loc-code", NULL); > - printk(KERN_ERR "EEH: %d reads ignored for recovering device at " > - "location=%s driver=%s pci addr=%s\n", > - pe->check_count, location, > - eeh_driver_name(dev), eeh_pci_name(dev)); > - printk(KERN_ERR "EEH: Might be infinite loop in %s driver\n", > - eeh_driver_name(dev)); > - dump_stack(); > - } > - goto dn_unlock; > - } > - > - /* > - * Now test for an EEH failure. This is VERY expensive. > - * Note that the eeh_config_addr may be a parent device > - * in the case of a device behind a bridge, or it may be > - * function zero of a multi-function device. > - * In any case they must share a common PHB. > - */ > - ret = eeh_ops->get_state(pe, NULL); > - > - /* Note that config-io to empty slots may fail; > - * they are empty when they don't have children. > - * We will punt with the following conditions: Failure to get > - * PE's state, EEH not support and Permanently unavailable > - * state, PE is in good state. > - */ > - if ((ret < 0) || > - (ret == EEH_STATE_NOT_SUPPORT) || > - (ret & (EEH_STATE_MMIO_ACTIVE | EEH_STATE_DMA_ACTIVE)) == > - (EEH_STATE_MMIO_ACTIVE | EEH_STATE_DMA_ACTIVE)) { > - eeh_stats.false_positives++; > - pe->false_positives++; > - rc = 0; > - goto dn_unlock; > - } > - > - eeh_stats.slot_resets++; > - > - /* Avoid repeated reports of this failure, including problems > - * with other functions on this device, and functions under > - * bridges. > - */ > - eeh_pe_state_mark(pe, EEH_PE_ISOLATED); > - raw_spin_unlock_irqrestore(&confirm_error_lock, flags); > - > - eeh_send_failure_event(pe); > - > - /* Most EEH events are due to device driver bugs. Having > - * a stack trace will help the device-driver authors figure > - * out what happened. So print that out. > - */ > - WARN(1, "EEH: failure detected\n"); > - return 1; > - > -dn_unlock: > - raw_spin_unlock_irqrestore(&confirm_error_lock, flags); > - return rc; > -} > - > -EXPORT_SYMBOL_GPL(eeh_dev_check_failure); > - > -/** > - * 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.... > - * @val: value, should be all 1's (XXX why do we need this arg??) > - * > - * 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 > - * find out if this is due to an EEH slot freeze event. This routine > - * will query firmware for the EEH status. > - * > - * Note this routine is safe to call in an interrupt context. > - */ > -unsigned long eeh_check_failure(const volatile void __iomem *token, unsigned long val) > -{ > - unsigned long addr; > - struct eeh_dev *edev; > - > - /* Finding the phys addr + pci device; this is pretty quick. */ > - addr = eeh_token_to_phys((unsigned long __force) token); > - edev = eeh_addr_cache_get_dev(addr); > - if (!edev) { > - eeh_stats.no_device++; > - return val; > - } > - > - eeh_dev_check_failure(edev); > - > - pci_dev_put(eeh_dev_to_pci_dev(edev)); > - return val; > -} > - > -EXPORT_SYMBOL(eeh_check_failure); > - > - > -/** > - * eeh_pci_enable - Enable MMIO or DMA transfers for this slot > - * @pe: EEH PE > - * > - * This routine should be called to reenable frozen MMIO or DMA > - * so that it would work correctly again. It's useful while doing > - * recovery or log collection on the indicated device. > - */ > -int eeh_pci_enable(struct eeh_pe *pe, int function) > -{ > - int rc; > - > - rc = eeh_ops->set_option(pe, function); > - if (rc) > - pr_warning("%s: Unexpected state change %d on PHB#%d-PE#%x, err=%d\n", > - __func__, function, pe->phb->global_number, pe->addr, rc); > - > - rc = eeh_ops->wait_state(pe, PCI_BUS_RESET_WAIT_MSEC); > - if (rc > 0 && (rc & EEH_STATE_MMIO_ENABLED) && > - (function == EEH_OPT_THAW_MMIO)) > - return 0; > - > - return rc; > -} > - > -/** > - * pcibios_set_pcie_slot_reset - Set PCI-E reset state > - * @dev: pci device struct > - * @state: reset state to enter > - * > - * Return value: > - * 0 if success > - */ > -int pcibios_set_pcie_reset_state(struct pci_dev *dev, enum pcie_reset_state state) > -{ > - struct eeh_dev *edev = pci_dev_to_eeh_dev(dev); > - struct eeh_pe *pe = edev->pe; > - > - if (!pe) { > - pr_err("%s: No PE found on PCI device %s\n", > - __func__, pci_name(dev)); > - return -EINVAL; > - } > - > - switch (state) { > - case pcie_deassert_reset: > - eeh_ops->reset(pe, EEH_RESET_DEACTIVATE); > - break; > - case pcie_hot_reset: > - eeh_ops->reset(pe, EEH_RESET_HOT); > - break; > - case pcie_warm_reset: > - eeh_ops->reset(pe, EEH_RESET_FUNDAMENTAL); > - break; > - default: > - return -EINVAL; > - }; > - > - return 0; > -} > - > -/** > - * eeh_set_pe_freset - Check the required reset for the indicated device > - * @data: EEH device > - * @flag: return value > - * > - * Each device might have its preferred reset type: fundamental or > - * hot reset. The routine is used to collected the information for > - * the indicated device and its children so that the bunch of the > - * devices could be reset properly. > - */ > -static void *eeh_set_dev_freset(void *data, void *flag) > -{ > - struct pci_dev *dev; > - unsigned int *freset = (unsigned int *)flag; > - struct eeh_dev *edev = (struct eeh_dev *)data; > - > - dev = eeh_dev_to_pci_dev(edev); > - if (dev) > - *freset |= dev->needs_freset; > - > - return NULL; > -} > - > -/** > - * eeh_reset_pe_once - Assert the pci #RST line for 1/4 second > - * @pe: EEH PE > - * > - * Assert the PCI #RST line for 1/4 second. > - */ > -static void eeh_reset_pe_once(struct eeh_pe *pe) > -{ > - unsigned int freset = 0; > - > - /* Determine type of EEH reset required for > - * Partitionable Endpoint, a hot-reset (1) > - * or a fundamental reset (3). > - * A fundamental reset required by any device under > - * Partitionable Endpoint trumps hot-reset. > - */ > - eeh_pe_dev_traverse(pe, eeh_set_dev_freset, &freset); > - > - if (freset) > - eeh_ops->reset(pe, EEH_RESET_FUNDAMENTAL); > - else > - eeh_ops->reset(pe, EEH_RESET_HOT); > - > - /* 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_pe_state_clear(pe, EEH_PE_ISOLATED); > - > - eeh_ops->reset(pe, EEH_RESET_DEACTIVATE); > - > - /* 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); > -} > - > -/** > - * eeh_reset_pe - Reset the indicated PE > - * @pe: EEH PE > - * > - * This routine should be called to reset indicated device, including > - * PE. A PE might include multiple PCI devices and sometimes PCI bridges > - * might be involved as well. > - */ > -int eeh_reset_pe(struct eeh_pe *pe) > -{ > - int i, rc; > - > - /* Take three shots at resetting the bus */ > - for (i=0; i<3; i++) { > - eeh_reset_pe_once(pe); > - > - rc = eeh_ops->wait_state(pe, PCI_BUS_RESET_WAIT_MSEC); > - if (rc == (EEH_STATE_MMIO_ACTIVE | EEH_STATE_DMA_ACTIVE)) > - return 0; > - > - if (rc < 0) { > - pr_err("%s: Unrecoverable slot failure on PHB#%d-PE#%x", > - __func__, pe->phb->global_number, pe->addr); > - return -1; > - } > - pr_err("EEH: bus reset %d failed on PHB#%d-PE#%x, rc=%d\n", > - i+1, pe->phb->global_number, pe->addr, rc); > - } > - > - return -1; > -} > - > -/** > - * eeh_save_bars - Save device bars > - * @edev: PCI device associated EEH device > - * > - * Save the values of the device bars. Unlike the restore > - * routine, this routine is *not* recursive. This is because > - * PCI devices are added individually; but, for the restore, > - * an entire slot is reset at a time. > - */ > -void eeh_save_bars(struct eeh_dev *edev) > -{ > - int i; > - struct device_node *dn; > - > - if (!edev) > - return; > - dn = eeh_dev_to_of_node(edev); > - > - for (i = 0; i < 16; i++) > - eeh_ops->read_config(dn, i * 4, 4, &edev->config_space[i]); > -} > - > -/** > - * eeh_ops_register - Register platform dependent EEH operations > - * @ops: platform dependent EEH operations > - * > - * Register the platform dependent EEH operation callback > - * functions. The platform should call this function before > - * any other EEH operations. > - */ > -int __init eeh_ops_register(struct eeh_ops *ops) > -{ > - if (!ops->name) { > - pr_warning("%s: Invalid EEH ops name for %p\n", > - __func__, ops); > - return -EINVAL; > - } > - > - if (eeh_ops && eeh_ops != ops) { > - pr_warning("%s: EEH ops of platform %s already existing (%s)\n", > - __func__, eeh_ops->name, ops->name); > - return -EEXIST; > - } > - > - eeh_ops = ops; > - > - return 0; > -} > - > -/** > - * eeh_ops_unregister - Unreigster platform dependent EEH operations > - * @name: name of EEH platform operations > - * > - * Unregister the platform dependent EEH operation callback > - * functions. > - */ > -int __exit eeh_ops_unregister(const char *name) > -{ > - if (!name || !strlen(name)) { > - pr_warning("%s: Invalid EEH ops name\n", > - __func__); > - return -EINVAL; > - } > - > - if (eeh_ops && !strcmp(eeh_ops->name, name)) { > - eeh_ops = NULL; > - return 0; > - } > - > - return -EEXIST; > -} > - > -/** > - * eeh_init - EEH initialization > - * > - * Initialize EEH by trying to enable it for all of the adapters in the system. > - * As a side effect we can determine here if eeh is supported at all. > - * Note that we leave EEH on so failed config cycles won't cause a machine > - * check. If a user turns off EEH for a particular adapter they are really > - * telling Linux to ignore errors. Some hardware (e.g. POWER5) won't > - * grant access to a slot if EEH isn't enabled, and so we always enable > - * EEH for all slots/all devices. > - * > - * The eeh-force-off option disables EEH checking globally, for all slots. > - * Even if force-off is set, the EEH hardware is still enabled, so that > - * newer systems can boot. > - */ > -static int __init eeh_init(void) > -{ > - struct pci_controller *hose, *tmp; > - struct device_node *phb; > - int ret; > - > - /* call platform initialization function */ > - if (!eeh_ops) { > - pr_warning("%s: Platform EEH operation not found\n", > - __func__); > - return -EEXIST; > - } else if ((ret = eeh_ops->init())) { > - pr_warning("%s: Failed to call platform init function (%d)\n", > - __func__, ret); > - return ret; > - } > - > - raw_spin_lock_init(&confirm_error_lock); > - > - /* Enable EEH for all adapters */ > - if (eeh_probe_mode_devtree()) { > - list_for_each_entry_safe(hose, tmp, > - &hose_list, list_node) { > - phb = hose->dn; > - traverse_pci_devices(phb, eeh_ops->of_probe, NULL); > - } > - } > - > - if (eeh_subsystem_enabled) > - pr_info("EEH: PCI Enhanced I/O Error Handling Enabled\n"); > - else > - pr_warning("EEH: No capable adapters found\n"); > - > - return ret; > -} > - > -core_initcall_sync(eeh_init); > - > -/** > - * eeh_add_device_early - Enable EEH for the indicated device_node > - * @dn: device node for which to set up EEH > - * > - * This routine must be used to perform EEH initialization for PCI > - * devices that were added after system boot (e.g. hotplug, dlpar). > - * This routine must be called before any i/o is performed to the > - * adapter (inluding any config-space i/o). > - * Whether this actually enables EEH or not for this device depends > - * on the CEC architecture, type of the device, on earlier boot > - * command-line arguments & etc. > - */ > -static void eeh_add_device_early(struct device_node *dn) > -{ > - struct pci_controller *phb; > - > - if (!of_node_to_eeh_dev(dn)) > - return; > - phb = of_node_to_eeh_dev(dn)->phb; > - > - /* USB Bus children of PCI devices will not have BUID's */ > - if (NULL == phb || 0 == phb->buid) > - return; > - > - /* FIXME: hotplug support on POWERNV */ > - eeh_ops->of_probe(dn, NULL); > -} > - > -/** > - * eeh_add_device_tree_early - Enable EEH for the indicated device > - * @dn: device node > - * > - * This routine must be used to perform EEH initialization for the > - * indicated PCI device that was added after system boot (e.g. > - * hotplug, dlpar). > - */ > -void eeh_add_device_tree_early(struct device_node *dn) > -{ > - struct device_node *sib; > - > - for_each_child_of_node(dn, sib) > - eeh_add_device_tree_early(sib); > - eeh_add_device_early(dn); > -} > -EXPORT_SYMBOL_GPL(eeh_add_device_tree_early); > - > -/** > - * eeh_add_device_late - Perform EEH initialization for the indicated pci device > - * @dev: pci device for which to set up EEH > - * > - * This routine must be used to complete EEH initialization for PCI > - * devices that were added after system boot (e.g. hotplug, dlpar). > - */ > -static void eeh_add_device_late(struct pci_dev *dev) > -{ > - struct device_node *dn; > - struct eeh_dev *edev; > - > - if (!dev || !eeh_subsystem_enabled) > - return; > - > - pr_debug("EEH: Adding device %s\n", pci_name(dev)); > - > - dn = pci_device_to_OF_node(dev); > - edev = of_node_to_eeh_dev(dn); > - if (edev->pdev == dev) { > - pr_debug("EEH: Already referenced !\n"); > - return; > - } > - WARN_ON(edev->pdev); > - > - pci_dev_get(dev); > - edev->pdev = dev; > - dev->dev.archdata.edev = edev; > - > - eeh_addr_cache_insert_dev(dev); > -} > - > -/** > - * eeh_add_device_tree_late - Perform EEH initialization for the indicated PCI bus > - * @bus: PCI bus > - * > - * This routine must be used to perform EEH initialization for PCI > - * devices which are attached to the indicated PCI bus. The PCI bus > - * is added after system boot through hotplug or dlpar. > - */ > -void eeh_add_device_tree_late(struct pci_bus *bus) > -{ > - struct pci_dev *dev; > - > - list_for_each_entry(dev, &bus->devices, bus_list) { > - eeh_add_device_late(dev); > - if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE) { > - struct pci_bus *subbus = dev->subordinate; > - if (subbus) > - eeh_add_device_tree_late(subbus); > - } > - } > -} > -EXPORT_SYMBOL_GPL(eeh_add_device_tree_late); > - > -/** > - * eeh_add_sysfs_files - Add EEH sysfs files for the indicated PCI bus > - * @bus: PCI bus > - * > - * This routine must be used to add EEH sysfs files for PCI > - * devices which are attached to the indicated PCI bus. The PCI bus > - * is added after system boot through hotplug or dlpar. > - */ > -void eeh_add_sysfs_files(struct pci_bus *bus) > -{ > - struct pci_dev *dev; > - > - list_for_each_entry(dev, &bus->devices, bus_list) { > - eeh_sysfs_add_device(dev); > - if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE) { > - struct pci_bus *subbus = dev->subordinate; > - if (subbus) > - eeh_add_sysfs_files(subbus); > - } > - } > -} > -EXPORT_SYMBOL_GPL(eeh_add_sysfs_files); > - > -/** > - * eeh_remove_device - Undo EEH setup for the indicated pci device > - * @dev: pci device to be removed > - * @purge_pe: remove the PE or not > - * > - * This routine should be called when a device is removed from > - * a running system (e.g. by hotplug or dlpar). It unregisters > - * the PCI device from the EEH subsystem. I/O errors affecting > - * this device will no longer be detected after this call; thus, > - * i/o errors affecting this slot may leave this device unusable. > - */ > -static void eeh_remove_device(struct pci_dev *dev, int purge_pe) > -{ > - struct eeh_dev *edev; > - > - if (!dev || !eeh_subsystem_enabled) > - return; > - edev = pci_dev_to_eeh_dev(dev); > - > - /* Unregister the device with the EEH/PCI address search system */ > - pr_debug("EEH: Removing device %s\n", pci_name(dev)); > - > - if (!edev || !edev->pdev) { > - pr_debug("EEH: Not referenced !\n"); > - return; > - } > - edev->pdev = NULL; > - dev->dev.archdata.edev = NULL; > - pci_dev_put(dev); > - > - eeh_rmv_from_parent_pe(edev, purge_pe); > - eeh_addr_cache_rmv_dev(dev); > - eeh_sysfs_remove_device(dev); > -} > - > -/** > - * eeh_remove_bus_device - Undo EEH setup for the indicated PCI device > - * @dev: PCI device > - * @purge_pe: remove the corresponding PE or not > - * > - * This routine must be called when a device is removed from the > - * running system through hotplug or dlpar. The corresponding > - * PCI address cache will be removed. > - */ > -void eeh_remove_bus_device(struct pci_dev *dev, int purge_pe) > -{ > - struct pci_bus *bus = dev->subordinate; > - struct pci_dev *child, *tmp; > - > - eeh_remove_device(dev, purge_pe); > - > - if (bus && dev->hdr_type == PCI_HEADER_TYPE_BRIDGE) { > - list_for_each_entry_safe(child, tmp, &bus->devices, bus_list) > - eeh_remove_bus_device(child, purge_pe); > - } > -} > -EXPORT_SYMBOL_GPL(eeh_remove_bus_device); > - > -static int proc_eeh_show(struct seq_file *m, void *v) > -{ > - if (0 == eeh_subsystem_enabled) { > - seq_printf(m, "EEH Subsystem is globally disabled\n"); > - seq_printf(m, "eeh_total_mmio_ffs=%llu\n", eeh_stats.total_mmio_ffs); > - } else { > - seq_printf(m, "EEH Subsystem is enabled\n"); > - seq_printf(m, > - "no device=%llu\n" > - "no device node=%llu\n" > - "no config address=%llu\n" > - "check not wanted=%llu\n" > - "eeh_total_mmio_ffs=%llu\n" > - "eeh_false_positives=%llu\n" > - "eeh_slot_resets=%llu\n", > - eeh_stats.no_device, > - eeh_stats.no_dn, > - eeh_stats.no_cfg_addr, > - eeh_stats.ignored_check, > - eeh_stats.total_mmio_ffs, > - eeh_stats.false_positives, > - eeh_stats.slot_resets); > - } > - > - return 0; > -} > - > -static int proc_eeh_open(struct inode *inode, struct file *file) > -{ > - return single_open(file, proc_eeh_show, NULL); > -} > - > -static const struct file_operations proc_eeh_operations = { > - .open = proc_eeh_open, > - .read = seq_read, > - .llseek = seq_lseek, > - .release = single_release, > -}; > - > -static int __init eeh_init_proc(void) > -{ > - if (machine_is(pseries)) > - proc_create("powerpc/eeh", 0, NULL, &proc_eeh_operations); > - return 0; > -} > -__initcall(eeh_init_proc); > diff --git a/arch/powerpc/platforms/pseries/eeh_cache.c b/arch/powerpc/platforms/pseries/eeh_cache.c > deleted file mode 100644 > index 5a4c879..0000000 > --- a/arch/powerpc/platforms/pseries/eeh_cache.c > +++ /dev/null > @@ -1,319 +0,0 @@ > -/* > - * PCI address cache; allows the lookup of PCI devices based on I/O address > - * > - * Copyright IBM Corporation 2004 > - * Copyright Linas Vepstas <linas@austin.ibm.com> 2004 > - * > - * 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 > - * the Free Software Foundation; either version 2 of the License, or > - * (at your option) any later version. > - * > - * This program is distributed in the hope that it will be useful, > - * but WITHOUT ANY WARRANTY; without even the implied warranty of > - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > - * GNU General Public License for more details. > - * > - * You should have received a copy of the GNU General Public License > - * along with this program; if not, write to the Free Software > - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA > - */ > - > -#include <linux/list.h> > -#include <linux/pci.h> > -#include <linux/rbtree.h> > -#include <linux/slab.h> > -#include <linux/spinlock.h> > -#include <linux/atomic.h> > -#include <asm/pci-bridge.h> > -#include <asm/ppc-pci.h> > - > - > -/** > - * The pci address cache subsystem. This subsystem places > - * PCI device address resources into a red-black tree, sorted > - * according to the address range, so that given only an i/o > - * address, the corresponding PCI device can be **quickly** > - * found. It is safe to perform an address lookup in an interrupt > - * context; this ability is an important feature. > - * > - * Currently, the only customer of this code is the EEH subsystem; > - * thus, this code has been somewhat tailored to suit EEH better. > - * In particular, the cache does *not* hold the addresses of devices > - * for which EEH is not enabled. > - * > - * (Implementation Note: The RB tree seems to be better/faster > - * than any hash algo I could think of for this problem, even > - * with the penalty of slow pointer chases for d-cache misses). > - */ > -struct pci_io_addr_range { > - struct rb_node rb_node; > - unsigned long addr_lo; > - unsigned long addr_hi; > - struct eeh_dev *edev; > - struct pci_dev *pcidev; > - unsigned int flags; > -}; > - > -static struct pci_io_addr_cache { > - struct rb_root rb_root; > - spinlock_t piar_lock; > -} pci_io_addr_cache_root; > - > -static inline struct eeh_dev *__eeh_addr_cache_get_device(unsigned long addr) > -{ > - struct rb_node *n = pci_io_addr_cache_root.rb_root.rb_node; > - > - while (n) { > - struct pci_io_addr_range *piar; > - piar = rb_entry(n, struct pci_io_addr_range, rb_node); > - > - if (addr < piar->addr_lo) { > - n = n->rb_left; > - } else { > - if (addr > piar->addr_hi) { > - n = n->rb_right; > - } else { > - pci_dev_get(piar->pcidev); > - return piar->edev; > - } > - } > - } > - > - return NULL; > -} > - > -/** > - * eeh_addr_cache_get_dev - Get device, given only address > - * @addr: mmio (PIO) phys address or i/o port number > - * > - * Given an mmio phys address, or a port number, find a pci device > - * that implements this address. Be sure to pci_dev_put the device > - * when finished. I/O port numbers are assumed to be offset > - * from zero (that is, they do *not* have pci_io_addr added in). > - * It is safe to call this function within an interrupt. > - */ > -struct eeh_dev *eeh_addr_cache_get_dev(unsigned long addr) > -{ > - struct eeh_dev *edev; > - unsigned long flags; > - > - spin_lock_irqsave(&pci_io_addr_cache_root.piar_lock, flags); > - edev = __eeh_addr_cache_get_device(addr); > - spin_unlock_irqrestore(&pci_io_addr_cache_root.piar_lock, flags); > - return edev; > -} > - > -#ifdef DEBUG > -/* > - * Handy-dandy debug print routine, does nothing more > - * than print out the contents of our addr cache. > - */ > -static void eeh_addr_cache_print(struct pci_io_addr_cache *cache) > -{ > - struct rb_node *n; > - int cnt = 0; > - > - n = rb_first(&cache->rb_root); > - while (n) { > - struct pci_io_addr_range *piar; > - piar = rb_entry(n, struct pci_io_addr_range, rb_node); > - pr_debug("PCI: %s addr range %d [%lx-%lx]: %s\n", > - (piar->flags & IORESOURCE_IO) ? "i/o" : "mem", cnt, > - piar->addr_lo, piar->addr_hi, pci_name(piar->pcidev)); > - cnt++; > - n = rb_next(n); > - } > -} > -#endif > - > -/* Insert address range into the rb tree. */ > -static struct pci_io_addr_range * > -eeh_addr_cache_insert(struct pci_dev *dev, unsigned long alo, > - unsigned long ahi, unsigned int flags) > -{ > - struct rb_node **p = &pci_io_addr_cache_root.rb_root.rb_node; > - struct rb_node *parent = NULL; > - struct pci_io_addr_range *piar; > - > - /* Walk tree, find a place to insert into tree */ > - while (*p) { > - parent = *p; > - piar = rb_entry(parent, struct pci_io_addr_range, rb_node); > - if (ahi < piar->addr_lo) { > - p = &parent->rb_left; > - } else if (alo > piar->addr_hi) { > - p = &parent->rb_right; > - } else { > - if (dev != piar->pcidev || > - alo != piar->addr_lo || ahi != piar->addr_hi) { > - pr_warning("PIAR: overlapping address range\n"); > - } > - return piar; > - } > - } > - piar = kzalloc(sizeof(struct pci_io_addr_range), GFP_ATOMIC); > - if (!piar) > - return NULL; > - > - pci_dev_get(dev); > - piar->addr_lo = alo; > - piar->addr_hi = ahi; > - piar->edev = pci_dev_to_eeh_dev(dev); > - piar->pcidev = dev; > - piar->flags = flags; > - > -#ifdef DEBUG > - pr_debug("PIAR: insert range=[%lx:%lx] dev=%s\n", > - alo, ahi, pci_name(dev)); > -#endif > - > - rb_link_node(&piar->rb_node, parent, p); > - rb_insert_color(&piar->rb_node, &pci_io_addr_cache_root.rb_root); > - > - return piar; > -} > - > -static void __eeh_addr_cache_insert_dev(struct pci_dev *dev) > -{ > - struct device_node *dn; > - struct eeh_dev *edev; > - int i; > - > - dn = pci_device_to_OF_node(dev); > - if (!dn) { > - pr_warning("PCI: no pci dn found for dev=%s\n", pci_name(dev)); > - return; > - } > - > - edev = of_node_to_eeh_dev(dn); > - if (!edev) { > - pr_warning("PCI: no EEH dev found for dn=%s\n", > - dn->full_name); > - return; > - } > - > - /* Skip any devices for which EEH is not enabled. */ > - if (!edev->pe) { > -#ifdef DEBUG > - pr_info("PCI: skip building address cache for=%s - %s\n", > - pci_name(dev), dn->full_name); > -#endif > - return; > - } > - > - /* Walk resources on this device, poke them into the tree */ > - for (i = 0; i < DEVICE_COUNT_RESOURCE; i++) { > - unsigned long start = pci_resource_start(dev,i); > - unsigned long end = pci_resource_end(dev,i); > - unsigned int flags = pci_resource_flags(dev,i); > - > - /* We are interested only bus addresses, not dma or other stuff */ > - if (0 == (flags & (IORESOURCE_IO | IORESOURCE_MEM))) > - continue; > - if (start == 0 || ~start == 0 || end == 0 || ~end == 0) > - continue; > - eeh_addr_cache_insert(dev, start, end, flags); > - } > -} > - > -/** > - * eeh_addr_cache_insert_dev - Add a device to the address cache > - * @dev: PCI device whose I/O addresses we are interested in. > - * > - * In order to support the fast lookup of devices based on addresses, > - * we maintain a cache of devices that can be quickly searched. > - * This routine adds a device to that cache. > - */ > -void eeh_addr_cache_insert_dev(struct pci_dev *dev) > -{ > - unsigned long flags; > - > - /* Ignore PCI bridges */ > - if ((dev->class >> 16) == PCI_BASE_CLASS_BRIDGE) > - return; > - > - spin_lock_irqsave(&pci_io_addr_cache_root.piar_lock, flags); > - __eeh_addr_cache_insert_dev(dev); > - spin_unlock_irqrestore(&pci_io_addr_cache_root.piar_lock, flags); > -} > - > -static inline void __eeh_addr_cache_rmv_dev(struct pci_dev *dev) > -{ > - struct rb_node *n; > - > -restart: > - n = rb_first(&pci_io_addr_cache_root.rb_root); > - while (n) { > - struct pci_io_addr_range *piar; > - piar = rb_entry(n, struct pci_io_addr_range, rb_node); > - > - if (piar->pcidev == dev) { > - rb_erase(n, &pci_io_addr_cache_root.rb_root); > - pci_dev_put(piar->pcidev); > - kfree(piar); > - goto restart; > - } > - n = rb_next(n); > - } > -} > - > -/** > - * eeh_addr_cache_rmv_dev - remove pci device from addr cache > - * @dev: device to remove > - * > - * Remove a device from the addr-cache tree. > - * This is potentially expensive, since it will walk > - * the tree multiple times (once per resource). > - * But so what; device removal doesn't need to be that fast. > - */ > -void eeh_addr_cache_rmv_dev(struct pci_dev *dev) > -{ > - unsigned long flags; > - > - spin_lock_irqsave(&pci_io_addr_cache_root.piar_lock, flags); > - __eeh_addr_cache_rmv_dev(dev); > - spin_unlock_irqrestore(&pci_io_addr_cache_root.piar_lock, flags); > -} > - > -/** > - * eeh_addr_cache_build - Build a cache of I/O addresses > - * > - * Build a cache of pci i/o addresses. This cache will be used to > - * find the pci device that corresponds to a given address. > - * This routine scans all pci busses to build the cache. > - * Must be run late in boot process, after the pci controllers > - * have been scanned for devices (after all device resources are known). > - */ > -void __init eeh_addr_cache_build(void) > -{ > - struct device_node *dn; > - struct eeh_dev *edev; > - struct pci_dev *dev = NULL; > - > - spin_lock_init(&pci_io_addr_cache_root.piar_lock); > - > - for_each_pci_dev(dev) { > - eeh_addr_cache_insert_dev(dev); > - > - dn = pci_device_to_OF_node(dev); > - if (!dn) > - continue; > - > - edev = of_node_to_eeh_dev(dn); > - if (!edev) > - continue; > - > - pci_dev_get(dev); /* matching put is in eeh_remove_device() */ > - dev->dev.archdata.edev = edev; > - edev->pdev = dev; > - > - eeh_sysfs_add_device(dev); > - } > - > -#ifdef DEBUG > - /* Verify tree built up above, echo back the list of addrs. */ > - eeh_addr_cache_print(&pci_io_addr_cache_root); > -#endif > -} > - > diff --git a/arch/powerpc/platforms/pseries/eeh_dev.c b/arch/powerpc/platforms/pseries/eeh_dev.c > deleted file mode 100644 > index 1efa28f..0000000 > --- a/arch/powerpc/platforms/pseries/eeh_dev.c > +++ /dev/null > @@ -1,112 +0,0 @@ > -/* > - * The file intends to implement dynamic creation of EEH device, which will > - * be bound with OF node and PCI device simutaneously. The EEH devices would > - * be foundamental information for EEH core components to work proerly. Besides, > - * We have to support multiple situations where dynamic creation of EEH device > - * is required: > - * > - * 1) Before PCI emunation starts, we need create EEH devices according to the > - * PCI sensitive OF nodes. > - * 2) When PCI emunation is done, we need do the binding between PCI device and > - * the associated EEH device. > - * 3) DR (Dynamic Reconfiguration) would create PCI sensitive OF node. EEH device > - * will be created while PCI sensitive OF node is detected from DR. > - * 4) PCI hotplug needs redoing the binding between PCI device and EEH device. If > - * PHB is newly inserted, we also need create EEH devices accordingly. > - * > - * Copyright Benjamin Herrenschmidt & Gavin Shan, IBM Corporation 2012. > - * > - * 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 > - * the Free Software Foundation; either version 2 of the License, or > - * (at your option) any later version. > - * > - * This program is distributed in the hope that it will be useful, > - * but WITHOUT ANY WARRANTY; without even the implied warranty of > - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > - * GNU General Public License for more details. > - * > - * You should have received a copy of the GNU General Public License > - * along with this program; if not, write to the Free Software > - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA > - */ > - > -#include <linux/export.h> > -#include <linux/gfp.h> > -#include <linux/init.h> > -#include <linux/kernel.h> > -#include <linux/pci.h> > -#include <linux/string.h> > - > -#include <asm/pci-bridge.h> > -#include <asm/ppc-pci.h> > - > -/** > - * eeh_dev_init - Create EEH device according to OF node > - * @dn: device node > - * @data: PHB > - * > - * It will create EEH device according to the given OF node. The function > - * might be called by PCI emunation, DR, PHB hotplug. > - */ > -void *eeh_dev_init(struct device_node *dn, void *data) > -{ > - struct pci_controller *phb = data; > - struct eeh_dev *edev; > - > - /* Allocate EEH device */ > - edev = kzalloc(sizeof(*edev), GFP_KERNEL); > - if (!edev) { > - pr_warning("%s: out of memory\n", __func__); > - return NULL; > - } > - > - /* Associate EEH device with OF node */ > - PCI_DN(dn)->edev = edev; > - edev->dn = dn; > - edev->phb = phb; > - INIT_LIST_HEAD(&edev->list); > - > - return NULL; > -} > - > -/** > - * eeh_dev_phb_init_dynamic - Create EEH devices for devices included in PHB > - * @phb: PHB > - * > - * Scan the PHB OF node and its child association, then create the > - * EEH devices accordingly > - */ > -void eeh_dev_phb_init_dynamic(struct pci_controller *phb) > -{ > - struct device_node *dn = phb->dn; > - > - /* EEH PE for PHB */ > - eeh_phb_pe_create(phb); > - > - /* EEH device for PHB */ > - eeh_dev_init(dn, phb); > - > - /* EEH devices for children OF nodes */ > - traverse_pci_devices(dn, eeh_dev_init, phb); > -} > - > -/** > - * eeh_dev_phb_init - Create EEH devices for devices included in existing PHBs > - * > - * Scan all the existing PHBs and create EEH devices for their OF > - * nodes and their children OF nodes > - */ > -static int __init eeh_dev_phb_init(void) > -{ > - struct pci_controller *phb, *tmp; > - > - list_for_each_entry_safe(phb, tmp, &hose_list, list_node) > - eeh_dev_phb_init_dynamic(phb); > - > - pr_info("EEH: devices created\n"); > - > - return 0; > -} > - > -core_initcall(eeh_dev_phb_init); > diff --git a/arch/powerpc/platforms/pseries/eeh_driver.c b/arch/powerpc/platforms/pseries/eeh_driver.c > deleted file mode 100644 > index a3fefb6..0000000 > --- a/arch/powerpc/platforms/pseries/eeh_driver.c > +++ /dev/null > @@ -1,552 +0,0 @@ > -/* > - * PCI Error Recovery Driver for RPA-compliant PPC64 platform. > - * Copyright IBM Corp. 2004 2005 > - * Copyright Linas Vepstas <linas@linas.org> 2004, 2005 > - * > - * All rights reserved. > - * > - * 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 > - * the Free Software Foundation; either version 2 of the License, or (at > - * your option) any later version. > - * > - * This program is distributed in the hope that it will be useful, but > - * WITHOUT ANY WARRANTY; without even the implied warranty of > - * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or > - * NON INFRINGEMENT. See the GNU General Public License for more > - * details. > - * > - * You should have received a copy of the GNU General Public License > - * along with this program; if not, write to the Free Software > - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. > - * > - * Send comments and feedback to Linas Vepstas <linas@austin.ibm.com> > - */ > -#include <linux/delay.h> > -#include <linux/interrupt.h> > -#include <linux/irq.h> > -#include <linux/module.h> > -#include <linux/pci.h> > -#include <asm/eeh.h> > -#include <asm/eeh_event.h> > -#include <asm/ppc-pci.h> > -#include <asm/pci-bridge.h> > -#include <asm/prom.h> > -#include <asm/rtas.h> > - > -/** > - * eeh_pcid_name - Retrieve name of PCI device driver > - * @pdev: PCI device > - * > - * This routine is used to retrieve the name of PCI device driver > - * if that's valid. > - */ > -static inline const char *eeh_pcid_name(struct pci_dev *pdev) > -{ > - if (pdev && pdev->dev.driver) > - return pdev->dev.driver->name; > - return ""; > -} > - > -/** > - * eeh_pcid_get - Get the PCI device driver > - * @pdev: PCI device > - * > - * The function is used to retrieve the PCI device driver for > - * the indicated PCI device. Besides, we will increase the reference > - * of the PCI device driver to prevent that being unloaded on > - * the fly. Otherwise, kernel crash would be seen. > - */ > -static inline struct pci_driver *eeh_pcid_get(struct pci_dev *pdev) > -{ > - if (!pdev || !pdev->driver) > - return NULL; > - > - if (!try_module_get(pdev->driver->driver.owner)) > - return NULL; > - > - return pdev->driver; > -} > - > -/** > - * eeh_pcid_put - Dereference on the PCI device driver > - * @pdev: PCI device > - * > - * The function is called to do dereference on the PCI device > - * driver of the indicated PCI device. > - */ > -static inline void eeh_pcid_put(struct pci_dev *pdev) > -{ > - if (!pdev || !pdev->driver) > - return; > - > - module_put(pdev->driver->driver.owner); > -} > - > -#if 0 > -static void print_device_node_tree(struct pci_dn *pdn, int dent) > -{ > - int i; > - struct device_node *pc; > - > - if (!pdn) > - return; > - for (i = 0; i < dent; i++) > - printk(" "); > - printk("dn=%s mode=%x \tcfg_addr=%x pe_addr=%x \tfull=%s\n", > - pdn->node->name, pdn->eeh_mode, pdn->eeh_config_addr, > - pdn->eeh_pe_config_addr, pdn->node->full_name); > - dent += 3; > - pc = pdn->node->child; > - while (pc) { > - print_device_node_tree(PCI_DN(pc), dent); > - pc = pc->sibling; > - } > -} > -#endif > - > -/** > - * eeh_disable_irq - Disable interrupt for the recovering device > - * @dev: PCI device > - * > - * This routine must be called when reporting temporary or permanent > - * error to the particular PCI device to disable interrupt of that > - * device. If the device has enabled MSI or MSI-X interrupt, we needn't > - * do real work because EEH should freeze DMA transfers for those PCI > - * devices encountering EEH errors, which includes MSI or MSI-X. > - */ > -static void eeh_disable_irq(struct pci_dev *dev) > -{ > - struct eeh_dev *edev = pci_dev_to_eeh_dev(dev); > - > - /* Don't disable MSI and MSI-X interrupts. They are > - * effectively disabled by the DMA Stopped state > - * when an EEH error occurs. > - */ > - if (dev->msi_enabled || dev->msix_enabled) > - return; > - > - if (!irq_has_action(dev->irq)) > - return; > - > - edev->mode |= EEH_DEV_IRQ_DISABLED; > - disable_irq_nosync(dev->irq); > -} > - > -/** > - * eeh_enable_irq - Enable interrupt for the recovering device > - * @dev: PCI device > - * > - * This routine must be called to enable interrupt while failed > - * device could be resumed. > - */ > -static void eeh_enable_irq(struct pci_dev *dev) > -{ > - struct eeh_dev *edev = pci_dev_to_eeh_dev(dev); > - > - if ((edev->mode) & EEH_DEV_IRQ_DISABLED) { > - edev->mode &= ~EEH_DEV_IRQ_DISABLED; > - enable_irq(dev->irq); > - } > -} > - > -/** > - * eeh_report_error - Report pci error to each device driver > - * @data: eeh device > - * @userdata: return value > - * > - * Report an EEH error to each device driver, collect up and > - * merge the device driver responses. Cumulative response > - * passed back in "userdata". > - */ > -static void *eeh_report_error(void *data, void *userdata) > -{ > - struct eeh_dev *edev = (struct eeh_dev *)data; > - struct pci_dev *dev = eeh_dev_to_pci_dev(edev); > - enum pci_ers_result rc, *res = userdata; > - struct pci_driver *driver; > - > - /* We might not have the associated PCI device, > - * then we should continue for next one. > - */ > - if (!dev) return NULL; > - dev->error_state = pci_channel_io_frozen; > - > - driver = eeh_pcid_get(dev); > - if (!driver) return NULL; > - > - eeh_disable_irq(dev); > - > - if (!driver->err_handler || > - !driver->err_handler->error_detected) { > - eeh_pcid_put(dev); > - return NULL; > - } > - > - rc = driver->err_handler->error_detected(dev, pci_channel_io_frozen); > - > - /* A driver that needs a reset trumps all others */ > - if (rc == PCI_ERS_RESULT_NEED_RESET) *res = rc; > - if (*res == PCI_ERS_RESULT_NONE) *res = rc; > - > - eeh_pcid_put(dev); > - return NULL; > -} > - > -/** > - * eeh_report_mmio_enabled - Tell drivers that MMIO has been enabled > - * @data: eeh device > - * @userdata: return value > - * > - * Tells each device driver that IO ports, MMIO and config space I/O > - * are now enabled. Collects up and merges the device driver responses. > - * Cumulative response passed back in "userdata". > - */ > -static void *eeh_report_mmio_enabled(void *data, void *userdata) > -{ > - struct eeh_dev *edev = (struct eeh_dev *)data; > - struct pci_dev *dev = eeh_dev_to_pci_dev(edev); > - enum pci_ers_result rc, *res = userdata; > - struct pci_driver *driver; > - > - driver = eeh_pcid_get(dev); > - if (!driver) return NULL; > - > - if (!driver->err_handler || > - !driver->err_handler->mmio_enabled) { > - eeh_pcid_put(dev); > - return NULL; > - } > - > - rc = driver->err_handler->mmio_enabled(dev); > - > - /* A driver that needs a reset trumps all others */ > - if (rc == PCI_ERS_RESULT_NEED_RESET) *res = rc; > - if (*res == PCI_ERS_RESULT_NONE) *res = rc; > - > - eeh_pcid_put(dev); > - return NULL; > -} > - > -/** > - * eeh_report_reset - Tell device that slot has been reset > - * @data: eeh device > - * @userdata: return value > - * > - * This routine must be called while EEH tries to reset particular > - * PCI device so that the associated PCI device driver could take > - * some actions, usually to save data the driver needs so that the > - * driver can work again while the device is recovered. > - */ > -static void *eeh_report_reset(void *data, void *userdata) > -{ > - struct eeh_dev *edev = (struct eeh_dev *)data; > - struct pci_dev *dev = eeh_dev_to_pci_dev(edev); > - enum pci_ers_result rc, *res = userdata; > - struct pci_driver *driver; > - > - if (!dev) return NULL; > - dev->error_state = pci_channel_io_normal; > - > - driver = eeh_pcid_get(dev); > - if (!driver) return NULL; > - > - eeh_enable_irq(dev); > - > - if (!driver->err_handler || > - !driver->err_handler->slot_reset) { > - eeh_pcid_put(dev); > - return NULL; > - } > - > - rc = driver->err_handler->slot_reset(dev); > - if ((*res == PCI_ERS_RESULT_NONE) || > - (*res == PCI_ERS_RESULT_RECOVERED)) *res = rc; > - if (*res == PCI_ERS_RESULT_DISCONNECT && > - rc == PCI_ERS_RESULT_NEED_RESET) *res = rc; > - > - eeh_pcid_put(dev); > - return NULL; > -} > - > -/** > - * eeh_report_resume - Tell device to resume normal operations > - * @data: eeh device > - * @userdata: return value > - * > - * This routine must be called to notify the device driver that it > - * could resume so that the device driver can do some initialization > - * to make the recovered device work again. > - */ > -static void *eeh_report_resume(void *data, void *userdata) > -{ > - struct eeh_dev *edev = (struct eeh_dev *)data; > - struct pci_dev *dev = eeh_dev_to_pci_dev(edev); > - struct pci_driver *driver; > - > - if (!dev) return NULL; > - dev->error_state = pci_channel_io_normal; > - > - driver = eeh_pcid_get(dev); > - if (!driver) return NULL; > - > - eeh_enable_irq(dev); > - > - if (!driver->err_handler || > - !driver->err_handler->resume) { > - eeh_pcid_put(dev); > - return NULL; > - } > - > - driver->err_handler->resume(dev); > - > - eeh_pcid_put(dev); > - return NULL; > -} > - > -/** > - * eeh_report_failure - Tell device driver that device is dead. > - * @data: eeh device > - * @userdata: return value > - * > - * This informs the device driver that the device is permanently > - * dead, and that no further recovery attempts will be made on it. > - */ > -static void *eeh_report_failure(void *data, void *userdata) > -{ > - struct eeh_dev *edev = (struct eeh_dev *)data; > - struct pci_dev *dev = eeh_dev_to_pci_dev(edev); > - struct pci_driver *driver; > - > - if (!dev) return NULL; > - dev->error_state = pci_channel_io_perm_failure; > - > - driver = eeh_pcid_get(dev); > - if (!driver) return NULL; > - > - eeh_disable_irq(dev); > - > - if (!driver->err_handler || > - !driver->err_handler->error_detected) { > - eeh_pcid_put(dev); > - return NULL; > - } > - > - driver->err_handler->error_detected(dev, pci_channel_io_perm_failure); > - > - eeh_pcid_put(dev); > - return NULL; > -} > - > -/** > - * eeh_reset_device - Perform actual reset of a pci slot > - * @pe: EEH PE > - * @bus: PCI bus corresponding to the isolcated slot > - * > - * This routine must be called to do reset on the indicated PE. > - * During the reset, udev might be invoked because those affected > - * PCI devices will be removed and then added. > - */ > -static int eeh_reset_device(struct eeh_pe *pe, struct pci_bus *bus) > -{ > - int cnt, rc; > - > - /* pcibios will clear the counter; save the value */ > - cnt = pe->freeze_count; > - > - /* > - * We don't remove the corresponding PE instances because > - * we need the information afterwords. The attached EEH > - * devices are expected to be attached soon when calling > - * into pcibios_add_pci_devices(). > - */ > - if (bus) > - __pcibios_remove_pci_devices(bus, 0); > - > - /* Reset the pci controller. (Asserts RST#; resets config space). > - * Reconfigure bridges and devices. Don't try to bring the system > - * up if the reset failed for some reason. > - */ > - rc = eeh_reset_pe(pe); > - if (rc) > - return rc; > - > - /* Restore PE */ > - eeh_ops->configure_bridge(pe); > - eeh_pe_restore_bars(pe); > - > - /* Give the system 5 seconds to finish running the user-space > - * hotplug shutdown scripts, e.g. ifdown for ethernet. Yes, > - * this is a hack, but if we don't do this, and try to bring > - * the device up before the scripts have taken it down, > - * potentially weird things happen. > - */ > - if (bus) { > - ssleep(5); > - pcibios_add_pci_devices(bus); > - } > - pe->freeze_count = cnt; > - > - return 0; > -} > - > -/* The longest amount of time to wait for a pci device > - * to come back on line, in seconds. > - */ > -#define MAX_WAIT_FOR_RECOVERY 150 > - > -/** > - * eeh_handle_event - Reset a PCI device after hard lockup. > - * @pe: EEH PE > - * > - * While PHB detects address or data parity errors on particular PCI > - * slot, the associated PE will be frozen. Besides, DMA's occurring > - * to wild addresses (which usually happen due to bugs in device > - * drivers or in PCI adapter firmware) can cause EEH error. #SERR, > - * #PERR or other misc PCI-related errors also can trigger EEH errors. > - * > - * Recovery process consists of unplugging the device driver (which > - * generated hotplug events to userspace), then issuing a PCI #RST to > - * the device, then reconfiguring the PCI config space for all bridges > - * & devices under this slot, and then finally restarting the device > - * drivers (which cause a second set of hotplug events to go out to > - * userspace). > - */ > -void eeh_handle_event(struct eeh_pe *pe) > -{ > - struct pci_bus *frozen_bus; > - int rc = 0; > - enum pci_ers_result result = PCI_ERS_RESULT_NONE; > - > - frozen_bus = eeh_pe_bus_get(pe); > - if (!frozen_bus) { > - pr_err("%s: Cannot find PCI bus for PHB#%d-PE#%x\n", > - __func__, pe->phb->global_number, pe->addr); > - return; > - } > - > - pe->freeze_count++; > - if (pe->freeze_count > EEH_MAX_ALLOWED_FREEZES) > - goto excess_failures; > - pr_warning("EEH: This PCI device has failed %d times in the last hour\n", > - pe->freeze_count); > - > - /* Walk the various device drivers attached to this slot through > - * a reset sequence, giving each an opportunity to do what it needs > - * to accomplish the reset. Each child gets a report of the > - * status ... if any child can't handle the reset, then the entire > - * slot is dlpar removed and added. > - */ > - eeh_pe_dev_traverse(pe, eeh_report_error, &result); > - > - /* Get the current PCI slot state. This can take a long time, > - * sometimes over 3 seconds for certain systems. > - */ > - rc = eeh_ops->wait_state(pe, MAX_WAIT_FOR_RECOVERY*1000); > - if (rc < 0 || rc == EEH_STATE_NOT_SUPPORT) { > - printk(KERN_WARNING "EEH: Permanent failure\n"); > - goto hard_fail; > - } > - > - /* Since rtas may enable MMIO when posting the error log, > - * don't post the error log until after all dev drivers > - * have been informed. > - */ > - eeh_slot_error_detail(pe, EEH_LOG_TEMP); > - > - /* If all device drivers were EEH-unaware, then shut > - * down all of the device drivers, and hope they > - * go down willingly, without panicing the system. > - */ > - if (result == PCI_ERS_RESULT_NONE) { > - rc = eeh_reset_device(pe, frozen_bus); > - if (rc) { > - printk(KERN_WARNING "EEH: Unable to reset, rc=%d\n", rc); > - goto hard_fail; > - } > - } > - > - /* If all devices reported they can proceed, then re-enable MMIO */ > - if (result == PCI_ERS_RESULT_CAN_RECOVER) { > - rc = eeh_pci_enable(pe, EEH_OPT_THAW_MMIO); > - > - if (rc < 0) > - goto hard_fail; > - if (rc) { > - result = PCI_ERS_RESULT_NEED_RESET; > - } else { > - result = PCI_ERS_RESULT_NONE; > - eeh_pe_dev_traverse(pe, eeh_report_mmio_enabled, &result); > - } > - } > - > - /* If all devices reported they can proceed, then re-enable DMA */ > - if (result == PCI_ERS_RESULT_CAN_RECOVER) { > - rc = eeh_pci_enable(pe, EEH_OPT_THAW_DMA); > - > - if (rc < 0) > - goto hard_fail; > - if (rc) > - result = PCI_ERS_RESULT_NEED_RESET; > - else > - result = PCI_ERS_RESULT_RECOVERED; > - } > - > - /* If any device has a hard failure, then shut off everything. */ > - if (result == PCI_ERS_RESULT_DISCONNECT) { > - printk(KERN_WARNING "EEH: Device driver gave up\n"); > - goto hard_fail; > - } > - > - /* If any device called out for a reset, then reset the slot */ > - if (result == PCI_ERS_RESULT_NEED_RESET) { > - rc = eeh_reset_device(pe, NULL); > - if (rc) { > - printk(KERN_WARNING "EEH: Cannot reset, rc=%d\n", rc); > - goto hard_fail; > - } > - result = PCI_ERS_RESULT_NONE; > - eeh_pe_dev_traverse(pe, eeh_report_reset, &result); > - } > - > - /* All devices should claim they have recovered by now. */ > - if ((result != PCI_ERS_RESULT_RECOVERED) && > - (result != PCI_ERS_RESULT_NONE)) { > - printk(KERN_WARNING "EEH: Not recovered\n"); > - goto hard_fail; > - } > - > - /* Tell all device drivers that they can resume operations */ > - eeh_pe_dev_traverse(pe, eeh_report_resume, NULL); > - > - return; > - > -excess_failures: > - /* > - * About 90% of all real-life EEH failures in the field > - * are due to poorly seated PCI cards. Only 10% or so are > - * due to actual, failed cards. > - */ > - pr_err("EEH: PHB#%d-PE#%x has failed %d times in the\n" > - "last hour and has been permanently disabled.\n" > - "Please try reseating or replacing it.\n", > - pe->phb->global_number, pe->addr, > - pe->freeze_count); > - goto perm_error; > - > -hard_fail: > - pr_err("EEH: Unable to recover from failure from PHB#%d-PE#%x.\n" > - "Please try reseating or replacing it\n", > - pe->phb->global_number, pe->addr); > - > -perm_error: > - eeh_slot_error_detail(pe, EEH_LOG_PERM); > - > - /* Notify all devices that they're about to go down. */ > - eeh_pe_dev_traverse(pe, eeh_report_failure, NULL); > - > - /* Shut down the device drivers for good. */ > - if (frozen_bus) > - pcibios_remove_pci_devices(frozen_bus); > -} > - > diff --git a/arch/powerpc/platforms/pseries/eeh_event.c b/arch/powerpc/platforms/pseries/eeh_event.c > deleted file mode 100644 > index 185bedd..0000000 > --- a/arch/powerpc/platforms/pseries/eeh_event.c > +++ /dev/null > @@ -1,142 +0,0 @@ > -/* > - * 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 > - * the Free Software Foundation; either version 2 of the License, or > - * (at your option) any later version. > - * > - * This program is distributed in the hope that it will be useful, > - * but WITHOUT ANY WARRANTY; without even the implied warranty of > - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > - * GNU General Public License for more details. > - * > - * You should have received a copy of the GNU General Public License > - * along with this program; if not, write to the Free Software > - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA > - * > - * Copyright (c) 2005 Linas Vepstas <linas@linas.org> > - */ > - > -#include <linux/delay.h> > -#include <linux/list.h> > -#include <linux/mutex.h> > -#include <linux/sched.h> > -#include <linux/pci.h> > -#include <linux/slab.h> > -#include <linux/workqueue.h> > -#include <linux/kthread.h> > -#include <asm/eeh_event.h> > -#include <asm/ppc-pci.h> > - > -/** Overview: > - * EEH error states may be detected within exception handlers; > - * however, the recovery processing needs to occur asynchronously > - * in a normal kernel context and not an interrupt context. > - * This pair of routines creates an event and queues it onto a > - * work-queue, where a worker thread can drive recovery. > - */ > - > -/* EEH event workqueue setup. */ > -static DEFINE_SPINLOCK(eeh_eventlist_lock); > -LIST_HEAD(eeh_eventlist); > -static void eeh_thread_launcher(struct work_struct *); > -DECLARE_WORK(eeh_event_wq, eeh_thread_launcher); > - > -/* Serialize reset sequences for a given pci device */ > -DEFINE_MUTEX(eeh_event_mutex); > - > -/** > - * eeh_event_handler - Dispatch EEH events. > - * @dummy - unused > - * > - * 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. > - */ > -static int eeh_event_handler(void * dummy) > -{ > - unsigned long flags; > - struct eeh_event *event; > - struct eeh_pe *pe; > - > - spin_lock_irqsave(&eeh_eventlist_lock, flags); > - event = NULL; > - > - /* Unqueue the event, get ready to process. */ > - 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) > - return 0; > - > - /* Serialize processing of EEH events */ > - mutex_lock(&eeh_event_mutex); > - pe = event->pe; > - eeh_pe_state_mark(pe, EEH_PE_RECOVERING); > - pr_info("EEH: Detected PCI bus error on PHB#%d-PE#%x\n", > - pe->phb->global_number, pe->addr); > - > - set_current_state(TASK_INTERRUPTIBLE); /* Don't add to load average */ > - eeh_handle_event(pe); > - eeh_pe_state_clear(pe, EEH_PE_RECOVERING); > - > - kfree(event); > - mutex_unlock(&eeh_event_mutex); > - > - /* If there are no new errors after an hour, clear the counter. */ > - if (pe && pe->freeze_count > 0) { > - msleep_interruptible(3600*1000); > - if (pe->freeze_count > 0) > - pe->freeze_count--; > - > - } > - > - return 0; > -} > - > -/** > - * eeh_thread_launcher - Start kernel thread to handle EEH events > - * @dummy - unused > - * > - * This routine is called to start the kernel thread for processing > - * EEH event. > - */ > -static void eeh_thread_launcher(struct work_struct *dummy) > -{ > - if (IS_ERR(kthread_run(eeh_event_handler, NULL, "eehd"))) > - printk(KERN_ERR "Failed to start EEH daemon\n"); > -} > - > -/** > - * eeh_send_failure_event - Generate a PCI error event > - * @pe: EEH PE > - * > - * This routine can be called within an interrupt context; > - * the actual event will be delivered in a normal context > - * (from a workqueue). > - */ > -int eeh_send_failure_event(struct eeh_pe *pe) > -{ > - unsigned long flags; > - struct eeh_event *event; > - > - event = kzalloc(sizeof(*event), GFP_ATOMIC); > - if (!event) { > - pr_err("EEH: out of memory, event not handled\n"); > - return -ENOMEM; > - } > - event->pe = pe; > - > - /* 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); > - > - schedule_work(&eeh_event_wq); > - > - return 0; > -} > diff --git a/arch/powerpc/platforms/pseries/eeh_pe.c b/arch/powerpc/platforms/pseries/eeh_pe.c > deleted file mode 100644 > index 9d4a9e8..0000000 > --- a/arch/powerpc/platforms/pseries/eeh_pe.c > +++ /dev/null > @@ -1,653 +0,0 @@ > -/* > - * The file intends to implement PE based on the information from > - * platforms. Basically, there have 3 types of PEs: PHB/Bus/Device. > - * All the PEs should be organized as hierarchy tree. The first level > - * of the tree will be associated to existing PHBs since the particular > - * PE is only meaningful in one PHB domain. > - * > - * Copyright Benjamin Herrenschmidt & Gavin Shan, IBM Corporation 2012. > - * > - * 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 > - * the Free Software Foundation; either version 2 of the License, or > - * (at your option) any later version. > - * > - * This program is distributed in the hope that it will be useful, > - * but WITHOUT ANY WARRANTY; without even the implied warranty of > - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > - * GNU General Public License for more details. > - * > - * You should have received a copy of the GNU General Public License > - * along with this program; if not, write to the Free Software > - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA > - */ > - > -#include <linux/export.h> > -#include <linux/gfp.h> > -#include <linux/init.h> > -#include <linux/kernel.h> > -#include <linux/pci.h> > -#include <linux/string.h> > - > -#include <asm/pci-bridge.h> > -#include <asm/ppc-pci.h> > - > -static LIST_HEAD(eeh_phb_pe); > - > -/** > - * eeh_pe_alloc - Allocate PE > - * @phb: PCI controller > - * @type: PE type > - * > - * Allocate PE instance dynamically. > - */ > -static struct eeh_pe *eeh_pe_alloc(struct pci_controller *phb, int type) > -{ > - struct eeh_pe *pe; > - > - /* Allocate PHB PE */ > - pe = kzalloc(sizeof(struct eeh_pe), GFP_KERNEL); > - if (!pe) return NULL; > - > - /* Initialize PHB PE */ > - pe->type = type; > - pe->phb = phb; > - INIT_LIST_HEAD(&pe->child_list); > - INIT_LIST_HEAD(&pe->child); > - INIT_LIST_HEAD(&pe->edevs); > - > - return pe; > -} > - > -/** > - * eeh_phb_pe_create - Create PHB PE > - * @phb: PCI controller > - * > - * The function should be called while the PHB is detected during > - * system boot or PCI hotplug in order to create PHB PE. > - */ > -int eeh_phb_pe_create(struct pci_controller *phb) > -{ > - struct eeh_pe *pe; > - > - /* Allocate PHB PE */ > - pe = eeh_pe_alloc(phb, EEH_PE_PHB); > - if (!pe) { > - pr_err("%s: out of memory!\n", __func__); > - return -ENOMEM; > - } > - > - /* Put it into the list */ > - eeh_lock(); > - list_add_tail(&pe->child, &eeh_phb_pe); > - eeh_unlock(); > - > - pr_debug("EEH: Add PE for PHB#%d\n", phb->global_number); > - > - return 0; > -} > - > -/** > - * eeh_phb_pe_get - Retrieve PHB PE based on the given PHB > - * @phb: PCI controller > - * > - * The overall PEs form hierarchy tree. The first layer of the > - * hierarchy tree is composed of PHB PEs. The function is used > - * to retrieve the corresponding PHB PE according to the given PHB. > - */ > -static struct eeh_pe *eeh_phb_pe_get(struct pci_controller *phb) > -{ > - struct eeh_pe *pe; > - > - list_for_each_entry(pe, &eeh_phb_pe, child) { > - /* > - * Actually, we needn't check the type since > - * the PE for PHB has been determined when that > - * was created. > - */ > - if ((pe->type & EEH_PE_PHB) && pe->phb == phb) > - return pe; > - } > - > - return NULL; > -} > - > -/** > - * eeh_pe_next - Retrieve the next PE in the tree > - * @pe: current PE > - * @root: root PE > - * > - * The function is used to retrieve the next PE in the > - * hierarchy PE tree. > - */ > -static struct eeh_pe *eeh_pe_next(struct eeh_pe *pe, > - struct eeh_pe *root) > -{ > - struct list_head *next = pe->child_list.next; > - > - if (next == &pe->child_list) { > - while (1) { > - if (pe == root) > - return NULL; > - next = pe->child.next; > - if (next != &pe->parent->child_list) > - break; > - pe = pe->parent; > - } > - } > - > - return list_entry(next, struct eeh_pe, child); > -} > - > -/** > - * eeh_pe_traverse - Traverse PEs in the specified PHB > - * @root: root PE > - * @fn: callback > - * @flag: extra parameter to callback > - * > - * The function is used to traverse the specified PE and its > - * child PEs. The traversing is to be terminated once the > - * callback returns something other than NULL, or no more PEs > - * to be traversed. > - */ > -static void *eeh_pe_traverse(struct eeh_pe *root, > - eeh_traverse_func fn, void *flag) > -{ > - struct eeh_pe *pe; > - void *ret; > - > - for (pe = root; pe; pe = eeh_pe_next(pe, root)) { > - ret = fn(pe, flag); > - if (ret) return ret; > - } > - > - return NULL; > -} > - > -/** > - * eeh_pe_dev_traverse - Traverse the devices from the PE > - * @root: EEH PE > - * @fn: function callback > - * @flag: extra parameter to callback > - * > - * The function is used to traverse the devices of the specified > - * PE and its child PEs. > - */ > -void *eeh_pe_dev_traverse(struct eeh_pe *root, > - eeh_traverse_func fn, void *flag) > -{ > - struct eeh_pe *pe; > - struct eeh_dev *edev; > - void *ret; > - > - if (!root) { > - pr_warning("%s: Invalid PE %p\n", __func__, root); > - return NULL; > - } > - > - eeh_lock(); > - > - /* Traverse root PE */ > - for (pe = root; pe; pe = eeh_pe_next(pe, root)) { > - eeh_pe_for_each_dev(pe, edev) { > - ret = fn(edev, flag); > - if (ret) { > - eeh_unlock(); > - return ret; > - } > - } > - } > - > - eeh_unlock(); > - > - return NULL; > -} > - > -/** > - * __eeh_pe_get - Check the PE address > - * @data: EEH PE > - * @flag: EEH device > - * > - * For one particular PE, it can be identified by PE address > - * or tranditional BDF address. BDF address is composed of > - * Bus/Device/Function number. The extra data referred by flag > - * indicates which type of address should be used. > - */ > -static void *__eeh_pe_get(void *data, void *flag) > -{ > - struct eeh_pe *pe = (struct eeh_pe *)data; > - struct eeh_dev *edev = (struct eeh_dev *)flag; > - > - /* Unexpected PHB PE */ > - if (pe->type & EEH_PE_PHB) > - return NULL; > - > - /* We prefer PE address */ > - if (edev->pe_config_addr && > - (edev->pe_config_addr == pe->addr)) > - return pe; > - > - /* Try BDF address */ > - if (edev->pe_config_addr && > - (edev->config_addr == pe->config_addr)) > - return pe; > - > - return NULL; > -} > - > -/** > - * eeh_pe_get - Search PE based on the given address > - * @edev: EEH device > - * > - * Search the corresponding PE based on the specified address which > - * is included in the eeh device. The function is used to check if > - * the associated PE has been created against the PE address. It's > - * notable that the PE address has 2 format: traditional PE address > - * which is composed of PCI bus/device/function number, or unified > - * PE address. > - */ > -static struct eeh_pe *eeh_pe_get(struct eeh_dev *edev) > -{ > - struct eeh_pe *root = eeh_phb_pe_get(edev->phb); > - struct eeh_pe *pe; > - > - pe = eeh_pe_traverse(root, __eeh_pe_get, edev); > - > - return pe; > -} > - > -/** > - * eeh_pe_get_parent - Retrieve the parent PE > - * @edev: EEH device > - * > - * The whole PEs existing in the system are organized as hierarchy > - * tree. The function is used to retrieve the parent PE according > - * to the parent EEH device. > - */ > -static struct eeh_pe *eeh_pe_get_parent(struct eeh_dev *edev) > -{ > - struct device_node *dn; > - struct eeh_dev *parent; > - > - /* > - * It might have the case for the indirect parent > - * EEH device already having associated PE, but > - * the direct parent EEH device doesn't have yet. > - */ > - dn = edev->dn->parent; > - while (dn) { > - /* We're poking out of PCI territory */ > - if (!PCI_DN(dn)) return NULL; > - > - parent = of_node_to_eeh_dev(dn); > - /* We're poking out of PCI territory */ > - if (!parent) return NULL; > - > - if (parent->pe) > - return parent->pe; > - > - dn = dn->parent; > - } > - > - return NULL; > -} > - > -/** > - * eeh_add_to_parent_pe - Add EEH device to parent PE > - * @edev: EEH device > - * > - * Add EEH device to the parent PE. If the parent PE already > - * exists, the PE type will be changed to EEH_PE_BUS. Otherwise, > - * we have to create new PE to hold the EEH device and the new > - * PE will be linked to its parent PE as well. > - */ > -int eeh_add_to_parent_pe(struct eeh_dev *edev) > -{ > - struct eeh_pe *pe, *parent; > - > - eeh_lock(); > - > - /* > - * Search the PE has been existing or not according > - * to the PE address. If that has been existing, the > - * PE should be composed of PCI bus and its subordinate > - * components. > - */ > - pe = eeh_pe_get(edev); > - if (pe && !(pe->type & EEH_PE_INVALID)) { > - if (!edev->pe_config_addr) { > - eeh_unlock(); > - pr_err("%s: PE with addr 0x%x already exists\n", > - __func__, edev->config_addr); > - return -EEXIST; > - } > - > - /* Mark the PE as type of PCI bus */ > - pe->type = EEH_PE_BUS; > - edev->pe = pe; > - > - /* Put the edev to PE */ > - list_add_tail(&edev->list, &pe->edevs); > - eeh_unlock(); > - pr_debug("EEH: Add %s to Bus PE#%x\n", > - edev->dn->full_name, pe->addr); > - > - return 0; > - } else if (pe && (pe->type & EEH_PE_INVALID)) { > - list_add_tail(&edev->list, &pe->edevs); > - edev->pe = pe; > - /* > - * We're running to here because of PCI hotplug caused by > - * EEH recovery. We need clear EEH_PE_INVALID until the top. > - */ > - parent = pe; > - while (parent) { > - if (!(parent->type & EEH_PE_INVALID)) > - break; > - parent->type &= ~EEH_PE_INVALID; > - parent = parent->parent; > - } > - eeh_unlock(); > - pr_debug("EEH: Add %s to Device PE#%x, Parent PE#%x\n", > - edev->dn->full_name, pe->addr, pe->parent->addr); > - > - return 0; > - } > - > - /* Create a new EEH PE */ > - pe = eeh_pe_alloc(edev->phb, EEH_PE_DEVICE); > - if (!pe) { > - eeh_unlock(); > - pr_err("%s: out of memory!\n", __func__); > - return -ENOMEM; > - } > - pe->addr = edev->pe_config_addr; > - pe->config_addr = edev->config_addr; > - > - /* > - * Put the new EEH PE into hierarchy tree. If the parent > - * can't be found, the newly created PE will be attached > - * to PHB directly. Otherwise, we have to associate the > - * PE with its parent. > - */ > - parent = eeh_pe_get_parent(edev); > - if (!parent) { > - parent = eeh_phb_pe_get(edev->phb); > - if (!parent) { > - eeh_unlock(); > - pr_err("%s: No PHB PE is found (PHB Domain=%d)\n", > - __func__, edev->phb->global_number); > - edev->pe = NULL; > - kfree(pe); > - return -EEXIST; > - } > - } > - pe->parent = parent; > - > - /* > - * Put the newly created PE into the child list and > - * link the EEH device accordingly. > - */ > - list_add_tail(&pe->child, &parent->child_list); > - list_add_tail(&edev->list, &pe->edevs); > - edev->pe = pe; > - eeh_unlock(); > - pr_debug("EEH: Add %s to Device PE#%x, Parent PE#%x\n", > - edev->dn->full_name, pe->addr, pe->parent->addr); > - > - return 0; > -} > - > -/** > - * eeh_rmv_from_parent_pe - Remove one EEH device from the associated PE > - * @edev: EEH device > - * @purge_pe: remove PE or not > - * > - * The PE hierarchy tree might be changed when doing PCI hotplug. > - * Also, the PCI devices or buses could be removed from the system > - * during EEH recovery. So we have to call the function remove the > - * corresponding PE accordingly if necessary. > - */ > -int eeh_rmv_from_parent_pe(struct eeh_dev *edev, int purge_pe) > -{ > - struct eeh_pe *pe, *parent, *child; > - int cnt; > - > - if (!edev->pe) { > - pr_warning("%s: No PE found for EEH device %s\n", > - __func__, edev->dn->full_name); > - return -EEXIST; > - } > - > - eeh_lock(); > - > - /* Remove the EEH device */ > - pe = edev->pe; > - edev->pe = NULL; > - list_del(&edev->list); > - > - /* > - * Check if the parent PE includes any EEH devices. > - * If not, we should delete that. Also, we should > - * delete the parent PE if it doesn't have associated > - * child PEs and EEH devices. > - */ > - while (1) { > - parent = pe->parent; > - if (pe->type & EEH_PE_PHB) > - break; > - > - if (purge_pe) { > - if (list_empty(&pe->edevs) && > - list_empty(&pe->child_list)) { > - list_del(&pe->child); > - kfree(pe); > - } else { > - break; > - } > - } else { > - if (list_empty(&pe->edevs)) { > - cnt = 0; > - list_for_each_entry(child, &pe->child_list, child) { > - if (!(child->type & EEH_PE_INVALID)) { > - cnt++; > - break; > - } > - } > - > - if (!cnt) > - pe->type |= EEH_PE_INVALID; > - else > - break; > - } > - } > - > - pe = parent; > - } > - > - eeh_unlock(); > - > - return 0; > -} > - > -/** > - * __eeh_pe_state_mark - Mark the state for the PE > - * @data: EEH PE > - * @flag: state > - * > - * The function is used to mark the indicated state for the given > - * PE. Also, the associated PCI devices will be put into IO frozen > - * state as well. > - */ > -static void *__eeh_pe_state_mark(void *data, void *flag) > -{ > - struct eeh_pe *pe = (struct eeh_pe *)data; > - int state = *((int *)flag); > - struct eeh_dev *tmp; > - struct pci_dev *pdev; > - > - /* > - * Mark the PE with the indicated state. Also, > - * the associated PCI device will be put into > - * I/O frozen state to avoid I/O accesses from > - * the PCI device driver. > - */ > - pe->state |= state; > - eeh_pe_for_each_dev(pe, tmp) { > - pdev = eeh_dev_to_pci_dev(tmp); > - if (pdev) > - pdev->error_state = pci_channel_io_frozen; > - } > - > - return NULL; > -} > - > -/** > - * eeh_pe_state_mark - Mark specified state for PE and its associated device > - * @pe: EEH PE > - * > - * EEH error affects the current PE and its child PEs. The function > - * is used to mark appropriate state for the affected PEs and the > - * associated devices. > - */ > -void eeh_pe_state_mark(struct eeh_pe *pe, int state) > -{ > - eeh_lock(); > - eeh_pe_traverse(pe, __eeh_pe_state_mark, &state); > - eeh_unlock(); > -} > - > -/** > - * __eeh_pe_state_clear - Clear state for the PE > - * @data: EEH PE > - * @flag: state > - * > - * The function is used to clear the indicated state from the > - * given PE. Besides, we also clear the check count of the PE > - * as well. > - */ > -static void *__eeh_pe_state_clear(void *data, void *flag) > -{ > - struct eeh_pe *pe = (struct eeh_pe *)data; > - int state = *((int *)flag); > - > - pe->state &= ~state; > - pe->check_count = 0; > - > - return NULL; > -} > - > -/** > - * eeh_pe_state_clear - Clear state for the PE and its children > - * @pe: PE > - * @state: state to be cleared > - * > - * When the PE and its children has been recovered from error, > - * we need clear the error state for that. The function is used > - * for the purpose. > - */ > -void eeh_pe_state_clear(struct eeh_pe *pe, int state) > -{ > - eeh_lock(); > - eeh_pe_traverse(pe, __eeh_pe_state_clear, &state); > - eeh_unlock(); > -} > - > -/** > - * eeh_restore_one_device_bars - Restore the Base Address Registers for one device > - * @data: EEH device > - * @flag: Unused > - * > - * 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 void *eeh_restore_one_device_bars(void *data, void *flag) > -{ > - int i; > - u32 cmd; > - struct eeh_dev *edev = (struct eeh_dev *)data; > - struct device_node *dn = eeh_dev_to_of_node(edev); > - > - for (i = 4; i < 10; i++) > - eeh_ops->write_config(dn, i*4, 4, edev->config_space[i]); > - /* 12 == Expansion ROM Address */ > - eeh_ops->write_config(dn, 12*4, 4, edev->config_space[12]); > - > -#define BYTE_SWAP(OFF) (8*((OFF)/4)+3-(OFF)) > -#define SAVED_BYTE(OFF) (((u8 *)(edev->config_space))[BYTE_SWAP(OFF)]) > - > - eeh_ops->write_config(dn, PCI_CACHE_LINE_SIZE, 1, > - SAVED_BYTE(PCI_CACHE_LINE_SIZE)); > - eeh_ops->write_config(dn, PCI_LATENCY_TIMER, 1, > - SAVED_BYTE(PCI_LATENCY_TIMER)); > - > - /* max latency, min grant, interrupt pin and line */ > - eeh_ops->write_config(dn, 15*4, 4, edev->config_space[15]); > - > - /* > - * Restore PERR & SERR bits, some devices require it, > - * don't touch the other command bits > - */ > - eeh_ops->read_config(dn, PCI_COMMAND, 4, &cmd); > - if (edev->config_space[1] & PCI_COMMAND_PARITY) > - cmd |= PCI_COMMAND_PARITY; > - else > - cmd &= ~PCI_COMMAND_PARITY; > - if (edev->config_space[1] & PCI_COMMAND_SERR) > - cmd |= PCI_COMMAND_SERR; > - else > - cmd &= ~PCI_COMMAND_SERR; > - eeh_ops->write_config(dn, PCI_COMMAND, 4, cmd); > - > - return NULL; > -} > - > -/** > - * eeh_pe_restore_bars - Restore the PCI config space info > - * @pe: EEH PE > - * > - * This routine performs a recursive walk to the children > - * of this device as well. > - */ > -void eeh_pe_restore_bars(struct eeh_pe *pe) > -{ > - /* > - * We needn't take the EEH lock since eeh_pe_dev_traverse() > - * will take that. > - */ > - eeh_pe_dev_traverse(pe, eeh_restore_one_device_bars, NULL); > -} > - > -/** > - * eeh_pe_bus_get - Retrieve PCI bus according to the given PE > - * @pe: EEH PE > - * > - * Retrieve the PCI bus according to the given PE. Basically, > - * there're 3 types of PEs: PHB/Bus/Device. For PHB PE, the > - * primary PCI bus will be retrieved. The parent bus will be > - * returned for BUS PE. However, we don't have associated PCI > - * bus for DEVICE PE. > - */ > -struct pci_bus *eeh_pe_bus_get(struct eeh_pe *pe) > -{ > - struct pci_bus *bus = NULL; > - struct eeh_dev *edev; > - struct pci_dev *pdev; > - > - eeh_lock(); > - > - if (pe->type & EEH_PE_PHB) { > - bus = pe->phb->bus; > - } else if (pe->type & EEH_PE_BUS || > - pe->type & EEH_PE_DEVICE) { > - edev = list_first_entry(&pe->edevs, struct eeh_dev, list); > - pdev = eeh_dev_to_pci_dev(edev); > - if (pdev) > - bus = pdev->bus; > - } > - > - eeh_unlock(); > - > - return bus; > -} > diff --git a/arch/powerpc/platforms/pseries/eeh_sysfs.c b/arch/powerpc/platforms/pseries/eeh_sysfs.c > deleted file mode 100644 > index d377083..0000000 > --- a/arch/powerpc/platforms/pseries/eeh_sysfs.c > +++ /dev/null > @@ -1,75 +0,0 @@ > -/* > - * Sysfs entries for PCI Error Recovery for PAPR-compliant platform. > - * Copyright IBM Corporation 2007 > - * Copyright Linas Vepstas <linas@austin.ibm.com> 2007 > - * > - * All rights reserved. > - * > - * 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 > - * the Free Software Foundation; either version 2 of the License, or (at > - * your option) any later version. > - * > - * This program is distributed in the hope that it will be useful, but > - * WITHOUT ANY WARRANTY; without even the implied warranty of > - * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or > - * NON INFRINGEMENT. See the GNU General Public License for more > - * details. > - * > - * You should have received a copy of the GNU General Public License > - * along with this program; if not, write to the Free Software > - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. > - * > - * Send comments and feedback to Linas Vepstas <linas@austin.ibm.com> > - */ > -#include <linux/pci.h> > -#include <linux/stat.h> > -#include <asm/ppc-pci.h> > -#include <asm/pci-bridge.h> > - > -/** > - * EEH_SHOW_ATTR -- Create sysfs entry for eeh statistic > - * @_name: name of file in sysfs directory > - * @_memb: name of member in struct pci_dn to access > - * @_format: printf format for display > - * > - * All of the attributes look very similar, so just > - * auto-gen a cut-n-paste routine to display them. > - */ > -#define EEH_SHOW_ATTR(_name,_memb,_format) \ > -static ssize_t eeh_show_##_name(struct device *dev, \ > - struct device_attribute *attr, char *buf) \ > -{ \ > - struct pci_dev *pdev = to_pci_dev(dev); \ > - struct eeh_dev *edev = pci_dev_to_eeh_dev(pdev); \ > - \ > - if (!edev) \ > - return 0; \ > - \ > - return sprintf(buf, _format "\n", edev->_memb); \ > -} \ > -static DEVICE_ATTR(_name, S_IRUGO, eeh_show_##_name, NULL); > - > -EEH_SHOW_ATTR(eeh_mode, mode, "0x%x"); > -EEH_SHOW_ATTR(eeh_config_addr, config_addr, "0x%x"); > -EEH_SHOW_ATTR(eeh_pe_config_addr, pe_config_addr, "0x%x"); > - > -void eeh_sysfs_add_device(struct pci_dev *pdev) > -{ > - int rc=0; > - > - rc += device_create_file(&pdev->dev, &dev_attr_eeh_mode); > - rc += device_create_file(&pdev->dev, &dev_attr_eeh_config_addr); > - rc += device_create_file(&pdev->dev, &dev_attr_eeh_pe_config_addr); > - > - if (rc) > - printk(KERN_WARNING "EEH: Unable to create sysfs entries\n"); > -} > - > -void eeh_sysfs_remove_device(struct pci_dev *pdev) > -{ > - device_remove_file(&pdev->dev, &dev_attr_eeh_mode); > - device_remove_file(&pdev->dev, &dev_attr_eeh_config_addr); > - device_remove_file(&pdev->dev, &dev_attr_eeh_pe_config_addr); > -} > - > diff --git a/arch/powerpc/platforms/pseries/pci_dlpar.c b/arch/powerpc/platforms/pseries/pci_dlpar.c > index c91b22b..efe6137 100644 > --- a/arch/powerpc/platforms/pseries/pci_dlpar.c > +++ b/arch/powerpc/platforms/pseries/pci_dlpar.c > @@ -64,91 +64,6 @@ pcibios_find_pci_bus(struct device_node *dn) > } > EXPORT_SYMBOL_GPL(pcibios_find_pci_bus); > > -/** > - * __pcibios_remove_pci_devices - remove all devices under this bus > - * @bus: the indicated PCI bus > - * @purge_pe: destroy the PE on removal of PCI devices > - * > - * Remove all of the PCI devices under this bus both from the > - * linux pci device tree, and from the powerpc EEH address cache. > - * By default, the corresponding PE will be destroied during the > - * normal PCI hotplug path. For PCI hotplug during EEH recovery, > - * the corresponding PE won't be destroied and deallocated. > - */ > -void __pcibios_remove_pci_devices(struct pci_bus *bus, int purge_pe) > -{ > - struct pci_dev *dev, *tmp; > - struct pci_bus *child_bus; > - > - /* First go down child busses */ > - list_for_each_entry(child_bus, &bus->children, node) > - __pcibios_remove_pci_devices(child_bus, purge_pe); > - > - pr_debug("PCI: Removing devices on bus %04x:%02x\n", > - pci_domain_nr(bus), bus->number); > - list_for_each_entry_safe(dev, tmp, &bus->devices, bus_list) { > - pr_debug(" * Removing %s...\n", pci_name(dev)); > - eeh_remove_bus_device(dev, purge_pe); > - pci_stop_and_remove_bus_device(dev); > - } > -} > - > -/** > - * pcibios_remove_pci_devices - remove all devices under this bus > - * > - * Remove all of the PCI devices under this bus both from the > - * linux pci device tree, and from the powerpc EEH address cache. > - */ > -void pcibios_remove_pci_devices(struct pci_bus *bus) > -{ > - __pcibios_remove_pci_devices(bus, 1); > -} > -EXPORT_SYMBOL_GPL(pcibios_remove_pci_devices); > - > -/** > - * pcibios_add_pci_devices - adds new pci devices to bus > - * > - * This routine will find and fixup new pci devices under > - * the indicated bus. This routine presumes that there > - * might already be some devices under this bridge, so > - * it carefully tries to add only new devices. (And that > - * is how this routine differs from other, similar pcibios > - * routines.) > - */ > -void pcibios_add_pci_devices(struct pci_bus * bus) > -{ > - int slotno, num, mode, pass, max; > - struct pci_dev *dev; > - struct device_node *dn = pci_bus_to_OF_node(bus); > - > - eeh_add_device_tree_early(dn); > - > - mode = PCI_PROBE_NORMAL; > - if (ppc_md.pci_probe_mode) > - mode = ppc_md.pci_probe_mode(bus); > - > - if (mode == PCI_PROBE_DEVTREE) { > - /* use ofdt-based probe */ > - of_rescan_bus(dn, bus); > - } else if (mode == PCI_PROBE_NORMAL) { > - /* use legacy probe */ > - slotno = PCI_SLOT(PCI_DN(dn->child)->devfn); > - num = pci_scan_slot(bus, PCI_DEVFN(slotno, 0)); > - if (!num) > - return; > - pcibios_setup_bus_devices(bus); > - max = bus->busn_res.start; > - for (pass=0; pass < 2; pass++) > - list_for_each_entry(dev, &bus->devices, bus_list) { > - if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE || > - dev->hdr_type == PCI_HEADER_TYPE_CARDBUS) > - max = pci_scan_bridge(bus, dev, max, pass); > - } > - } > - pcibios_finish_adding_to_bus(bus); > -} > -EXPORT_SYMBOL_GPL(pcibios_add_pci_devices); > - > struct pci_controller *init_phb_dynamic(struct device_node *dn) > { > struct pci_controller *phb; > -- > 1.7.5.4 > > _______________________________________________ > Linuxppc-dev mailing list > Linuxppc-dev@lists.ozlabs.org > https://lists.ozlabs.org/listinfo/linuxppc-dev >
diff --git a/arch/powerpc/kernel/Makefile b/arch/powerpc/kernel/Makefile index f960a79..5826906 100644 --- a/arch/powerpc/kernel/Makefile +++ b/arch/powerpc/kernel/Makefile @@ -58,6 +58,8 @@ obj-$(CONFIG_RTAS_PROC) += rtas-proc.o obj-$(CONFIG_LPARCFG) += lparcfg.o obj-$(CONFIG_IBMVIO) += vio.o obj-$(CONFIG_IBMEBUS) += ibmebus.o +obj-$(CONFIG_EEH) += eeh.o eeh_pe.o eeh_dev.o eeh_cache.o \ + eeh_driver.o eeh_event.o eeh_sysfs.o obj-$(CONFIG_GENERIC_TBSYNC) += smp-tbsync.o obj-$(CONFIG_CRASH_DUMP) += crash_dump.o obj-$(CONFIG_FA_DUMP) += fadump.o @@ -100,7 +102,7 @@ obj-$(CONFIG_PPC_UDBG_16550) += legacy_serial.o udbg_16550.o obj-$(CONFIG_STACKTRACE) += stacktrace.o obj-$(CONFIG_SWIOTLB) += dma-swiotlb.o -pci64-$(CONFIG_PPC64) += pci_dn.o isa-bridge.o +pci64-$(CONFIG_PPC64) += pci_hotplug.o pci_dn.o isa-bridge.o obj-$(CONFIG_PCI) += pci_$(CONFIG_WORD_SIZE).o $(pci64-y) \ pci-common.o pci_of_scan.o obj-$(CONFIG_PCI_MSI) += msi.o diff --git a/arch/powerpc/kernel/eeh.c b/arch/powerpc/kernel/eeh.c new file mode 100644 index 0000000..6b73d6c --- /dev/null +++ b/arch/powerpc/kernel/eeh.c @@ -0,0 +1,942 @@ +/* + * Copyright IBM Corporation 2001, 2005, 2006 + * Copyright Dave Engebretsen & Todd Inglett 2001 + * Copyright Linas Vepstas 2005, 2006 + * Copyright 2001-2012 IBM Corporation. + * + * 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 + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Please address comments and feedback to Linas Vepstas <linas@austin.ibm.com> + */ + +#include <linux/delay.h> +#include <linux/sched.h> +#include <linux/init.h> +#include <linux/list.h> +#include <linux/pci.h> +#include <linux/proc_fs.h> +#include <linux/rbtree.h> +#include <linux/seq_file.h> +#include <linux/spinlock.h> +#include <linux/export.h> +#include <linux/of.h> + +#include <linux/atomic.h> +#include <asm/eeh.h> +#include <asm/eeh_event.h> +#include <asm/io.h> +#include <asm/machdep.h> +#include <asm/ppc-pci.h> +#include <asm/rtas.h> + + +/** Overview: + * EEH, or "Extended Error Handling" is a PCI bridge technology for + * dealing with PCI bus errors that can't be dealt with within the + * usual PCI framework, except by check-stopping the CPU. Systems + * that are designed for high-availability/reliability cannot afford + * to crash due to a "mere" PCI error, thus the need for EEH. + * An EEH-capable bridge operates by converting a detected error + * into a "slot freeze", taking the PCI adapter off-line, making + * the slot behave, from the OS'es point of view, as if the slot + * were "empty": all reads return 0xff's and all writes are silently + * ignored. EEH slot isolation events can be triggered by parity + * errors on the address or data busses (e.g. during posted writes), + * 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 + * freeze events are buggy device drivers, buggy device microcode, + * or buggy device hardware. This is because any attempt by the + * device to bus-master data to a memory address that is not + * assigned to the device will trigger a slot freeze. (The idea + * is to prevent devices-gone-wild from corrupting system memory). + * Buggy hardware/drivers will have a miserable time co-existing + * with EEH. + * + * Ideally, a PCI device driver, when suspecting that an isolation + * event has occurred (e.g. by reading 0xff's), will then ask EEH + * whether this is the case, and then take appropriate steps to + * reset the PCI slot, the PCI device, and then resume operations. + * However, until that day, the checking is done here, with the + * eeh_check_failure() routine embedded in the MMIO macros. If + * the slot is found to be isolated, an "EEH Event" is synthesized + * and sent out for processing. + */ + +/* If a device driver keeps reading an MMIO register in an interrupt + * handler after a slot isolation event, it might be broken. + * This sets the threshold for how many read attempts we allow + * before printing an error message. + */ +#define EEH_MAX_FAILS 2100000 + +/* Time to wait for a PCI slot to report status, in milliseconds */ +#define PCI_BUS_RESET_WAIT_MSEC (60*1000) + +/* Platform dependent EEH operations */ +struct eeh_ops *eeh_ops = NULL; + +int eeh_subsystem_enabled; +EXPORT_SYMBOL(eeh_subsystem_enabled); + +/* + * EEH probe mode support. The intention is to support multiple + * platforms for EEH. Some platforms like pSeries do PCI emunation + * based on device tree. However, other platforms like powernv probe + * PCI devices from hardware. The flag is used to distinguish that. + * In addition, struct eeh_ops::probe would be invoked for particular + * OF node or PCI device so that the corresponding PE would be created + * there. + */ +int eeh_probe_mode; + +/* Global EEH mutex */ +DEFINE_MUTEX(eeh_mutex); + +/* Lock to avoid races due to multiple reports of an error */ +static DEFINE_RAW_SPINLOCK(confirm_error_lock); + +/* Buffer for reporting pci register dumps. Its here in BSS, and + * not dynamically alloced, so that it ends up in RMO where RTAS + * can access it. + */ +#define EEH_PCI_REGS_LOG_LEN 4096 +static unsigned char pci_regs_buf[EEH_PCI_REGS_LOG_LEN]; + +/* + * The struct is used to maintain the EEH global statistic + * information. Besides, the EEH global statistics will be + * exported to user space through procfs + */ +struct eeh_stats { + u64 no_device; /* PCI device not found */ + u64 no_dn; /* OF node not found */ + u64 no_cfg_addr; /* Config address not found */ + u64 ignored_check; /* EEH check skipped */ + u64 total_mmio_ffs; /* Total EEH checks */ + u64 false_positives; /* Unnecessary EEH checks */ + u64 slot_resets; /* PE reset */ +}; + +static struct eeh_stats eeh_stats; + +#define IS_BRIDGE(class_code) (((class_code)<<16) == PCI_BASE_CLASS_BRIDGE) + +/** + * eeh_gather_pci_data - Copy assorted PCI config space registers to buff + * @edev: device to report data for + * @buf: point to buffer in which to log + * @len: amount of room in buffer + * + * This routine captures assorted PCI configuration space data, + * and puts them into a buffer for RTAS error logging. + */ +static size_t eeh_gather_pci_data(struct eeh_dev *edev, char * buf, size_t len) +{ + struct device_node *dn = eeh_dev_to_of_node(edev); + struct pci_dev *dev = eeh_dev_to_pci_dev(edev); + u32 cfg; + int cap, i; + int n = 0; + + n += scnprintf(buf+n, len-n, "%s\n", dn->full_name); + printk(KERN_WARNING "EEH: of node=%s\n", dn->full_name); + + eeh_ops->read_config(dn, PCI_VENDOR_ID, 4, &cfg); + n += scnprintf(buf+n, len-n, "dev/vend:%08x\n", cfg); + printk(KERN_WARNING "EEH: PCI device/vendor: %08x\n", cfg); + + eeh_ops->read_config(dn, PCI_COMMAND, 4, &cfg); + n += scnprintf(buf+n, len-n, "cmd/stat:%x\n", cfg); + printk(KERN_WARNING "EEH: PCI cmd/status register: %08x\n", cfg); + + if (!dev) { + printk(KERN_WARNING "EEH: no PCI device for this of node\n"); + return n; + } + + /* Gather bridge-specific registers */ + if (dev->class >> 16 == PCI_BASE_CLASS_BRIDGE) { + eeh_ops->read_config(dn, PCI_SEC_STATUS, 2, &cfg); + n += scnprintf(buf+n, len-n, "sec stat:%x\n", cfg); + printk(KERN_WARNING "EEH: Bridge secondary status: %04x\n", cfg); + + eeh_ops->read_config(dn, PCI_BRIDGE_CONTROL, 2, &cfg); + n += scnprintf(buf+n, len-n, "brdg ctl:%x\n", cfg); + printk(KERN_WARNING "EEH: Bridge control: %04x\n", cfg); + } + + /* Dump out the PCI-X command and status regs */ + cap = pci_find_capability(dev, PCI_CAP_ID_PCIX); + if (cap) { + eeh_ops->read_config(dn, cap, 4, &cfg); + n += scnprintf(buf+n, len-n, "pcix-cmd:%x\n", cfg); + printk(KERN_WARNING "EEH: PCI-X cmd: %08x\n", cfg); + + eeh_ops->read_config(dn, cap+4, 4, &cfg); + n += scnprintf(buf+n, len-n, "pcix-stat:%x\n", cfg); + printk(KERN_WARNING "EEH: PCI-X status: %08x\n", cfg); + } + + /* If PCI-E capable, dump PCI-E cap 10, and the AER */ + cap = pci_find_capability(dev, PCI_CAP_ID_EXP); + if (cap) { + n += scnprintf(buf+n, len-n, "pci-e cap10:\n"); + printk(KERN_WARNING + "EEH: PCI-E capabilities and status follow:\n"); + + for (i=0; i<=8; i++) { + eeh_ops->read_config(dn, cap+4*i, 4, &cfg); + n += scnprintf(buf+n, len-n, "%02x:%x\n", 4*i, cfg); + printk(KERN_WARNING "EEH: PCI-E %02x: %08x\n", i, cfg); + } + + cap = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ERR); + if (cap) { + n += scnprintf(buf+n, len-n, "pci-e AER:\n"); + printk(KERN_WARNING + "EEH: PCI-E AER capability register set follows:\n"); + + for (i=0; i<14; i++) { + eeh_ops->read_config(dn, cap+4*i, 4, &cfg); + n += scnprintf(buf+n, len-n, "%02x:%x\n", 4*i, cfg); + printk(KERN_WARNING "EEH: PCI-E AER %02x: %08x\n", i, cfg); + } + } + } + + return n; +} + +/** + * eeh_slot_error_detail - Generate combined log including driver log and error log + * @pe: EEH PE + * @severity: temporary or permanent error log + * + * This routine should be called to generate the combined log, which + * is comprised of driver log and error log. The driver log is figured + * out from the config space of the corresponding PCI device, while + * the error log is fetched through platform dependent function call. + */ +void eeh_slot_error_detail(struct eeh_pe *pe, int severity) +{ + size_t loglen = 0; + struct eeh_dev *edev; + + eeh_pci_enable(pe, EEH_OPT_THAW_MMIO); + eeh_ops->configure_bridge(pe); + eeh_pe_restore_bars(pe); + + pci_regs_buf[0] = 0; + eeh_pe_for_each_dev(pe, edev) { + loglen += eeh_gather_pci_data(edev, pci_regs_buf, + EEH_PCI_REGS_LOG_LEN); + } + + eeh_ops->get_log(pe, severity, pci_regs_buf, loglen); +} + +/** + * eeh_token_to_phys - Convert EEH address token to phys address + * @token: I/O token, should be address in the form 0xA.... + * + * This routine should be called to convert virtual I/O address + * to physical one. + */ +static inline unsigned long eeh_token_to_phys(unsigned long token) +{ + 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_dev_check_failure - Check if all 1's data is due to EEH slot freeze + * @edev: eeh device + * + * Check for an EEH failure for the given device node. Call this + * 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. This routine + * will query firmware for the EEH status. + * + * Returns 0 if there has not been an EEH error; otherwise returns + * a non-zero value and queues up a slot isolation event notification. + * + * It is safe to call this routine in an interrupt context. + */ +int eeh_dev_check_failure(struct eeh_dev *edev) +{ + int ret; + unsigned long flags; + struct device_node *dn; + struct pci_dev *dev; + struct eeh_pe *pe; + int rc = 0; + const char *location; + + eeh_stats.total_mmio_ffs++; + + if (!eeh_subsystem_enabled) + return 0; + + if (!edev) { + eeh_stats.no_dn++; + return 0; + } + dn = eeh_dev_to_of_node(edev); + dev = eeh_dev_to_pci_dev(edev); + pe = edev->pe; + + /* Access to IO BARs might get this far and still not want checking. */ + if (!pe) { + eeh_stats.ignored_check++; + pr_debug("EEH: Ignored check for %s %s\n", + eeh_pci_name(dev), dn->full_name); + return 0; + } + + if (!pe->addr && !pe->config_addr) { + eeh_stats.no_cfg_addr++; + 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. + * 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. + */ + raw_spin_lock_irqsave(&confirm_error_lock, flags); + rc = 1; + if (pe->state & EEH_PE_ISOLATED) { + pe->check_count++; + if (pe->check_count % EEH_MAX_FAILS == 0) { + location = of_get_property(dn, "ibm,loc-code", NULL); + printk(KERN_ERR "EEH: %d reads ignored for recovering device at " + "location=%s driver=%s pci addr=%s\n", + pe->check_count, location, + eeh_driver_name(dev), eeh_pci_name(dev)); + printk(KERN_ERR "EEH: Might be infinite loop in %s driver\n", + eeh_driver_name(dev)); + dump_stack(); + } + goto dn_unlock; + } + + /* + * Now test for an EEH failure. This is VERY expensive. + * Note that the eeh_config_addr may be a parent device + * in the case of a device behind a bridge, or it may be + * function zero of a multi-function device. + * In any case they must share a common PHB. + */ + ret = eeh_ops->get_state(pe, NULL); + + /* Note that config-io to empty slots may fail; + * they are empty when they don't have children. + * We will punt with the following conditions: Failure to get + * PE's state, EEH not support and Permanently unavailable + * state, PE is in good state. + */ + if ((ret < 0) || + (ret == EEH_STATE_NOT_SUPPORT) || + (ret & (EEH_STATE_MMIO_ACTIVE | EEH_STATE_DMA_ACTIVE)) == + (EEH_STATE_MMIO_ACTIVE | EEH_STATE_DMA_ACTIVE)) { + eeh_stats.false_positives++; + pe->false_positives++; + rc = 0; + goto dn_unlock; + } + + eeh_stats.slot_resets++; + + /* Avoid repeated reports of this failure, including problems + * with other functions on this device, and functions under + * bridges. + */ + eeh_pe_state_mark(pe, EEH_PE_ISOLATED); + raw_spin_unlock_irqrestore(&confirm_error_lock, flags); + + eeh_send_failure_event(pe); + + /* Most EEH events are due to device driver bugs. Having + * a stack trace will help the device-driver authors figure + * out what happened. So print that out. + */ + WARN(1, "EEH: failure detected\n"); + return 1; + +dn_unlock: + raw_spin_unlock_irqrestore(&confirm_error_lock, flags); + return rc; +} + +EXPORT_SYMBOL_GPL(eeh_dev_check_failure); + +/** + * 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.... + * @val: value, should be all 1's (XXX why do we need this arg??) + * + * 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 + * find out if this is due to an EEH slot freeze event. This routine + * will query firmware for the EEH status. + * + * Note this routine is safe to call in an interrupt context. + */ +unsigned long eeh_check_failure(const volatile void __iomem *token, unsigned long val) +{ + unsigned long addr; + struct eeh_dev *edev; + + /* Finding the phys addr + pci device; this is pretty quick. */ + addr = eeh_token_to_phys((unsigned long __force) token); + edev = eeh_addr_cache_get_dev(addr); + if (!edev) { + eeh_stats.no_device++; + return val; + } + + eeh_dev_check_failure(edev); + + pci_dev_put(eeh_dev_to_pci_dev(edev)); + return val; +} + +EXPORT_SYMBOL(eeh_check_failure); + + +/** + * eeh_pci_enable - Enable MMIO or DMA transfers for this slot + * @pe: EEH PE + * + * This routine should be called to reenable frozen MMIO or DMA + * so that it would work correctly again. It's useful while doing + * recovery or log collection on the indicated device. + */ +int eeh_pci_enable(struct eeh_pe *pe, int function) +{ + int rc; + + rc = eeh_ops->set_option(pe, function); + if (rc) + pr_warning("%s: Unexpected state change %d on PHB#%d-PE#%x, err=%d\n", + __func__, function, pe->phb->global_number, pe->addr, rc); + + rc = eeh_ops->wait_state(pe, PCI_BUS_RESET_WAIT_MSEC); + if (rc > 0 && (rc & EEH_STATE_MMIO_ENABLED) && + (function == EEH_OPT_THAW_MMIO)) + return 0; + + return rc; +} + +/** + * pcibios_set_pcie_slot_reset - Set PCI-E reset state + * @dev: pci device struct + * @state: reset state to enter + * + * Return value: + * 0 if success + */ +int pcibios_set_pcie_reset_state(struct pci_dev *dev, enum pcie_reset_state state) +{ + struct eeh_dev *edev = pci_dev_to_eeh_dev(dev); + struct eeh_pe *pe = edev->pe; + + if (!pe) { + pr_err("%s: No PE found on PCI device %s\n", + __func__, pci_name(dev)); + return -EINVAL; + } + + switch (state) { + case pcie_deassert_reset: + eeh_ops->reset(pe, EEH_RESET_DEACTIVATE); + break; + case pcie_hot_reset: + eeh_ops->reset(pe, EEH_RESET_HOT); + break; + case pcie_warm_reset: + eeh_ops->reset(pe, EEH_RESET_FUNDAMENTAL); + break; + default: + return -EINVAL; + }; + + return 0; +} + +/** + * eeh_set_pe_freset - Check the required reset for the indicated device + * @data: EEH device + * @flag: return value + * + * Each device might have its preferred reset type: fundamental or + * hot reset. The routine is used to collected the information for + * the indicated device and its children so that the bunch of the + * devices could be reset properly. + */ +static void *eeh_set_dev_freset(void *data, void *flag) +{ + struct pci_dev *dev; + unsigned int *freset = (unsigned int *)flag; + struct eeh_dev *edev = (struct eeh_dev *)data; + + dev = eeh_dev_to_pci_dev(edev); + if (dev) + *freset |= dev->needs_freset; + + return NULL; +} + +/** + * eeh_reset_pe_once - Assert the pci #RST line for 1/4 second + * @pe: EEH PE + * + * Assert the PCI #RST line for 1/4 second. + */ +static void eeh_reset_pe_once(struct eeh_pe *pe) +{ + unsigned int freset = 0; + + /* Determine type of EEH reset required for + * Partitionable Endpoint, a hot-reset (1) + * or a fundamental reset (3). + * A fundamental reset required by any device under + * Partitionable Endpoint trumps hot-reset. + */ + eeh_pe_dev_traverse(pe, eeh_set_dev_freset, &freset); + + if (freset) + eeh_ops->reset(pe, EEH_RESET_FUNDAMENTAL); + else + eeh_ops->reset(pe, EEH_RESET_HOT); + + /* 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_pe_state_clear(pe, EEH_PE_ISOLATED); + + eeh_ops->reset(pe, EEH_RESET_DEACTIVATE); + + /* 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); +} + +/** + * eeh_reset_pe - Reset the indicated PE + * @pe: EEH PE + * + * This routine should be called to reset indicated device, including + * PE. A PE might include multiple PCI devices and sometimes PCI bridges + * might be involved as well. + */ +int eeh_reset_pe(struct eeh_pe *pe) +{ + int i, rc; + + /* Take three shots at resetting the bus */ + for (i=0; i<3; i++) { + eeh_reset_pe_once(pe); + + rc = eeh_ops->wait_state(pe, PCI_BUS_RESET_WAIT_MSEC); + if (rc == (EEH_STATE_MMIO_ACTIVE | EEH_STATE_DMA_ACTIVE)) + return 0; + + if (rc < 0) { + pr_err("%s: Unrecoverable slot failure on PHB#%d-PE#%x", + __func__, pe->phb->global_number, pe->addr); + return -1; + } + pr_err("EEH: bus reset %d failed on PHB#%d-PE#%x, rc=%d\n", + i+1, pe->phb->global_number, pe->addr, rc); + } + + return -1; +} + +/** + * eeh_save_bars - Save device bars + * @edev: PCI device associated EEH device + * + * Save the values of the device bars. Unlike the restore + * routine, this routine is *not* recursive. This is because + * PCI devices are added individually; but, for the restore, + * an entire slot is reset at a time. + */ +void eeh_save_bars(struct eeh_dev *edev) +{ + int i; + struct device_node *dn; + + if (!edev) + return; + dn = eeh_dev_to_of_node(edev); + + for (i = 0; i < 16; i++) + eeh_ops->read_config(dn, i * 4, 4, &edev->config_space[i]); +} + +/** + * eeh_ops_register - Register platform dependent EEH operations + * @ops: platform dependent EEH operations + * + * Register the platform dependent EEH operation callback + * functions. The platform should call this function before + * any other EEH operations. + */ +int __init eeh_ops_register(struct eeh_ops *ops) +{ + if (!ops->name) { + pr_warning("%s: Invalid EEH ops name for %p\n", + __func__, ops); + return -EINVAL; + } + + if (eeh_ops && eeh_ops != ops) { + pr_warning("%s: EEH ops of platform %s already existing (%s)\n", + __func__, eeh_ops->name, ops->name); + return -EEXIST; + } + + eeh_ops = ops; + + return 0; +} + +/** + * eeh_ops_unregister - Unreigster platform dependent EEH operations + * @name: name of EEH platform operations + * + * Unregister the platform dependent EEH operation callback + * functions. + */ +int __exit eeh_ops_unregister(const char *name) +{ + if (!name || !strlen(name)) { + pr_warning("%s: Invalid EEH ops name\n", + __func__); + return -EINVAL; + } + + if (eeh_ops && !strcmp(eeh_ops->name, name)) { + eeh_ops = NULL; + return 0; + } + + return -EEXIST; +} + +/** + * eeh_init - EEH initialization + * + * Initialize EEH by trying to enable it for all of the adapters in the system. + * As a side effect we can determine here if eeh is supported at all. + * Note that we leave EEH on so failed config cycles won't cause a machine + * check. If a user turns off EEH for a particular adapter they are really + * telling Linux to ignore errors. Some hardware (e.g. POWER5) won't + * grant access to a slot if EEH isn't enabled, and so we always enable + * EEH for all slots/all devices. + * + * The eeh-force-off option disables EEH checking globally, for all slots. + * Even if force-off is set, the EEH hardware is still enabled, so that + * newer systems can boot. + */ +static int __init eeh_init(void) +{ + struct pci_controller *hose, *tmp; + struct device_node *phb; + int ret; + + /* call platform initialization function */ + if (!eeh_ops) { + pr_warning("%s: Platform EEH operation not found\n", + __func__); + return -EEXIST; + } else if ((ret = eeh_ops->init())) { + pr_warning("%s: Failed to call platform init function (%d)\n", + __func__, ret); + return ret; + } + + raw_spin_lock_init(&confirm_error_lock); + + /* Enable EEH for all adapters */ + if (eeh_probe_mode_devtree()) { + list_for_each_entry_safe(hose, tmp, + &hose_list, list_node) { + phb = hose->dn; + traverse_pci_devices(phb, eeh_ops->of_probe, NULL); + } + } + + if (eeh_subsystem_enabled) + pr_info("EEH: PCI Enhanced I/O Error Handling Enabled\n"); + else + pr_warning("EEH: No capable adapters found\n"); + + return ret; +} + +core_initcall_sync(eeh_init); + +/** + * eeh_add_device_early - Enable EEH for the indicated device_node + * @dn: device node for which to set up EEH + * + * This routine must be used to perform EEH initialization for PCI + * devices that were added after system boot (e.g. hotplug, dlpar). + * This routine must be called before any i/o is performed to the + * adapter (inluding any config-space i/o). + * Whether this actually enables EEH or not for this device depends + * on the CEC architecture, type of the device, on earlier boot + * command-line arguments & etc. + */ +static void eeh_add_device_early(struct device_node *dn) +{ + struct pci_controller *phb; + + if (!of_node_to_eeh_dev(dn)) + return; + phb = of_node_to_eeh_dev(dn)->phb; + + /* USB Bus children of PCI devices will not have BUID's */ + if (NULL == phb || 0 == phb->buid) + return; + + /* FIXME: hotplug support on POWERNV */ + eeh_ops->of_probe(dn, NULL); +} + +/** + * eeh_add_device_tree_early - Enable EEH for the indicated device + * @dn: device node + * + * This routine must be used to perform EEH initialization for the + * indicated PCI device that was added after system boot (e.g. + * hotplug, dlpar). + */ +void eeh_add_device_tree_early(struct device_node *dn) +{ + struct device_node *sib; + + for_each_child_of_node(dn, sib) + eeh_add_device_tree_early(sib); + eeh_add_device_early(dn); +} +EXPORT_SYMBOL_GPL(eeh_add_device_tree_early); + +/** + * eeh_add_device_late - Perform EEH initialization for the indicated pci device + * @dev: pci device for which to set up EEH + * + * This routine must be used to complete EEH initialization for PCI + * devices that were added after system boot (e.g. hotplug, dlpar). + */ +static void eeh_add_device_late(struct pci_dev *dev) +{ + struct device_node *dn; + struct eeh_dev *edev; + + if (!dev || !eeh_subsystem_enabled) + return; + + pr_debug("EEH: Adding device %s\n", pci_name(dev)); + + dn = pci_device_to_OF_node(dev); + edev = of_node_to_eeh_dev(dn); + if (edev->pdev == dev) { + pr_debug("EEH: Already referenced !\n"); + return; + } + WARN_ON(edev->pdev); + + pci_dev_get(dev); + edev->pdev = dev; + dev->dev.archdata.edev = edev; + + eeh_addr_cache_insert_dev(dev); +} + +/** + * eeh_add_device_tree_late - Perform EEH initialization for the indicated PCI bus + * @bus: PCI bus + * + * This routine must be used to perform EEH initialization for PCI + * devices which are attached to the indicated PCI bus. The PCI bus + * is added after system boot through hotplug or dlpar. + */ +void eeh_add_device_tree_late(struct pci_bus *bus) +{ + struct pci_dev *dev; + + list_for_each_entry(dev, &bus->devices, bus_list) { + eeh_add_device_late(dev); + if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE) { + struct pci_bus *subbus = dev->subordinate; + if (subbus) + eeh_add_device_tree_late(subbus); + } + } +} +EXPORT_SYMBOL_GPL(eeh_add_device_tree_late); + +/** + * eeh_add_sysfs_files - Add EEH sysfs files for the indicated PCI bus + * @bus: PCI bus + * + * This routine must be used to add EEH sysfs files for PCI + * devices which are attached to the indicated PCI bus. The PCI bus + * is added after system boot through hotplug or dlpar. + */ +void eeh_add_sysfs_files(struct pci_bus *bus) +{ + struct pci_dev *dev; + + list_for_each_entry(dev, &bus->devices, bus_list) { + eeh_sysfs_add_device(dev); + if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE) { + struct pci_bus *subbus = dev->subordinate; + if (subbus) + eeh_add_sysfs_files(subbus); + } + } +} +EXPORT_SYMBOL_GPL(eeh_add_sysfs_files); + +/** + * eeh_remove_device - Undo EEH setup for the indicated pci device + * @dev: pci device to be removed + * @purge_pe: remove the PE or not + * + * This routine should be called when a device is removed from + * a running system (e.g. by hotplug or dlpar). It unregisters + * the PCI device from the EEH subsystem. I/O errors affecting + * this device will no longer be detected after this call; thus, + * i/o errors affecting this slot may leave this device unusable. + */ +static void eeh_remove_device(struct pci_dev *dev, int purge_pe) +{ + struct eeh_dev *edev; + + if (!dev || !eeh_subsystem_enabled) + return; + edev = pci_dev_to_eeh_dev(dev); + + /* Unregister the device with the EEH/PCI address search system */ + pr_debug("EEH: Removing device %s\n", pci_name(dev)); + + if (!edev || !edev->pdev) { + pr_debug("EEH: Not referenced !\n"); + return; + } + edev->pdev = NULL; + dev->dev.archdata.edev = NULL; + pci_dev_put(dev); + + eeh_rmv_from_parent_pe(edev, purge_pe); + eeh_addr_cache_rmv_dev(dev); + eeh_sysfs_remove_device(dev); +} + +/** + * eeh_remove_bus_device - Undo EEH setup for the indicated PCI device + * @dev: PCI device + * @purge_pe: remove the corresponding PE or not + * + * This routine must be called when a device is removed from the + * running system through hotplug or dlpar. The corresponding + * PCI address cache will be removed. + */ +void eeh_remove_bus_device(struct pci_dev *dev, int purge_pe) +{ + struct pci_bus *bus = dev->subordinate; + struct pci_dev *child, *tmp; + + eeh_remove_device(dev, purge_pe); + + if (bus && dev->hdr_type == PCI_HEADER_TYPE_BRIDGE) { + list_for_each_entry_safe(child, tmp, &bus->devices, bus_list) + eeh_remove_bus_device(child, purge_pe); + } +} +EXPORT_SYMBOL_GPL(eeh_remove_bus_device); + +static int proc_eeh_show(struct seq_file *m, void *v) +{ + if (0 == eeh_subsystem_enabled) { + seq_printf(m, "EEH Subsystem is globally disabled\n"); + seq_printf(m, "eeh_total_mmio_ffs=%llu\n", eeh_stats.total_mmio_ffs); + } else { + seq_printf(m, "EEH Subsystem is enabled\n"); + seq_printf(m, + "no device=%llu\n" + "no device node=%llu\n" + "no config address=%llu\n" + "check not wanted=%llu\n" + "eeh_total_mmio_ffs=%llu\n" + "eeh_false_positives=%llu\n" + "eeh_slot_resets=%llu\n", + eeh_stats.no_device, + eeh_stats.no_dn, + eeh_stats.no_cfg_addr, + eeh_stats.ignored_check, + eeh_stats.total_mmio_ffs, + eeh_stats.false_positives, + eeh_stats.slot_resets); + } + + return 0; +} + +static int proc_eeh_open(struct inode *inode, struct file *file) +{ + return single_open(file, proc_eeh_show, NULL); +} + +static const struct file_operations proc_eeh_operations = { + .open = proc_eeh_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int __init eeh_init_proc(void) +{ + if (machine_is(pseries)) + proc_create("powerpc/eeh", 0, NULL, &proc_eeh_operations); + return 0; +} +__initcall(eeh_init_proc); diff --git a/arch/powerpc/kernel/eeh_cache.c b/arch/powerpc/kernel/eeh_cache.c new file mode 100644 index 0000000..5a4c879 --- /dev/null +++ b/arch/powerpc/kernel/eeh_cache.c @@ -0,0 +1,319 @@ +/* + * PCI address cache; allows the lookup of PCI devices based on I/O address + * + * Copyright IBM Corporation 2004 + * Copyright Linas Vepstas <linas@austin.ibm.com> 2004 + * + * 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 + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/list.h> +#include <linux/pci.h> +#include <linux/rbtree.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/atomic.h> +#include <asm/pci-bridge.h> +#include <asm/ppc-pci.h> + + +/** + * The pci address cache subsystem. This subsystem places + * PCI device address resources into a red-black tree, sorted + * according to the address range, so that given only an i/o + * address, the corresponding PCI device can be **quickly** + * found. It is safe to perform an address lookup in an interrupt + * context; this ability is an important feature. + * + * Currently, the only customer of this code is the EEH subsystem; + * thus, this code has been somewhat tailored to suit EEH better. + * In particular, the cache does *not* hold the addresses of devices + * for which EEH is not enabled. + * + * (Implementation Note: The RB tree seems to be better/faster + * than any hash algo I could think of for this problem, even + * with the penalty of slow pointer chases for d-cache misses). + */ +struct pci_io_addr_range { + struct rb_node rb_node; + unsigned long addr_lo; + unsigned long addr_hi; + struct eeh_dev *edev; + struct pci_dev *pcidev; + unsigned int flags; +}; + +static struct pci_io_addr_cache { + struct rb_root rb_root; + spinlock_t piar_lock; +} pci_io_addr_cache_root; + +static inline struct eeh_dev *__eeh_addr_cache_get_device(unsigned long addr) +{ + struct rb_node *n = pci_io_addr_cache_root.rb_root.rb_node; + + while (n) { + struct pci_io_addr_range *piar; + piar = rb_entry(n, struct pci_io_addr_range, rb_node); + + if (addr < piar->addr_lo) { + n = n->rb_left; + } else { + if (addr > piar->addr_hi) { + n = n->rb_right; + } else { + pci_dev_get(piar->pcidev); + return piar->edev; + } + } + } + + return NULL; +} + +/** + * eeh_addr_cache_get_dev - Get device, given only address + * @addr: mmio (PIO) phys address or i/o port number + * + * Given an mmio phys address, or a port number, find a pci device + * that implements this address. Be sure to pci_dev_put the device + * when finished. I/O port numbers are assumed to be offset + * from zero (that is, they do *not* have pci_io_addr added in). + * It is safe to call this function within an interrupt. + */ +struct eeh_dev *eeh_addr_cache_get_dev(unsigned long addr) +{ + struct eeh_dev *edev; + unsigned long flags; + + spin_lock_irqsave(&pci_io_addr_cache_root.piar_lock, flags); + edev = __eeh_addr_cache_get_device(addr); + spin_unlock_irqrestore(&pci_io_addr_cache_root.piar_lock, flags); + return edev; +} + +#ifdef DEBUG +/* + * Handy-dandy debug print routine, does nothing more + * than print out the contents of our addr cache. + */ +static void eeh_addr_cache_print(struct pci_io_addr_cache *cache) +{ + struct rb_node *n; + int cnt = 0; + + n = rb_first(&cache->rb_root); + while (n) { + struct pci_io_addr_range *piar; + piar = rb_entry(n, struct pci_io_addr_range, rb_node); + pr_debug("PCI: %s addr range %d [%lx-%lx]: %s\n", + (piar->flags & IORESOURCE_IO) ? "i/o" : "mem", cnt, + piar->addr_lo, piar->addr_hi, pci_name(piar->pcidev)); + cnt++; + n = rb_next(n); + } +} +#endif + +/* Insert address range into the rb tree. */ +static struct pci_io_addr_range * +eeh_addr_cache_insert(struct pci_dev *dev, unsigned long alo, + unsigned long ahi, unsigned int flags) +{ + struct rb_node **p = &pci_io_addr_cache_root.rb_root.rb_node; + struct rb_node *parent = NULL; + struct pci_io_addr_range *piar; + + /* Walk tree, find a place to insert into tree */ + while (*p) { + parent = *p; + piar = rb_entry(parent, struct pci_io_addr_range, rb_node); + if (ahi < piar->addr_lo) { + p = &parent->rb_left; + } else if (alo > piar->addr_hi) { + p = &parent->rb_right; + } else { + if (dev != piar->pcidev || + alo != piar->addr_lo || ahi != piar->addr_hi) { + pr_warning("PIAR: overlapping address range\n"); + } + return piar; + } + } + piar = kzalloc(sizeof(struct pci_io_addr_range), GFP_ATOMIC); + if (!piar) + return NULL; + + pci_dev_get(dev); + piar->addr_lo = alo; + piar->addr_hi = ahi; + piar->edev = pci_dev_to_eeh_dev(dev); + piar->pcidev = dev; + piar->flags = flags; + +#ifdef DEBUG + pr_debug("PIAR: insert range=[%lx:%lx] dev=%s\n", + alo, ahi, pci_name(dev)); +#endif + + rb_link_node(&piar->rb_node, parent, p); + rb_insert_color(&piar->rb_node, &pci_io_addr_cache_root.rb_root); + + return piar; +} + +static void __eeh_addr_cache_insert_dev(struct pci_dev *dev) +{ + struct device_node *dn; + struct eeh_dev *edev; + int i; + + dn = pci_device_to_OF_node(dev); + if (!dn) { + pr_warning("PCI: no pci dn found for dev=%s\n", pci_name(dev)); + return; + } + + edev = of_node_to_eeh_dev(dn); + if (!edev) { + pr_warning("PCI: no EEH dev found for dn=%s\n", + dn->full_name); + return; + } + + /* Skip any devices for which EEH is not enabled. */ + if (!edev->pe) { +#ifdef DEBUG + pr_info("PCI: skip building address cache for=%s - %s\n", + pci_name(dev), dn->full_name); +#endif + return; + } + + /* Walk resources on this device, poke them into the tree */ + for (i = 0; i < DEVICE_COUNT_RESOURCE; i++) { + unsigned long start = pci_resource_start(dev,i); + unsigned long end = pci_resource_end(dev,i); + unsigned int flags = pci_resource_flags(dev,i); + + /* We are interested only bus addresses, not dma or other stuff */ + if (0 == (flags & (IORESOURCE_IO | IORESOURCE_MEM))) + continue; + if (start == 0 || ~start == 0 || end == 0 || ~end == 0) + continue; + eeh_addr_cache_insert(dev, start, end, flags); + } +} + +/** + * eeh_addr_cache_insert_dev - Add a device to the address cache + * @dev: PCI device whose I/O addresses we are interested in. + * + * In order to support the fast lookup of devices based on addresses, + * we maintain a cache of devices that can be quickly searched. + * This routine adds a device to that cache. + */ +void eeh_addr_cache_insert_dev(struct pci_dev *dev) +{ + unsigned long flags; + + /* Ignore PCI bridges */ + if ((dev->class >> 16) == PCI_BASE_CLASS_BRIDGE) + return; + + spin_lock_irqsave(&pci_io_addr_cache_root.piar_lock, flags); + __eeh_addr_cache_insert_dev(dev); + spin_unlock_irqrestore(&pci_io_addr_cache_root.piar_lock, flags); +} + +static inline void __eeh_addr_cache_rmv_dev(struct pci_dev *dev) +{ + struct rb_node *n; + +restart: + n = rb_first(&pci_io_addr_cache_root.rb_root); + while (n) { + struct pci_io_addr_range *piar; + piar = rb_entry(n, struct pci_io_addr_range, rb_node); + + if (piar->pcidev == dev) { + rb_erase(n, &pci_io_addr_cache_root.rb_root); + pci_dev_put(piar->pcidev); + kfree(piar); + goto restart; + } + n = rb_next(n); + } +} + +/** + * eeh_addr_cache_rmv_dev - remove pci device from addr cache + * @dev: device to remove + * + * Remove a device from the addr-cache tree. + * This is potentially expensive, since it will walk + * the tree multiple times (once per resource). + * But so what; device removal doesn't need to be that fast. + */ +void eeh_addr_cache_rmv_dev(struct pci_dev *dev) +{ + unsigned long flags; + + spin_lock_irqsave(&pci_io_addr_cache_root.piar_lock, flags); + __eeh_addr_cache_rmv_dev(dev); + spin_unlock_irqrestore(&pci_io_addr_cache_root.piar_lock, flags); +} + +/** + * eeh_addr_cache_build - Build a cache of I/O addresses + * + * Build a cache of pci i/o addresses. This cache will be used to + * find the pci device that corresponds to a given address. + * This routine scans all pci busses to build the cache. + * Must be run late in boot process, after the pci controllers + * have been scanned for devices (after all device resources are known). + */ +void __init eeh_addr_cache_build(void) +{ + struct device_node *dn; + struct eeh_dev *edev; + struct pci_dev *dev = NULL; + + spin_lock_init(&pci_io_addr_cache_root.piar_lock); + + for_each_pci_dev(dev) { + eeh_addr_cache_insert_dev(dev); + + dn = pci_device_to_OF_node(dev); + if (!dn) + continue; + + edev = of_node_to_eeh_dev(dn); + if (!edev) + continue; + + pci_dev_get(dev); /* matching put is in eeh_remove_device() */ + dev->dev.archdata.edev = edev; + edev->pdev = dev; + + eeh_sysfs_add_device(dev); + } + +#ifdef DEBUG + /* Verify tree built up above, echo back the list of addrs. */ + eeh_addr_cache_print(&pci_io_addr_cache_root); +#endif +} + diff --git a/arch/powerpc/kernel/eeh_dev.c b/arch/powerpc/kernel/eeh_dev.c new file mode 100644 index 0000000..1efa28f --- /dev/null +++ b/arch/powerpc/kernel/eeh_dev.c @@ -0,0 +1,112 @@ +/* + * The file intends to implement dynamic creation of EEH device, which will + * be bound with OF node and PCI device simutaneously. The EEH devices would + * be foundamental information for EEH core components to work proerly. Besides, + * We have to support multiple situations where dynamic creation of EEH device + * is required: + * + * 1) Before PCI emunation starts, we need create EEH devices according to the + * PCI sensitive OF nodes. + * 2) When PCI emunation is done, we need do the binding between PCI device and + * the associated EEH device. + * 3) DR (Dynamic Reconfiguration) would create PCI sensitive OF node. EEH device + * will be created while PCI sensitive OF node is detected from DR. + * 4) PCI hotplug needs redoing the binding between PCI device and EEH device. If + * PHB is newly inserted, we also need create EEH devices accordingly. + * + * Copyright Benjamin Herrenschmidt & Gavin Shan, IBM Corporation 2012. + * + * 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 + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/export.h> +#include <linux/gfp.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/pci.h> +#include <linux/string.h> + +#include <asm/pci-bridge.h> +#include <asm/ppc-pci.h> + +/** + * eeh_dev_init - Create EEH device according to OF node + * @dn: device node + * @data: PHB + * + * It will create EEH device according to the given OF node. The function + * might be called by PCI emunation, DR, PHB hotplug. + */ +void *eeh_dev_init(struct device_node *dn, void *data) +{ + struct pci_controller *phb = data; + struct eeh_dev *edev; + + /* Allocate EEH device */ + edev = kzalloc(sizeof(*edev), GFP_KERNEL); + if (!edev) { + pr_warning("%s: out of memory\n", __func__); + return NULL; + } + + /* Associate EEH device with OF node */ + PCI_DN(dn)->edev = edev; + edev->dn = dn; + edev->phb = phb; + INIT_LIST_HEAD(&edev->list); + + return NULL; +} + +/** + * eeh_dev_phb_init_dynamic - Create EEH devices for devices included in PHB + * @phb: PHB + * + * Scan the PHB OF node and its child association, then create the + * EEH devices accordingly + */ +void eeh_dev_phb_init_dynamic(struct pci_controller *phb) +{ + struct device_node *dn = phb->dn; + + /* EEH PE for PHB */ + eeh_phb_pe_create(phb); + + /* EEH device for PHB */ + eeh_dev_init(dn, phb); + + /* EEH devices for children OF nodes */ + traverse_pci_devices(dn, eeh_dev_init, phb); +} + +/** + * eeh_dev_phb_init - Create EEH devices for devices included in existing PHBs + * + * Scan all the existing PHBs and create EEH devices for their OF + * nodes and their children OF nodes + */ +static int __init eeh_dev_phb_init(void) +{ + struct pci_controller *phb, *tmp; + + list_for_each_entry_safe(phb, tmp, &hose_list, list_node) + eeh_dev_phb_init_dynamic(phb); + + pr_info("EEH: devices created\n"); + + return 0; +} + +core_initcall(eeh_dev_phb_init); diff --git a/arch/powerpc/kernel/eeh_driver.c b/arch/powerpc/kernel/eeh_driver.c new file mode 100644 index 0000000..a3fefb6 --- /dev/null +++ b/arch/powerpc/kernel/eeh_driver.c @@ -0,0 +1,552 @@ +/* + * PCI Error Recovery Driver for RPA-compliant PPC64 platform. + * Copyright IBM Corp. 2004 2005 + * Copyright Linas Vepstas <linas@linas.org> 2004, 2005 + * + * All rights reserved. + * + * 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 + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or + * NON INFRINGEMENT. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Send comments and feedback to Linas Vepstas <linas@austin.ibm.com> + */ +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <asm/eeh.h> +#include <asm/eeh_event.h> +#include <asm/ppc-pci.h> +#include <asm/pci-bridge.h> +#include <asm/prom.h> +#include <asm/rtas.h> + +/** + * eeh_pcid_name - Retrieve name of PCI device driver + * @pdev: PCI device + * + * This routine is used to retrieve the name of PCI device driver + * if that's valid. + */ +static inline const char *eeh_pcid_name(struct pci_dev *pdev) +{ + if (pdev && pdev->dev.driver) + return pdev->dev.driver->name; + return ""; +} + +/** + * eeh_pcid_get - Get the PCI device driver + * @pdev: PCI device + * + * The function is used to retrieve the PCI device driver for + * the indicated PCI device. Besides, we will increase the reference + * of the PCI device driver to prevent that being unloaded on + * the fly. Otherwise, kernel crash would be seen. + */ +static inline struct pci_driver *eeh_pcid_get(struct pci_dev *pdev) +{ + if (!pdev || !pdev->driver) + return NULL; + + if (!try_module_get(pdev->driver->driver.owner)) + return NULL; + + return pdev->driver; +} + +/** + * eeh_pcid_put - Dereference on the PCI device driver + * @pdev: PCI device + * + * The function is called to do dereference on the PCI device + * driver of the indicated PCI device. + */ +static inline void eeh_pcid_put(struct pci_dev *pdev) +{ + if (!pdev || !pdev->driver) + return; + + module_put(pdev->driver->driver.owner); +} + +#if 0 +static void print_device_node_tree(struct pci_dn *pdn, int dent) +{ + int i; + struct device_node *pc; + + if (!pdn) + return; + for (i = 0; i < dent; i++) + printk(" "); + printk("dn=%s mode=%x \tcfg_addr=%x pe_addr=%x \tfull=%s\n", + pdn->node->name, pdn->eeh_mode, pdn->eeh_config_addr, + pdn->eeh_pe_config_addr, pdn->node->full_name); + dent += 3; + pc = pdn->node->child; + while (pc) { + print_device_node_tree(PCI_DN(pc), dent); + pc = pc->sibling; + } +} +#endif + +/** + * eeh_disable_irq - Disable interrupt for the recovering device + * @dev: PCI device + * + * This routine must be called when reporting temporary or permanent + * error to the particular PCI device to disable interrupt of that + * device. If the device has enabled MSI or MSI-X interrupt, we needn't + * do real work because EEH should freeze DMA transfers for those PCI + * devices encountering EEH errors, which includes MSI or MSI-X. + */ +static void eeh_disable_irq(struct pci_dev *dev) +{ + struct eeh_dev *edev = pci_dev_to_eeh_dev(dev); + + /* Don't disable MSI and MSI-X interrupts. They are + * effectively disabled by the DMA Stopped state + * when an EEH error occurs. + */ + if (dev->msi_enabled || dev->msix_enabled) + return; + + if (!irq_has_action(dev->irq)) + return; + + edev->mode |= EEH_DEV_IRQ_DISABLED; + disable_irq_nosync(dev->irq); +} + +/** + * eeh_enable_irq - Enable interrupt for the recovering device + * @dev: PCI device + * + * This routine must be called to enable interrupt while failed + * device could be resumed. + */ +static void eeh_enable_irq(struct pci_dev *dev) +{ + struct eeh_dev *edev = pci_dev_to_eeh_dev(dev); + + if ((edev->mode) & EEH_DEV_IRQ_DISABLED) { + edev->mode &= ~EEH_DEV_IRQ_DISABLED; + enable_irq(dev->irq); + } +} + +/** + * eeh_report_error - Report pci error to each device driver + * @data: eeh device + * @userdata: return value + * + * Report an EEH error to each device driver, collect up and + * merge the device driver responses. Cumulative response + * passed back in "userdata". + */ +static void *eeh_report_error(void *data, void *userdata) +{ + struct eeh_dev *edev = (struct eeh_dev *)data; + struct pci_dev *dev = eeh_dev_to_pci_dev(edev); + enum pci_ers_result rc, *res = userdata; + struct pci_driver *driver; + + /* We might not have the associated PCI device, + * then we should continue for next one. + */ + if (!dev) return NULL; + dev->error_state = pci_channel_io_frozen; + + driver = eeh_pcid_get(dev); + if (!driver) return NULL; + + eeh_disable_irq(dev); + + if (!driver->err_handler || + !driver->err_handler->error_detected) { + eeh_pcid_put(dev); + return NULL; + } + + rc = driver->err_handler->error_detected(dev, pci_channel_io_frozen); + + /* A driver that needs a reset trumps all others */ + if (rc == PCI_ERS_RESULT_NEED_RESET) *res = rc; + if (*res == PCI_ERS_RESULT_NONE) *res = rc; + + eeh_pcid_put(dev); + return NULL; +} + +/** + * eeh_report_mmio_enabled - Tell drivers that MMIO has been enabled + * @data: eeh device + * @userdata: return value + * + * Tells each device driver that IO ports, MMIO and config space I/O + * are now enabled. Collects up and merges the device driver responses. + * Cumulative response passed back in "userdata". + */ +static void *eeh_report_mmio_enabled(void *data, void *userdata) +{ + struct eeh_dev *edev = (struct eeh_dev *)data; + struct pci_dev *dev = eeh_dev_to_pci_dev(edev); + enum pci_ers_result rc, *res = userdata; + struct pci_driver *driver; + + driver = eeh_pcid_get(dev); + if (!driver) return NULL; + + if (!driver->err_handler || + !driver->err_handler->mmio_enabled) { + eeh_pcid_put(dev); + return NULL; + } + + rc = driver->err_handler->mmio_enabled(dev); + + /* A driver that needs a reset trumps all others */ + if (rc == PCI_ERS_RESULT_NEED_RESET) *res = rc; + if (*res == PCI_ERS_RESULT_NONE) *res = rc; + + eeh_pcid_put(dev); + return NULL; +} + +/** + * eeh_report_reset - Tell device that slot has been reset + * @data: eeh device + * @userdata: return value + * + * This routine must be called while EEH tries to reset particular + * PCI device so that the associated PCI device driver could take + * some actions, usually to save data the driver needs so that the + * driver can work again while the device is recovered. + */ +static void *eeh_report_reset(void *data, void *userdata) +{ + struct eeh_dev *edev = (struct eeh_dev *)data; + struct pci_dev *dev = eeh_dev_to_pci_dev(edev); + enum pci_ers_result rc, *res = userdata; + struct pci_driver *driver; + + if (!dev) return NULL; + dev->error_state = pci_channel_io_normal; + + driver = eeh_pcid_get(dev); + if (!driver) return NULL; + + eeh_enable_irq(dev); + + if (!driver->err_handler || + !driver->err_handler->slot_reset) { + eeh_pcid_put(dev); + return NULL; + } + + rc = driver->err_handler->slot_reset(dev); + if ((*res == PCI_ERS_RESULT_NONE) || + (*res == PCI_ERS_RESULT_RECOVERED)) *res = rc; + if (*res == PCI_ERS_RESULT_DISCONNECT && + rc == PCI_ERS_RESULT_NEED_RESET) *res = rc; + + eeh_pcid_put(dev); + return NULL; +} + +/** + * eeh_report_resume - Tell device to resume normal operations + * @data: eeh device + * @userdata: return value + * + * This routine must be called to notify the device driver that it + * could resume so that the device driver can do some initialization + * to make the recovered device work again. + */ +static void *eeh_report_resume(void *data, void *userdata) +{ + struct eeh_dev *edev = (struct eeh_dev *)data; + struct pci_dev *dev = eeh_dev_to_pci_dev(edev); + struct pci_driver *driver; + + if (!dev) return NULL; + dev->error_state = pci_channel_io_normal; + + driver = eeh_pcid_get(dev); + if (!driver) return NULL; + + eeh_enable_irq(dev); + + if (!driver->err_handler || + !driver->err_handler->resume) { + eeh_pcid_put(dev); + return NULL; + } + + driver->err_handler->resume(dev); + + eeh_pcid_put(dev); + return NULL; +} + +/** + * eeh_report_failure - Tell device driver that device is dead. + * @data: eeh device + * @userdata: return value + * + * This informs the device driver that the device is permanently + * dead, and that no further recovery attempts will be made on it. + */ +static void *eeh_report_failure(void *data, void *userdata) +{ + struct eeh_dev *edev = (struct eeh_dev *)data; + struct pci_dev *dev = eeh_dev_to_pci_dev(edev); + struct pci_driver *driver; + + if (!dev) return NULL; + dev->error_state = pci_channel_io_perm_failure; + + driver = eeh_pcid_get(dev); + if (!driver) return NULL; + + eeh_disable_irq(dev); + + if (!driver->err_handler || + !driver->err_handler->error_detected) { + eeh_pcid_put(dev); + return NULL; + } + + driver->err_handler->error_detected(dev, pci_channel_io_perm_failure); + + eeh_pcid_put(dev); + return NULL; +} + +/** + * eeh_reset_device - Perform actual reset of a pci slot + * @pe: EEH PE + * @bus: PCI bus corresponding to the isolcated slot + * + * This routine must be called to do reset on the indicated PE. + * During the reset, udev might be invoked because those affected + * PCI devices will be removed and then added. + */ +static int eeh_reset_device(struct eeh_pe *pe, struct pci_bus *bus) +{ + int cnt, rc; + + /* pcibios will clear the counter; save the value */ + cnt = pe->freeze_count; + + /* + * We don't remove the corresponding PE instances because + * we need the information afterwords. The attached EEH + * devices are expected to be attached soon when calling + * into pcibios_add_pci_devices(). + */ + if (bus) + __pcibios_remove_pci_devices(bus, 0); + + /* Reset the pci controller. (Asserts RST#; resets config space). + * Reconfigure bridges and devices. Don't try to bring the system + * up if the reset failed for some reason. + */ + rc = eeh_reset_pe(pe); + if (rc) + return rc; + + /* Restore PE */ + eeh_ops->configure_bridge(pe); + eeh_pe_restore_bars(pe); + + /* Give the system 5 seconds to finish running the user-space + * hotplug shutdown scripts, e.g. ifdown for ethernet. Yes, + * this is a hack, but if we don't do this, and try to bring + * the device up before the scripts have taken it down, + * potentially weird things happen. + */ + if (bus) { + ssleep(5); + pcibios_add_pci_devices(bus); + } + pe->freeze_count = cnt; + + return 0; +} + +/* The longest amount of time to wait for a pci device + * to come back on line, in seconds. + */ +#define MAX_WAIT_FOR_RECOVERY 150 + +/** + * eeh_handle_event - Reset a PCI device after hard lockup. + * @pe: EEH PE + * + * While PHB detects address or data parity errors on particular PCI + * slot, the associated PE will be frozen. Besides, DMA's occurring + * to wild addresses (which usually happen due to bugs in device + * drivers or in PCI adapter firmware) can cause EEH error. #SERR, + * #PERR or other misc PCI-related errors also can trigger EEH errors. + * + * Recovery process consists of unplugging the device driver (which + * generated hotplug events to userspace), then issuing a PCI #RST to + * the device, then reconfiguring the PCI config space for all bridges + * & devices under this slot, and then finally restarting the device + * drivers (which cause a second set of hotplug events to go out to + * userspace). + */ +void eeh_handle_event(struct eeh_pe *pe) +{ + struct pci_bus *frozen_bus; + int rc = 0; + enum pci_ers_result result = PCI_ERS_RESULT_NONE; + + frozen_bus = eeh_pe_bus_get(pe); + if (!frozen_bus) { + pr_err("%s: Cannot find PCI bus for PHB#%d-PE#%x\n", + __func__, pe->phb->global_number, pe->addr); + return; + } + + pe->freeze_count++; + if (pe->freeze_count > EEH_MAX_ALLOWED_FREEZES) + goto excess_failures; + pr_warning("EEH: This PCI device has failed %d times in the last hour\n", + pe->freeze_count); + + /* Walk the various device drivers attached to this slot through + * a reset sequence, giving each an opportunity to do what it needs + * to accomplish the reset. Each child gets a report of the + * status ... if any child can't handle the reset, then the entire + * slot is dlpar removed and added. + */ + eeh_pe_dev_traverse(pe, eeh_report_error, &result); + + /* Get the current PCI slot state. This can take a long time, + * sometimes over 3 seconds for certain systems. + */ + rc = eeh_ops->wait_state(pe, MAX_WAIT_FOR_RECOVERY*1000); + if (rc < 0 || rc == EEH_STATE_NOT_SUPPORT) { + printk(KERN_WARNING "EEH: Permanent failure\n"); + goto hard_fail; + } + + /* Since rtas may enable MMIO when posting the error log, + * don't post the error log until after all dev drivers + * have been informed. + */ + eeh_slot_error_detail(pe, EEH_LOG_TEMP); + + /* If all device drivers were EEH-unaware, then shut + * down all of the device drivers, and hope they + * go down willingly, without panicing the system. + */ + if (result == PCI_ERS_RESULT_NONE) { + rc = eeh_reset_device(pe, frozen_bus); + if (rc) { + printk(KERN_WARNING "EEH: Unable to reset, rc=%d\n", rc); + goto hard_fail; + } + } + + /* If all devices reported they can proceed, then re-enable MMIO */ + if (result == PCI_ERS_RESULT_CAN_RECOVER) { + rc = eeh_pci_enable(pe, EEH_OPT_THAW_MMIO); + + if (rc < 0) + goto hard_fail; + if (rc) { + result = PCI_ERS_RESULT_NEED_RESET; + } else { + result = PCI_ERS_RESULT_NONE; + eeh_pe_dev_traverse(pe, eeh_report_mmio_enabled, &result); + } + } + + /* If all devices reported they can proceed, then re-enable DMA */ + if (result == PCI_ERS_RESULT_CAN_RECOVER) { + rc = eeh_pci_enable(pe, EEH_OPT_THAW_DMA); + + if (rc < 0) + goto hard_fail; + if (rc) + result = PCI_ERS_RESULT_NEED_RESET; + else + result = PCI_ERS_RESULT_RECOVERED; + } + + /* If any device has a hard failure, then shut off everything. */ + if (result == PCI_ERS_RESULT_DISCONNECT) { + printk(KERN_WARNING "EEH: Device driver gave up\n"); + goto hard_fail; + } + + /* If any device called out for a reset, then reset the slot */ + if (result == PCI_ERS_RESULT_NEED_RESET) { + rc = eeh_reset_device(pe, NULL); + if (rc) { + printk(KERN_WARNING "EEH: Cannot reset, rc=%d\n", rc); + goto hard_fail; + } + result = PCI_ERS_RESULT_NONE; + eeh_pe_dev_traverse(pe, eeh_report_reset, &result); + } + + /* All devices should claim they have recovered by now. */ + if ((result != PCI_ERS_RESULT_RECOVERED) && + (result != PCI_ERS_RESULT_NONE)) { + printk(KERN_WARNING "EEH: Not recovered\n"); + goto hard_fail; + } + + /* Tell all device drivers that they can resume operations */ + eeh_pe_dev_traverse(pe, eeh_report_resume, NULL); + + return; + +excess_failures: + /* + * About 90% of all real-life EEH failures in the field + * are due to poorly seated PCI cards. Only 10% or so are + * due to actual, failed cards. + */ + pr_err("EEH: PHB#%d-PE#%x has failed %d times in the\n" + "last hour and has been permanently disabled.\n" + "Please try reseating or replacing it.\n", + pe->phb->global_number, pe->addr, + pe->freeze_count); + goto perm_error; + +hard_fail: + pr_err("EEH: Unable to recover from failure from PHB#%d-PE#%x.\n" + "Please try reseating or replacing it\n", + pe->phb->global_number, pe->addr); + +perm_error: + eeh_slot_error_detail(pe, EEH_LOG_PERM); + + /* Notify all devices that they're about to go down. */ + eeh_pe_dev_traverse(pe, eeh_report_failure, NULL); + + /* Shut down the device drivers for good. */ + if (frozen_bus) + pcibios_remove_pci_devices(frozen_bus); +} + diff --git a/arch/powerpc/kernel/eeh_event.c b/arch/powerpc/kernel/eeh_event.c new file mode 100644 index 0000000..185bedd --- /dev/null +++ b/arch/powerpc/kernel/eeh_event.c @@ -0,0 +1,142 @@ +/* + * 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 + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Copyright (c) 2005 Linas Vepstas <linas@linas.org> + */ + +#include <linux/delay.h> +#include <linux/list.h> +#include <linux/mutex.h> +#include <linux/sched.h> +#include <linux/pci.h> +#include <linux/slab.h> +#include <linux/workqueue.h> +#include <linux/kthread.h> +#include <asm/eeh_event.h> +#include <asm/ppc-pci.h> + +/** Overview: + * EEH error states may be detected within exception handlers; + * however, the recovery processing needs to occur asynchronously + * in a normal kernel context and not an interrupt context. + * This pair of routines creates an event and queues it onto a + * work-queue, where a worker thread can drive recovery. + */ + +/* EEH event workqueue setup. */ +static DEFINE_SPINLOCK(eeh_eventlist_lock); +LIST_HEAD(eeh_eventlist); +static void eeh_thread_launcher(struct work_struct *); +DECLARE_WORK(eeh_event_wq, eeh_thread_launcher); + +/* Serialize reset sequences for a given pci device */ +DEFINE_MUTEX(eeh_event_mutex); + +/** + * eeh_event_handler - Dispatch EEH events. + * @dummy - unused + * + * 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. + */ +static int eeh_event_handler(void * dummy) +{ + unsigned long flags; + struct eeh_event *event; + struct eeh_pe *pe; + + spin_lock_irqsave(&eeh_eventlist_lock, flags); + event = NULL; + + /* Unqueue the event, get ready to process. */ + 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) + return 0; + + /* Serialize processing of EEH events */ + mutex_lock(&eeh_event_mutex); + pe = event->pe; + eeh_pe_state_mark(pe, EEH_PE_RECOVERING); + pr_info("EEH: Detected PCI bus error on PHB#%d-PE#%x\n", + pe->phb->global_number, pe->addr); + + set_current_state(TASK_INTERRUPTIBLE); /* Don't add to load average */ + eeh_handle_event(pe); + eeh_pe_state_clear(pe, EEH_PE_RECOVERING); + + kfree(event); + mutex_unlock(&eeh_event_mutex); + + /* If there are no new errors after an hour, clear the counter. */ + if (pe && pe->freeze_count > 0) { + msleep_interruptible(3600*1000); + if (pe->freeze_count > 0) + pe->freeze_count--; + + } + + return 0; +} + +/** + * eeh_thread_launcher - Start kernel thread to handle EEH events + * @dummy - unused + * + * This routine is called to start the kernel thread for processing + * EEH event. + */ +static void eeh_thread_launcher(struct work_struct *dummy) +{ + if (IS_ERR(kthread_run(eeh_event_handler, NULL, "eehd"))) + printk(KERN_ERR "Failed to start EEH daemon\n"); +} + +/** + * eeh_send_failure_event - Generate a PCI error event + * @pe: EEH PE + * + * This routine can be called within an interrupt context; + * the actual event will be delivered in a normal context + * (from a workqueue). + */ +int eeh_send_failure_event(struct eeh_pe *pe) +{ + unsigned long flags; + struct eeh_event *event; + + event = kzalloc(sizeof(*event), GFP_ATOMIC); + if (!event) { + pr_err("EEH: out of memory, event not handled\n"); + return -ENOMEM; + } + event->pe = pe; + + /* 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); + + schedule_work(&eeh_event_wq); + + return 0; +} diff --git a/arch/powerpc/kernel/eeh_pe.c b/arch/powerpc/kernel/eeh_pe.c new file mode 100644 index 0000000..9d4a9e8 --- /dev/null +++ b/arch/powerpc/kernel/eeh_pe.c @@ -0,0 +1,653 @@ +/* + * The file intends to implement PE based on the information from + * platforms. Basically, there have 3 types of PEs: PHB/Bus/Device. + * All the PEs should be organized as hierarchy tree. The first level + * of the tree will be associated to existing PHBs since the particular + * PE is only meaningful in one PHB domain. + * + * Copyright Benjamin Herrenschmidt & Gavin Shan, IBM Corporation 2012. + * + * 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 + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/export.h> +#include <linux/gfp.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/pci.h> +#include <linux/string.h> + +#include <asm/pci-bridge.h> +#include <asm/ppc-pci.h> + +static LIST_HEAD(eeh_phb_pe); + +/** + * eeh_pe_alloc - Allocate PE + * @phb: PCI controller + * @type: PE type + * + * Allocate PE instance dynamically. + */ +static struct eeh_pe *eeh_pe_alloc(struct pci_controller *phb, int type) +{ + struct eeh_pe *pe; + + /* Allocate PHB PE */ + pe = kzalloc(sizeof(struct eeh_pe), GFP_KERNEL); + if (!pe) return NULL; + + /* Initialize PHB PE */ + pe->type = type; + pe->phb = phb; + INIT_LIST_HEAD(&pe->child_list); + INIT_LIST_HEAD(&pe->child); + INIT_LIST_HEAD(&pe->edevs); + + return pe; +} + +/** + * eeh_phb_pe_create - Create PHB PE + * @phb: PCI controller + * + * The function should be called while the PHB is detected during + * system boot or PCI hotplug in order to create PHB PE. + */ +int eeh_phb_pe_create(struct pci_controller *phb) +{ + struct eeh_pe *pe; + + /* Allocate PHB PE */ + pe = eeh_pe_alloc(phb, EEH_PE_PHB); + if (!pe) { + pr_err("%s: out of memory!\n", __func__); + return -ENOMEM; + } + + /* Put it into the list */ + eeh_lock(); + list_add_tail(&pe->child, &eeh_phb_pe); + eeh_unlock(); + + pr_debug("EEH: Add PE for PHB#%d\n", phb->global_number); + + return 0; +} + +/** + * eeh_phb_pe_get - Retrieve PHB PE based on the given PHB + * @phb: PCI controller + * + * The overall PEs form hierarchy tree. The first layer of the + * hierarchy tree is composed of PHB PEs. The function is used + * to retrieve the corresponding PHB PE according to the given PHB. + */ +static struct eeh_pe *eeh_phb_pe_get(struct pci_controller *phb) +{ + struct eeh_pe *pe; + + list_for_each_entry(pe, &eeh_phb_pe, child) { + /* + * Actually, we needn't check the type since + * the PE for PHB has been determined when that + * was created. + */ + if ((pe->type & EEH_PE_PHB) && pe->phb == phb) + return pe; + } + + return NULL; +} + +/** + * eeh_pe_next - Retrieve the next PE in the tree + * @pe: current PE + * @root: root PE + * + * The function is used to retrieve the next PE in the + * hierarchy PE tree. + */ +static struct eeh_pe *eeh_pe_next(struct eeh_pe *pe, + struct eeh_pe *root) +{ + struct list_head *next = pe->child_list.next; + + if (next == &pe->child_list) { + while (1) { + if (pe == root) + return NULL; + next = pe->child.next; + if (next != &pe->parent->child_list) + break; + pe = pe->parent; + } + } + + return list_entry(next, struct eeh_pe, child); +} + +/** + * eeh_pe_traverse - Traverse PEs in the specified PHB + * @root: root PE + * @fn: callback + * @flag: extra parameter to callback + * + * The function is used to traverse the specified PE and its + * child PEs. The traversing is to be terminated once the + * callback returns something other than NULL, or no more PEs + * to be traversed. + */ +static void *eeh_pe_traverse(struct eeh_pe *root, + eeh_traverse_func fn, void *flag) +{ + struct eeh_pe *pe; + void *ret; + + for (pe = root; pe; pe = eeh_pe_next(pe, root)) { + ret = fn(pe, flag); + if (ret) return ret; + } + + return NULL; +} + +/** + * eeh_pe_dev_traverse - Traverse the devices from the PE + * @root: EEH PE + * @fn: function callback + * @flag: extra parameter to callback + * + * The function is used to traverse the devices of the specified + * PE and its child PEs. + */ +void *eeh_pe_dev_traverse(struct eeh_pe *root, + eeh_traverse_func fn, void *flag) +{ + struct eeh_pe *pe; + struct eeh_dev *edev; + void *ret; + + if (!root) { + pr_warning("%s: Invalid PE %p\n", __func__, root); + return NULL; + } + + eeh_lock(); + + /* Traverse root PE */ + for (pe = root; pe; pe = eeh_pe_next(pe, root)) { + eeh_pe_for_each_dev(pe, edev) { + ret = fn(edev, flag); + if (ret) { + eeh_unlock(); + return ret; + } + } + } + + eeh_unlock(); + + return NULL; +} + +/** + * __eeh_pe_get - Check the PE address + * @data: EEH PE + * @flag: EEH device + * + * For one particular PE, it can be identified by PE address + * or tranditional BDF address. BDF address is composed of + * Bus/Device/Function number. The extra data referred by flag + * indicates which type of address should be used. + */ +static void *__eeh_pe_get(void *data, void *flag) +{ + struct eeh_pe *pe = (struct eeh_pe *)data; + struct eeh_dev *edev = (struct eeh_dev *)flag; + + /* Unexpected PHB PE */ + if (pe->type & EEH_PE_PHB) + return NULL; + + /* We prefer PE address */ + if (edev->pe_config_addr && + (edev->pe_config_addr == pe->addr)) + return pe; + + /* Try BDF address */ + if (edev->pe_config_addr && + (edev->config_addr == pe->config_addr)) + return pe; + + return NULL; +} + +/** + * eeh_pe_get - Search PE based on the given address + * @edev: EEH device + * + * Search the corresponding PE based on the specified address which + * is included in the eeh device. The function is used to check if + * the associated PE has been created against the PE address. It's + * notable that the PE address has 2 format: traditional PE address + * which is composed of PCI bus/device/function number, or unified + * PE address. + */ +static struct eeh_pe *eeh_pe_get(struct eeh_dev *edev) +{ + struct eeh_pe *root = eeh_phb_pe_get(edev->phb); + struct eeh_pe *pe; + + pe = eeh_pe_traverse(root, __eeh_pe_get, edev); + + return pe; +} + +/** + * eeh_pe_get_parent - Retrieve the parent PE + * @edev: EEH device + * + * The whole PEs existing in the system are organized as hierarchy + * tree. The function is used to retrieve the parent PE according + * to the parent EEH device. + */ +static struct eeh_pe *eeh_pe_get_parent(struct eeh_dev *edev) +{ + struct device_node *dn; + struct eeh_dev *parent; + + /* + * It might have the case for the indirect parent + * EEH device already having associated PE, but + * the direct parent EEH device doesn't have yet. + */ + dn = edev->dn->parent; + while (dn) { + /* We're poking out of PCI territory */ + if (!PCI_DN(dn)) return NULL; + + parent = of_node_to_eeh_dev(dn); + /* We're poking out of PCI territory */ + if (!parent) return NULL; + + if (parent->pe) + return parent->pe; + + dn = dn->parent; + } + + return NULL; +} + +/** + * eeh_add_to_parent_pe - Add EEH device to parent PE + * @edev: EEH device + * + * Add EEH device to the parent PE. If the parent PE already + * exists, the PE type will be changed to EEH_PE_BUS. Otherwise, + * we have to create new PE to hold the EEH device and the new + * PE will be linked to its parent PE as well. + */ +int eeh_add_to_parent_pe(struct eeh_dev *edev) +{ + struct eeh_pe *pe, *parent; + + eeh_lock(); + + /* + * Search the PE has been existing or not according + * to the PE address. If that has been existing, the + * PE should be composed of PCI bus and its subordinate + * components. + */ + pe = eeh_pe_get(edev); + if (pe && !(pe->type & EEH_PE_INVALID)) { + if (!edev->pe_config_addr) { + eeh_unlock(); + pr_err("%s: PE with addr 0x%x already exists\n", + __func__, edev->config_addr); + return -EEXIST; + } + + /* Mark the PE as type of PCI bus */ + pe->type = EEH_PE_BUS; + edev->pe = pe; + + /* Put the edev to PE */ + list_add_tail(&edev->list, &pe->edevs); + eeh_unlock(); + pr_debug("EEH: Add %s to Bus PE#%x\n", + edev->dn->full_name, pe->addr); + + return 0; + } else if (pe && (pe->type & EEH_PE_INVALID)) { + list_add_tail(&edev->list, &pe->edevs); + edev->pe = pe; + /* + * We're running to here because of PCI hotplug caused by + * EEH recovery. We need clear EEH_PE_INVALID until the top. + */ + parent = pe; + while (parent) { + if (!(parent->type & EEH_PE_INVALID)) + break; + parent->type &= ~EEH_PE_INVALID; + parent = parent->parent; + } + eeh_unlock(); + pr_debug("EEH: Add %s to Device PE#%x, Parent PE#%x\n", + edev->dn->full_name, pe->addr, pe->parent->addr); + + return 0; + } + + /* Create a new EEH PE */ + pe = eeh_pe_alloc(edev->phb, EEH_PE_DEVICE); + if (!pe) { + eeh_unlock(); + pr_err("%s: out of memory!\n", __func__); + return -ENOMEM; + } + pe->addr = edev->pe_config_addr; + pe->config_addr = edev->config_addr; + + /* + * Put the new EEH PE into hierarchy tree. If the parent + * can't be found, the newly created PE will be attached + * to PHB directly. Otherwise, we have to associate the + * PE with its parent. + */ + parent = eeh_pe_get_parent(edev); + if (!parent) { + parent = eeh_phb_pe_get(edev->phb); + if (!parent) { + eeh_unlock(); + pr_err("%s: No PHB PE is found (PHB Domain=%d)\n", + __func__, edev->phb->global_number); + edev->pe = NULL; + kfree(pe); + return -EEXIST; + } + } + pe->parent = parent; + + /* + * Put the newly created PE into the child list and + * link the EEH device accordingly. + */ + list_add_tail(&pe->child, &parent->child_list); + list_add_tail(&edev->list, &pe->edevs); + edev->pe = pe; + eeh_unlock(); + pr_debug("EEH: Add %s to Device PE#%x, Parent PE#%x\n", + edev->dn->full_name, pe->addr, pe->parent->addr); + + return 0; +} + +/** + * eeh_rmv_from_parent_pe - Remove one EEH device from the associated PE + * @edev: EEH device + * @purge_pe: remove PE or not + * + * The PE hierarchy tree might be changed when doing PCI hotplug. + * Also, the PCI devices or buses could be removed from the system + * during EEH recovery. So we have to call the function remove the + * corresponding PE accordingly if necessary. + */ +int eeh_rmv_from_parent_pe(struct eeh_dev *edev, int purge_pe) +{ + struct eeh_pe *pe, *parent, *child; + int cnt; + + if (!edev->pe) { + pr_warning("%s: No PE found for EEH device %s\n", + __func__, edev->dn->full_name); + return -EEXIST; + } + + eeh_lock(); + + /* Remove the EEH device */ + pe = edev->pe; + edev->pe = NULL; + list_del(&edev->list); + + /* + * Check if the parent PE includes any EEH devices. + * If not, we should delete that. Also, we should + * delete the parent PE if it doesn't have associated + * child PEs and EEH devices. + */ + while (1) { + parent = pe->parent; + if (pe->type & EEH_PE_PHB) + break; + + if (purge_pe) { + if (list_empty(&pe->edevs) && + list_empty(&pe->child_list)) { + list_del(&pe->child); + kfree(pe); + } else { + break; + } + } else { + if (list_empty(&pe->edevs)) { + cnt = 0; + list_for_each_entry(child, &pe->child_list, child) { + if (!(child->type & EEH_PE_INVALID)) { + cnt++; + break; + } + } + + if (!cnt) + pe->type |= EEH_PE_INVALID; + else + break; + } + } + + pe = parent; + } + + eeh_unlock(); + + return 0; +} + +/** + * __eeh_pe_state_mark - Mark the state for the PE + * @data: EEH PE + * @flag: state + * + * The function is used to mark the indicated state for the given + * PE. Also, the associated PCI devices will be put into IO frozen + * state as well. + */ +static void *__eeh_pe_state_mark(void *data, void *flag) +{ + struct eeh_pe *pe = (struct eeh_pe *)data; + int state = *((int *)flag); + struct eeh_dev *tmp; + struct pci_dev *pdev; + + /* + * Mark the PE with the indicated state. Also, + * the associated PCI device will be put into + * I/O frozen state to avoid I/O accesses from + * the PCI device driver. + */ + pe->state |= state; + eeh_pe_for_each_dev(pe, tmp) { + pdev = eeh_dev_to_pci_dev(tmp); + if (pdev) + pdev->error_state = pci_channel_io_frozen; + } + + return NULL; +} + +/** + * eeh_pe_state_mark - Mark specified state for PE and its associated device + * @pe: EEH PE + * + * EEH error affects the current PE and its child PEs. The function + * is used to mark appropriate state for the affected PEs and the + * associated devices. + */ +void eeh_pe_state_mark(struct eeh_pe *pe, int state) +{ + eeh_lock(); + eeh_pe_traverse(pe, __eeh_pe_state_mark, &state); + eeh_unlock(); +} + +/** + * __eeh_pe_state_clear - Clear state for the PE + * @data: EEH PE + * @flag: state + * + * The function is used to clear the indicated state from the + * given PE. Besides, we also clear the check count of the PE + * as well. + */ +static void *__eeh_pe_state_clear(void *data, void *flag) +{ + struct eeh_pe *pe = (struct eeh_pe *)data; + int state = *((int *)flag); + + pe->state &= ~state; + pe->check_count = 0; + + return NULL; +} + +/** + * eeh_pe_state_clear - Clear state for the PE and its children + * @pe: PE + * @state: state to be cleared + * + * When the PE and its children has been recovered from error, + * we need clear the error state for that. The function is used + * for the purpose. + */ +void eeh_pe_state_clear(struct eeh_pe *pe, int state) +{ + eeh_lock(); + eeh_pe_traverse(pe, __eeh_pe_state_clear, &state); + eeh_unlock(); +} + +/** + * eeh_restore_one_device_bars - Restore the Base Address Registers for one device + * @data: EEH device + * @flag: Unused + * + * 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 void *eeh_restore_one_device_bars(void *data, void *flag) +{ + int i; + u32 cmd; + struct eeh_dev *edev = (struct eeh_dev *)data; + struct device_node *dn = eeh_dev_to_of_node(edev); + + for (i = 4; i < 10; i++) + eeh_ops->write_config(dn, i*4, 4, edev->config_space[i]); + /* 12 == Expansion ROM Address */ + eeh_ops->write_config(dn, 12*4, 4, edev->config_space[12]); + +#define BYTE_SWAP(OFF) (8*((OFF)/4)+3-(OFF)) +#define SAVED_BYTE(OFF) (((u8 *)(edev->config_space))[BYTE_SWAP(OFF)]) + + eeh_ops->write_config(dn, PCI_CACHE_LINE_SIZE, 1, + SAVED_BYTE(PCI_CACHE_LINE_SIZE)); + eeh_ops->write_config(dn, PCI_LATENCY_TIMER, 1, + SAVED_BYTE(PCI_LATENCY_TIMER)); + + /* max latency, min grant, interrupt pin and line */ + eeh_ops->write_config(dn, 15*4, 4, edev->config_space[15]); + + /* + * Restore PERR & SERR bits, some devices require it, + * don't touch the other command bits + */ + eeh_ops->read_config(dn, PCI_COMMAND, 4, &cmd); + if (edev->config_space[1] & PCI_COMMAND_PARITY) + cmd |= PCI_COMMAND_PARITY; + else + cmd &= ~PCI_COMMAND_PARITY; + if (edev->config_space[1] & PCI_COMMAND_SERR) + cmd |= PCI_COMMAND_SERR; + else + cmd &= ~PCI_COMMAND_SERR; + eeh_ops->write_config(dn, PCI_COMMAND, 4, cmd); + + return NULL; +} + +/** + * eeh_pe_restore_bars - Restore the PCI config space info + * @pe: EEH PE + * + * This routine performs a recursive walk to the children + * of this device as well. + */ +void eeh_pe_restore_bars(struct eeh_pe *pe) +{ + /* + * We needn't take the EEH lock since eeh_pe_dev_traverse() + * will take that. + */ + eeh_pe_dev_traverse(pe, eeh_restore_one_device_bars, NULL); +} + +/** + * eeh_pe_bus_get - Retrieve PCI bus according to the given PE + * @pe: EEH PE + * + * Retrieve the PCI bus according to the given PE. Basically, + * there're 3 types of PEs: PHB/Bus/Device. For PHB PE, the + * primary PCI bus will be retrieved. The parent bus will be + * returned for BUS PE. However, we don't have associated PCI + * bus for DEVICE PE. + */ +struct pci_bus *eeh_pe_bus_get(struct eeh_pe *pe) +{ + struct pci_bus *bus = NULL; + struct eeh_dev *edev; + struct pci_dev *pdev; + + eeh_lock(); + + if (pe->type & EEH_PE_PHB) { + bus = pe->phb->bus; + } else if (pe->type & EEH_PE_BUS || + pe->type & EEH_PE_DEVICE) { + edev = list_first_entry(&pe->edevs, struct eeh_dev, list); + pdev = eeh_dev_to_pci_dev(edev); + if (pdev) + bus = pdev->bus; + } + + eeh_unlock(); + + return bus; +} diff --git a/arch/powerpc/kernel/eeh_sysfs.c b/arch/powerpc/kernel/eeh_sysfs.c new file mode 100644 index 0000000..d377083 --- /dev/null +++ b/arch/powerpc/kernel/eeh_sysfs.c @@ -0,0 +1,75 @@ +/* + * Sysfs entries for PCI Error Recovery for PAPR-compliant platform. + * Copyright IBM Corporation 2007 + * Copyright Linas Vepstas <linas@austin.ibm.com> 2007 + * + * All rights reserved. + * + * 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 + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or + * NON INFRINGEMENT. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Send comments and feedback to Linas Vepstas <linas@austin.ibm.com> + */ +#include <linux/pci.h> +#include <linux/stat.h> +#include <asm/ppc-pci.h> +#include <asm/pci-bridge.h> + +/** + * EEH_SHOW_ATTR -- Create sysfs entry for eeh statistic + * @_name: name of file in sysfs directory + * @_memb: name of member in struct pci_dn to access + * @_format: printf format for display + * + * All of the attributes look very similar, so just + * auto-gen a cut-n-paste routine to display them. + */ +#define EEH_SHOW_ATTR(_name,_memb,_format) \ +static ssize_t eeh_show_##_name(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + struct pci_dev *pdev = to_pci_dev(dev); \ + struct eeh_dev *edev = pci_dev_to_eeh_dev(pdev); \ + \ + if (!edev) \ + return 0; \ + \ + return sprintf(buf, _format "\n", edev->_memb); \ +} \ +static DEVICE_ATTR(_name, S_IRUGO, eeh_show_##_name, NULL); + +EEH_SHOW_ATTR(eeh_mode, mode, "0x%x"); +EEH_SHOW_ATTR(eeh_config_addr, config_addr, "0x%x"); +EEH_SHOW_ATTR(eeh_pe_config_addr, pe_config_addr, "0x%x"); + +void eeh_sysfs_add_device(struct pci_dev *pdev) +{ + int rc=0; + + rc += device_create_file(&pdev->dev, &dev_attr_eeh_mode); + rc += device_create_file(&pdev->dev, &dev_attr_eeh_config_addr); + rc += device_create_file(&pdev->dev, &dev_attr_eeh_pe_config_addr); + + if (rc) + printk(KERN_WARNING "EEH: Unable to create sysfs entries\n"); +} + +void eeh_sysfs_remove_device(struct pci_dev *pdev) +{ + device_remove_file(&pdev->dev, &dev_attr_eeh_mode); + device_remove_file(&pdev->dev, &dev_attr_eeh_config_addr); + device_remove_file(&pdev->dev, &dev_attr_eeh_pe_config_addr); +} + diff --git a/arch/powerpc/kernel/pci_hotplug.c b/arch/powerpc/kernel/pci_hotplug.c new file mode 100644 index 0000000..3f60880 --- /dev/null +++ b/arch/powerpc/kernel/pci_hotplug.c @@ -0,0 +1,111 @@ +/* + * Derived from "arch/powerpc/platforms/pseries/pci_dlpar.c" + * + * Copyright (C) 2003 Linda Xie <lxie@us.ibm.com> + * Copyright (C) 2005 International Business Machines + * + * Updates, 2005, John Rose <johnrose@austin.ibm.com> + * Updates, 2005, Linas Vepstas <linas@austin.ibm.com> + * Updates, 2013, Gavin Shan <shangw@linux.vnet.ibm.com> + * + * 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 + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/pci.h> +#include <linux/export.h> +#include <asm/pci-bridge.h> +#include <asm/ppc-pci.h> +#include <asm/firmware.h> +#include <asm/eeh.h> + +/** + * __pcibios_remove_pci_devices - remove all devices under this bus + * @bus: the indicated PCI bus + * @purge_pe: destroy the PE on removal of PCI devices + * + * Remove all of the PCI devices under this bus both from the + * linux pci device tree, and from the powerpc EEH address cache. + * By default, the corresponding PE will be destroied during the + * normal PCI hotplug path. For PCI hotplug during EEH recovery, + * the corresponding PE won't be destroied and deallocated. + */ +void __pcibios_remove_pci_devices(struct pci_bus *bus, int purge_pe) +{ + struct pci_dev *dev, *tmp; + struct pci_bus *child_bus; + + /* First go down child busses */ + list_for_each_entry(child_bus, &bus->children, node) + __pcibios_remove_pci_devices(child_bus, purge_pe); + + pr_debug("PCI: Removing devices on bus %04x:%02x\n", + pci_domain_nr(bus), bus->number); + list_for_each_entry_safe(dev, tmp, &bus->devices, bus_list) { + pr_debug(" * Removing %s...\n", pci_name(dev)); + eeh_remove_bus_device(dev, purge_pe); + pci_stop_and_remove_bus_device(dev); + } +} + +/** + * pcibios_remove_pci_devices - remove all devices under this bus + * @bus: the indicated PCI bus + * + * Remove all of the PCI devices under this bus both from the + * linux pci device tree, and from the powerpc EEH address cache. + */ +void pcibios_remove_pci_devices(struct pci_bus *bus) +{ + __pcibios_remove_pci_devices(bus, 1); +} +EXPORT_SYMBOL_GPL(pcibios_remove_pci_devices); + +/** + * pcibios_add_pci_devices - adds new pci devices to bus + * @bus: the indicated PCI bus + * + * This routine will find and fixup new pci devices under + * the indicated bus. This routine presumes that there + * might already be some devices under this bridge, so + * it carefully tries to add only new devices. (And that + * is how this routine differs from other, similar pcibios + * routines.) + */ +void pcibios_add_pci_devices(struct pci_bus * bus) +{ + int slotno, num, mode, pass, max; + struct pci_dev *dev; + struct device_node *dn = pci_bus_to_OF_node(bus); + + eeh_add_device_tree_early(dn); + + mode = PCI_PROBE_NORMAL; + if (ppc_md.pci_probe_mode) + mode = ppc_md.pci_probe_mode(bus); + + if (mode == PCI_PROBE_DEVTREE) { + /* use ofdt-based probe */ + of_rescan_bus(dn, bus); + } else if (mode == PCI_PROBE_NORMAL) { + /* use legacy probe */ + slotno = PCI_SLOT(PCI_DN(dn->child)->devfn); + num = pci_scan_slot(bus, PCI_DEVFN(slotno, 0)); + if (!num) + return; + pcibios_setup_bus_devices(bus); + max = bus->busn_res.start; + for (pass = 0; pass < 2; pass++) { + list_for_each_entry(dev, &bus->devices, bus_list) { + if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE || + dev->hdr_type == PCI_HEADER_TYPE_CARDBUS) + max = pci_scan_bridge(bus, dev, + max, pass); + } + } + } + pcibios_finish_adding_to_bus(bus); +} +EXPORT_SYMBOL_GPL(pcibios_add_pci_devices); diff --git a/arch/powerpc/platforms/Kconfig b/arch/powerpc/platforms/Kconfig index b62aab3..bed8c60 100644 --- a/arch/powerpc/platforms/Kconfig +++ b/arch/powerpc/platforms/Kconfig @@ -164,6 +164,11 @@ config IBMEBUS help Bus device driver for GX bus based adapters. +config EEH + bool + depends on (PPC_POWERNV || PPC_PSERIES) && PCI + default y + config PPC_MPC106 bool default n diff --git a/arch/powerpc/platforms/pseries/Kconfig b/arch/powerpc/platforms/pseries/Kconfig index 4459eff..1bd3399 100644 --- a/arch/powerpc/platforms/pseries/Kconfig +++ b/arch/powerpc/platforms/pseries/Kconfig @@ -33,11 +33,6 @@ config PPC_SPLPAR processors, that is, which share physical processors between two or more partitions. -config EEH - bool - depends on PPC_PSERIES && PCI - default y - config PSERIES_MSI bool depends on PCI_MSI && EEH diff --git a/arch/powerpc/platforms/pseries/Makefile b/arch/powerpc/platforms/pseries/Makefile index 53866e5..8ae0103 100644 --- a/arch/powerpc/platforms/pseries/Makefile +++ b/arch/powerpc/platforms/pseries/Makefile @@ -6,9 +6,7 @@ obj-y := lpar.o hvCall.o nvram.o reconfig.o \ firmware.o power.o dlpar.o mobility.o obj-$(CONFIG_SMP) += smp.o obj-$(CONFIG_SCANLOG) += scanlog.o -obj-$(CONFIG_EEH) += eeh.o eeh_pe.o eeh_dev.o eeh_cache.o \ - eeh_driver.o eeh_event.o eeh_sysfs.o \ - eeh_pseries.o +obj-$(CONFIG_EEH) += eeh_pseries.o obj-$(CONFIG_KEXEC) += kexec.o obj-$(CONFIG_PCI) += pci.o pci_dlpar.o obj-$(CONFIG_PSERIES_MSI) += msi.o diff --git a/arch/powerpc/platforms/pseries/eeh.c b/arch/powerpc/platforms/pseries/eeh.c deleted file mode 100644 index 6b73d6c..0000000 --- a/arch/powerpc/platforms/pseries/eeh.c +++ /dev/null @@ -1,942 +0,0 @@ -/* - * Copyright IBM Corporation 2001, 2005, 2006 - * Copyright Dave Engebretsen & Todd Inglett 2001 - * Copyright Linas Vepstas 2005, 2006 - * Copyright 2001-2012 IBM Corporation. - * - * 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 - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - * Please address comments and feedback to Linas Vepstas <linas@austin.ibm.com> - */ - -#include <linux/delay.h> -#include <linux/sched.h> -#include <linux/init.h> -#include <linux/list.h> -#include <linux/pci.h> -#include <linux/proc_fs.h> -#include <linux/rbtree.h> -#include <linux/seq_file.h> -#include <linux/spinlock.h> -#include <linux/export.h> -#include <linux/of.h> - -#include <linux/atomic.h> -#include <asm/eeh.h> -#include <asm/eeh_event.h> -#include <asm/io.h> -#include <asm/machdep.h> -#include <asm/ppc-pci.h> -#include <asm/rtas.h> - - -/** Overview: - * EEH, or "Extended Error Handling" is a PCI bridge technology for - * dealing with PCI bus errors that can't be dealt with within the - * usual PCI framework, except by check-stopping the CPU. Systems - * that are designed for high-availability/reliability cannot afford - * to crash due to a "mere" PCI error, thus the need for EEH. - * An EEH-capable bridge operates by converting a detected error - * into a "slot freeze", taking the PCI adapter off-line, making - * the slot behave, from the OS'es point of view, as if the slot - * were "empty": all reads return 0xff's and all writes are silently - * ignored. EEH slot isolation events can be triggered by parity - * errors on the address or data busses (e.g. during posted writes), - * 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 - * freeze events are buggy device drivers, buggy device microcode, - * or buggy device hardware. This is because any attempt by the - * device to bus-master data to a memory address that is not - * assigned to the device will trigger a slot freeze. (The idea - * is to prevent devices-gone-wild from corrupting system memory). - * Buggy hardware/drivers will have a miserable time co-existing - * with EEH. - * - * Ideally, a PCI device driver, when suspecting that an isolation - * event has occurred (e.g. by reading 0xff's), will then ask EEH - * whether this is the case, and then take appropriate steps to - * reset the PCI slot, the PCI device, and then resume operations. - * However, until that day, the checking is done here, with the - * eeh_check_failure() routine embedded in the MMIO macros. If - * the slot is found to be isolated, an "EEH Event" is synthesized - * and sent out for processing. - */ - -/* If a device driver keeps reading an MMIO register in an interrupt - * handler after a slot isolation event, it might be broken. - * This sets the threshold for how many read attempts we allow - * before printing an error message. - */ -#define EEH_MAX_FAILS 2100000 - -/* Time to wait for a PCI slot to report status, in milliseconds */ -#define PCI_BUS_RESET_WAIT_MSEC (60*1000) - -/* Platform dependent EEH operations */ -struct eeh_ops *eeh_ops = NULL; - -int eeh_subsystem_enabled; -EXPORT_SYMBOL(eeh_subsystem_enabled); - -/* - * EEH probe mode support. The intention is to support multiple - * platforms for EEH. Some platforms like pSeries do PCI emunation - * based on device tree. However, other platforms like powernv probe - * PCI devices from hardware. The flag is used to distinguish that. - * In addition, struct eeh_ops::probe would be invoked for particular - * OF node or PCI device so that the corresponding PE would be created - * there. - */ -int eeh_probe_mode; - -/* Global EEH mutex */ -DEFINE_MUTEX(eeh_mutex); - -/* Lock to avoid races due to multiple reports of an error */ -static DEFINE_RAW_SPINLOCK(confirm_error_lock); - -/* Buffer for reporting pci register dumps. Its here in BSS, and - * not dynamically alloced, so that it ends up in RMO where RTAS - * can access it. - */ -#define EEH_PCI_REGS_LOG_LEN 4096 -static unsigned char pci_regs_buf[EEH_PCI_REGS_LOG_LEN]; - -/* - * The struct is used to maintain the EEH global statistic - * information. Besides, the EEH global statistics will be - * exported to user space through procfs - */ -struct eeh_stats { - u64 no_device; /* PCI device not found */ - u64 no_dn; /* OF node not found */ - u64 no_cfg_addr; /* Config address not found */ - u64 ignored_check; /* EEH check skipped */ - u64 total_mmio_ffs; /* Total EEH checks */ - u64 false_positives; /* Unnecessary EEH checks */ - u64 slot_resets; /* PE reset */ -}; - -static struct eeh_stats eeh_stats; - -#define IS_BRIDGE(class_code) (((class_code)<<16) == PCI_BASE_CLASS_BRIDGE) - -/** - * eeh_gather_pci_data - Copy assorted PCI config space registers to buff - * @edev: device to report data for - * @buf: point to buffer in which to log - * @len: amount of room in buffer - * - * This routine captures assorted PCI configuration space data, - * and puts them into a buffer for RTAS error logging. - */ -static size_t eeh_gather_pci_data(struct eeh_dev *edev, char * buf, size_t len) -{ - struct device_node *dn = eeh_dev_to_of_node(edev); - struct pci_dev *dev = eeh_dev_to_pci_dev(edev); - u32 cfg; - int cap, i; - int n = 0; - - n += scnprintf(buf+n, len-n, "%s\n", dn->full_name); - printk(KERN_WARNING "EEH: of node=%s\n", dn->full_name); - - eeh_ops->read_config(dn, PCI_VENDOR_ID, 4, &cfg); - n += scnprintf(buf+n, len-n, "dev/vend:%08x\n", cfg); - printk(KERN_WARNING "EEH: PCI device/vendor: %08x\n", cfg); - - eeh_ops->read_config(dn, PCI_COMMAND, 4, &cfg); - n += scnprintf(buf+n, len-n, "cmd/stat:%x\n", cfg); - printk(KERN_WARNING "EEH: PCI cmd/status register: %08x\n", cfg); - - if (!dev) { - printk(KERN_WARNING "EEH: no PCI device for this of node\n"); - return n; - } - - /* Gather bridge-specific registers */ - if (dev->class >> 16 == PCI_BASE_CLASS_BRIDGE) { - eeh_ops->read_config(dn, PCI_SEC_STATUS, 2, &cfg); - n += scnprintf(buf+n, len-n, "sec stat:%x\n", cfg); - printk(KERN_WARNING "EEH: Bridge secondary status: %04x\n", cfg); - - eeh_ops->read_config(dn, PCI_BRIDGE_CONTROL, 2, &cfg); - n += scnprintf(buf+n, len-n, "brdg ctl:%x\n", cfg); - printk(KERN_WARNING "EEH: Bridge control: %04x\n", cfg); - } - - /* Dump out the PCI-X command and status regs */ - cap = pci_find_capability(dev, PCI_CAP_ID_PCIX); - if (cap) { - eeh_ops->read_config(dn, cap, 4, &cfg); - n += scnprintf(buf+n, len-n, "pcix-cmd:%x\n", cfg); - printk(KERN_WARNING "EEH: PCI-X cmd: %08x\n", cfg); - - eeh_ops->read_config(dn, cap+4, 4, &cfg); - n += scnprintf(buf+n, len-n, "pcix-stat:%x\n", cfg); - printk(KERN_WARNING "EEH: PCI-X status: %08x\n", cfg); - } - - /* If PCI-E capable, dump PCI-E cap 10, and the AER */ - cap = pci_find_capability(dev, PCI_CAP_ID_EXP); - if (cap) { - n += scnprintf(buf+n, len-n, "pci-e cap10:\n"); - printk(KERN_WARNING - "EEH: PCI-E capabilities and status follow:\n"); - - for (i=0; i<=8; i++) { - eeh_ops->read_config(dn, cap+4*i, 4, &cfg); - n += scnprintf(buf+n, len-n, "%02x:%x\n", 4*i, cfg); - printk(KERN_WARNING "EEH: PCI-E %02x: %08x\n", i, cfg); - } - - cap = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ERR); - if (cap) { - n += scnprintf(buf+n, len-n, "pci-e AER:\n"); - printk(KERN_WARNING - "EEH: PCI-E AER capability register set follows:\n"); - - for (i=0; i<14; i++) { - eeh_ops->read_config(dn, cap+4*i, 4, &cfg); - n += scnprintf(buf+n, len-n, "%02x:%x\n", 4*i, cfg); - printk(KERN_WARNING "EEH: PCI-E AER %02x: %08x\n", i, cfg); - } - } - } - - return n; -} - -/** - * eeh_slot_error_detail - Generate combined log including driver log and error log - * @pe: EEH PE - * @severity: temporary or permanent error log - * - * This routine should be called to generate the combined log, which - * is comprised of driver log and error log. The driver log is figured - * out from the config space of the corresponding PCI device, while - * the error log is fetched through platform dependent function call. - */ -void eeh_slot_error_detail(struct eeh_pe *pe, int severity) -{ - size_t loglen = 0; - struct eeh_dev *edev; - - eeh_pci_enable(pe, EEH_OPT_THAW_MMIO); - eeh_ops->configure_bridge(pe); - eeh_pe_restore_bars(pe); - - pci_regs_buf[0] = 0; - eeh_pe_for_each_dev(pe, edev) { - loglen += eeh_gather_pci_data(edev, pci_regs_buf, - EEH_PCI_REGS_LOG_LEN); - } - - eeh_ops->get_log(pe, severity, pci_regs_buf, loglen); -} - -/** - * eeh_token_to_phys - Convert EEH address token to phys address - * @token: I/O token, should be address in the form 0xA.... - * - * This routine should be called to convert virtual I/O address - * to physical one. - */ -static inline unsigned long eeh_token_to_phys(unsigned long token) -{ - 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_dev_check_failure - Check if all 1's data is due to EEH slot freeze - * @edev: eeh device - * - * Check for an EEH failure for the given device node. Call this - * 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. This routine - * will query firmware for the EEH status. - * - * Returns 0 if there has not been an EEH error; otherwise returns - * a non-zero value and queues up a slot isolation event notification. - * - * It is safe to call this routine in an interrupt context. - */ -int eeh_dev_check_failure(struct eeh_dev *edev) -{ - int ret; - unsigned long flags; - struct device_node *dn; - struct pci_dev *dev; - struct eeh_pe *pe; - int rc = 0; - const char *location; - - eeh_stats.total_mmio_ffs++; - - if (!eeh_subsystem_enabled) - return 0; - - if (!edev) { - eeh_stats.no_dn++; - return 0; - } - dn = eeh_dev_to_of_node(edev); - dev = eeh_dev_to_pci_dev(edev); - pe = edev->pe; - - /* Access to IO BARs might get this far and still not want checking. */ - if (!pe) { - eeh_stats.ignored_check++; - pr_debug("EEH: Ignored check for %s %s\n", - eeh_pci_name(dev), dn->full_name); - return 0; - } - - if (!pe->addr && !pe->config_addr) { - eeh_stats.no_cfg_addr++; - 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. - * 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. - */ - raw_spin_lock_irqsave(&confirm_error_lock, flags); - rc = 1; - if (pe->state & EEH_PE_ISOLATED) { - pe->check_count++; - if (pe->check_count % EEH_MAX_FAILS == 0) { - location = of_get_property(dn, "ibm,loc-code", NULL); - printk(KERN_ERR "EEH: %d reads ignored for recovering device at " - "location=%s driver=%s pci addr=%s\n", - pe->check_count, location, - eeh_driver_name(dev), eeh_pci_name(dev)); - printk(KERN_ERR "EEH: Might be infinite loop in %s driver\n", - eeh_driver_name(dev)); - dump_stack(); - } - goto dn_unlock; - } - - /* - * Now test for an EEH failure. This is VERY expensive. - * Note that the eeh_config_addr may be a parent device - * in the case of a device behind a bridge, or it may be - * function zero of a multi-function device. - * In any case they must share a common PHB. - */ - ret = eeh_ops->get_state(pe, NULL); - - /* Note that config-io to empty slots may fail; - * they are empty when they don't have children. - * We will punt with the following conditions: Failure to get - * PE's state, EEH not support and Permanently unavailable - * state, PE is in good state. - */ - if ((ret < 0) || - (ret == EEH_STATE_NOT_SUPPORT) || - (ret & (EEH_STATE_MMIO_ACTIVE | EEH_STATE_DMA_ACTIVE)) == - (EEH_STATE_MMIO_ACTIVE | EEH_STATE_DMA_ACTIVE)) { - eeh_stats.false_positives++; - pe->false_positives++; - rc = 0; - goto dn_unlock; - } - - eeh_stats.slot_resets++; - - /* Avoid repeated reports of this failure, including problems - * with other functions on this device, and functions under - * bridges. - */ - eeh_pe_state_mark(pe, EEH_PE_ISOLATED); - raw_spin_unlock_irqrestore(&confirm_error_lock, flags); - - eeh_send_failure_event(pe); - - /* Most EEH events are due to device driver bugs. Having - * a stack trace will help the device-driver authors figure - * out what happened. So print that out. - */ - WARN(1, "EEH: failure detected\n"); - return 1; - -dn_unlock: - raw_spin_unlock_irqrestore(&confirm_error_lock, flags); - return rc; -} - -EXPORT_SYMBOL_GPL(eeh_dev_check_failure); - -/** - * 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.... - * @val: value, should be all 1's (XXX why do we need this arg??) - * - * 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 - * find out if this is due to an EEH slot freeze event. This routine - * will query firmware for the EEH status. - * - * Note this routine is safe to call in an interrupt context. - */ -unsigned long eeh_check_failure(const volatile void __iomem *token, unsigned long val) -{ - unsigned long addr; - struct eeh_dev *edev; - - /* Finding the phys addr + pci device; this is pretty quick. */ - addr = eeh_token_to_phys((unsigned long __force) token); - edev = eeh_addr_cache_get_dev(addr); - if (!edev) { - eeh_stats.no_device++; - return val; - } - - eeh_dev_check_failure(edev); - - pci_dev_put(eeh_dev_to_pci_dev(edev)); - return val; -} - -EXPORT_SYMBOL(eeh_check_failure); - - -/** - * eeh_pci_enable - Enable MMIO or DMA transfers for this slot - * @pe: EEH PE - * - * This routine should be called to reenable frozen MMIO or DMA - * so that it would work correctly again. It's useful while doing - * recovery or log collection on the indicated device. - */ -int eeh_pci_enable(struct eeh_pe *pe, int function) -{ - int rc; - - rc = eeh_ops->set_option(pe, function); - if (rc) - pr_warning("%s: Unexpected state change %d on PHB#%d-PE#%x, err=%d\n", - __func__, function, pe->phb->global_number, pe->addr, rc); - - rc = eeh_ops->wait_state(pe, PCI_BUS_RESET_WAIT_MSEC); - if (rc > 0 && (rc & EEH_STATE_MMIO_ENABLED) && - (function == EEH_OPT_THAW_MMIO)) - return 0; - - return rc; -} - -/** - * pcibios_set_pcie_slot_reset - Set PCI-E reset state - * @dev: pci device struct - * @state: reset state to enter - * - * Return value: - * 0 if success - */ -int pcibios_set_pcie_reset_state(struct pci_dev *dev, enum pcie_reset_state state) -{ - struct eeh_dev *edev = pci_dev_to_eeh_dev(dev); - struct eeh_pe *pe = edev->pe; - - if (!pe) { - pr_err("%s: No PE found on PCI device %s\n", - __func__, pci_name(dev)); - return -EINVAL; - } - - switch (state) { - case pcie_deassert_reset: - eeh_ops->reset(pe, EEH_RESET_DEACTIVATE); - break; - case pcie_hot_reset: - eeh_ops->reset(pe, EEH_RESET_HOT); - break; - case pcie_warm_reset: - eeh_ops->reset(pe, EEH_RESET_FUNDAMENTAL); - break; - default: - return -EINVAL; - }; - - return 0; -} - -/** - * eeh_set_pe_freset - Check the required reset for the indicated device - * @data: EEH device - * @flag: return value - * - * Each device might have its preferred reset type: fundamental or - * hot reset. The routine is used to collected the information for - * the indicated device and its children so that the bunch of the - * devices could be reset properly. - */ -static void *eeh_set_dev_freset(void *data, void *flag) -{ - struct pci_dev *dev; - unsigned int *freset = (unsigned int *)flag; - struct eeh_dev *edev = (struct eeh_dev *)data; - - dev = eeh_dev_to_pci_dev(edev); - if (dev) - *freset |= dev->needs_freset; - - return NULL; -} - -/** - * eeh_reset_pe_once - Assert the pci #RST line for 1/4 second - * @pe: EEH PE - * - * Assert the PCI #RST line for 1/4 second. - */ -static void eeh_reset_pe_once(struct eeh_pe *pe) -{ - unsigned int freset = 0; - - /* Determine type of EEH reset required for - * Partitionable Endpoint, a hot-reset (1) - * or a fundamental reset (3). - * A fundamental reset required by any device under - * Partitionable Endpoint trumps hot-reset. - */ - eeh_pe_dev_traverse(pe, eeh_set_dev_freset, &freset); - - if (freset) - eeh_ops->reset(pe, EEH_RESET_FUNDAMENTAL); - else - eeh_ops->reset(pe, EEH_RESET_HOT); - - /* 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_pe_state_clear(pe, EEH_PE_ISOLATED); - - eeh_ops->reset(pe, EEH_RESET_DEACTIVATE); - - /* 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); -} - -/** - * eeh_reset_pe - Reset the indicated PE - * @pe: EEH PE - * - * This routine should be called to reset indicated device, including - * PE. A PE might include multiple PCI devices and sometimes PCI bridges - * might be involved as well. - */ -int eeh_reset_pe(struct eeh_pe *pe) -{ - int i, rc; - - /* Take three shots at resetting the bus */ - for (i=0; i<3; i++) { - eeh_reset_pe_once(pe); - - rc = eeh_ops->wait_state(pe, PCI_BUS_RESET_WAIT_MSEC); - if (rc == (EEH_STATE_MMIO_ACTIVE | EEH_STATE_DMA_ACTIVE)) - return 0; - - if (rc < 0) { - pr_err("%s: Unrecoverable slot failure on PHB#%d-PE#%x", - __func__, pe->phb->global_number, pe->addr); - return -1; - } - pr_err("EEH: bus reset %d failed on PHB#%d-PE#%x, rc=%d\n", - i+1, pe->phb->global_number, pe->addr, rc); - } - - return -1; -} - -/** - * eeh_save_bars - Save device bars - * @edev: PCI device associated EEH device - * - * Save the values of the device bars. Unlike the restore - * routine, this routine is *not* recursive. This is because - * PCI devices are added individually; but, for the restore, - * an entire slot is reset at a time. - */ -void eeh_save_bars(struct eeh_dev *edev) -{ - int i; - struct device_node *dn; - - if (!edev) - return; - dn = eeh_dev_to_of_node(edev); - - for (i = 0; i < 16; i++) - eeh_ops->read_config(dn, i * 4, 4, &edev->config_space[i]); -} - -/** - * eeh_ops_register - Register platform dependent EEH operations - * @ops: platform dependent EEH operations - * - * Register the platform dependent EEH operation callback - * functions. The platform should call this function before - * any other EEH operations. - */ -int __init eeh_ops_register(struct eeh_ops *ops) -{ - if (!ops->name) { - pr_warning("%s: Invalid EEH ops name for %p\n", - __func__, ops); - return -EINVAL; - } - - if (eeh_ops && eeh_ops != ops) { - pr_warning("%s: EEH ops of platform %s already existing (%s)\n", - __func__, eeh_ops->name, ops->name); - return -EEXIST; - } - - eeh_ops = ops; - - return 0; -} - -/** - * eeh_ops_unregister - Unreigster platform dependent EEH operations - * @name: name of EEH platform operations - * - * Unregister the platform dependent EEH operation callback - * functions. - */ -int __exit eeh_ops_unregister(const char *name) -{ - if (!name || !strlen(name)) { - pr_warning("%s: Invalid EEH ops name\n", - __func__); - return -EINVAL; - } - - if (eeh_ops && !strcmp(eeh_ops->name, name)) { - eeh_ops = NULL; - return 0; - } - - return -EEXIST; -} - -/** - * eeh_init - EEH initialization - * - * Initialize EEH by trying to enable it for all of the adapters in the system. - * As a side effect we can determine here if eeh is supported at all. - * Note that we leave EEH on so failed config cycles won't cause a machine - * check. If a user turns off EEH for a particular adapter they are really - * telling Linux to ignore errors. Some hardware (e.g. POWER5) won't - * grant access to a slot if EEH isn't enabled, and so we always enable - * EEH for all slots/all devices. - * - * The eeh-force-off option disables EEH checking globally, for all slots. - * Even if force-off is set, the EEH hardware is still enabled, so that - * newer systems can boot. - */ -static int __init eeh_init(void) -{ - struct pci_controller *hose, *tmp; - struct device_node *phb; - int ret; - - /* call platform initialization function */ - if (!eeh_ops) { - pr_warning("%s: Platform EEH operation not found\n", - __func__); - return -EEXIST; - } else if ((ret = eeh_ops->init())) { - pr_warning("%s: Failed to call platform init function (%d)\n", - __func__, ret); - return ret; - } - - raw_spin_lock_init(&confirm_error_lock); - - /* Enable EEH for all adapters */ - if (eeh_probe_mode_devtree()) { - list_for_each_entry_safe(hose, tmp, - &hose_list, list_node) { - phb = hose->dn; - traverse_pci_devices(phb, eeh_ops->of_probe, NULL); - } - } - - if (eeh_subsystem_enabled) - pr_info("EEH: PCI Enhanced I/O Error Handling Enabled\n"); - else - pr_warning("EEH: No capable adapters found\n"); - - return ret; -} - -core_initcall_sync(eeh_init); - -/** - * eeh_add_device_early - Enable EEH for the indicated device_node - * @dn: device node for which to set up EEH - * - * This routine must be used to perform EEH initialization for PCI - * devices that were added after system boot (e.g. hotplug, dlpar). - * This routine must be called before any i/o is performed to the - * adapter (inluding any config-space i/o). - * Whether this actually enables EEH or not for this device depends - * on the CEC architecture, type of the device, on earlier boot - * command-line arguments & etc. - */ -static void eeh_add_device_early(struct device_node *dn) -{ - struct pci_controller *phb; - - if (!of_node_to_eeh_dev(dn)) - return; - phb = of_node_to_eeh_dev(dn)->phb; - - /* USB Bus children of PCI devices will not have BUID's */ - if (NULL == phb || 0 == phb->buid) - return; - - /* FIXME: hotplug support on POWERNV */ - eeh_ops->of_probe(dn, NULL); -} - -/** - * eeh_add_device_tree_early - Enable EEH for the indicated device - * @dn: device node - * - * This routine must be used to perform EEH initialization for the - * indicated PCI device that was added after system boot (e.g. - * hotplug, dlpar). - */ -void eeh_add_device_tree_early(struct device_node *dn) -{ - struct device_node *sib; - - for_each_child_of_node(dn, sib) - eeh_add_device_tree_early(sib); - eeh_add_device_early(dn); -} -EXPORT_SYMBOL_GPL(eeh_add_device_tree_early); - -/** - * eeh_add_device_late - Perform EEH initialization for the indicated pci device - * @dev: pci device for which to set up EEH - * - * This routine must be used to complete EEH initialization for PCI - * devices that were added after system boot (e.g. hotplug, dlpar). - */ -static void eeh_add_device_late(struct pci_dev *dev) -{ - struct device_node *dn; - struct eeh_dev *edev; - - if (!dev || !eeh_subsystem_enabled) - return; - - pr_debug("EEH: Adding device %s\n", pci_name(dev)); - - dn = pci_device_to_OF_node(dev); - edev = of_node_to_eeh_dev(dn); - if (edev->pdev == dev) { - pr_debug("EEH: Already referenced !\n"); - return; - } - WARN_ON(edev->pdev); - - pci_dev_get(dev); - edev->pdev = dev; - dev->dev.archdata.edev = edev; - - eeh_addr_cache_insert_dev(dev); -} - -/** - * eeh_add_device_tree_late - Perform EEH initialization for the indicated PCI bus - * @bus: PCI bus - * - * This routine must be used to perform EEH initialization for PCI - * devices which are attached to the indicated PCI bus. The PCI bus - * is added after system boot through hotplug or dlpar. - */ -void eeh_add_device_tree_late(struct pci_bus *bus) -{ - struct pci_dev *dev; - - list_for_each_entry(dev, &bus->devices, bus_list) { - eeh_add_device_late(dev); - if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE) { - struct pci_bus *subbus = dev->subordinate; - if (subbus) - eeh_add_device_tree_late(subbus); - } - } -} -EXPORT_SYMBOL_GPL(eeh_add_device_tree_late); - -/** - * eeh_add_sysfs_files - Add EEH sysfs files for the indicated PCI bus - * @bus: PCI bus - * - * This routine must be used to add EEH sysfs files for PCI - * devices which are attached to the indicated PCI bus. The PCI bus - * is added after system boot through hotplug or dlpar. - */ -void eeh_add_sysfs_files(struct pci_bus *bus) -{ - struct pci_dev *dev; - - list_for_each_entry(dev, &bus->devices, bus_list) { - eeh_sysfs_add_device(dev); - if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE) { - struct pci_bus *subbus = dev->subordinate; - if (subbus) - eeh_add_sysfs_files(subbus); - } - } -} -EXPORT_SYMBOL_GPL(eeh_add_sysfs_files); - -/** - * eeh_remove_device - Undo EEH setup for the indicated pci device - * @dev: pci device to be removed - * @purge_pe: remove the PE or not - * - * This routine should be called when a device is removed from - * a running system (e.g. by hotplug or dlpar). It unregisters - * the PCI device from the EEH subsystem. I/O errors affecting - * this device will no longer be detected after this call; thus, - * i/o errors affecting this slot may leave this device unusable. - */ -static void eeh_remove_device(struct pci_dev *dev, int purge_pe) -{ - struct eeh_dev *edev; - - if (!dev || !eeh_subsystem_enabled) - return; - edev = pci_dev_to_eeh_dev(dev); - - /* Unregister the device with the EEH/PCI address search system */ - pr_debug("EEH: Removing device %s\n", pci_name(dev)); - - if (!edev || !edev->pdev) { - pr_debug("EEH: Not referenced !\n"); - return; - } - edev->pdev = NULL; - dev->dev.archdata.edev = NULL; - pci_dev_put(dev); - - eeh_rmv_from_parent_pe(edev, purge_pe); - eeh_addr_cache_rmv_dev(dev); - eeh_sysfs_remove_device(dev); -} - -/** - * eeh_remove_bus_device - Undo EEH setup for the indicated PCI device - * @dev: PCI device - * @purge_pe: remove the corresponding PE or not - * - * This routine must be called when a device is removed from the - * running system through hotplug or dlpar. The corresponding - * PCI address cache will be removed. - */ -void eeh_remove_bus_device(struct pci_dev *dev, int purge_pe) -{ - struct pci_bus *bus = dev->subordinate; - struct pci_dev *child, *tmp; - - eeh_remove_device(dev, purge_pe); - - if (bus && dev->hdr_type == PCI_HEADER_TYPE_BRIDGE) { - list_for_each_entry_safe(child, tmp, &bus->devices, bus_list) - eeh_remove_bus_device(child, purge_pe); - } -} -EXPORT_SYMBOL_GPL(eeh_remove_bus_device); - -static int proc_eeh_show(struct seq_file *m, void *v) -{ - if (0 == eeh_subsystem_enabled) { - seq_printf(m, "EEH Subsystem is globally disabled\n"); - seq_printf(m, "eeh_total_mmio_ffs=%llu\n", eeh_stats.total_mmio_ffs); - } else { - seq_printf(m, "EEH Subsystem is enabled\n"); - seq_printf(m, - "no device=%llu\n" - "no device node=%llu\n" - "no config address=%llu\n" - "check not wanted=%llu\n" - "eeh_total_mmio_ffs=%llu\n" - "eeh_false_positives=%llu\n" - "eeh_slot_resets=%llu\n", - eeh_stats.no_device, - eeh_stats.no_dn, - eeh_stats.no_cfg_addr, - eeh_stats.ignored_check, - eeh_stats.total_mmio_ffs, - eeh_stats.false_positives, - eeh_stats.slot_resets); - } - - return 0; -} - -static int proc_eeh_open(struct inode *inode, struct file *file) -{ - return single_open(file, proc_eeh_show, NULL); -} - -static const struct file_operations proc_eeh_operations = { - .open = proc_eeh_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; - -static int __init eeh_init_proc(void) -{ - if (machine_is(pseries)) - proc_create("powerpc/eeh", 0, NULL, &proc_eeh_operations); - return 0; -} -__initcall(eeh_init_proc); diff --git a/arch/powerpc/platforms/pseries/eeh_cache.c b/arch/powerpc/platforms/pseries/eeh_cache.c deleted file mode 100644 index 5a4c879..0000000 --- a/arch/powerpc/platforms/pseries/eeh_cache.c +++ /dev/null @@ -1,319 +0,0 @@ -/* - * PCI address cache; allows the lookup of PCI devices based on I/O address - * - * Copyright IBM Corporation 2004 - * Copyright Linas Vepstas <linas@austin.ibm.com> 2004 - * - * 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 - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#include <linux/list.h> -#include <linux/pci.h> -#include <linux/rbtree.h> -#include <linux/slab.h> -#include <linux/spinlock.h> -#include <linux/atomic.h> -#include <asm/pci-bridge.h> -#include <asm/ppc-pci.h> - - -/** - * The pci address cache subsystem. This subsystem places - * PCI device address resources into a red-black tree, sorted - * according to the address range, so that given only an i/o - * address, the corresponding PCI device can be **quickly** - * found. It is safe to perform an address lookup in an interrupt - * context; this ability is an important feature. - * - * Currently, the only customer of this code is the EEH subsystem; - * thus, this code has been somewhat tailored to suit EEH better. - * In particular, the cache does *not* hold the addresses of devices - * for which EEH is not enabled. - * - * (Implementation Note: The RB tree seems to be better/faster - * than any hash algo I could think of for this problem, even - * with the penalty of slow pointer chases for d-cache misses). - */ -struct pci_io_addr_range { - struct rb_node rb_node; - unsigned long addr_lo; - unsigned long addr_hi; - struct eeh_dev *edev; - struct pci_dev *pcidev; - unsigned int flags; -}; - -static struct pci_io_addr_cache { - struct rb_root rb_root; - spinlock_t piar_lock; -} pci_io_addr_cache_root; - -static inline struct eeh_dev *__eeh_addr_cache_get_device(unsigned long addr) -{ - struct rb_node *n = pci_io_addr_cache_root.rb_root.rb_node; - - while (n) { - struct pci_io_addr_range *piar; - piar = rb_entry(n, struct pci_io_addr_range, rb_node); - - if (addr < piar->addr_lo) { - n = n->rb_left; - } else { - if (addr > piar->addr_hi) { - n = n->rb_right; - } else { - pci_dev_get(piar->pcidev); - return piar->edev; - } - } - } - - return NULL; -} - -/** - * eeh_addr_cache_get_dev - Get device, given only address - * @addr: mmio (PIO) phys address or i/o port number - * - * Given an mmio phys address, or a port number, find a pci device - * that implements this address. Be sure to pci_dev_put the device - * when finished. I/O port numbers are assumed to be offset - * from zero (that is, they do *not* have pci_io_addr added in). - * It is safe to call this function within an interrupt. - */ -struct eeh_dev *eeh_addr_cache_get_dev(unsigned long addr) -{ - struct eeh_dev *edev; - unsigned long flags; - - spin_lock_irqsave(&pci_io_addr_cache_root.piar_lock, flags); - edev = __eeh_addr_cache_get_device(addr); - spin_unlock_irqrestore(&pci_io_addr_cache_root.piar_lock, flags); - return edev; -} - -#ifdef DEBUG -/* - * Handy-dandy debug print routine, does nothing more - * than print out the contents of our addr cache. - */ -static void eeh_addr_cache_print(struct pci_io_addr_cache *cache) -{ - struct rb_node *n; - int cnt = 0; - - n = rb_first(&cache->rb_root); - while (n) { - struct pci_io_addr_range *piar; - piar = rb_entry(n, struct pci_io_addr_range, rb_node); - pr_debug("PCI: %s addr range %d [%lx-%lx]: %s\n", - (piar->flags & IORESOURCE_IO) ? "i/o" : "mem", cnt, - piar->addr_lo, piar->addr_hi, pci_name(piar->pcidev)); - cnt++; - n = rb_next(n); - } -} -#endif - -/* Insert address range into the rb tree. */ -static struct pci_io_addr_range * -eeh_addr_cache_insert(struct pci_dev *dev, unsigned long alo, - unsigned long ahi, unsigned int flags) -{ - struct rb_node **p = &pci_io_addr_cache_root.rb_root.rb_node; - struct rb_node *parent = NULL; - struct pci_io_addr_range *piar; - - /* Walk tree, find a place to insert into tree */ - while (*p) { - parent = *p; - piar = rb_entry(parent, struct pci_io_addr_range, rb_node); - if (ahi < piar->addr_lo) { - p = &parent->rb_left; - } else if (alo > piar->addr_hi) { - p = &parent->rb_right; - } else { - if (dev != piar->pcidev || - alo != piar->addr_lo || ahi != piar->addr_hi) { - pr_warning("PIAR: overlapping address range\n"); - } - return piar; - } - } - piar = kzalloc(sizeof(struct pci_io_addr_range), GFP_ATOMIC); - if (!piar) - return NULL; - - pci_dev_get(dev); - piar->addr_lo = alo; - piar->addr_hi = ahi; - piar->edev = pci_dev_to_eeh_dev(dev); - piar->pcidev = dev; - piar->flags = flags; - -#ifdef DEBUG - pr_debug("PIAR: insert range=[%lx:%lx] dev=%s\n", - alo, ahi, pci_name(dev)); -#endif - - rb_link_node(&piar->rb_node, parent, p); - rb_insert_color(&piar->rb_node, &pci_io_addr_cache_root.rb_root); - - return piar; -} - -static void __eeh_addr_cache_insert_dev(struct pci_dev *dev) -{ - struct device_node *dn; - struct eeh_dev *edev; - int i; - - dn = pci_device_to_OF_node(dev); - if (!dn) { - pr_warning("PCI: no pci dn found for dev=%s\n", pci_name(dev)); - return; - } - - edev = of_node_to_eeh_dev(dn); - if (!edev) { - pr_warning("PCI: no EEH dev found for dn=%s\n", - dn->full_name); - return; - } - - /* Skip any devices for which EEH is not enabled. */ - if (!edev->pe) { -#ifdef DEBUG - pr_info("PCI: skip building address cache for=%s - %s\n", - pci_name(dev), dn->full_name); -#endif - return; - } - - /* Walk resources on this device, poke them into the tree */ - for (i = 0; i < DEVICE_COUNT_RESOURCE; i++) { - unsigned long start = pci_resource_start(dev,i); - unsigned long end = pci_resource_end(dev,i); - unsigned int flags = pci_resource_flags(dev,i); - - /* We are interested only bus addresses, not dma or other stuff */ - if (0 == (flags & (IORESOURCE_IO | IORESOURCE_MEM))) - continue; - if (start == 0 || ~start == 0 || end == 0 || ~end == 0) - continue; - eeh_addr_cache_insert(dev, start, end, flags); - } -} - -/** - * eeh_addr_cache_insert_dev - Add a device to the address cache - * @dev: PCI device whose I/O addresses we are interested in. - * - * In order to support the fast lookup of devices based on addresses, - * we maintain a cache of devices that can be quickly searched. - * This routine adds a device to that cache. - */ -void eeh_addr_cache_insert_dev(struct pci_dev *dev) -{ - unsigned long flags; - - /* Ignore PCI bridges */ - if ((dev->class >> 16) == PCI_BASE_CLASS_BRIDGE) - return; - - spin_lock_irqsave(&pci_io_addr_cache_root.piar_lock, flags); - __eeh_addr_cache_insert_dev(dev); - spin_unlock_irqrestore(&pci_io_addr_cache_root.piar_lock, flags); -} - -static inline void __eeh_addr_cache_rmv_dev(struct pci_dev *dev) -{ - struct rb_node *n; - -restart: - n = rb_first(&pci_io_addr_cache_root.rb_root); - while (n) { - struct pci_io_addr_range *piar; - piar = rb_entry(n, struct pci_io_addr_range, rb_node); - - if (piar->pcidev == dev) { - rb_erase(n, &pci_io_addr_cache_root.rb_root); - pci_dev_put(piar->pcidev); - kfree(piar); - goto restart; - } - n = rb_next(n); - } -} - -/** - * eeh_addr_cache_rmv_dev - remove pci device from addr cache - * @dev: device to remove - * - * Remove a device from the addr-cache tree. - * This is potentially expensive, since it will walk - * the tree multiple times (once per resource). - * But so what; device removal doesn't need to be that fast. - */ -void eeh_addr_cache_rmv_dev(struct pci_dev *dev) -{ - unsigned long flags; - - spin_lock_irqsave(&pci_io_addr_cache_root.piar_lock, flags); - __eeh_addr_cache_rmv_dev(dev); - spin_unlock_irqrestore(&pci_io_addr_cache_root.piar_lock, flags); -} - -/** - * eeh_addr_cache_build - Build a cache of I/O addresses - * - * Build a cache of pci i/o addresses. This cache will be used to - * find the pci device that corresponds to a given address. - * This routine scans all pci busses to build the cache. - * Must be run late in boot process, after the pci controllers - * have been scanned for devices (after all device resources are known). - */ -void __init eeh_addr_cache_build(void) -{ - struct device_node *dn; - struct eeh_dev *edev; - struct pci_dev *dev = NULL; - - spin_lock_init(&pci_io_addr_cache_root.piar_lock); - - for_each_pci_dev(dev) { - eeh_addr_cache_insert_dev(dev); - - dn = pci_device_to_OF_node(dev); - if (!dn) - continue; - - edev = of_node_to_eeh_dev(dn); - if (!edev) - continue; - - pci_dev_get(dev); /* matching put is in eeh_remove_device() */ - dev->dev.archdata.edev = edev; - edev->pdev = dev; - - eeh_sysfs_add_device(dev); - } - -#ifdef DEBUG - /* Verify tree built up above, echo back the list of addrs. */ - eeh_addr_cache_print(&pci_io_addr_cache_root); -#endif -} - diff --git a/arch/powerpc/platforms/pseries/eeh_dev.c b/arch/powerpc/platforms/pseries/eeh_dev.c deleted file mode 100644 index 1efa28f..0000000 --- a/arch/powerpc/platforms/pseries/eeh_dev.c +++ /dev/null @@ -1,112 +0,0 @@ -/* - * The file intends to implement dynamic creation of EEH device, which will - * be bound with OF node and PCI device simutaneously. The EEH devices would - * be foundamental information for EEH core components to work proerly. Besides, - * We have to support multiple situations where dynamic creation of EEH device - * is required: - * - * 1) Before PCI emunation starts, we need create EEH devices according to the - * PCI sensitive OF nodes. - * 2) When PCI emunation is done, we need do the binding between PCI device and - * the associated EEH device. - * 3) DR (Dynamic Reconfiguration) would create PCI sensitive OF node. EEH device - * will be created while PCI sensitive OF node is detected from DR. - * 4) PCI hotplug needs redoing the binding between PCI device and EEH device. If - * PHB is newly inserted, we also need create EEH devices accordingly. - * - * Copyright Benjamin Herrenschmidt & Gavin Shan, IBM Corporation 2012. - * - * 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 - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#include <linux/export.h> -#include <linux/gfp.h> -#include <linux/init.h> -#include <linux/kernel.h> -#include <linux/pci.h> -#include <linux/string.h> - -#include <asm/pci-bridge.h> -#include <asm/ppc-pci.h> - -/** - * eeh_dev_init - Create EEH device according to OF node - * @dn: device node - * @data: PHB - * - * It will create EEH device according to the given OF node. The function - * might be called by PCI emunation, DR, PHB hotplug. - */ -void *eeh_dev_init(struct device_node *dn, void *data) -{ - struct pci_controller *phb = data; - struct eeh_dev *edev; - - /* Allocate EEH device */ - edev = kzalloc(sizeof(*edev), GFP_KERNEL); - if (!edev) { - pr_warning("%s: out of memory\n", __func__); - return NULL; - } - - /* Associate EEH device with OF node */ - PCI_DN(dn)->edev = edev; - edev->dn = dn; - edev->phb = phb; - INIT_LIST_HEAD(&edev->list); - - return NULL; -} - -/** - * eeh_dev_phb_init_dynamic - Create EEH devices for devices included in PHB - * @phb: PHB - * - * Scan the PHB OF node and its child association, then create the - * EEH devices accordingly - */ -void eeh_dev_phb_init_dynamic(struct pci_controller *phb) -{ - struct device_node *dn = phb->dn; - - /* EEH PE for PHB */ - eeh_phb_pe_create(phb); - - /* EEH device for PHB */ - eeh_dev_init(dn, phb); - - /* EEH devices for children OF nodes */ - traverse_pci_devices(dn, eeh_dev_init, phb); -} - -/** - * eeh_dev_phb_init - Create EEH devices for devices included in existing PHBs - * - * Scan all the existing PHBs and create EEH devices for their OF - * nodes and their children OF nodes - */ -static int __init eeh_dev_phb_init(void) -{ - struct pci_controller *phb, *tmp; - - list_for_each_entry_safe(phb, tmp, &hose_list, list_node) - eeh_dev_phb_init_dynamic(phb); - - pr_info("EEH: devices created\n"); - - return 0; -} - -core_initcall(eeh_dev_phb_init); diff --git a/arch/powerpc/platforms/pseries/eeh_driver.c b/arch/powerpc/platforms/pseries/eeh_driver.c deleted file mode 100644 index a3fefb6..0000000 --- a/arch/powerpc/platforms/pseries/eeh_driver.c +++ /dev/null @@ -1,552 +0,0 @@ -/* - * PCI Error Recovery Driver for RPA-compliant PPC64 platform. - * Copyright IBM Corp. 2004 2005 - * Copyright Linas Vepstas <linas@linas.org> 2004, 2005 - * - * All rights reserved. - * - * 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 - * the Free Software Foundation; either version 2 of the License, or (at - * your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or - * NON INFRINGEMENT. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - * - * Send comments and feedback to Linas Vepstas <linas@austin.ibm.com> - */ -#include <linux/delay.h> -#include <linux/interrupt.h> -#include <linux/irq.h> -#include <linux/module.h> -#include <linux/pci.h> -#include <asm/eeh.h> -#include <asm/eeh_event.h> -#include <asm/ppc-pci.h> -#include <asm/pci-bridge.h> -#include <asm/prom.h> -#include <asm/rtas.h> - -/** - * eeh_pcid_name - Retrieve name of PCI device driver - * @pdev: PCI device - * - * This routine is used to retrieve the name of PCI device driver - * if that's valid. - */ -static inline const char *eeh_pcid_name(struct pci_dev *pdev) -{ - if (pdev && pdev->dev.driver) - return pdev->dev.driver->name; - return ""; -} - -/** - * eeh_pcid_get - Get the PCI device driver - * @pdev: PCI device - * - * The function is used to retrieve the PCI device driver for - * the indicated PCI device. Besides, we will increase the reference - * of the PCI device driver to prevent that being unloaded on - * the fly. Otherwise, kernel crash would be seen. - */ -static inline struct pci_driver *eeh_pcid_get(struct pci_dev *pdev) -{ - if (!pdev || !pdev->driver) - return NULL; - - if (!try_module_get(pdev->driver->driver.owner)) - return NULL; - - return pdev->driver; -} - -/** - * eeh_pcid_put - Dereference on the PCI device driver - * @pdev: PCI device - * - * The function is called to do dereference on the PCI device - * driver of the indicated PCI device. - */ -static inline void eeh_pcid_put(struct pci_dev *pdev) -{ - if (!pdev || !pdev->driver) - return; - - module_put(pdev->driver->driver.owner); -} - -#if 0 -static void print_device_node_tree(struct pci_dn *pdn, int dent) -{ - int i; - struct device_node *pc; - - if (!pdn) - return; - for (i = 0; i < dent; i++) - printk(" "); - printk("dn=%s mode=%x \tcfg_addr=%x pe_addr=%x \tfull=%s\n", - pdn->node->name, pdn->eeh_mode, pdn->eeh_config_addr, - pdn->eeh_pe_config_addr, pdn->node->full_name); - dent += 3; - pc = pdn->node->child; - while (pc) { - print_device_node_tree(PCI_DN(pc), dent); - pc = pc->sibling; - } -} -#endif - -/** - * eeh_disable_irq - Disable interrupt for the recovering device - * @dev: PCI device - * - * This routine must be called when reporting temporary or permanent - * error to the particular PCI device to disable interrupt of that - * device. If the device has enabled MSI or MSI-X interrupt, we needn't - * do real work because EEH should freeze DMA transfers for those PCI - * devices encountering EEH errors, which includes MSI or MSI-X. - */ -static void eeh_disable_irq(struct pci_dev *dev) -{ - struct eeh_dev *edev = pci_dev_to_eeh_dev(dev); - - /* Don't disable MSI and MSI-X interrupts. They are - * effectively disabled by the DMA Stopped state - * when an EEH error occurs. - */ - if (dev->msi_enabled || dev->msix_enabled) - return; - - if (!irq_has_action(dev->irq)) - return; - - edev->mode |= EEH_DEV_IRQ_DISABLED; - disable_irq_nosync(dev->irq); -} - -/** - * eeh_enable_irq - Enable interrupt for the recovering device - * @dev: PCI device - * - * This routine must be called to enable interrupt while failed - * device could be resumed. - */ -static void eeh_enable_irq(struct pci_dev *dev) -{ - struct eeh_dev *edev = pci_dev_to_eeh_dev(dev); - - if ((edev->mode) & EEH_DEV_IRQ_DISABLED) { - edev->mode &= ~EEH_DEV_IRQ_DISABLED; - enable_irq(dev->irq); - } -} - -/** - * eeh_report_error - Report pci error to each device driver - * @data: eeh device - * @userdata: return value - * - * Report an EEH error to each device driver, collect up and - * merge the device driver responses. Cumulative response - * passed back in "userdata". - */ -static void *eeh_report_error(void *data, void *userdata) -{ - struct eeh_dev *edev = (struct eeh_dev *)data; - struct pci_dev *dev = eeh_dev_to_pci_dev(edev); - enum pci_ers_result rc, *res = userdata; - struct pci_driver *driver; - - /* We might not have the associated PCI device, - * then we should continue for next one. - */ - if (!dev) return NULL; - dev->error_state = pci_channel_io_frozen; - - driver = eeh_pcid_get(dev); - if (!driver) return NULL; - - eeh_disable_irq(dev); - - if (!driver->err_handler || - !driver->err_handler->error_detected) { - eeh_pcid_put(dev); - return NULL; - } - - rc = driver->err_handler->error_detected(dev, pci_channel_io_frozen); - - /* A driver that needs a reset trumps all others */ - if (rc == PCI_ERS_RESULT_NEED_RESET) *res = rc; - if (*res == PCI_ERS_RESULT_NONE) *res = rc; - - eeh_pcid_put(dev); - return NULL; -} - -/** - * eeh_report_mmio_enabled - Tell drivers that MMIO has been enabled - * @data: eeh device - * @userdata: return value - * - * Tells each device driver that IO ports, MMIO and config space I/O - * are now enabled. Collects up and merges the device driver responses. - * Cumulative response passed back in "userdata". - */ -static void *eeh_report_mmio_enabled(void *data, void *userdata) -{ - struct eeh_dev *edev = (struct eeh_dev *)data; - struct pci_dev *dev = eeh_dev_to_pci_dev(edev); - enum pci_ers_result rc, *res = userdata; - struct pci_driver *driver; - - driver = eeh_pcid_get(dev); - if (!driver) return NULL; - - if (!driver->err_handler || - !driver->err_handler->mmio_enabled) { - eeh_pcid_put(dev); - return NULL; - } - - rc = driver->err_handler->mmio_enabled(dev); - - /* A driver that needs a reset trumps all others */ - if (rc == PCI_ERS_RESULT_NEED_RESET) *res = rc; - if (*res == PCI_ERS_RESULT_NONE) *res = rc; - - eeh_pcid_put(dev); - return NULL; -} - -/** - * eeh_report_reset - Tell device that slot has been reset - * @data: eeh device - * @userdata: return value - * - * This routine must be called while EEH tries to reset particular - * PCI device so that the associated PCI device driver could take - * some actions, usually to save data the driver needs so that the - * driver can work again while the device is recovered. - */ -static void *eeh_report_reset(void *data, void *userdata) -{ - struct eeh_dev *edev = (struct eeh_dev *)data; - struct pci_dev *dev = eeh_dev_to_pci_dev(edev); - enum pci_ers_result rc, *res = userdata; - struct pci_driver *driver; - - if (!dev) return NULL; - dev->error_state = pci_channel_io_normal; - - driver = eeh_pcid_get(dev); - if (!driver) return NULL; - - eeh_enable_irq(dev); - - if (!driver->err_handler || - !driver->err_handler->slot_reset) { - eeh_pcid_put(dev); - return NULL; - } - - rc = driver->err_handler->slot_reset(dev); - if ((*res == PCI_ERS_RESULT_NONE) || - (*res == PCI_ERS_RESULT_RECOVERED)) *res = rc; - if (*res == PCI_ERS_RESULT_DISCONNECT && - rc == PCI_ERS_RESULT_NEED_RESET) *res = rc; - - eeh_pcid_put(dev); - return NULL; -} - -/** - * eeh_report_resume - Tell device to resume normal operations - * @data: eeh device - * @userdata: return value - * - * This routine must be called to notify the device driver that it - * could resume so that the device driver can do some initialization - * to make the recovered device work again. - */ -static void *eeh_report_resume(void *data, void *userdata) -{ - struct eeh_dev *edev = (struct eeh_dev *)data; - struct pci_dev *dev = eeh_dev_to_pci_dev(edev); - struct pci_driver *driver; - - if (!dev) return NULL; - dev->error_state = pci_channel_io_normal; - - driver = eeh_pcid_get(dev); - if (!driver) return NULL; - - eeh_enable_irq(dev); - - if (!driver->err_handler || - !driver->err_handler->resume) { - eeh_pcid_put(dev); - return NULL; - } - - driver->err_handler->resume(dev); - - eeh_pcid_put(dev); - return NULL; -} - -/** - * eeh_report_failure - Tell device driver that device is dead. - * @data: eeh device - * @userdata: return value - * - * This informs the device driver that the device is permanently - * dead, and that no further recovery attempts will be made on it. - */ -static void *eeh_report_failure(void *data, void *userdata) -{ - struct eeh_dev *edev = (struct eeh_dev *)data; - struct pci_dev *dev = eeh_dev_to_pci_dev(edev); - struct pci_driver *driver; - - if (!dev) return NULL; - dev->error_state = pci_channel_io_perm_failure; - - driver = eeh_pcid_get(dev); - if (!driver) return NULL; - - eeh_disable_irq(dev); - - if (!driver->err_handler || - !driver->err_handler->error_detected) { - eeh_pcid_put(dev); - return NULL; - } - - driver->err_handler->error_detected(dev, pci_channel_io_perm_failure); - - eeh_pcid_put(dev); - return NULL; -} - -/** - * eeh_reset_device - Perform actual reset of a pci slot - * @pe: EEH PE - * @bus: PCI bus corresponding to the isolcated slot - * - * This routine must be called to do reset on the indicated PE. - * During the reset, udev might be invoked because those affected - * PCI devices will be removed and then added. - */ -static int eeh_reset_device(struct eeh_pe *pe, struct pci_bus *bus) -{ - int cnt, rc; - - /* pcibios will clear the counter; save the value */ - cnt = pe->freeze_count; - - /* - * We don't remove the corresponding PE instances because - * we need the information afterwords. The attached EEH - * devices are expected to be attached soon when calling - * into pcibios_add_pci_devices(). - */ - if (bus) - __pcibios_remove_pci_devices(bus, 0); - - /* Reset the pci controller. (Asserts RST#; resets config space). - * Reconfigure bridges and devices. Don't try to bring the system - * up if the reset failed for some reason. - */ - rc = eeh_reset_pe(pe); - if (rc) - return rc; - - /* Restore PE */ - eeh_ops->configure_bridge(pe); - eeh_pe_restore_bars(pe); - - /* Give the system 5 seconds to finish running the user-space - * hotplug shutdown scripts, e.g. ifdown for ethernet. Yes, - * this is a hack, but if we don't do this, and try to bring - * the device up before the scripts have taken it down, - * potentially weird things happen. - */ - if (bus) { - ssleep(5); - pcibios_add_pci_devices(bus); - } - pe->freeze_count = cnt; - - return 0; -} - -/* The longest amount of time to wait for a pci device - * to come back on line, in seconds. - */ -#define MAX_WAIT_FOR_RECOVERY 150 - -/** - * eeh_handle_event - Reset a PCI device after hard lockup. - * @pe: EEH PE - * - * While PHB detects address or data parity errors on particular PCI - * slot, the associated PE will be frozen. Besides, DMA's occurring - * to wild addresses (which usually happen due to bugs in device - * drivers or in PCI adapter firmware) can cause EEH error. #SERR, - * #PERR or other misc PCI-related errors also can trigger EEH errors. - * - * Recovery process consists of unplugging the device driver (which - * generated hotplug events to userspace), then issuing a PCI #RST to - * the device, then reconfiguring the PCI config space for all bridges - * & devices under this slot, and then finally restarting the device - * drivers (which cause a second set of hotplug events to go out to - * userspace). - */ -void eeh_handle_event(struct eeh_pe *pe) -{ - struct pci_bus *frozen_bus; - int rc = 0; - enum pci_ers_result result = PCI_ERS_RESULT_NONE; - - frozen_bus = eeh_pe_bus_get(pe); - if (!frozen_bus) { - pr_err("%s: Cannot find PCI bus for PHB#%d-PE#%x\n", - __func__, pe->phb->global_number, pe->addr); - return; - } - - pe->freeze_count++; - if (pe->freeze_count > EEH_MAX_ALLOWED_FREEZES) - goto excess_failures; - pr_warning("EEH: This PCI device has failed %d times in the last hour\n", - pe->freeze_count); - - /* Walk the various device drivers attached to this slot through - * a reset sequence, giving each an opportunity to do what it needs - * to accomplish the reset. Each child gets a report of the - * status ... if any child can't handle the reset, then the entire - * slot is dlpar removed and added. - */ - eeh_pe_dev_traverse(pe, eeh_report_error, &result); - - /* Get the current PCI slot state. This can take a long time, - * sometimes over 3 seconds for certain systems. - */ - rc = eeh_ops->wait_state(pe, MAX_WAIT_FOR_RECOVERY*1000); - if (rc < 0 || rc == EEH_STATE_NOT_SUPPORT) { - printk(KERN_WARNING "EEH: Permanent failure\n"); - goto hard_fail; - } - - /* Since rtas may enable MMIO when posting the error log, - * don't post the error log until after all dev drivers - * have been informed. - */ - eeh_slot_error_detail(pe, EEH_LOG_TEMP); - - /* If all device drivers were EEH-unaware, then shut - * down all of the device drivers, and hope they - * go down willingly, without panicing the system. - */ - if (result == PCI_ERS_RESULT_NONE) { - rc = eeh_reset_device(pe, frozen_bus); - if (rc) { - printk(KERN_WARNING "EEH: Unable to reset, rc=%d\n", rc); - goto hard_fail; - } - } - - /* If all devices reported they can proceed, then re-enable MMIO */ - if (result == PCI_ERS_RESULT_CAN_RECOVER) { - rc = eeh_pci_enable(pe, EEH_OPT_THAW_MMIO); - - if (rc < 0) - goto hard_fail; - if (rc) { - result = PCI_ERS_RESULT_NEED_RESET; - } else { - result = PCI_ERS_RESULT_NONE; - eeh_pe_dev_traverse(pe, eeh_report_mmio_enabled, &result); - } - } - - /* If all devices reported they can proceed, then re-enable DMA */ - if (result == PCI_ERS_RESULT_CAN_RECOVER) { - rc = eeh_pci_enable(pe, EEH_OPT_THAW_DMA); - - if (rc < 0) - goto hard_fail; - if (rc) - result = PCI_ERS_RESULT_NEED_RESET; - else - result = PCI_ERS_RESULT_RECOVERED; - } - - /* If any device has a hard failure, then shut off everything. */ - if (result == PCI_ERS_RESULT_DISCONNECT) { - printk(KERN_WARNING "EEH: Device driver gave up\n"); - goto hard_fail; - } - - /* If any device called out for a reset, then reset the slot */ - if (result == PCI_ERS_RESULT_NEED_RESET) { - rc = eeh_reset_device(pe, NULL); - if (rc) { - printk(KERN_WARNING "EEH: Cannot reset, rc=%d\n", rc); - goto hard_fail; - } - result = PCI_ERS_RESULT_NONE; - eeh_pe_dev_traverse(pe, eeh_report_reset, &result); - } - - /* All devices should claim they have recovered by now. */ - if ((result != PCI_ERS_RESULT_RECOVERED) && - (result != PCI_ERS_RESULT_NONE)) { - printk(KERN_WARNING "EEH: Not recovered\n"); - goto hard_fail; - } - - /* Tell all device drivers that they can resume operations */ - eeh_pe_dev_traverse(pe, eeh_report_resume, NULL); - - return; - -excess_failures: - /* - * About 90% of all real-life EEH failures in the field - * are due to poorly seated PCI cards. Only 10% or so are - * due to actual, failed cards. - */ - pr_err("EEH: PHB#%d-PE#%x has failed %d times in the\n" - "last hour and has been permanently disabled.\n" - "Please try reseating or replacing it.\n", - pe->phb->global_number, pe->addr, - pe->freeze_count); - goto perm_error; - -hard_fail: - pr_err("EEH: Unable to recover from failure from PHB#%d-PE#%x.\n" - "Please try reseating or replacing it\n", - pe->phb->global_number, pe->addr); - -perm_error: - eeh_slot_error_detail(pe, EEH_LOG_PERM); - - /* Notify all devices that they're about to go down. */ - eeh_pe_dev_traverse(pe, eeh_report_failure, NULL); - - /* Shut down the device drivers for good. */ - if (frozen_bus) - pcibios_remove_pci_devices(frozen_bus); -} - diff --git a/arch/powerpc/platforms/pseries/eeh_event.c b/arch/powerpc/platforms/pseries/eeh_event.c deleted file mode 100644 index 185bedd..0000000 --- a/arch/powerpc/platforms/pseries/eeh_event.c +++ /dev/null @@ -1,142 +0,0 @@ -/* - * 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 - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - * Copyright (c) 2005 Linas Vepstas <linas@linas.org> - */ - -#include <linux/delay.h> -#include <linux/list.h> -#include <linux/mutex.h> -#include <linux/sched.h> -#include <linux/pci.h> -#include <linux/slab.h> -#include <linux/workqueue.h> -#include <linux/kthread.h> -#include <asm/eeh_event.h> -#include <asm/ppc-pci.h> - -/** Overview: - * EEH error states may be detected within exception handlers; - * however, the recovery processing needs to occur asynchronously - * in a normal kernel context and not an interrupt context. - * This pair of routines creates an event and queues it onto a - * work-queue, where a worker thread can drive recovery. - */ - -/* EEH event workqueue setup. */ -static DEFINE_SPINLOCK(eeh_eventlist_lock); -LIST_HEAD(eeh_eventlist); -static void eeh_thread_launcher(struct work_struct *); -DECLARE_WORK(eeh_event_wq, eeh_thread_launcher); - -/* Serialize reset sequences for a given pci device */ -DEFINE_MUTEX(eeh_event_mutex); - -/** - * eeh_event_handler - Dispatch EEH events. - * @dummy - unused - * - * 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. - */ -static int eeh_event_handler(void * dummy) -{ - unsigned long flags; - struct eeh_event *event; - struct eeh_pe *pe; - - spin_lock_irqsave(&eeh_eventlist_lock, flags); - event = NULL; - - /* Unqueue the event, get ready to process. */ - 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) - return 0; - - /* Serialize processing of EEH events */ - mutex_lock(&eeh_event_mutex); - pe = event->pe; - eeh_pe_state_mark(pe, EEH_PE_RECOVERING); - pr_info("EEH: Detected PCI bus error on PHB#%d-PE#%x\n", - pe->phb->global_number, pe->addr); - - set_current_state(TASK_INTERRUPTIBLE); /* Don't add to load average */ - eeh_handle_event(pe); - eeh_pe_state_clear(pe, EEH_PE_RECOVERING); - - kfree(event); - mutex_unlock(&eeh_event_mutex); - - /* If there are no new errors after an hour, clear the counter. */ - if (pe && pe->freeze_count > 0) { - msleep_interruptible(3600*1000); - if (pe->freeze_count > 0) - pe->freeze_count--; - - } - - return 0; -} - -/** - * eeh_thread_launcher - Start kernel thread to handle EEH events - * @dummy - unused - * - * This routine is called to start the kernel thread for processing - * EEH event. - */ -static void eeh_thread_launcher(struct work_struct *dummy) -{ - if (IS_ERR(kthread_run(eeh_event_handler, NULL, "eehd"))) - printk(KERN_ERR "Failed to start EEH daemon\n"); -} - -/** - * eeh_send_failure_event - Generate a PCI error event - * @pe: EEH PE - * - * This routine can be called within an interrupt context; - * the actual event will be delivered in a normal context - * (from a workqueue). - */ -int eeh_send_failure_event(struct eeh_pe *pe) -{ - unsigned long flags; - struct eeh_event *event; - - event = kzalloc(sizeof(*event), GFP_ATOMIC); - if (!event) { - pr_err("EEH: out of memory, event not handled\n"); - return -ENOMEM; - } - event->pe = pe; - - /* 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); - - schedule_work(&eeh_event_wq); - - return 0; -} diff --git a/arch/powerpc/platforms/pseries/eeh_pe.c b/arch/powerpc/platforms/pseries/eeh_pe.c deleted file mode 100644 index 9d4a9e8..0000000 --- a/arch/powerpc/platforms/pseries/eeh_pe.c +++ /dev/null @@ -1,653 +0,0 @@ -/* - * The file intends to implement PE based on the information from - * platforms. Basically, there have 3 types of PEs: PHB/Bus/Device. - * All the PEs should be organized as hierarchy tree. The first level - * of the tree will be associated to existing PHBs since the particular - * PE is only meaningful in one PHB domain. - * - * Copyright Benjamin Herrenschmidt & Gavin Shan, IBM Corporation 2012. - * - * 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 - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#include <linux/export.h> -#include <linux/gfp.h> -#include <linux/init.h> -#include <linux/kernel.h> -#include <linux/pci.h> -#include <linux/string.h> - -#include <asm/pci-bridge.h> -#include <asm/ppc-pci.h> - -static LIST_HEAD(eeh_phb_pe); - -/** - * eeh_pe_alloc - Allocate PE - * @phb: PCI controller - * @type: PE type - * - * Allocate PE instance dynamically. - */ -static struct eeh_pe *eeh_pe_alloc(struct pci_controller *phb, int type) -{ - struct eeh_pe *pe; - - /* Allocate PHB PE */ - pe = kzalloc(sizeof(struct eeh_pe), GFP_KERNEL); - if (!pe) return NULL; - - /* Initialize PHB PE */ - pe->type = type; - pe->phb = phb; - INIT_LIST_HEAD(&pe->child_list); - INIT_LIST_HEAD(&pe->child); - INIT_LIST_HEAD(&pe->edevs); - - return pe; -} - -/** - * eeh_phb_pe_create - Create PHB PE - * @phb: PCI controller - * - * The function should be called while the PHB is detected during - * system boot or PCI hotplug in order to create PHB PE. - */ -int eeh_phb_pe_create(struct pci_controller *phb) -{ - struct eeh_pe *pe; - - /* Allocate PHB PE */ - pe = eeh_pe_alloc(phb, EEH_PE_PHB); - if (!pe) { - pr_err("%s: out of memory!\n", __func__); - return -ENOMEM; - } - - /* Put it into the list */ - eeh_lock(); - list_add_tail(&pe->child, &eeh_phb_pe); - eeh_unlock(); - - pr_debug("EEH: Add PE for PHB#%d\n", phb->global_number); - - return 0; -} - -/** - * eeh_phb_pe_get - Retrieve PHB PE based on the given PHB - * @phb: PCI controller - * - * The overall PEs form hierarchy tree. The first layer of the - * hierarchy tree is composed of PHB PEs. The function is used - * to retrieve the corresponding PHB PE according to the given PHB. - */ -static struct eeh_pe *eeh_phb_pe_get(struct pci_controller *phb) -{ - struct eeh_pe *pe; - - list_for_each_entry(pe, &eeh_phb_pe, child) { - /* - * Actually, we needn't check the type since - * the PE for PHB has been determined when that - * was created. - */ - if ((pe->type & EEH_PE_PHB) && pe->phb == phb) - return pe; - } - - return NULL; -} - -/** - * eeh_pe_next - Retrieve the next PE in the tree - * @pe: current PE - * @root: root PE - * - * The function is used to retrieve the next PE in the - * hierarchy PE tree. - */ -static struct eeh_pe *eeh_pe_next(struct eeh_pe *pe, - struct eeh_pe *root) -{ - struct list_head *next = pe->child_list.next; - - if (next == &pe->child_list) { - while (1) { - if (pe == root) - return NULL; - next = pe->child.next; - if (next != &pe->parent->child_list) - break; - pe = pe->parent; - } - } - - return list_entry(next, struct eeh_pe, child); -} - -/** - * eeh_pe_traverse - Traverse PEs in the specified PHB - * @root: root PE - * @fn: callback - * @flag: extra parameter to callback - * - * The function is used to traverse the specified PE and its - * child PEs. The traversing is to be terminated once the - * callback returns something other than NULL, or no more PEs - * to be traversed. - */ -static void *eeh_pe_traverse(struct eeh_pe *root, - eeh_traverse_func fn, void *flag) -{ - struct eeh_pe *pe; - void *ret; - - for (pe = root; pe; pe = eeh_pe_next(pe, root)) { - ret = fn(pe, flag); - if (ret) return ret; - } - - return NULL; -} - -/** - * eeh_pe_dev_traverse - Traverse the devices from the PE - * @root: EEH PE - * @fn: function callback - * @flag: extra parameter to callback - * - * The function is used to traverse the devices of the specified - * PE and its child PEs. - */ -void *eeh_pe_dev_traverse(struct eeh_pe *root, - eeh_traverse_func fn, void *flag) -{ - struct eeh_pe *pe; - struct eeh_dev *edev; - void *ret; - - if (!root) { - pr_warning("%s: Invalid PE %p\n", __func__, root); - return NULL; - } - - eeh_lock(); - - /* Traverse root PE */ - for (pe = root; pe; pe = eeh_pe_next(pe, root)) { - eeh_pe_for_each_dev(pe, edev) { - ret = fn(edev, flag); - if (ret) { - eeh_unlock(); - return ret; - } - } - } - - eeh_unlock(); - - return NULL; -} - -/** - * __eeh_pe_get - Check the PE address - * @data: EEH PE - * @flag: EEH device - * - * For one particular PE, it can be identified by PE address - * or tranditional BDF address. BDF address is composed of - * Bus/Device/Function number. The extra data referred by flag - * indicates which type of address should be used. - */ -static void *__eeh_pe_get(void *data, void *flag) -{ - struct eeh_pe *pe = (struct eeh_pe *)data; - struct eeh_dev *edev = (struct eeh_dev *)flag; - - /* Unexpected PHB PE */ - if (pe->type & EEH_PE_PHB) - return NULL; - - /* We prefer PE address */ - if (edev->pe_config_addr && - (edev->pe_config_addr == pe->addr)) - return pe; - - /* Try BDF address */ - if (edev->pe_config_addr && - (edev->config_addr == pe->config_addr)) - return pe; - - return NULL; -} - -/** - * eeh_pe_get - Search PE based on the given address - * @edev: EEH device - * - * Search the corresponding PE based on the specified address which - * is included in the eeh device. The function is used to check if - * the associated PE has been created against the PE address. It's - * notable that the PE address has 2 format: traditional PE address - * which is composed of PCI bus/device/function number, or unified - * PE address. - */ -static struct eeh_pe *eeh_pe_get(struct eeh_dev *edev) -{ - struct eeh_pe *root = eeh_phb_pe_get(edev->phb); - struct eeh_pe *pe; - - pe = eeh_pe_traverse(root, __eeh_pe_get, edev); - - return pe; -} - -/** - * eeh_pe_get_parent - Retrieve the parent PE - * @edev: EEH device - * - * The whole PEs existing in the system are organized as hierarchy - * tree. The function is used to retrieve the parent PE according - * to the parent EEH device. - */ -static struct eeh_pe *eeh_pe_get_parent(struct eeh_dev *edev) -{ - struct device_node *dn; - struct eeh_dev *parent; - - /* - * It might have the case for the indirect parent - * EEH device already having associated PE, but - * the direct parent EEH device doesn't have yet. - */ - dn = edev->dn->parent; - while (dn) { - /* We're poking out of PCI territory */ - if (!PCI_DN(dn)) return NULL; - - parent = of_node_to_eeh_dev(dn); - /* We're poking out of PCI territory */ - if (!parent) return NULL; - - if (parent->pe) - return parent->pe; - - dn = dn->parent; - } - - return NULL; -} - -/** - * eeh_add_to_parent_pe - Add EEH device to parent PE - * @edev: EEH device - * - * Add EEH device to the parent PE. If the parent PE already - * exists, the PE type will be changed to EEH_PE_BUS. Otherwise, - * we have to create new PE to hold the EEH device and the new - * PE will be linked to its parent PE as well. - */ -int eeh_add_to_parent_pe(struct eeh_dev *edev) -{ - struct eeh_pe *pe, *parent; - - eeh_lock(); - - /* - * Search the PE has been existing or not according - * to the PE address. If that has been existing, the - * PE should be composed of PCI bus and its subordinate - * components. - */ - pe = eeh_pe_get(edev); - if (pe && !(pe->type & EEH_PE_INVALID)) { - if (!edev->pe_config_addr) { - eeh_unlock(); - pr_err("%s: PE with addr 0x%x already exists\n", - __func__, edev->config_addr); - return -EEXIST; - } - - /* Mark the PE as type of PCI bus */ - pe->type = EEH_PE_BUS; - edev->pe = pe; - - /* Put the edev to PE */ - list_add_tail(&edev->list, &pe->edevs); - eeh_unlock(); - pr_debug("EEH: Add %s to Bus PE#%x\n", - edev->dn->full_name, pe->addr); - - return 0; - } else if (pe && (pe->type & EEH_PE_INVALID)) { - list_add_tail(&edev->list, &pe->edevs); - edev->pe = pe; - /* - * We're running to here because of PCI hotplug caused by - * EEH recovery. We need clear EEH_PE_INVALID until the top. - */ - parent = pe; - while (parent) { - if (!(parent->type & EEH_PE_INVALID)) - break; - parent->type &= ~EEH_PE_INVALID; - parent = parent->parent; - } - eeh_unlock(); - pr_debug("EEH: Add %s to Device PE#%x, Parent PE#%x\n", - edev->dn->full_name, pe->addr, pe->parent->addr); - - return 0; - } - - /* Create a new EEH PE */ - pe = eeh_pe_alloc(edev->phb, EEH_PE_DEVICE); - if (!pe) { - eeh_unlock(); - pr_err("%s: out of memory!\n", __func__); - return -ENOMEM; - } - pe->addr = edev->pe_config_addr; - pe->config_addr = edev->config_addr; - - /* - * Put the new EEH PE into hierarchy tree. If the parent - * can't be found, the newly created PE will be attached - * to PHB directly. Otherwise, we have to associate the - * PE with its parent. - */ - parent = eeh_pe_get_parent(edev); - if (!parent) { - parent = eeh_phb_pe_get(edev->phb); - if (!parent) { - eeh_unlock(); - pr_err("%s: No PHB PE is found (PHB Domain=%d)\n", - __func__, edev->phb->global_number); - edev->pe = NULL; - kfree(pe); - return -EEXIST; - } - } - pe->parent = parent; - - /* - * Put the newly created PE into the child list and - * link the EEH device accordingly. - */ - list_add_tail(&pe->child, &parent->child_list); - list_add_tail(&edev->list, &pe->edevs); - edev->pe = pe; - eeh_unlock(); - pr_debug("EEH: Add %s to Device PE#%x, Parent PE#%x\n", - edev->dn->full_name, pe->addr, pe->parent->addr); - - return 0; -} - -/** - * eeh_rmv_from_parent_pe - Remove one EEH device from the associated PE - * @edev: EEH device - * @purge_pe: remove PE or not - * - * The PE hierarchy tree might be changed when doing PCI hotplug. - * Also, the PCI devices or buses could be removed from the system - * during EEH recovery. So we have to call the function remove the - * corresponding PE accordingly if necessary. - */ -int eeh_rmv_from_parent_pe(struct eeh_dev *edev, int purge_pe) -{ - struct eeh_pe *pe, *parent, *child; - int cnt; - - if (!edev->pe) { - pr_warning("%s: No PE found for EEH device %s\n", - __func__, edev->dn->full_name); - return -EEXIST; - } - - eeh_lock(); - - /* Remove the EEH device */ - pe = edev->pe; - edev->pe = NULL; - list_del(&edev->list); - - /* - * Check if the parent PE includes any EEH devices. - * If not, we should delete that. Also, we should - * delete the parent PE if it doesn't have associated - * child PEs and EEH devices. - */ - while (1) { - parent = pe->parent; - if (pe->type & EEH_PE_PHB) - break; - - if (purge_pe) { - if (list_empty(&pe->edevs) && - list_empty(&pe->child_list)) { - list_del(&pe->child); - kfree(pe); - } else { - break; - } - } else { - if (list_empty(&pe->edevs)) { - cnt = 0; - list_for_each_entry(child, &pe->child_list, child) { - if (!(child->type & EEH_PE_INVALID)) { - cnt++; - break; - } - } - - if (!cnt) - pe->type |= EEH_PE_INVALID; - else - break; - } - } - - pe = parent; - } - - eeh_unlock(); - - return 0; -} - -/** - * __eeh_pe_state_mark - Mark the state for the PE - * @data: EEH PE - * @flag: state - * - * The function is used to mark the indicated state for the given - * PE. Also, the associated PCI devices will be put into IO frozen - * state as well. - */ -static void *__eeh_pe_state_mark(void *data, void *flag) -{ - struct eeh_pe *pe = (struct eeh_pe *)data; - int state = *((int *)flag); - struct eeh_dev *tmp; - struct pci_dev *pdev; - - /* - * Mark the PE with the indicated state. Also, - * the associated PCI device will be put into - * I/O frozen state to avoid I/O accesses from - * the PCI device driver. - */ - pe->state |= state; - eeh_pe_for_each_dev(pe, tmp) { - pdev = eeh_dev_to_pci_dev(tmp); - if (pdev) - pdev->error_state = pci_channel_io_frozen; - } - - return NULL; -} - -/** - * eeh_pe_state_mark - Mark specified state for PE and its associated device - * @pe: EEH PE - * - * EEH error affects the current PE and its child PEs. The function - * is used to mark appropriate state for the affected PEs and the - * associated devices. - */ -void eeh_pe_state_mark(struct eeh_pe *pe, int state) -{ - eeh_lock(); - eeh_pe_traverse(pe, __eeh_pe_state_mark, &state); - eeh_unlock(); -} - -/** - * __eeh_pe_state_clear - Clear state for the PE - * @data: EEH PE - * @flag: state - * - * The function is used to clear the indicated state from the - * given PE. Besides, we also clear the check count of the PE - * as well. - */ -static void *__eeh_pe_state_clear(void *data, void *flag) -{ - struct eeh_pe *pe = (struct eeh_pe *)data; - int state = *((int *)flag); - - pe->state &= ~state; - pe->check_count = 0; - - return NULL; -} - -/** - * eeh_pe_state_clear - Clear state for the PE and its children - * @pe: PE - * @state: state to be cleared - * - * When the PE and its children has been recovered from error, - * we need clear the error state for that. The function is used - * for the purpose. - */ -void eeh_pe_state_clear(struct eeh_pe *pe, int state) -{ - eeh_lock(); - eeh_pe_traverse(pe, __eeh_pe_state_clear, &state); - eeh_unlock(); -} - -/** - * eeh_restore_one_device_bars - Restore the Base Address Registers for one device - * @data: EEH device - * @flag: Unused - * - * 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 void *eeh_restore_one_device_bars(void *data, void *flag) -{ - int i; - u32 cmd; - struct eeh_dev *edev = (struct eeh_dev *)data; - struct device_node *dn = eeh_dev_to_of_node(edev); - - for (i = 4; i < 10; i++) - eeh_ops->write_config(dn, i*4, 4, edev->config_space[i]); - /* 12 == Expansion ROM Address */ - eeh_ops->write_config(dn, 12*4, 4, edev->config_space[12]); - -#define BYTE_SWAP(OFF) (8*((OFF)/4)+3-(OFF)) -#define SAVED_BYTE(OFF) (((u8 *)(edev->config_space))[BYTE_SWAP(OFF)]) - - eeh_ops->write_config(dn, PCI_CACHE_LINE_SIZE, 1, - SAVED_BYTE(PCI_CACHE_LINE_SIZE)); - eeh_ops->write_config(dn, PCI_LATENCY_TIMER, 1, - SAVED_BYTE(PCI_LATENCY_TIMER)); - - /* max latency, min grant, interrupt pin and line */ - eeh_ops->write_config(dn, 15*4, 4, edev->config_space[15]); - - /* - * Restore PERR & SERR bits, some devices require it, - * don't touch the other command bits - */ - eeh_ops->read_config(dn, PCI_COMMAND, 4, &cmd); - if (edev->config_space[1] & PCI_COMMAND_PARITY) - cmd |= PCI_COMMAND_PARITY; - else - cmd &= ~PCI_COMMAND_PARITY; - if (edev->config_space[1] & PCI_COMMAND_SERR) - cmd |= PCI_COMMAND_SERR; - else - cmd &= ~PCI_COMMAND_SERR; - eeh_ops->write_config(dn, PCI_COMMAND, 4, cmd); - - return NULL; -} - -/** - * eeh_pe_restore_bars - Restore the PCI config space info - * @pe: EEH PE - * - * This routine performs a recursive walk to the children - * of this device as well. - */ -void eeh_pe_restore_bars(struct eeh_pe *pe) -{ - /* - * We needn't take the EEH lock since eeh_pe_dev_traverse() - * will take that. - */ - eeh_pe_dev_traverse(pe, eeh_restore_one_device_bars, NULL); -} - -/** - * eeh_pe_bus_get - Retrieve PCI bus according to the given PE - * @pe: EEH PE - * - * Retrieve the PCI bus according to the given PE. Basically, - * there're 3 types of PEs: PHB/Bus/Device. For PHB PE, the - * primary PCI bus will be retrieved. The parent bus will be - * returned for BUS PE. However, we don't have associated PCI - * bus for DEVICE PE. - */ -struct pci_bus *eeh_pe_bus_get(struct eeh_pe *pe) -{ - struct pci_bus *bus = NULL; - struct eeh_dev *edev; - struct pci_dev *pdev; - - eeh_lock(); - - if (pe->type & EEH_PE_PHB) { - bus = pe->phb->bus; - } else if (pe->type & EEH_PE_BUS || - pe->type & EEH_PE_DEVICE) { - edev = list_first_entry(&pe->edevs, struct eeh_dev, list); - pdev = eeh_dev_to_pci_dev(edev); - if (pdev) - bus = pdev->bus; - } - - eeh_unlock(); - - return bus; -} diff --git a/arch/powerpc/platforms/pseries/eeh_sysfs.c b/arch/powerpc/platforms/pseries/eeh_sysfs.c deleted file mode 100644 index d377083..0000000 --- a/arch/powerpc/platforms/pseries/eeh_sysfs.c +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Sysfs entries for PCI Error Recovery for PAPR-compliant platform. - * Copyright IBM Corporation 2007 - * Copyright Linas Vepstas <linas@austin.ibm.com> 2007 - * - * All rights reserved. - * - * 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 - * the Free Software Foundation; either version 2 of the License, or (at - * your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or - * NON INFRINGEMENT. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - * - * Send comments and feedback to Linas Vepstas <linas@austin.ibm.com> - */ -#include <linux/pci.h> -#include <linux/stat.h> -#include <asm/ppc-pci.h> -#include <asm/pci-bridge.h> - -/** - * EEH_SHOW_ATTR -- Create sysfs entry for eeh statistic - * @_name: name of file in sysfs directory - * @_memb: name of member in struct pci_dn to access - * @_format: printf format for display - * - * All of the attributes look very similar, so just - * auto-gen a cut-n-paste routine to display them. - */ -#define EEH_SHOW_ATTR(_name,_memb,_format) \ -static ssize_t eeh_show_##_name(struct device *dev, \ - struct device_attribute *attr, char *buf) \ -{ \ - struct pci_dev *pdev = to_pci_dev(dev); \ - struct eeh_dev *edev = pci_dev_to_eeh_dev(pdev); \ - \ - if (!edev) \ - return 0; \ - \ - return sprintf(buf, _format "\n", edev->_memb); \ -} \ -static DEVICE_ATTR(_name, S_IRUGO, eeh_show_##_name, NULL); - -EEH_SHOW_ATTR(eeh_mode, mode, "0x%x"); -EEH_SHOW_ATTR(eeh_config_addr, config_addr, "0x%x"); -EEH_SHOW_ATTR(eeh_pe_config_addr, pe_config_addr, "0x%x"); - -void eeh_sysfs_add_device(struct pci_dev *pdev) -{ - int rc=0; - - rc += device_create_file(&pdev->dev, &dev_attr_eeh_mode); - rc += device_create_file(&pdev->dev, &dev_attr_eeh_config_addr); - rc += device_create_file(&pdev->dev, &dev_attr_eeh_pe_config_addr); - - if (rc) - printk(KERN_WARNING "EEH: Unable to create sysfs entries\n"); -} - -void eeh_sysfs_remove_device(struct pci_dev *pdev) -{ - device_remove_file(&pdev->dev, &dev_attr_eeh_mode); - device_remove_file(&pdev->dev, &dev_attr_eeh_config_addr); - device_remove_file(&pdev->dev, &dev_attr_eeh_pe_config_addr); -} - diff --git a/arch/powerpc/platforms/pseries/pci_dlpar.c b/arch/powerpc/platforms/pseries/pci_dlpar.c index c91b22b..efe6137 100644 --- a/arch/powerpc/platforms/pseries/pci_dlpar.c +++ b/arch/powerpc/platforms/pseries/pci_dlpar.c @@ -64,91 +64,6 @@ pcibios_find_pci_bus(struct device_node *dn) } EXPORT_SYMBOL_GPL(pcibios_find_pci_bus); -/** - * __pcibios_remove_pci_devices - remove all devices under this bus - * @bus: the indicated PCI bus - * @purge_pe: destroy the PE on removal of PCI devices - * - * Remove all of the PCI devices under this bus both from the - * linux pci device tree, and from the powerpc EEH address cache. - * By default, the corresponding PE will be destroied during the - * normal PCI hotplug path. For PCI hotplug during EEH recovery, - * the corresponding PE won't be destroied and deallocated. - */ -void __pcibios_remove_pci_devices(struct pci_bus *bus, int purge_pe) -{ - struct pci_dev *dev, *tmp; - struct pci_bus *child_bus; - - /* First go down child busses */ - list_for_each_entry(child_bus, &bus->children, node) - __pcibios_remove_pci_devices(child_bus, purge_pe); - - pr_debug("PCI: Removing devices on bus %04x:%02x\n", - pci_domain_nr(bus), bus->number); - list_for_each_entry_safe(dev, tmp, &bus->devices, bus_list) { - pr_debug(" * Removing %s...\n", pci_name(dev)); - eeh_remove_bus_device(dev, purge_pe); - pci_stop_and_remove_bus_device(dev); - } -} - -/** - * pcibios_remove_pci_devices - remove all devices under this bus - * - * Remove all of the PCI devices under this bus both from the - * linux pci device tree, and from the powerpc EEH address cache. - */ -void pcibios_remove_pci_devices(struct pci_bus *bus) -{ - __pcibios_remove_pci_devices(bus, 1); -} -EXPORT_SYMBOL_GPL(pcibios_remove_pci_devices); - -/** - * pcibios_add_pci_devices - adds new pci devices to bus - * - * This routine will find and fixup new pci devices under - * the indicated bus. This routine presumes that there - * might already be some devices under this bridge, so - * it carefully tries to add only new devices. (And that - * is how this routine differs from other, similar pcibios - * routines.) - */ -void pcibios_add_pci_devices(struct pci_bus * bus) -{ - int slotno, num, mode, pass, max; - struct pci_dev *dev; - struct device_node *dn = pci_bus_to_OF_node(bus); - - eeh_add_device_tree_early(dn); - - mode = PCI_PROBE_NORMAL; - if (ppc_md.pci_probe_mode) - mode = ppc_md.pci_probe_mode(bus); - - if (mode == PCI_PROBE_DEVTREE) { - /* use ofdt-based probe */ - of_rescan_bus(dn, bus); - } else if (mode == PCI_PROBE_NORMAL) { - /* use legacy probe */ - slotno = PCI_SLOT(PCI_DN(dn->child)->devfn); - num = pci_scan_slot(bus, PCI_DEVFN(slotno, 0)); - if (!num) - return; - pcibios_setup_bus_devices(bus); - max = bus->busn_res.start; - for (pass=0; pass < 2; pass++) - list_for_each_entry(dev, &bus->devices, bus_list) { - if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE || - dev->hdr_type == PCI_HEADER_TYPE_CARDBUS) - max = pci_scan_bridge(bus, dev, max, pass); - } - } - pcibios_finish_adding_to_bus(bus); -} -EXPORT_SYMBOL_GPL(pcibios_add_pci_devices); - struct pci_controller *init_phb_dynamic(struct device_node *dn) { struct pci_controller *phb;
The patch moves the common part of EEH core into arch/powerpc/kernel directory so that we needn't PPC_PSERIES while compiling POWERNV platform: * Move the EEH common part into arch/powerpc/kernel * Move the functions for PCI hotplug from pSeries platform to arch/powerpc/kernel/pci_hotplug.c * Move CONFIG_EEH from arch/powerpc/platforms/pseries/Kconfig to arch/powerpc/platforms/Kconfig * Adjust makefile accordingly Signed-off-by: Gavin Shan <shangw@linux.vnet.ibm.com> --- arch/powerpc/kernel/Makefile | 4 +- arch/powerpc/kernel/eeh.c | 942 +++++++++++++++++++++++++++ arch/powerpc/kernel/eeh_cache.c | 319 +++++++++ arch/powerpc/kernel/eeh_dev.c | 112 ++++ arch/powerpc/kernel/eeh_driver.c | 552 ++++++++++++++++ arch/powerpc/kernel/eeh_event.c | 142 ++++ arch/powerpc/kernel/eeh_pe.c | 653 +++++++++++++++++++ arch/powerpc/kernel/eeh_sysfs.c | 75 +++ arch/powerpc/kernel/pci_hotplug.c | 111 ++++ arch/powerpc/platforms/Kconfig | 5 + arch/powerpc/platforms/pseries/Kconfig | 5 - arch/powerpc/platforms/pseries/Makefile | 4 +- arch/powerpc/platforms/pseries/eeh.c | 942 --------------------------- arch/powerpc/platforms/pseries/eeh_cache.c | 319 --------- arch/powerpc/platforms/pseries/eeh_dev.c | 112 ---- arch/powerpc/platforms/pseries/eeh_driver.c | 552 ---------------- arch/powerpc/platforms/pseries/eeh_event.c | 142 ---- arch/powerpc/platforms/pseries/eeh_pe.c | 653 ------------------- arch/powerpc/platforms/pseries/eeh_sysfs.c | 75 --- arch/powerpc/platforms/pseries/pci_dlpar.c | 85 --- 20 files changed, 2915 insertions(+), 2889 deletions(-) create mode 100644 arch/powerpc/kernel/eeh.c create mode 100644 arch/powerpc/kernel/eeh_cache.c create mode 100644 arch/powerpc/kernel/eeh_dev.c create mode 100644 arch/powerpc/kernel/eeh_driver.c create mode 100644 arch/powerpc/kernel/eeh_event.c create mode 100644 arch/powerpc/kernel/eeh_pe.c create mode 100644 arch/powerpc/kernel/eeh_sysfs.c create mode 100644 arch/powerpc/kernel/pci_hotplug.c delete mode 100644 arch/powerpc/platforms/pseries/eeh.c delete mode 100644 arch/powerpc/platforms/pseries/eeh_cache.c delete mode 100644 arch/powerpc/platforms/pseries/eeh_dev.c delete mode 100644 arch/powerpc/platforms/pseries/eeh_driver.c delete mode 100644 arch/powerpc/platforms/pseries/eeh_event.c delete mode 100644 arch/powerpc/platforms/pseries/eeh_pe.c delete mode 100644 arch/powerpc/platforms/pseries/eeh_sysfs.c