diff --git a/arch/powerpc/include/asm/eeh.h b/arch/powerpc/include/asm/eeh.h
index dd55d1bf1cfd..46dec5b2482e 100644
--- a/arch/powerpc/include/asm/eeh.h
+++ b/arch/powerpc/include/asm/eeh.h
@@ -230,6 +230,7 @@ struct eeh_ops {
 	int (*next_error)(struct eeh_pe **pe);
 	int (*restore_config)(struct pci_dn *pdn);
 	int (*notify_resume)(struct pci_dn *pdn);
+	void (*pe_plat_state_dump)(char *buf, size_t len, struct eeh_pe *pe);
 };
 
 extern int eeh_subsystem_flags;
@@ -324,6 +325,8 @@ int eeh_pe_configure(struct eeh_pe *pe);
 int eeh_pe_inject_err(struct eeh_pe *pe, int type, int func,
 		      unsigned long addr, unsigned long mask);
 int eeh_restore_vf_config(struct pci_dn *pdn);
+void eeh_tree_state_dump(void (*pfn)(void *, const char *, ...),
+			 void *s, struct eeh_pe *pe, int level, int xlevel);
 
 /**
  * EEH_POSSIBLE_ERROR() -- test for possible MMIO failure.
diff --git a/arch/powerpc/kernel/eeh.c b/arch/powerpc/kernel/eeh.c
index 54f921ff7621..6f675f277d26 100644
--- a/arch/powerpc/kernel/eeh.c
+++ b/arch/powerpc/kernel/eeh.c
@@ -839,6 +839,96 @@ int eeh_restore_vf_config(struct pci_dn *pdn)
 	return 0;
 }
 
+static void eeh_tree_state_indent(void (*pfn)(void *, const char *, ...),
+				  void *s, int level, int xlevel, bool node)
+{
+	int i;
+
+	for (i = 0; i < level; i++)
+		pfn(s, "%c   ", ((xlevel & (1 << i)) ? '|' : ' '));
+	if (node)
+		pfn(s, "%c---", ((xlevel & (1 << level)) ? '+' : '\\'));
+	else
+		pfn(s, "%c...", ((xlevel & (1 << level)) ? '|' : ' '));
+}
+
+void eeh_tree_state_dump(void (*pfn)(void *, const char *, ...),
+			 void *s, struct eeh_pe *pe, int level, int xlevel)
+{
+	struct eeh_dev *edev;
+	struct eeh_pe *child_pe;
+	int slevel, sxlevel;
+	char buf[1024];
+
+	eeh_recovery_must_be_locked();
+	eeh_tree_state_indent(pfn, s, level, xlevel, true);
+	pfn(s, "* [PE#0x%x] type=%s%s%s%s%s config_addr=0x%x pass_count=%d\n",
+	    pe->addr,
+	    ((pe->type & EEH_PE_INVALID) ? "INVALID " : ""),
+	    ((pe->type & EEH_PE_PHB) ? "PHB " : ""),
+	    ((pe->type & EEH_PE_DEVICE) ? "DEVICE " : ""),
+	    ((pe->type & EEH_PE_BUS) ? "BUS " : ""),
+	    ((pe->type & EEH_PE_VF) ? "VF " : ""),
+	    pe->config_addr,
+	    atomic_read(&pe->pass_dev_cnt));
+
+	slevel = level + 1;
+	sxlevel = xlevel;
+	if (!list_empty(&pe->edevs) || !list_empty(&pe->child_list))
+		sxlevel |= (1 << slevel);
+	eeh_tree_state_indent(pfn, s, slevel, sxlevel, false);
+	pfn(s, "  check_count=%d freeze_count=%d false_positives=%d first_freeze=%llu\n",
+		   pe->check_count, pe->freeze_count, pe->false_positives,
+		   pe->tstamp);
+	eeh_tree_state_indent(pfn, s, slevel, sxlevel, false);
+	pfn(s, "  kernel state=0x%x %s%s%s%s%s%s%s%s\n", pe->state,
+	    ((pe->state & EEH_PE_ISOLATED) ? "ISOLATED " : ""),
+	    ((pe->state & EEH_PE_RECOVERING) ? "RECOVERING " : ""),
+	    ((pe->state & EEH_PE_CFG_BLOCKED) ? "CFG_BLOCKED " : ""),
+	    ((pe->state & EEH_PE_RESET) ? "RESET " : ""),
+	    ((pe->state & EEH_PE_KEEP) ? "KEEP " : ""),
+	    ((pe->state & EEH_PE_CFG_RESTRICTED) ? "CFG_RESTRICTED " : ""),
+	    ((pe->state & EEH_PE_REMOVED) ? "REMOVED" : ""),
+	    ((pe->state & EEH_PE_PRI_BUS) ? "PRI_BUS" : ""));
+	if (eeh_ops->pe_plat_state_dump) {
+		eeh_tree_state_indent(pfn, s, slevel, sxlevel, false);
+		eeh_ops->pe_plat_state_dump(buf, sizeof(buf), pe);
+		pfn(s, "  %.*s\n", sizeof(buf), buf);
+	}
+
+	slevel = level + 1;
+	list_for_each_entry(edev, &pe->edevs, entry) {
+		struct pci_dev *pdev = edev->pdev;
+
+		sxlevel = xlevel;
+		if ((edev != list_last_entry(&pe->edevs, struct eeh_dev, entry)) ||
+		    !list_empty(&pe->child_list))
+			sxlevel |= (1 << slevel);
+		eeh_tree_state_indent(pfn, s, slevel, sxlevel, true);
+		pfn(s, "* [DEV %s] pe_config_addr=0x%x driver=%s\n",
+		    pci_name(pdev), edev->pe_config_addr, eeh_driver_name(pdev));
+		eeh_tree_state_indent(pfn, s, slevel + 1, sxlevel, false);
+		pfn(s, "mode=0x%x %s%s%s%s%s%s%s%s\n", edev->mode,
+		    ((edev->mode & EEH_DEV_BRIDGE) ? "BRIDGE " : ""),
+		    ((edev->mode & EEH_DEV_ROOT_PORT) ? "ROOT_PORT " : ""),
+		    ((edev->mode & EEH_DEV_DS_PORT) ? "DS_PORT " : ""),
+		    ((edev->mode & EEH_DEV_IRQ_DISABLED) ? "IRQ_DISABLED " : ""),
+		    ((edev->mode & EEH_DEV_DISCONNECTED) ? "DISCONNECTED " : ""),
+		    ((edev->mode & EEH_DEV_NO_HANDLER) ? "NO_HANDLER " : ""),
+		    ((edev->mode & EEH_DEV_SYSFS) ? "SYSFS " : ""),
+		    ((edev->mode & EEH_DEV_REMOVED) ? "REMOVED " : ""));
+	}
+
+	slevel = level + 1;
+	list_for_each_entry(child_pe, &pe->child_list, child) {
+		sxlevel = xlevel;
+		if (child_pe != list_last_entry(&pe->child_list, struct eeh_pe, child))
+			sxlevel |= (1 << slevel);
+		eeh_tree_state_dump(pfn, s, child_pe, slevel, sxlevel);
+	}
+}
+
+
 /**
  * pcibios_set_pcie_reset_state - Set PCI-E reset state
  * @dev: pci device struct
diff --git a/arch/powerpc/kernel/eeh_driver.c b/arch/powerpc/kernel/eeh_driver.c
index 9d03292f66a7..7b077599c567 100644
--- a/arch/powerpc/kernel/eeh_driver.c
+++ b/arch/powerpc/kernel/eeh_driver.c
@@ -15,6 +15,7 @@
 #include <linux/kthread.h>
 #include <linux/workqueue.h>
 #include <linux/completion.h>
+#include <linux/seq_buf.h>
 #include <asm/eeh.h>
 #include <asm/eeh_event.h>
 #include <asm/ppc-pci.h>
@@ -996,6 +997,26 @@ static int eeh_reset_device(unsigned int event_id,
 	return 0;
 }
 
+static void eeh_tree_state_dump_kprintf(struct eeh_pe *root_pe)
+{
+	struct seq_buf s;
+	char *buf, *p, *q;
+
+	buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
+	seq_buf_init(&s, buf, PAGE_SIZE);
+
+	eeh_tree_state_dump((void (*)(void *, const char *, ...))seq_buf_printf, &s, root_pe, 0, 0);
+
+	/* printk's output is limited to LOG_LINE_MAX, so split into lines: */
+	/* buf must end with a \n */
+	p = buf;
+	while ((q = strstr(p, "\n"))) {
+		printk(KERN_INFO "%.*s", (int)(q - p + 1), p);
+		p = q + 1;
+	}
+	kfree(buf);
+}
+
 /* The longest amount of time to wait for a pci device
  * to come back on line, in seconds.
  */
