eeh.c

来自「LINUX 2.6.17.4的源码」· C语言 代码 · 共 1,030 行 · 第 1/2 页

C
1,030
字号
/* * eeh.c * Copyright (C) 2001 Dave Engebretsen & Todd Inglett 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 */#include <linux/delay.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 <asm/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>#undef DEBUG/** 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 occured (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 has occurred, we assume it * is broken and panic.  This sets the threshold for how many read * attempts we allow before panicking. */#define EEH_MAX_FAILS	100000/* RTAS tokens */static int ibm_set_eeh_option;static int ibm_set_slot_reset;static int ibm_read_slot_reset_state;static int ibm_read_slot_reset_state2;static int ibm_slot_error_detail;static int ibm_get_config_addr_info;static int ibm_configure_bridge;int eeh_subsystem_enabled;EXPORT_SYMBOL(eeh_subsystem_enabled);/* Lock to avoid races due to multiple reports of an error */static DEFINE_SPINLOCK(confirm_error_lock);/* Buffer for reporting slot-error-detail rtas calls */static unsigned char slot_errbuf[RTAS_ERROR_LOG_MAX];static DEFINE_SPINLOCK(slot_errbuf_lock);static int eeh_error_buf_size;/* System monitoring statistics */static unsigned long no_device;static unsigned long no_dn;static unsigned long no_cfg_addr;static unsigned long ignored_check;static unsigned long total_mmio_ffs;static unsigned long false_positives;static unsigned long ignored_failures;static unsigned long slot_resets;#define IS_BRIDGE(class_code) (((class_code)<<16) == PCI_BASE_CLASS_BRIDGE)/* --------------------------------------------------------------- *//* Below lies the EEH event infrastructure */void eeh_slot_error_detail (struct pci_dn *pdn, int severity){	int config_addr;	unsigned long flags;	int rc;	/* Log the error with the rtas logger */	spin_lock_irqsave(&slot_errbuf_lock, flags);	memset(slot_errbuf, 0, eeh_error_buf_size);	/* Use PE configuration address, if present */	config_addr = pdn->eeh_config_addr;	if (pdn->eeh_pe_config_addr)		config_addr = pdn->eeh_pe_config_addr;	rc = rtas_call(ibm_slot_error_detail,	               8, 1, NULL, config_addr,	               BUID_HI(pdn->phb->buid),	               BUID_LO(pdn->phb->buid), NULL, 0,	               virt_to_phys(slot_errbuf),	               eeh_error_buf_size,	               severity);	if (rc == 0)		log_error(slot_errbuf, ERR_TYPE_RTAS_LOG, 0);	spin_unlock_irqrestore(&slot_errbuf_lock, flags);}/** * read_slot_reset_state - Read the reset state of a device node's slot * @dn: device node to read * @rets: array to return results in */static int read_slot_reset_state(struct pci_dn *pdn, int rets[]){	int token, outputs;	int config_addr;	if (ibm_read_slot_reset_state2 != RTAS_UNKNOWN_SERVICE) {		token = ibm_read_slot_reset_state2;		outputs = 4;	} else {		token = ibm_read_slot_reset_state;		rets[2] = 0; /* fake PE Unavailable info */		outputs = 3;	}	/* Use PE configuration address, if present */	config_addr = pdn->eeh_config_addr;	if (pdn->eeh_pe_config_addr)		config_addr = pdn->eeh_pe_config_addr;	return rtas_call(token, 3, outputs, rets, config_addr,			 BUID_HI(pdn->phb->buid), BUID_LO(pdn->phb->buid));}/** * eeh_token_to_phys - convert EEH address token to phys address * @token i/o token, should be address in the form 0xA.... */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));}/**  * Return the "partitionable endpoint" (pe) under which this device lies */struct device_node * find_device_pe(struct device_node *dn){	while ((dn->parent) && PCI_DN(dn->parent) &&	      (PCI_DN(dn->parent)->eeh_mode & EEH_MODE_SUPPORTED)) {		dn = dn->parent;	}	return dn;}/** Mark all devices that are peers of this device as failed. *  Mark the device driver too, so that it can see the failure *  immediately; this is critical, since some drivers poll *  status registers in interrupts ... If a driver is polling, *  and the slot is frozen, then the driver can deadlock in *  an interrupt context, which is bad. */static void __eeh_mark_slot (struct device_node *dn, int mode_flag){	while (dn) {		if (PCI_DN(dn)) {			/* Mark the pci device driver too */			struct pci_dev *dev = PCI_DN(dn)->pcidev;			PCI_DN(dn)->eeh_mode |= mode_flag;			if (dev && dev->driver)				dev->error_state = pci_channel_io_frozen;			if (dn->child)				__eeh_mark_slot (dn->child, mode_flag);		}		dn = dn->sibling;	}}void eeh_mark_slot (struct device_node *dn, int mode_flag){	dn = find_device_pe (dn);	/* Back up one, since config addrs might be shared */	if (PCI_DN(dn) && PCI_DN(dn)->eeh_pe_config_addr)		dn = dn->parent;	PCI_DN(dn)->eeh_mode |= mode_flag;	__eeh_mark_slot (dn->child, mode_flag);}static void __eeh_clear_slot (struct device_node *dn, int mode_flag){	while (dn) {		if (PCI_DN(dn)) {			PCI_DN(dn)->eeh_mode &= ~mode_flag;			PCI_DN(dn)->eeh_check_count = 0;			if (dn->child)				__eeh_clear_slot (dn->child, mode_flag);		}		dn = dn->sibling;	}}void eeh_clear_slot (struct device_node *dn, int mode_flag){	unsigned long flags;	spin_lock_irqsave(&confirm_error_lock, flags);		dn = find_device_pe (dn);		/* Back up one, since config addrs might be shared */	if (PCI_DN(dn) && PCI_DN(dn)->eeh_pe_config_addr)		dn = dn->parent;	PCI_DN(dn)->eeh_mode &= ~mode_flag;	PCI_DN(dn)->eeh_check_count = 0;	__eeh_clear_slot (dn->child, mode_flag);	spin_unlock_irqrestore(&confirm_error_lock, flags);}/** * eeh_dn_check_failure - check if all 1's data is due to EEH slot freeze * @dn device node * @dev pci device, if known * * 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_dn_check_failure(struct device_node *dn, struct pci_dev *dev){	int ret;	int rets[3];	unsigned long flags;	struct pci_dn *pdn;	enum pci_channel_state state;	int rc = 0;	total_mmio_ffs++;	if (!eeh_subsystem_enabled)		return 0;	if (!dn) {		no_dn++;		return 0;	}	pdn = PCI_DN(dn);	/* Access to IO BARs might get this far and still not want checking. */	if (!(pdn->eeh_mode & EEH_MODE_SUPPORTED) ||	    pdn->eeh_mode & EEH_MODE_NOCHECK) {		ignored_check++;#ifdef DEBUG		printk ("EEH:ignored check (%x) for %s %s\n", 		        pdn->eeh_mode, pci_name (dev), dn->full_name);#endif		return 0;	}	if (!pdn->eeh_config_addr && !pdn->eeh_pe_config_addr) {		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.	 */	spin_lock_irqsave(&confirm_error_lock, flags);	rc = 1;	if (pdn->eeh_mode & EEH_MODE_ISOLATED) {		pdn->eeh_check_count ++;		if (pdn->eeh_check_count >= EEH_MAX_FAILS) {			printk (KERN_ERR "EEH: Device driver ignored %d bad reads, panicing\n",			        pdn->eeh_check_count);			dump_stack();						/* re-read the slot reset state */			if (read_slot_reset_state(pdn, rets) != 0)				rets[0] = -1;	/* reset state unknown */			/* If we are here, then we hit an infinite loop. Stop. */			panic("EEH: MMIO halt (%d) on device:%s\n", rets[0], pci_name(dev));		}		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 = read_slot_reset_state(pdn, rets);	/* If the call to firmware failed, punt */	if (ret != 0) {		printk(KERN_WARNING "EEH: read_slot_reset_state() failed; rc=%d dn=%s\n",		       ret, dn->full_name);		false_positives++;		rc = 0;		goto dn_unlock;	}	/* If EEH is not supported on this device, punt. */	if (rets[1] != 1) {		printk(KERN_WARNING "EEH: event on unsupported device, rc=%d dn=%s\n",		       ret, dn->full_name);		false_positives++;		rc = 0;		goto dn_unlock;	}	/* If not the kind of error we know about, punt. */	if (rets[0] != 2 && rets[0] != 4 && rets[0] != 5) {		false_positives++;		rc = 0;		goto dn_unlock;	}	/* Note that config-io to empty slots may fail;	 * we recognize empty because they don't have children. */	if ((rets[0] == 5) && (dn->child == NULL)) {		false_positives++;		rc = 0;		goto dn_unlock;	}	slot_resets++; 	/* Avoid repeated reports of this failure, including problems	 * with other functions on this device, and functions under	 * bridges. */	eeh_mark_slot (dn, EEH_MODE_ISOLATED);	spin_unlock_irqrestore(&confirm_error_lock, flags);	state = pci_channel_io_normal;	if ((rets[0] == 2) || (rets[0] == 4))		state = pci_channel_io_frozen;	if (rets[0] == 5)		state = pci_channel_io_perm_failure;	eeh_send_failure_event (dn, dev, state, rets[2]);	/* 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. */	if (rets[0] != 5) dump_stack();	return 1;dn_unlock:	spin_unlock_irqrestore(&confirm_error_lock, flags);	return rc;}EXPORT_SYMBOL_GPL(eeh_dn_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 pci_dev *dev;	struct device_node *dn;	/* Finding the phys addr + pci device; this is pretty quick. */	addr = eeh_token_to_phys((unsigned long __force) token);	dev = pci_get_device_by_addr(addr);	if (!dev) {		no_device++;		return val;	}	dn = pci_device_to_OF_node(dev);	eeh_dn_check_failure (dn, dev);	pci_dev_put(dev);	return val;}EXPORT_SYMBOL(eeh_check_failure);/* ------------------------------------------------------------- *//* The code below deals with error recovery *//** Return negative value if a permanent error, else return * a number of milliseconds to wait until the PCI slot is * ready to be used. */static inteeh_slot_availability(struct pci_dn *pdn){	int rc;	int rets[3];	rc = read_slot_reset_state(pdn, rets);	if (rc) return rc;	if (rets[1] == 0) return -1;  /* EEH is not supported */	if (rets[0] == 0) return 0;   /* Oll Korrect */	if (rets[0] == 5) {		if (rets[2] == 0) return -1; /* permanently unavailable */		return rets[2]; /* number of millisecs to wait */	}	if (rets[0] == 1)		return 250;	printk (KERN_ERR "EEH: Slot unavailable: rc=%d, rets=%d %d %d\n",		rc, rets[0], rets[1], rets[2]);	return -1;}/** rtas_pci_slot_reset raises/lowers the pci #RST line *  state: 1/0 to raise/lower the #RST * * Clear the EEH-frozen condition on a slot.  This routine * asserts the PCI #RST line if the 'state' argument is '1', * and drops the #RST line if 'state is '0'.  This routine is * safe to call in an interrupt context. * */static voidrtas_pci_slot_reset(struct pci_dn *pdn, int state){	int config_addr;	int rc;	BUG_ON (pdn==NULL); 	if (!pdn->phb) {		printk (KERN_WARNING "EEH: in slot reset, device node %s has no phb\n",		        pdn->node->full_name);		return;	}	/* Use PE configuration address, if present */	config_addr = pdn->eeh_config_addr;	if (pdn->eeh_pe_config_addr)		config_addr = pdn->eeh_pe_config_addr;	rc = rtas_call(ibm_set_slot_reset,4,1, NULL,	               config_addr,	               BUID_HI(pdn->phb->buid),	               BUID_LO(pdn->phb->buid),	               state);	if (rc) {		printk (KERN_WARNING "EEH: Unable to reset the failed slot, (%d) #RST=%d dn=%s\n", 

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?