* another tree except the currently existing tree of PCI
  * buses and PCI devices
  */
-#define EEH_DEV_IRQ_DISABLED   (1<<0)  /* Interrupt disabled           */
+#define EEH_DEV_IRQ_DISABLED   (1 << 0)        /* Interrupt disabled   */
+#define EEH_DEV_DISCONNECTED   (1 << 1)        /* Removing from PE     */
 
 struct eeh_dev {
        int mode;                       /* EEH mode                     */
        struct pci_controller *phb;     /* Associated PHB               */
        struct device_node *dn;         /* Associated device node       */
        struct pci_dev *pdev;           /* Associated PCI device        */
+       struct pci_bus *bus;            /* PCI bus for partial hotplug  */
 };
 
 static inline struct device_node *eeh_dev_to_of_node(struct eeh_dev *edev)
 int eeh_add_to_parent_pe(struct eeh_dev *edev);
 int eeh_rmv_from_parent_pe(struct eeh_dev *edev);
 void eeh_pe_update_time_stamp(struct eeh_pe *pe);
+void *eeh_pe_traverse(struct eeh_pe *root,
+               eeh_traverse_func fn, void *flag);
 void *eeh_pe_dev_traverse(struct eeh_pe *root,
                eeh_traverse_func fn, void *flag);
 void eeh_pe_restore_bars(struct eeh_pe *pe);
 
                pr_debug("EEH: Already referenced !\n");
                return;
        }
-       WARN_ON(edev->pdev);
+
+       /*
+        * The EEH cache might not be removed correctly because of
+        * unbalanced kref to the device during unplug time, which
+        * relies on pcibios_release_device(). So we have to remove
+        * that here explicitly.
+        */
+       if (edev->pdev) {
+               eeh_rmv_from_parent_pe(edev);
+               eeh_addr_cache_rmv_dev(edev->pdev);
+               eeh_sysfs_remove_device(edev->pdev);
+
+               edev->pdev = NULL;
+               dev->dev.archdata.edev = NULL;
+       }
 
        edev->pdev = dev;
        dev->dev.archdata.edev = edev;
        /* Unregister the device with the EEH/PCI address search system */
        pr_debug("EEH: Removing device %s\n", pci_name(dev));
 
-       if (!edev || !edev->pdev) {
+       if (!edev || !edev->pdev || !edev->pe) {
                pr_debug("EEH: Not referenced !\n");
                return;
        }
+
+       /*
+        * During the hotplug for EEH error recovery, we need the EEH
+        * device attached to the parent PE in order for BAR restore
+        * a bit later. So we keep it for BAR restore and remove it
+        * from the parent PE during the BAR resotre.
+        */
        edev->pdev = NULL;
        dev->dev.archdata.edev = NULL;
+       if (!(edev->pe->state & EEH_PE_KEEP))
+               eeh_rmv_from_parent_pe(edev);
+       else
+               edev->mode |= EEH_DEV_DISCONNECTED;
 
-       eeh_rmv_from_parent_pe(edev);
        eeh_addr_cache_rmv_dev(dev);
        eeh_sysfs_remove_device(dev);
 }
 
        return NULL;
 }
 