@@ -1129,6 +1150,9 @@ void eeh_handle_normal_event(unsigned int event_id, struct eeh_pe *pe)
 	int devices = 0;
 
 	eeh_recovery_lock();
+	pr_info("EEH(%u): PE state before recovery:\n", event_id);
+	eeh_tree_state_dump_kprintf(pe);
+
 	bus = eeh_pe_bus_get(pe);
 	if (!bus) {
 		pr_err("EEH(%u): %s: Cannot find PCI bus for PHB#%x-PE#%x\n",
@@ -1381,6 +1405,8 @@ void eeh_handle_normal_event(unsigned int event_id, struct eeh_pe *pe)
 			eeh_pe_state_clear(pe, EEH_PE_PRI_BUS, true);
 			eeh_pe_dev_mode_mark(pe, EEH_DEV_REMOVED);
 
+			pr_info("PE state after recovery (before final device removal):\n");
+			eeh_tree_state_dump_kprintf(pe);
 			eeh_recovery_unlock();
 			pci_lock_rescan_remove();
 			pci_hp_remove_devices(bus);
@@ -1405,6 +1431,8 @@ void eeh_handle_normal_event(unsigned int event_id, struct eeh_pe *pe)
 			eeh_clear_slot_attention(edev->pdev);
 
 	eeh_pe_state_clear(pe, EEH_PE_RECOVERING, true);
+	pr_info("EEH(%u): PE state after recovery:\n", event_id);
+	eeh_tree_state_dump_kprintf(pe);
 	eeh_recovery_unlock();
 
 }
diff --git a/arch/powerpc/platforms/powernv/eeh-powernv.c b/arch/powerpc/platforms/powernv/eeh-powernv.c
index 1f6ec78ad88b..82b705e3f1e7 100644
--- a/arch/powerpc/platforms/powernv/eeh-powernv.c
+++ b/arch/powerpc/platforms/powernv/eeh-powernv.c
@@ -206,6 +206,30 @@ PNV_EEH_DBGFS_ENTRY(outb, 0xD10);
 PNV_EEH_DBGFS_ENTRY(inbA, 0xD90);
 PNV_EEH_DBGFS_ENTRY(inbB, 0xE10);
 
+static int eeh_tree_state_dump_seq_file(struct seq_file *s, void *unused)
+{
+	struct eeh_pe *root_pe = s->private;
+
+	eeh_recovery_lock();
+	eeh_tree_state_dump((void (*)(void *, const char *, ...))seq_printf,
+			    s, root_pe, 0, 0);
+	eeh_recovery_unlock();
+	return 0;
+}
+
+static int eeh_tree_state_dbgfs_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, eeh_tree_state_dump_seq_file, eeh_phb_pe_get(inode->i_private));
+}
+
+static const struct file_operations eeh_tree_state_debugfs_ops = {
+	.owner		= THIS_MODULE,
+	.open		= eeh_tree_state_dbgfs_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+};
+
 #endif /* CONFIG_DEBUG_FS */
 
 void pnv_eeh_enable_phbs(void)
