Skip to content
eeh.c 37.4 KiB
Newer Older
	}

	/* Use older config-addr token on power5 */
	if (ibm_get_config_addr_info != RTAS_UNKNOWN_SERVICE) {
		ret = rtas_call (ibm_get_config_addr_info, 4, 2, rets,
			config_addr, info->buid_hi, info->buid_lo, 0);
		if (ret)
			return 0;
		return rets[0];
	}
	return 0;
}

Linus Torvalds's avatar
Linus Torvalds committed
/* Enable eeh for the given device node. */
static void *early_enable_eeh(struct device_node *dn, void *data)
{
Linus Torvalds's avatar
Linus Torvalds committed
	struct eeh_early_enable_info *info = data;
	int ret;
	const u32 *class_code = of_get_property(dn, "class-code", NULL);
	const u32 *vendor_id = of_get_property(dn, "vendor-id", NULL);
	const u32 *device_id = of_get_property(dn, "device-id", NULL);
Linus Torvalds's avatar
Linus Torvalds committed
	int enable;
	struct pci_dn *pdn = PCI_DN(dn);
Linus Torvalds's avatar
Linus Torvalds committed

	pdn->eeh_check_count = 0;
	pdn->eeh_freeze_count = 0;
	pdn->eeh_false_positives = 0;
Linus Torvalds's avatar
Linus Torvalds committed

	if (!of_device_is_available(dn))
		return NULL;
Linus Torvalds's avatar
Linus Torvalds committed

	/* Ignore bad nodes. */
	if (!class_code || !vendor_id || !device_id)
		return NULL;

	/* There is nothing to check on PCI to ISA bridges */
	if (dn->type && !strcmp(dn->type, "isa")) {
		pdn->eeh_mode |= EEH_MODE_NOCHECK;
Linus Torvalds's avatar
Linus Torvalds committed
		return NULL;
	}
	pdn->class_code = *class_code;
Linus Torvalds's avatar
Linus Torvalds committed

	/* Ok... see if this device supports EEH.  Some do, some don't,
	 * and the only way to find out is to check each and every one. */
	regs = of_get_property(dn, "reg", NULL);
Linus Torvalds's avatar
Linus Torvalds committed
	if (regs) {
		/* First register entry is addr (00BBSS00)  */
		/* Try to enable eeh */
		ret = rtas_call(ibm_set_eeh_option, 4, 1, NULL,
		                regs[0], info->buid_hi, info->buid_lo,
		                EEH_ENABLE);

Linus Torvalds's avatar
Linus Torvalds committed
		if (ret == 0) {
			pdn->eeh_config_addr = regs[0];

			/* If the newer, better, ibm,get-config-addr-info is supported, 
			 * then use that instead. */
			pdn->eeh_pe_config_addr = get_pe_addr(pdn->eeh_config_addr, info);

			/* Some older systems (Power4) allow the
			 * ibm,set-eeh-option call to succeed even on nodes
			 * where EEH is not supported. Verify support
			 * explicitly. */
			ret = read_slot_reset_state(pdn, rets);
			if ((ret == 0) && (rets[1] == 1))
				enable = 1;
		}

		if (enable) {
			eeh_subsystem_enabled = 1;
			pdn->eeh_mode |= EEH_MODE_SUPPORTED;

			pr_debug("EEH: %s: eeh enabled, config=%x pe_config=%x\n",
				 dn->full_name, pdn->eeh_config_addr,
				 pdn->eeh_pe_config_addr);
Linus Torvalds's avatar
Linus Torvalds committed
		} else {

			/* This device doesn't support EEH, but it may have an
			 * EEH parent, in which case we mark it as supported. */
			if (dn->parent && PCI_DN(dn->parent)
			    && (PCI_DN(dn->parent)->eeh_mode & EEH_MODE_SUPPORTED)) {
Linus Torvalds's avatar
Linus Torvalds committed
				/* Parent supports EEH. */
				pdn->eeh_mode |= EEH_MODE_SUPPORTED;
				pdn->eeh_config_addr = PCI_DN(dn->parent)->eeh_config_addr;
Linus Torvalds's avatar
Linus Torvalds committed
				return NULL;
			}
		}
	} else {
		printk(KERN_WARNING "EEH: %s: unable to get reg property.\n",
		       dn->full_name);
	}

	return NULL;
Linus Torvalds's avatar
Linus Torvalds committed
}

/*
 * 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.
 */