+static void *eeh_rmv_device(void *data, void *userdata)
+{
+       struct pci_driver *driver;
+       struct eeh_dev *edev = (struct eeh_dev *)data;
+       struct pci_dev *dev = eeh_dev_to_pci_dev(edev);
+       int *removed = (int *)userdata;
+
+       /*
+        * Actually, we should remove the PCI bridges as well.
+        * However, that's lots of complexity to do that,
+        * particularly some of devices under the bridge might
+        * support EEH. So we just care about PCI devices for
+        * simplicity here.
+        */
+       if (!dev || (dev->hdr_type & PCI_HEADER_TYPE_BRIDGE))
+               return NULL;
+       driver = eeh_pcid_get(dev);
+       if (driver && driver->err_handler)
+               return NULL;
+
+       /* Remove it from PCI subsystem */
+       pr_debug("EEH: Removing %s without EEH sensitive driver\n",
+                pci_name(dev));
+       edev->bus = dev->bus;
+       edev->mode |= EEH_DEV_DISCONNECTED;
+       (*removed)++;
+
+       pci_stop_and_remove_bus_device(dev);
+
+       return NULL;
+}
+
+static void *eeh_pe_detach_dev(void *data, void *userdata)
+{
+       struct eeh_pe *pe = (struct eeh_pe *)data;
+       struct eeh_dev *edev, *tmp;
+
+       eeh_pe_for_each_dev(pe, edev, tmp) {
+               if (!(edev->mode & EEH_DEV_DISCONNECTED))
+                       continue;
+
+               edev->mode &= ~(EEH_DEV_DISCONNECTED | EEH_DEV_IRQ_DISABLED);
+               eeh_rmv_from_parent_pe(edev);
+       }
+
+       return NULL;
+}
+
 /**
  * eeh_reset_device - Perform actual reset of a pci slot
  * @pe: EEH PE
  */
 static int eeh_reset_device(struct eeh_pe *pe, struct pci_bus *bus)
 {
+       struct pci_bus *frozen_bus = eeh_pe_bus_get(pe);
        struct timeval tstamp;
-       int cnt, rc;
+       int cnt, rc, removed = 0;
 
        /* pcibios will clear the counter; save the value */
        cnt = pe->freeze_count;
         * devices are expected to be attached soon when calling
         * into pcibios_add_pci_devices().
         */
-       if (bus) {
-               eeh_pe_state_mark(pe, EEH_PE_KEEP);
+       eeh_pe_state_mark(pe, EEH_PE_KEEP);
+       if (bus)
                pcibios_remove_pci_devices(bus);
-       }
+       else if (frozen_bus)
+               eeh_pe_dev_traverse(pe, eeh_rmv_device, &removed);
 
        /* Reset the pci controller. (Asserts RST#; resets config space).
         * Reconfigure bridges and devices. Don't try to bring the system
         * potentially weird things happen.
         */
        if (bus) {
+               pr_info("EEH: Sleep 5s ahead of complete hotplug\n");
                ssleep(5);
+
+               /*
+                * The EEH device is still connected with its parent
+                * PE. We should disconnect it so the binding can be
+                * rebuilt when adding PCI devices.
+                */
+               eeh_pe_traverse(pe, eeh_pe_detach_dev, NULL);
                pcibios_add_pci_devices(bus);
-               eeh_pe_state_clear(pe, EEH_PE_KEEP);
+       } else if (frozen_bus && removed) {
+               pr_info("EEH: Sleep 5s ahead of partial hotplug\n");
+               ssleep(5);
+
+               eeh_pe_traverse(pe, eeh_pe_detach_dev, NULL);
+               pcibios_add_pci_devices(frozen_bus);
        }
+       eeh_pe_state_clear(pe, EEH_PE_KEEP);
 
        pe->tstamp = tstamp;
        pe->freeze_count = cnt;
 
  * callback returns something other than NULL, or no more PEs
  * to be traversed.
  */
-static void *eeh_pe_traverse(struct eeh_pe *root,
-                       eeh_traverse_func fn, void *flag)
+void *eeh_pe_traverse(struct eeh_pe *root,
+                     eeh_traverse_func fn, void *flag)
 {
        struct eeh_pe *pe;
        void *ret;
        int cnt;
 
        if (!edev->pe) {
-               pr_warning("%s: No PE found for EEH device %s\n",
-                       __func__, edev->dn->full_name);
+               pr_debug("%s: No PE found for EEH device %s\n",
+                        __func__, edev->dn->full_name);
                return -EEXIST;
        }
 
  */
 static void *eeh_restore_one_device_bars(void *data, void *flag)
 {
-       struct pci_dev *pdev = NULL;
        struct eeh_dev *edev = (struct eeh_dev *)data;
+       struct pci_dev *pdev = eeh_dev_to_pci_dev(edev);
        struct device_node *dn = eeh_dev_to_of_node(edev);
 
-       /* Trace the PCI bridge */
-       if (eeh_probe_mode_dev()) {
-               pdev = eeh_dev_to_pci_dev(edev);
-               if (pdev->hdr_type != PCI_HEADER_TYPE_BRIDGE)
-                        pdev = NULL;
-        }
-
-       if (pdev)
+       /* Do special restore for bridges */
+       if (pdev->hdr_type == PCI_HEADER_TYPE_BRIDGE)
                eeh_restore_bridge_bars(pdev, edev, dn);
        else
                eeh_restore_device_bars(edev, dn);
 
 
 void eeh_sysfs_remove_device(struct pci_dev *pdev)
 {
+       /*
+        * The parent directory might have been removed. We needn't
+        * continue for that case.
+        */
+       if (!pdev->dev.kobj.sd)
+               return;
+
        device_remove_file(&pdev->dev, &dev_attr_eeh_mode);
        device_remove_file(&pdev->dev, &dev_attr_eeh_config_addr);
        device_remove_file(&pdev->dev, &dev_attr_eeh_pe_config_addr);
 
         * the root bridge. So it's not reasonable to continue
         * the probing.
         */
-       if (!dn || !edev)
+       if (!dn || !edev || edev->pe)
                return 0;
 
        /* Skip for PCI-ISA bridge */
 
 
        /* Retrieve OF node and eeh device */
        edev = of_node_to_eeh_dev(dn);
-       if (!of_device_is_available(dn))
+       if (edev->pe || !of_device_is_available(dn))
                return NULL;
 
        /* Retrieve class/vendor/device IDs */