@@ -287,6 +311,9 @@ int pnv_eeh_post_init(void)
 		debugfs_create_file("err_injct_inboundB", 0600,
 				    phb->dbgfs, hose,
 				    &pnv_eeh_dbgfs_ops_inbB);
+		debugfs_create_file("eeh_pe_tree", 0400,
+				    phb->dbgfs, hose,
+				    &eeh_tree_state_debugfs_ops);
 #endif /* CONFIG_DEBUG_FS */
 	}
 
@@ -1685,6 +1712,39 @@ static int pnv_eeh_restore_config(struct pci_dn *pdn)
 	return ret;
 }
 
+void pnv_eeh_pe_plat_state_dump(char *buf, size_t len, struct eeh_pe *pe)
+{
+	struct pnv_phb *phb = pe->phb->private_data;
+	s64 rc;
+	u8 state = 0;
+	__be16 pcierr = 0;
+
+	/* Collect state directly from OPAL to avoid side effects: */
+	rc = opal_pci_eeh_freeze_status(phb->opal_id, pe->addr, &state,
+					&pcierr, NULL);
+	pcierr = be16_to_cpu(pcierr);
+
+	if (rc == OPAL_SUCCESS)
+		snprintf(buf, len, "OPAL state=0x%x %s%s%s%s%s%s%s pcierr=0x%x %s%s%s%s%s%s",
+			 state,
+			 ((state == OPAL_EEH_STOPPED_NOT_FROZEN) ? "OK" : ""),
+			 ((state == OPAL_EEH_STOPPED_MMIO_FREEZE) ? "MMIO_FREEZE" : ""),
+			 ((state == OPAL_EEH_STOPPED_DMA_FREEZE) ? "DMA_FREEZE" : ""),
+			 ((state == OPAL_EEH_STOPPED_MMIO_DMA_FREEZE) ? "MMIO_DMA_FREEZE" : ""),
+			 ((state == OPAL_EEH_STOPPED_RESET) ? "STOPPED_RESET" : ""),
+			 ((state == OPAL_EEH_STOPPED_TEMP_UNAVAIL) ? "STOPPED_TEMP_UNAVAIL" : ""),
+			 ((state == OPAL_EEH_STOPPED_PERM_UNAVAIL) ? "STOPPED_PERM_UNAVAIL" : ""),
+			 pcierr,
+			 ((pcierr == OPAL_EEH_NO_ERROR) ? "OK" : ""),
+			 ((pcierr == OPAL_EEH_IOC_ERROR) ? "IOC_ERROR" : ""),
+			 ((pcierr == OPAL_EEH_PHB_ERROR) ? "PHB_ERROR" : ""),
+			 ((pcierr == OPAL_EEH_PE_ERROR) ? "PE_ERROR" : ""),
+			 ((pcierr == OPAL_EEH_PE_MMIO_ERROR) ? "MMIO_ERROR" : ""),
+			 ((pcierr == OPAL_EEH_PE_DMA_ERROR) ? "DMA_ERROR" : ""));
+	else
+		snprintf(buf, len, "OPAL error=0x%llx", rc);
+}
+
 static struct eeh_ops pnv_eeh_ops = {
 	.name                   = "powernv",
 	.init                   = pnv_eeh_init,
@@ -1700,7 +1760,8 @@ static struct eeh_ops pnv_eeh_ops = {
 	.write_config           = pnv_eeh_write_config,
 	.next_error		= pnv_eeh_next_error,
 	.restore_config		= pnv_eeh_restore_config,
-	.notify_resume		= NULL
+	.notify_resume		= NULL,
+	.pe_plat_state_dump	= pnv_eeh_pe_plat_state_dump,
 };
 
 #ifdef CONFIG_PCI_IOV
diff --git a/arch/powerpc/platforms/pseries/eeh_pseries.c b/arch/powerpc/platforms/pseries/eeh_pseries.c
index c49fab23f1c9..3fbbd8583350 100644
--- a/arch/powerpc/platforms/pseries/eeh_pseries.c
+++ b/arch/powerpc/platforms/pseries/eeh_pseries.c
@@ -782,6 +782,24 @@ static int pseries_notify_resume(struct pci_dn *pdn)
 }
 #endif
 