void __init eeh_init(void)
{
	struct device_node *phb, *np;
	struct eeh_early_enable_info info;

	raw_spin_lock_init(&confirm_error_lock);
	spin_lock_init(&slot_errbuf_lock);

Linus Torvalds's avatar
Linus Torvalds committed
	np = of_find_node_by_path("/rtas");
	if (np == NULL)
		return;

	ibm_set_eeh_option = rtas_token("ibm,set-eeh-option");
	ibm_set_slot_reset = rtas_token("ibm,set-slot-reset");
	ibm_read_slot_reset_state2 = rtas_token("ibm,read-slot-reset-state2");
	ibm_read_slot_reset_state = rtas_token("ibm,read-slot-reset-state");
	ibm_slot_error_detail = rtas_token("ibm,slot-error-detail");
	ibm_get_config_addr_info = rtas_token("ibm,get-config-addr-info");
	ibm_get_config_addr_info2 = rtas_token("ibm,get-config-addr-info2");
	ibm_configure_bridge = rtas_token ("ibm,configure-bridge");
	ibm_configure_pe = rtas_token("ibm,configure-pe");
Linus Torvalds's avatar
Linus Torvalds committed

	if (ibm_set_eeh_option == RTAS_UNKNOWN_SERVICE)
		return;

	eeh_error_buf_size = rtas_token("rtas-error-log-max");
	if (eeh_error_buf_size == RTAS_UNKNOWN_SERVICE) {
		eeh_error_buf_size = 1024;
	}
	if (eeh_error_buf_size > RTAS_ERROR_LOG_MAX) {
		printk(KERN_WARNING "EEH: rtas-error-log-max is bigger than allocated "
		      "buffer ! (%d vs %d)", eeh_error_buf_size, RTAS_ERROR_LOG_MAX);
		eeh_error_buf_size = RTAS_ERROR_LOG_MAX;
	}

	/* Enable EEH for all adapters.  Note that eeh requires buid's */
	for (phb = of_find_node_by_name(NULL, "pci"); phb;
	     phb = of_find_node_by_name(phb, "pci")) {
		unsigned long buid;

		buid = get_phb_buid(phb);
		if (buid == 0 || PCI_DN(phb) == NULL)
Linus Torvalds's avatar
Linus Torvalds committed
			continue;

		info.buid_lo = BUID_LO(buid);
		info.buid_hi = BUID_HI(buid);
		traverse_pci_devices(phb, early_enable_eeh, &info);
	}

	if (eeh_subsystem_enabled)
		printk(KERN_INFO "EEH: PCI Enhanced I/O Error Handling Enabled\n");
	else
		printk(KERN_WARNING "EEH: No capable adapters found\n");
}

/**
 * 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)
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct pci_controller *phb;
	struct eeh_early_enable_info info;

	if (!dn || !PCI_DN(dn))
Linus Torvalds's avatar
Linus Torvalds committed
		return;
	phb = PCI_DN(dn)->phb;

	/* USB Bus children of PCI devices will not have BUID's */
	if (NULL == phb || 0 == phb->buid)
Linus Torvalds's avatar
Linus Torvalds committed
		return;

	info.buid_hi = BUID_HI(phb->buid);
	info.buid_lo = BUID_LO(phb->buid);
	early_enable_eeh(dn, &info);
}

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);

Linus Torvalds's avatar
Linus Torvalds committed
/**
 * 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)
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct device_node *dn;
	struct pci_dn *pdn;
Linus Torvalds's avatar
Linus Torvalds committed
	if (!dev || !eeh_subsystem_enabled)
		return;

	pr_debug("EEH: Adding device %s\n", pci_name(dev));
Linus Torvalds's avatar
Linus Torvalds committed

	dn = pci_device_to_OF_node(dev);
	if (pdn->pcidev == dev) {
		pr_debug("EEH: Already referenced !\n");
		return;
	}
	WARN_ON(pdn->pcidev);

	pci_dev_get (dev);
	pci_addr_cache_insert_device(dev);
	eeh_sysfs_add_device(dev);
Linus Torvalds's avatar
Linus Torvalds committed
}

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);
Linus Torvalds's avatar
Linus Torvalds committed

/**
 * eeh_remove_device - undo EEH setup for the indicated pci device
 * @dev: pci device to be removed
 *
 * 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.
Linus Torvalds's avatar
Linus Torvalds committed
 */
static void eeh_remove_device(struct pci_dev *dev)
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct device_node *dn;
Linus Torvalds's avatar
Linus Torvalds committed
	if (!dev || !eeh_subsystem_enabled)
		return;

	/* Unregister the device with the EEH/PCI address search system */
	pr_debug("EEH: Removing device %s\n", pci_name(dev));

	dn = pci_device_to_OF_node(dev);
	if (PCI_DN(dn)->pcidev == NULL) {
		pr_debug("EEH: Not referenced !\n");
		return;
	PCI_DN(dn)->pcidev = NULL;
	pci_dev_put (dev);

	pci_addr_cache_remove_device(dev);
	eeh_sysfs_remove_device(dev);
void eeh_remove_bus_device(struct pci_dev *dev)
{
	struct pci_bus *bus = dev->subordinate;
	struct pci_dev *child, *tmp;

	eeh_remove_device(dev);

	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);
	}
}
EXPORT_SYMBOL_GPL(eeh_remove_bus_device);

Linus Torvalds's avatar
Linus Torvalds committed
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=%ld\n", total_mmio_ffs);
Linus Torvalds's avatar
Linus Torvalds committed
	} else {
		seq_printf(m, "EEH Subsystem is enabled\n");
		seq_printf(m,
				"no device=%ld\n"
				"no device node=%ld\n"
				"no config address=%ld\n"
				"check not wanted=%ld\n"
				"eeh_total_mmio_ffs=%ld\n"
				"eeh_false_positives=%ld\n"
				"eeh_slot_resets=%ld\n",
				no_device, no_dn, no_cfg_addr, 
				ignored_check, total_mmio_ffs, 
				false_positives,
Linus Torvalds's avatar
Linus Torvalds committed
	}

	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 = {
Linus Torvalds's avatar
Linus Torvalds committed
	.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);
Linus Torvalds's avatar
Linus Torvalds committed
	return 0;
}
__initcall(eeh_init_proc);