1928 lines · 49.9 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-or-later/* * Copyright IBM Corporation 2001, 2005, 2006 * Copyright Dave Engebretsen & Todd Inglett 2001 * Copyright Linas Vepstas 2005, 2006 * Copyright 2001-2012 IBM Corporation. * * 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/iommu.h>#include <linux/proc_fs.h>#include <linux/rbtree.h>#include <linux/reboot.h>#include <linux/seq_file.h>#include <linux/spinlock.h>#include <linux/export.h>#include <linux/of.h>#include <linux/debugfs.h> #include <linux/atomic.h>#include <asm/eeh.h>#include <asm/eeh_event.h>#include <asm/io.h>#include <asm/iommu.h>#include <asm/machdep.h>#include <asm/ppc-pci.h>#include <asm/rtas.h>#include <asm/pte-walk.h>  /** Overview: *  EEH, or "Enhanced 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 (5*60*1000) /* * EEH probe mode support, which is part of the flags, * 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_subsystem_flags;EXPORT_SYMBOL(eeh_subsystem_flags); /* * EEH allowed maximal frozen times. If one particular PE's * frozen count in last hour exceeds this limit, the PE will * be forced to be offline permanently. */u32 eeh_max_freezes = 5; /* * Controls whether a recovery event should be scheduled when an * isolated device is discovered. This is only really useful for * debugging problems with the EEH core. */bool eeh_debugfs_no_recover; /* Platform dependent EEH operations */struct eeh_ops *eeh_ops = NULL; /* Lock to avoid races due to multiple reports of an error */DEFINE_RAW_SPINLOCK(confirm_error_lock);EXPORT_SYMBOL_GPL(confirm_error_lock); /* Lock to protect passed flags */static DEFINE_MUTEX(eeh_dev_mutex); /* 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 8192static 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; static int __init eeh_setup(char *str){	if (!strcmp(str, "off"))		eeh_add_flag(EEH_FORCE_DISABLED);	else if (!strcmp(str, "early_log"))		eeh_add_flag(EEH_EARLY_DUMP_LOG); 	return 1;}__setup("eeh=", eeh_setup); void eeh_show_enabled(void){	if (eeh_has_flag(EEH_FORCE_DISABLED))		pr_info("EEH: Recovery disabled by kernel parameter.\n");	else if (eeh_has_flag(EEH_ENABLED))		pr_info("EEH: Capable adapter found: recovery enabled.\n");	else		pr_info("EEH: No capable adapters found: recovery disabled.\n");} /* * This routine captures assorted PCI configuration space data * for the indicated PCI device, and puts them into a buffer * for RTAS error logging. */static size_t eeh_dump_dev_log(struct eeh_dev *edev, char *buf, size_t len){	u32 cfg;	int cap, i;	int n = 0, l = 0;	char buffer[128]; 	n += scnprintf(buf+n, len-n, "%04x:%02x:%02x.%01x\n",			edev->pe->phb->global_number, edev->bdfn >> 8,			PCI_SLOT(edev->bdfn), PCI_FUNC(edev->bdfn));	pr_warn("EEH: of node=%04x:%02x:%02x.%01x\n",		edev->pe->phb->global_number, edev->bdfn >> 8,		PCI_SLOT(edev->bdfn), PCI_FUNC(edev->bdfn)); 	eeh_ops->read_config(edev, PCI_VENDOR_ID, 4, &cfg);	n += scnprintf(buf+n, len-n, "dev/vend:%08x\n", cfg);	pr_warn("EEH: PCI device/vendor: %08x\n", cfg); 	eeh_ops->read_config(edev, PCI_COMMAND, 4, &cfg);	n += scnprintf(buf+n, len-n, "cmd/stat:%x\n", cfg);	pr_warn("EEH: PCI cmd/status register: %08x\n", cfg); 	/* Gather bridge-specific registers */	if (edev->mode & EEH_DEV_BRIDGE) {		eeh_ops->read_config(edev, PCI_SEC_STATUS, 2, &cfg);		n += scnprintf(buf+n, len-n, "sec stat:%x\n", cfg);		pr_warn("EEH: Bridge secondary status: %04x\n", cfg); 		eeh_ops->read_config(edev, PCI_BRIDGE_CONTROL, 2, &cfg);		n += scnprintf(buf+n, len-n, "brdg ctl:%x\n", cfg);		pr_warn("EEH: Bridge control: %04x\n", cfg);	} 	/* Dump out the PCI-X command and status regs */	cap = edev->pcix_cap;	if (cap) {		eeh_ops->read_config(edev, cap, 4, &cfg);		n += scnprintf(buf+n, len-n, "pcix-cmd:%x\n", cfg);		pr_warn("EEH: PCI-X cmd: %08x\n", cfg); 		eeh_ops->read_config(edev, cap+4, 4, &cfg);		n += scnprintf(buf+n, len-n, "pcix-stat:%x\n", cfg);		pr_warn("EEH: PCI-X status: %08x\n", cfg);	} 	/* If PCI-E capable, dump PCI-E cap 10 */	cap = edev->pcie_cap;	if (cap) {		n += scnprintf(buf+n, len-n, "pci-e cap10:\n");		pr_warn("EEH: PCI-E capabilities and status follow:\n"); 		for (i=0; i<=8; i++) {			eeh_ops->read_config(edev, cap+4*i, 4, &cfg);			n += scnprintf(buf+n, len-n, "%02x:%x\n", 4*i, cfg); 			if ((i % 4) == 0) {				if (i != 0)					pr_warn("%s\n", buffer); 				l = scnprintf(buffer, sizeof(buffer),					      "EEH: PCI-E %02x: %08x ",					      4*i, cfg);			} else {				l += scnprintf(buffer+l, sizeof(buffer)-l,					       "%08x ", cfg);			} 		} 		pr_warn("%s\n", buffer);	} 	/* If AER capable, dump it */	cap = edev->aer_cap;	if (cap) {		n += scnprintf(buf+n, len-n, "pci-e AER:\n");		pr_warn("EEH: PCI-E AER capability register set follows:\n"); 		for (i=0; i<=13; i++) {			eeh_ops->read_config(edev, cap+4*i, 4, &cfg);			n += scnprintf(buf+n, len-n, "%02x:%x\n", 4*i, cfg); 			if ((i % 4) == 0) {				if (i != 0)					pr_warn("%s\n", buffer); 				l = scnprintf(buffer, sizeof(buffer),					      "EEH: PCI-E AER %02x: %08x ",					      4*i, cfg);			} else {				l += scnprintf(buffer+l, sizeof(buffer)-l,					       "%08x ", cfg);			}		} 		pr_warn("%s\n", buffer);	} 	return n;} static void *eeh_dump_pe_log(struct eeh_pe *pe, void *flag){	struct eeh_dev *edev, *tmp;	size_t *plen = flag; 	eeh_pe_for_each_dev(pe, edev, tmp)		*plen += eeh_dump_dev_log(edev, pci_regs_buf + *plen,					  EEH_PCI_REGS_LOG_LEN - *plen); 	return NULL;} /** * 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; 	/*	 * When the PHB is fenced or dead, it's pointless to collect	 * the data from PCI config space because it should return	 * 0xFF's. For ER, we still retrieve the data from the PCI	 * config space.	 *	 * For pHyp, we have to enable IO for log retrieval. Otherwise,	 * 0xFF's is always returned from PCI config space.	 *	 * When the @severity is EEH_LOG_PERM, the PE is going to be	 * removed. Prior to that, the drivers for devices included in	 * the PE will be closed. The drivers rely on working IO path	 * to bring the devices to quiet state. Otherwise, PCI traffic	 * from those devices after they are removed is like to cause	 * another unexpected EEH error.	 */	if (!(pe->type & EEH_PE_PHB)) {		if (eeh_has_flag(EEH_ENABLE_IO_FOR_LOG) ||		    severity == EEH_LOG_PERM)			eeh_pci_enable(pe, EEH_OPT_THAW_MMIO); 		/*		 * The config space of some PCI devices can't be accessed		 * when their PEs are in frozen state. Otherwise, fenced		 * PHB might be seen. Those PEs are identified with flag		 * EEH_PE_CFG_RESTRICTED, indicating EEH_PE_CFG_BLOCKED		 * is set automatically when the PE is put to EEH_PE_ISOLATED.		 *		 * Restoring BARs possibly triggers PCI config access in		 * (OPAL) firmware and then causes fenced PHB. If the		 * PCI config is blocked with flag EEH_PE_CFG_BLOCKED, it's		 * pointless to restore BARs and dump config space.		 */		eeh_ops->configure_bridge(pe);		if (!(pe->state & EEH_PE_CFG_BLOCKED)) {			eeh_pe_restore_bars(pe); 			pci_regs_buf[0] = 0;			eeh_pe_traverse(pe, eeh_dump_pe_log, &loglen);		}	} 	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){	return ppc_find_vmap_phys(token);} /* * On PowerNV platform, we might already have fenced PHB there. * For that case, it's meaningless to recover frozen PE. Intead, * We have to handle fenced PHB firstly. */static int eeh_phb_check_failure(struct eeh_pe *pe){	struct eeh_pe *phb_pe;	unsigned long flags;	int ret; 	if (!eeh_has_flag(EEH_PROBE_MODE_DEV))		return -EPERM; 	/* Find the PHB PE */	phb_pe = eeh_phb_pe_get(pe->phb);	if (!phb_pe) {		pr_warn("%s Can't find PE for PHB#%x\n",			__func__, pe->phb->global_number);		return -EEXIST;	} 	/* If the PHB has been in problematic state */	eeh_serialize_lock(&flags);	if (phb_pe->state & EEH_PE_ISOLATED) {		ret = 0;		goto out;	} 	/* Check PHB state */	ret = eeh_ops->get_state(phb_pe, NULL);	if ((ret < 0) ||	    (ret == EEH_STATE_NOT_SUPPORT) || eeh_state_active(ret)) {		ret = 0;		goto out;	} 	/* Isolate the PHB and send event */	eeh_pe_mark_isolated(phb_pe);	eeh_serialize_unlock(flags); 	pr_debug("EEH: PHB#%x failure detected, location: %s\n",		phb_pe->phb->global_number, eeh_pe_loc_get(phb_pe));	eeh_send_failure_event(phb_pe);	return 1;out:	eeh_serialize_unlock(flags);	return ret;} static inline const char *eeh_driver_name(struct pci_dev *pdev){	if (pdev)		return dev_driver_string(&pdev->dev); 	return "<null>";} /** * 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, *parent_pe;	int rc = 0;	const char *location = NULL; 	eeh_stats.total_mmio_ffs++; 	if (!eeh_enabled())		return 0; 	if (!edev) {		eeh_stats.no_dn++;		return 0;	}	dev = eeh_dev_to_pci_dev(edev);	pe = eeh_dev_to_pe(edev); 	/* Access to IO BARs might get this far and still not want checking. */	if (!pe) {		eeh_stats.ignored_check++;		eeh_edev_dbg(edev, "Ignored check\n");		return 0;	} 	/*	 * On PowerNV platform, we might already have fenced PHB	 * there and we need take care of that firstly.	 */	ret = eeh_phb_check_failure(pe);	if (ret > 0)		return ret; 	/*	 * If the PE isn't owned by us, we shouldn't check the	 * state. Instead, let the owner handle it if the PE has	 * been frozen.	 */	if (eeh_pe_passed(pe))		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.	 */	eeh_serialize_lock(&flags);	rc = 1;	if (pe->state & EEH_PE_ISOLATED) {		pe->check_count++;		if (pe->check_count == EEH_MAX_FAILS) {			dn = pci_device_to_OF_node(dev);			if (dn)				location = of_get_property(dn, "ibm,loc-code",						NULL);			eeh_edev_err(edev, "%d reads ignored for recovering device at location=%s driver=%s\n",				pe->check_count,				location ? location : "unknown",				eeh_driver_name(dev));			eeh_edev_err(edev, "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.

Lines 1–500 of 1928.