aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGavin Shan <shangw@linux.vnet.ibm.com>2013-07-23 22:24:58 -0400
committerBenjamin Herrenschmidt <benh@kernel.crashing.org>2013-07-24 00:18:48 -0400
commitf5c57710dd62dd06f176934a8b4b8accbf00f9f8 (patch)
tree6d6eb17f320ac2d57d5a801f9e0773a290040f3b
parentab444ec97e8bd65fff9d489b9a409fc03979268b (diff)
powerpc/eeh: Use partial hotplug for EEH unaware drivers
When EEH error happens to one specific PE, some devices with drivers supporting EEH won't except hotplug on the device. However, there might have other deivces without driver, or with driver without EEH support. For the case, we need do partial hotplug in order to make sure that the PE becomes absolutely quite during reset. Otherise, the PE reset might fail and leads to failure of error recovery. The current code doesn't handle that 'mixed' case properly, it either uses the error callbacks to the drivers, or tries hotplug, but doesn't handle a PE (EEH domain) composed of a combination of the two. The patch intends to support so-called "partial" hotplug for EEH: Before we do reset, we stop and remove those PCI devices without EEH sensitive driver. The corresponding EEH devices are not detached from its PE, but with special flag. After the reset is done, those EEH devices with the special flag will be scanned one by one. Signed-off-by: Gavin Shan <shangw@linux.vnet.ibm.com> Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
-rw-r--r--arch/powerpc/include/asm/eeh.h6
-rw-r--r--arch/powerpc/kernel/eeh.c30
-rw-r--r--arch/powerpc/kernel/eeh_driver.c74
-rw-r--r--arch/powerpc/kernel/eeh_pe.c20
-rw-r--r--arch/powerpc/kernel/eeh_sysfs.c7
-rw-r--r--arch/powerpc/platforms/powernv/eeh-powernv.c2
-rw-r--r--arch/powerpc/platforms/pseries/eeh_pseries.c2
7 files changed, 117 insertions, 24 deletions
diff --git a/arch/powerpc/include/asm/eeh.h b/arch/powerpc/include/asm/eeh.h
index e8c411b63caf..f54a60131de5 100644
--- a/arch/powerpc/include/asm/eeh.h
+++ b/arch/powerpc/include/asm/eeh.h
@@ -84,7 +84,8 @@ struct eeh_pe {
84 * another tree except the currently existing tree of PCI 84 * another tree except the currently existing tree of PCI
85 * buses and PCI devices 85 * buses and PCI devices
86 */ 86 */
87#define EEH_DEV_IRQ_DISABLED (1<<0) /* Interrupt disabled */ 87#define EEH_DEV_IRQ_DISABLED (1 << 0) /* Interrupt disabled */
88#define EEH_DEV_DISCONNECTED (1 << 1) /* Removing from PE */
88 89
89struct eeh_dev { 90struct eeh_dev {
90 int mode; /* EEH mode */ 91 int mode; /* EEH mode */
@@ -97,6 +98,7 @@ struct eeh_dev {
97 struct pci_controller *phb; /* Associated PHB */ 98 struct pci_controller *phb; /* Associated PHB */
98 struct device_node *dn; /* Associated device node */ 99 struct device_node *dn; /* Associated device node */
99 struct pci_dev *pdev; /* Associated PCI device */ 100 struct pci_dev *pdev; /* Associated PCI device */
101 struct pci_bus *bus; /* PCI bus for partial hotplug */
100}; 102};
101 103
102static inline struct device_node *eeh_dev_to_of_node(struct eeh_dev *edev) 104static inline struct device_node *eeh_dev_to_of_node(struct eeh_dev *edev)
@@ -197,6 +199,8 @@ struct eeh_pe *eeh_pe_get(struct eeh_dev *edev);
197int eeh_add_to_parent_pe(struct eeh_dev *edev); 199int eeh_add_to_parent_pe(struct eeh_dev *edev);
198int eeh_rmv_from_parent_pe(struct eeh_dev *edev); 200int eeh_rmv_from_parent_pe(struct eeh_dev *edev);
199void eeh_pe_update_time_stamp(struct eeh_pe *pe); 201void eeh_pe_update_time_stamp(struct eeh_pe *pe);
202void *eeh_pe_traverse(struct eeh_pe *root,
203 eeh_traverse_func fn, void *flag);
200void *eeh_pe_dev_traverse(struct eeh_pe *root, 204void *eeh_pe_dev_traverse(struct eeh_pe *root,
201 eeh_traverse_func fn, void *flag); 205 eeh_traverse_func fn, void *flag);
202void eeh_pe_restore_bars(struct eeh_pe *pe); 206void eeh_pe_restore_bars(struct eeh_pe *pe);
diff --git a/arch/powerpc/kernel/eeh.c b/arch/powerpc/kernel/eeh.c
index 56bd4584f61f..a5783f1a7a96 100644
--- a/arch/powerpc/kernel/eeh.c
+++ b/arch/powerpc/kernel/eeh.c
@@ -900,7 +900,21 @@ void eeh_add_device_late(struct pci_dev *dev)
900 pr_debug("EEH: Already referenced !\n"); 900 pr_debug("EEH: Already referenced !\n");
901 return; 901 return;
902 } 902 }
903 WARN_ON(edev->pdev); 903
904 /*
905 * The EEH cache might not be removed correctly because of
906 * unbalanced kref to the device during unplug time, which
907 * relies on pcibios_release_device(). So we have to remove
908 * that here explicitly.
909 */
910 if (edev->pdev) {
911 eeh_rmv_from_parent_pe(edev);
912 eeh_addr_cache_rmv_dev(edev->pdev);
913 eeh_sysfs_remove_device(edev->pdev);
914
915 edev->pdev = NULL;
916 dev->dev.archdata.edev = NULL;
917 }
904 918
905 edev->pdev = dev; 919 edev->pdev = dev;
906 dev->dev.archdata.edev = edev; 920 dev->dev.archdata.edev = edev;
@@ -982,14 +996,24 @@ void eeh_remove_device(struct pci_dev *dev)
982 /* Unregister the device with the EEH/PCI address search system */ 996 /* Unregister the device with the EEH/PCI address search system */
983 pr_debug("EEH: Removing device %s\n", pci_name(dev)); 997 pr_debug("EEH: Removing device %s\n", pci_name(dev));
984 998
985 if (!edev || !edev->pdev) { 999 if (!edev || !edev->pdev || !edev->pe) {
986 pr_debug("EEH: Not referenced !\n"); 1000 pr_debug("EEH: Not referenced !\n");
987 return; 1001 return;
988 } 1002 }
1003
1004 /*
1005 * During the hotplug for EEH error recovery, we need the EEH
1006 * device attached to the parent PE in order for BAR restore
1007 * a bit later. So we keep it for BAR restore and remove it
1008 * from the parent PE during the BAR resotre.
1009 */
989 edev->pdev = NULL; 1010 edev->pdev = NULL;
990 dev->dev.archdata.edev = NULL; 1011 dev->dev.archdata.edev = NULL;
1012 if (!(edev->pe->state & EEH_PE_KEEP))
1013 eeh_rmv_from_parent_pe(edev);
1014 else
1015 edev->mode |= EEH_DEV_DISCONNECTED;
991 1016
992 eeh_rmv_from_parent_pe(edev);
993 eeh_addr_cache_rmv_dev(dev); 1017 eeh_addr_cache_rmv_dev(dev);
994 eeh_sysfs_remove_device(dev); 1018 eeh_sysfs_remove_device(dev);
995} 1019}
diff --git a/arch/powerpc/kernel/eeh_driver.c b/arch/powerpc/kernel/eeh_driver.c
index 9ef3bbb8580a..9fda75d1f5aa 100644
--- a/arch/powerpc/kernel/eeh_driver.c
+++ b/arch/powerpc/kernel/eeh_driver.c
@@ -338,6 +338,54 @@ static void *eeh_report_failure(void *data, void *userdata)
338 return NULL; 338 return NULL;
339} 339}
340 340
341static void *eeh_rmv_device(void *data, void *userdata)
342{
343 struct pci_driver *driver;
344 struct eeh_dev *edev = (struct eeh_dev *)data;
345 struct pci_dev *dev = eeh_dev_to_pci_dev(edev);
346 int *removed = (int *)userdata;
347
348 /*
349 * Actually, we should remove the PCI bridges as well.
350 * However, that's lots of complexity to do that,
351 * particularly some of devices under the bridge might
352 * support EEH. So we just care about PCI devices for
353 * simplicity here.
354 */
355 if (!dev || (dev->hdr_type & PCI_HEADER_TYPE_BRIDGE))
356 return NULL;
357 driver = eeh_pcid_get(dev);
358 if (driver && driver->err_handler)
359 return NULL;
360
361 /* Remove it from PCI subsystem */
362 pr_debug("EEH: Removing %s without EEH sensitive driver\n",
363 pci_name(dev));
364 edev->bus = dev->bus;
365 edev->mode |= EEH_DEV_DISCONNECTED;
366 (*removed)++;
367
368 pci_stop_and_remove_bus_device(dev);
369
370 return NULL;
371}
372
373static void *eeh_pe_detach_dev(void *data, void *userdata)
374{
375 struct eeh_pe *pe = (struct eeh_pe *)data;
376 struct eeh_dev *edev, *tmp;
377
378 eeh_pe_for_each_dev(pe, edev, tmp) {
379 if (!(edev->mode & EEH_DEV_DISCONNECTED))
380 continue;
381
382 edev->mode &= ~(EEH_DEV_DISCONNECTED | EEH_DEV_IRQ_DISABLED);
383 eeh_rmv_from_parent_pe(edev);
384 }
385
386 return NULL;
387}
388
341/** 389/**
342 * eeh_reset_device - Perform actual reset of a pci slot 390 * eeh_reset_device - Perform actual reset of a pci slot
343 * @pe: EEH PE 391 * @pe: EEH PE
@@ -349,8 +397,9 @@ static void *eeh_report_failure(void *data, void *userdata)
349 */ 397 */
350static int eeh_reset_device(struct eeh_pe *pe, struct pci_bus *bus) 398static int eeh_reset_device(struct eeh_pe *pe, struct pci_bus *bus)
351{ 399{
400 struct pci_bus *frozen_bus = eeh_pe_bus_get(pe);
352 struct timeval tstamp; 401 struct timeval tstamp;
353 int cnt, rc; 402 int cnt, rc, removed = 0;
354 403
355 /* pcibios will clear the counter; save the value */ 404 /* pcibios will clear the counter; save the value */
356 cnt = pe->freeze_count; 405 cnt = pe->freeze_count;
@@ -362,10 +411,11 @@ static int eeh_reset_device(struct eeh_pe *pe, struct pci_bus *bus)
362 * devices are expected to be attached soon when calling 411 * devices are expected to be attached soon when calling
363 * into pcibios_add_pci_devices(). 412 * into pcibios_add_pci_devices().
364 */ 413 */
365 if (bus) { 414 eeh_pe_state_mark(pe, EEH_PE_KEEP);
366 eeh_pe_state_mark(pe, EEH_PE_KEEP); 415 if (bus)
367 pcibios_remove_pci_devices(bus); 416 pcibios_remove_pci_devices(bus);
368 } 417 else if (frozen_bus)
418 eeh_pe_dev_traverse(pe, eeh_rmv_device, &removed);
369 419
370 /* Reset the pci controller. (Asserts RST#; resets config space). 420 /* Reset the pci controller. (Asserts RST#; resets config space).
371 * Reconfigure bridges and devices. Don't try to bring the system 421 * Reconfigure bridges and devices. Don't try to bring the system
@@ -386,10 +436,24 @@ static int eeh_reset_device(struct eeh_pe *pe, struct pci_bus *bus)
386 * potentially weird things happen. 436 * potentially weird things happen.
387 */ 437 */
388 if (bus) { 438 if (bus) {
439 pr_info("EEH: Sleep 5s ahead of complete hotplug\n");
389 ssleep(5); 440 ssleep(5);
441
442 /*
443 * The EEH device is still connected with its parent
444 * PE. We should disconnect it so the binding can be
445 * rebuilt when adding PCI devices.
446 */
447 eeh_pe_traverse(pe, eeh_pe_detach_dev, NULL);
390 pcibios_add_pci_devices(bus); 448 pcibios_add_pci_devices(bus);
391 eeh_pe_state_clear(pe, EEH_PE_KEEP); 449 } else if (frozen_bus && removed) {
450 pr_info("EEH: Sleep 5s ahead of partial hotplug\n");
451 ssleep(5);
452
453 eeh_pe_traverse(pe, eeh_pe_detach_dev, NULL);
454 pcibios_add_pci_devices(frozen_bus);
392 } 455 }
456 eeh_pe_state_clear(pe, EEH_PE_KEEP);
393 457
394 pe->tstamp = tstamp; 458 pe->tstamp = tstamp;
395 pe->freeze_count = cnt; 459 pe->freeze_count = cnt;
diff --git a/arch/powerpc/kernel/eeh_pe.c b/arch/powerpc/kernel/eeh_pe.c
index c8b815e45c8f..2aa955ae01a1 100644
--- a/arch/powerpc/kernel/eeh_pe.c
+++ b/arch/powerpc/kernel/eeh_pe.c
@@ -149,8 +149,8 @@ static struct eeh_pe *eeh_pe_next(struct eeh_pe *pe,
149 * callback returns something other than NULL, or no more PEs 149 * callback returns something other than NULL, or no more PEs
150 * to be traversed. 150 * to be traversed.
151 */ 151 */
152static void *eeh_pe_traverse(struct eeh_pe *root, 152void *eeh_pe_traverse(struct eeh_pe *root,
153 eeh_traverse_func fn, void *flag) 153 eeh_traverse_func fn, void *flag)
154{ 154{
155 struct eeh_pe *pe; 155 struct eeh_pe *pe;
156 void *ret; 156 void *ret;
@@ -409,8 +409,8 @@ int eeh_rmv_from_parent_pe(struct eeh_dev *edev)
409 int cnt; 409 int cnt;
410 410
411 if (!edev->pe) { 411 if (!edev->pe) {
412 pr_warning("%s: No PE found for EEH device %s\n", 412 pr_debug("%s: No PE found for EEH device %s\n",
413 __func__, edev->dn->full_name); 413 __func__, edev->dn->full_name);
414 return -EEXIST; 414 return -EEXIST;
415 } 415 }
416 416
@@ -728,18 +728,12 @@ static void eeh_restore_device_bars(struct eeh_dev *edev,
728 */ 728 */
729static void *eeh_restore_one_device_bars(void *data, void *flag) 729static void *eeh_restore_one_device_bars(void *data, void *flag)
730{ 730{
731 struct pci_dev *pdev = NULL;
732 struct eeh_dev *edev = (struct eeh_dev *)data; 731 struct eeh_dev *edev = (struct eeh_dev *)data;
732 struct pci_dev *pdev = eeh_dev_to_pci_dev(edev);
733 struct device_node *dn = eeh_dev_to_of_node(edev); 733 struct device_node *dn = eeh_dev_to_of_node(edev);
734 734
735 /* Trace the PCI bridge */ 735 /* Do special restore for bridges */
736 if (eeh_probe_mode_dev()) { 736 if (pdev->hdr_type == PCI_HEADER_TYPE_BRIDGE)
737 pdev = eeh_dev_to_pci_dev(edev);
738 if (pdev->hdr_type != PCI_HEADER_TYPE_BRIDGE)
739 pdev = NULL;
740 }
741
742 if (pdev)
743 eeh_restore_bridge_bars(pdev, edev, dn); 737 eeh_restore_bridge_bars(pdev, edev, dn);
744 else 738 else
745 eeh_restore_device_bars(edev, dn); 739 eeh_restore_device_bars(edev, dn);
diff --git a/arch/powerpc/kernel/eeh_sysfs.c b/arch/powerpc/kernel/eeh_sysfs.c
index e7ae3484918c..61e2a1452131 100644
--- a/arch/powerpc/kernel/eeh_sysfs.c
+++ b/arch/powerpc/kernel/eeh_sysfs.c
@@ -68,6 +68,13 @@ void eeh_sysfs_add_device(struct pci_dev *pdev)
68 68
69void eeh_sysfs_remove_device(struct pci_dev *pdev) 69void eeh_sysfs_remove_device(struct pci_dev *pdev)
70{ 70{
71 /*
72 * The parent directory might have been removed. We needn't
73 * continue for that case.
74 */
75 if (!pdev->dev.kobj.sd)
76 return;
77
71 device_remove_file(&pdev->dev, &dev_attr_eeh_mode); 78 device_remove_file(&pdev->dev, &dev_attr_eeh_mode);
72 device_remove_file(&pdev->dev, &dev_attr_eeh_config_addr); 79 device_remove_file(&pdev->dev, &dev_attr_eeh_config_addr);
73 device_remove_file(&pdev->dev, &dev_attr_eeh_pe_config_addr); 80 device_remove_file(&pdev->dev, &dev_attr_eeh_pe_config_addr);
diff --git a/arch/powerpc/platforms/powernv/eeh-powernv.c b/arch/powerpc/platforms/powernv/eeh-powernv.c
index 969cce73055a..a380428cf9ce 100644
--- a/arch/powerpc/platforms/powernv/eeh-powernv.c
+++ b/arch/powerpc/platforms/powernv/eeh-powernv.c
@@ -114,7 +114,7 @@ static int powernv_eeh_dev_probe(struct pci_dev *dev, void *flag)
114 * the root bridge. So it's not reasonable to continue 114 * the root bridge. So it's not reasonable to continue
115 * the probing. 115 * the probing.
116 */ 116 */
117 if (!dn || !edev) 117 if (!dn || !edev || edev->pe)
118 return 0; 118 return 0;
119 119
120 /* Skip for PCI-ISA bridge */ 120 /* Skip for PCI-ISA bridge */
diff --git a/arch/powerpc/platforms/pseries/eeh_pseries.c b/arch/powerpc/platforms/pseries/eeh_pseries.c
index b456b157d33d..0f44f9fe49ac 100644
--- a/arch/powerpc/platforms/pseries/eeh_pseries.c
+++ b/arch/powerpc/platforms/pseries/eeh_pseries.c
@@ -153,7 +153,7 @@ static void *pseries_eeh_of_probe(struct device_node *dn, void *flag)
153 153
154 /* Retrieve OF node and eeh device */ 154 /* Retrieve OF node and eeh device */
155 edev = of_node_to_eeh_dev(dn); 155 edev = of_node_to_eeh_dev(dn);
156 if (!of_device_is_available(dn)) 156 if (edev->pe || !of_device_is_available(dn))
157 return NULL; 157 return NULL;
158 158
159 /* Retrieve class/vendor/device IDs */ 159 /* Retrieve class/vendor/device IDs */