+void pseries_eeh_pe_plat_state_dump(char *buf, size_t len, struct eeh_pe *pe)
+{
+	int state, delay;
+
+	/* pseries_eeh_get_state() doesn't have side-effects, so we can use it here: */
+	state = pseries_eeh_get_state(pe, &delay);
+
+	snprintf(buf, len, "RTAS state=0x%x %s%s%s%s%s%s%s",
+	    state,
+	    ((state & EEH_STATE_UNAVAILABLE) ? "UNAVAILABLE " : ""),
+	    ((state & EEH_STATE_NOT_SUPPORT) ? "NOT_SUPPORT " : ""),
+	    ((state & EEH_STATE_RESET_ACTIVE) ? "RESET_ACTIVE " : ""),
+	    ((state & EEH_STATE_MMIO_ACTIVE) ? "MMIO_ACTIVE " : ""),
+	    ((state & EEH_STATE_DMA_ACTIVE) ? "DMA_ACTIVE " : ""),
+	    ((state & EEH_STATE_MMIO_ENABLED) ? "MMIO_ENABLED " : ""),
+	    ((state & EEH_STATE_DMA_ENABLED) ? "DMA_ENABLED " : ""));
+}
+
 static struct eeh_ops pseries_eeh_ops = {
 	.name			= "pseries",
 	.init			= pseries_eeh_init,
@@ -798,8 +816,9 @@ static struct eeh_ops pseries_eeh_ops = {
 	.next_error		= NULL,
 	.restore_config		= pseries_eeh_restore_config,
 #ifdef CONFIG_PCI_IOV
-	.notify_resume		= pseries_notify_resume
+	.notify_resume		= pseries_notify_resume,
 #endif
+	.pe_plat_state_dump	= pseries_eeh_pe_plat_state_dump,
 };
 
 /**
