|
@@ -22,6 +22,7 @@
|
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
*/
|
|
|
|
|
|
+#include <linux/delay.h>
|
|
|
#include <linux/export.h>
|
|
|
#include <linux/gfp.h>
|
|
|
#include <linux/init.h>
|
|
@@ -78,9 +79,7 @@ int eeh_phb_pe_create(struct pci_controller *phb)
|
|
|
}
|
|
|
|
|
|
/* Put it into the list */
|
|
|
- eeh_lock();
|
|
|
list_add_tail(&pe->child, &eeh_phb_pe);
|
|
|
- eeh_unlock();
|
|
|
|
|
|
pr_debug("EEH: Add PE for PHB#%d\n", phb->global_number);
|
|
|
|
|
@@ -95,7 +94,7 @@ int eeh_phb_pe_create(struct pci_controller *phb)
|
|
|
* hierarchy tree is composed of PHB PEs. The function is used
|
|
|
* to retrieve the corresponding PHB PE according to the given PHB.
|
|
|
*/
|
|
|
-static struct eeh_pe *eeh_phb_pe_get(struct pci_controller *phb)
|
|
|
+struct eeh_pe *eeh_phb_pe_get(struct pci_controller *phb)
|
|
|
{
|
|
|
struct eeh_pe *pe;
|
|
|
|
|
@@ -185,21 +184,15 @@ void *eeh_pe_dev_traverse(struct eeh_pe *root,
|
|
|
return NULL;
|
|
|
}
|
|
|
|
|
|
- eeh_lock();
|
|
|
-
|
|
|
/* Traverse root PE */
|
|
|
for (pe = root; pe; pe = eeh_pe_next(pe, root)) {
|
|
|
eeh_pe_for_each_dev(pe, edev) {
|
|
|
ret = fn(edev, flag);
|
|
|
- if (ret) {
|
|
|
- eeh_unlock();
|
|
|
+ if (ret)
|
|
|
return ret;
|
|
|
- }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- eeh_unlock();
|
|
|
-
|
|
|
return NULL;
|
|
|
}
|
|
|
|
|
@@ -228,7 +221,7 @@ static void *__eeh_pe_get(void *data, void *flag)
|
|
|
return pe;
|
|
|
|
|
|
/* Try BDF address */
|
|
|
- if (edev->pe_config_addr &&
|
|
|
+ if (edev->config_addr &&
|
|
|
(edev->config_addr == pe->config_addr))
|
|
|
return pe;
|
|
|
|
|
@@ -246,7 +239,7 @@ static void *__eeh_pe_get(void *data, void *flag)
|
|
|
* which is composed of PCI bus/device/function number, or unified
|
|
|
* PE address.
|
|
|
*/
|
|
|
-static struct eeh_pe *eeh_pe_get(struct eeh_dev *edev)
|
|
|
+struct eeh_pe *eeh_pe_get(struct eeh_dev *edev)
|
|
|
{
|
|
|
struct eeh_pe *root = eeh_phb_pe_get(edev->phb);
|
|
|
struct eeh_pe *pe;
|
|
@@ -305,8 +298,6 @@ int eeh_add_to_parent_pe(struct eeh_dev *edev)
|
|
|
{
|
|
|
struct eeh_pe *pe, *parent;
|
|
|
|
|
|
- eeh_lock();
|
|
|
-
|
|
|
/*
|
|
|
* Search the PE has been existing or not according
|
|
|
* to the PE address. If that has been existing, the
|
|
@@ -316,7 +307,6 @@ int eeh_add_to_parent_pe(struct eeh_dev *edev)
|
|
|
pe = eeh_pe_get(edev);
|
|
|
if (pe && !(pe->type & EEH_PE_INVALID)) {
|
|
|
if (!edev->pe_config_addr) {
|
|
|
- eeh_unlock();
|
|
|
pr_err("%s: PE with addr 0x%x already exists\n",
|
|
|
__func__, edev->config_addr);
|
|
|
return -EEXIST;
|
|
@@ -328,7 +318,6 @@ int eeh_add_to_parent_pe(struct eeh_dev *edev)
|
|
|
|
|
|
/* Put the edev to PE */
|
|
|
list_add_tail(&edev->list, &pe->edevs);
|
|
|
- eeh_unlock();
|
|
|
pr_debug("EEH: Add %s to Bus PE#%x\n",
|
|
|
edev->dn->full_name, pe->addr);
|
|
|
|
|
@@ -347,7 +336,6 @@ int eeh_add_to_parent_pe(struct eeh_dev *edev)
|
|
|
parent->type &= ~EEH_PE_INVALID;
|
|
|
parent = parent->parent;
|
|
|
}
|
|
|
- eeh_unlock();
|
|
|
pr_debug("EEH: Add %s to Device PE#%x, Parent PE#%x\n",
|
|
|
edev->dn->full_name, pe->addr, pe->parent->addr);
|
|
|
|
|
@@ -357,13 +345,23 @@ int eeh_add_to_parent_pe(struct eeh_dev *edev)
|
|
|
/* Create a new EEH PE */
|
|
|
pe = eeh_pe_alloc(edev->phb, EEH_PE_DEVICE);
|
|
|
if (!pe) {
|
|
|
- eeh_unlock();
|
|
|
pr_err("%s: out of memory!\n", __func__);
|
|
|
return -ENOMEM;
|
|
|
}
|
|
|
pe->addr = edev->pe_config_addr;
|
|
|
pe->config_addr = edev->config_addr;
|
|
|
|
|
|
+ /*
|
|
|
+ * While doing PE reset, we probably hot-reset the
|
|
|
+ * upstream bridge. However, the PCI devices including
|
|
|
+ * the associated EEH devices might be removed when EEH
|
|
|
+ * core is doing recovery. So that won't safe to retrieve
|
|
|
+ * the bridge through downstream EEH device. We have to
|
|
|
+ * trace the parent PCI bus, then the upstream bridge.
|
|
|
+ */
|
|
|
+ if (eeh_probe_mode_dev())
|
|
|
+ pe->bus = eeh_dev_to_pci_dev(edev)->bus;
|
|
|
+
|
|
|
/*
|
|
|
* Put the new EEH PE into hierarchy tree. If the parent
|
|
|
* can't be found, the newly created PE will be attached
|
|
@@ -374,7 +372,6 @@ int eeh_add_to_parent_pe(struct eeh_dev *edev)
|
|
|
if (!parent) {
|
|
|
parent = eeh_phb_pe_get(edev->phb);
|
|
|
if (!parent) {
|
|
|
- eeh_unlock();
|
|
|
pr_err("%s: No PHB PE is found (PHB Domain=%d)\n",
|
|
|
__func__, edev->phb->global_number);
|
|
|
edev->pe = NULL;
|
|
@@ -391,7 +388,6 @@ int eeh_add_to_parent_pe(struct eeh_dev *edev)
|
|
|
list_add_tail(&pe->child, &parent->child_list);
|
|
|
list_add_tail(&edev->list, &pe->edevs);
|
|
|
edev->pe = pe;
|
|
|
- eeh_unlock();
|
|
|
pr_debug("EEH: Add %s to Device PE#%x, Parent PE#%x\n",
|
|
|
edev->dn->full_name, pe->addr, pe->parent->addr);
|
|
|
|
|
@@ -419,8 +415,6 @@ int eeh_rmv_from_parent_pe(struct eeh_dev *edev, int purge_pe)
|
|
|
return -EEXIST;
|
|
|
}
|
|
|
|
|
|
- eeh_lock();
|
|
|
-
|
|
|
/* Remove the EEH device */
|
|
|
pe = edev->pe;
|
|
|
edev->pe = NULL;
|
|
@@ -465,11 +459,36 @@ int eeh_rmv_from_parent_pe(struct eeh_dev *edev, int purge_pe)
|
|
|
pe = parent;
|
|
|
}
|
|
|
|
|
|
- eeh_unlock();
|
|
|
-
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
+/**
|
|
|
+ * eeh_pe_update_time_stamp - Update PE's frozen time stamp
|
|
|
+ * @pe: EEH PE
|
|
|
+ *
|
|
|
+ * We have time stamp for each PE to trace its time of getting
|
|
|
+ * frozen in last hour. The function should be called to update
|
|
|
+ * the time stamp on first error of the specific PE. On the other
|
|
|
+ * handle, we needn't account for errors happened in last hour.
|
|
|
+ */
|
|
|
+void eeh_pe_update_time_stamp(struct eeh_pe *pe)
|
|
|
+{
|
|
|
+ struct timeval tstamp;
|
|
|
+
|
|
|
+ if (!pe) return;
|
|
|
+
|
|
|
+ if (pe->freeze_count <= 0) {
|
|
|
+ pe->freeze_count = 0;
|
|
|
+ do_gettimeofday(&pe->tstamp);
|
|
|
+ } else {
|
|
|
+ do_gettimeofday(&tstamp);
|
|
|
+ if (tstamp.tv_sec - pe->tstamp.tv_sec > 3600) {
|
|
|
+ pe->tstamp = tstamp;
|
|
|
+ pe->freeze_count = 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
/**
|
|
|
* __eeh_pe_state_mark - Mark the state for the PE
|
|
|
* @data: EEH PE
|
|
@@ -512,9 +531,7 @@ static void *__eeh_pe_state_mark(void *data, void *flag)
|
|
|
*/
|
|
|
void eeh_pe_state_mark(struct eeh_pe *pe, int state)
|
|
|
{
|
|
|
- eeh_lock();
|
|
|
eeh_pe_traverse(pe, __eeh_pe_state_mark, &state);
|
|
|
- eeh_unlock();
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -548,35 +565,135 @@ static void *__eeh_pe_state_clear(void *data, void *flag)
|
|
|
*/
|
|
|
void eeh_pe_state_clear(struct eeh_pe *pe, int state)
|
|
|
{
|
|
|
- eeh_lock();
|
|
|
eeh_pe_traverse(pe, __eeh_pe_state_clear, &state);
|
|
|
- eeh_unlock();
|
|
|
}
|
|
|
|
|
|
-/**
|
|
|
- * eeh_restore_one_device_bars - Restore the Base Address Registers for one device
|
|
|
- * @data: EEH device
|
|
|
- * @flag: Unused
|
|
|
+/*
|
|
|
+ * Some PCI bridges (e.g. PLX bridges) have primary/secondary
|
|
|
+ * buses assigned explicitly by firmware, and we probably have
|
|
|
+ * lost that after reset. So we have to delay the check until
|
|
|
+ * the PCI-CFG registers have been restored for the parent
|
|
|
+ * bridge.
|
|
|
*
|
|
|
- * Loads the PCI configuration space base address registers,
|
|
|
- * the expansion ROM base address, the latency timer, and etc.
|
|
|
- * from the saved values in the device node.
|
|
|
+ * Don't use normal PCI-CFG accessors, which probably has been
|
|
|
+ * blocked on normal path during the stage. So we need utilize
|
|
|
+ * eeh operations, which is always permitted.
|
|
|
*/
|
|
|
-static void *eeh_restore_one_device_bars(void *data, void *flag)
|
|
|
+static void eeh_bridge_check_link(struct pci_dev *pdev,
|
|
|
+ struct device_node *dn)
|
|
|
+{
|
|
|
+ int cap;
|
|
|
+ uint32_t val;
|
|
|
+ int timeout = 0;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * We only check root port and downstream ports of
|
|
|
+ * PCIe switches
|
|
|
+ */
|
|
|
+ if (!pci_is_pcie(pdev) ||
|
|
|
+ (pci_pcie_type(pdev) != PCI_EXP_TYPE_ROOT_PORT &&
|
|
|
+ pci_pcie_type(pdev) != PCI_EXP_TYPE_DOWNSTREAM))
|
|
|
+ return;
|
|
|
+
|
|
|
+ pr_debug("%s: Check PCIe link for %s ...\n",
|
|
|
+ __func__, pci_name(pdev));
|
|
|
+
|
|
|
+ /* Check slot status */
|
|
|
+ cap = pdev->pcie_cap;
|
|
|
+ eeh_ops->read_config(dn, cap + PCI_EXP_SLTSTA, 2, &val);
|
|
|
+ if (!(val & PCI_EXP_SLTSTA_PDS)) {
|
|
|
+ pr_debug(" No card in the slot (0x%04x) !\n", val);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Check power status if we have the capability */
|
|
|
+ eeh_ops->read_config(dn, cap + PCI_EXP_SLTCAP, 2, &val);
|
|
|
+ if (val & PCI_EXP_SLTCAP_PCP) {
|
|
|
+ eeh_ops->read_config(dn, cap + PCI_EXP_SLTCTL, 2, &val);
|
|
|
+ if (val & PCI_EXP_SLTCTL_PCC) {
|
|
|
+ pr_debug(" In power-off state, power it on ...\n");
|
|
|
+ val &= ~(PCI_EXP_SLTCTL_PCC | PCI_EXP_SLTCTL_PIC);
|
|
|
+ val |= (0x0100 & PCI_EXP_SLTCTL_PIC);
|
|
|
+ eeh_ops->write_config(dn, cap + PCI_EXP_SLTCTL, 2, val);
|
|
|
+ msleep(2 * 1000);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Enable link */
|
|
|
+ eeh_ops->read_config(dn, cap + PCI_EXP_LNKCTL, 2, &val);
|
|
|
+ val &= ~PCI_EXP_LNKCTL_LD;
|
|
|
+ eeh_ops->write_config(dn, cap + PCI_EXP_LNKCTL, 2, val);
|
|
|
+
|
|
|
+ /* Check link */
|
|
|
+ eeh_ops->read_config(dn, cap + PCI_EXP_LNKCAP, 4, &val);
|
|
|
+ if (!(val & PCI_EXP_LNKCAP_DLLLARC)) {
|
|
|
+ pr_debug(" No link reporting capability (0x%08x) \n", val);
|
|
|
+ msleep(1000);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Wait the link is up until timeout (5s) */
|
|
|
+ timeout = 0;
|
|
|
+ while (timeout < 5000) {
|
|
|
+ msleep(20);
|
|
|
+ timeout += 20;
|
|
|
+
|
|
|
+ eeh_ops->read_config(dn, cap + PCI_EXP_LNKSTA, 2, &val);
|
|
|
+ if (val & PCI_EXP_LNKSTA_DLLLA)
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (val & PCI_EXP_LNKSTA_DLLLA)
|
|
|
+ pr_debug(" Link up (%s)\n",
|
|
|
+ (val & PCI_EXP_LNKSTA_CLS_2_5GB) ? "2.5GB" : "5GB");
|
|
|
+ else
|
|
|
+ pr_debug(" Link not ready (0x%04x)\n", val);
|
|
|
+}
|
|
|
+
|
|
|
+#define BYTE_SWAP(OFF) (8*((OFF)/4)+3-(OFF))
|
|
|
+#define SAVED_BYTE(OFF) (((u8 *)(edev->config_space))[BYTE_SWAP(OFF)])
|
|
|
+
|
|
|
+static void eeh_restore_bridge_bars(struct pci_dev *pdev,
|
|
|
+ struct eeh_dev *edev,
|
|
|
+ struct device_node *dn)
|
|
|
+{
|
|
|
+ int i;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Device BARs: 0x10 - 0x18
|
|
|
+ * Bus numbers and windows: 0x18 - 0x30
|
|
|
+ */
|
|
|
+ for (i = 4; i < 13; i++)
|
|
|
+ eeh_ops->write_config(dn, i*4, 4, edev->config_space[i]);
|
|
|
+ /* Rom: 0x38 */
|
|
|
+ eeh_ops->write_config(dn, 14*4, 4, edev->config_space[14]);
|
|
|
+
|
|
|
+ /* Cache line & Latency timer: 0xC 0xD */
|
|
|
+ eeh_ops->write_config(dn, PCI_CACHE_LINE_SIZE, 1,
|
|
|
+ SAVED_BYTE(PCI_CACHE_LINE_SIZE));
|
|
|
+ eeh_ops->write_config(dn, PCI_LATENCY_TIMER, 1,
|
|
|
+ SAVED_BYTE(PCI_LATENCY_TIMER));
|
|
|
+ /* Max latency, min grant, interrupt ping and line: 0x3C */
|
|
|
+ eeh_ops->write_config(dn, 15*4, 4, edev->config_space[15]);
|
|
|
+
|
|
|
+ /* PCI Command: 0x4 */
|
|
|
+ eeh_ops->write_config(dn, PCI_COMMAND, 4, edev->config_space[1]);
|
|
|
+
|
|
|
+ /* Check the PCIe link is ready */
|
|
|
+ eeh_bridge_check_link(pdev, dn);
|
|
|
+}
|
|
|
+
|
|
|
+static void eeh_restore_device_bars(struct eeh_dev *edev,
|
|
|
+ struct device_node *dn)
|
|
|
{
|
|
|
int i;
|
|
|
u32 cmd;
|
|
|
- struct eeh_dev *edev = (struct eeh_dev *)data;
|
|
|
- struct device_node *dn = eeh_dev_to_of_node(edev);
|
|
|
|
|
|
for (i = 4; i < 10; i++)
|
|
|
eeh_ops->write_config(dn, i*4, 4, edev->config_space[i]);
|
|
|
/* 12 == Expansion ROM Address */
|
|
|
eeh_ops->write_config(dn, 12*4, 4, edev->config_space[12]);
|
|
|
|
|
|
-#define BYTE_SWAP(OFF) (8*((OFF)/4)+3-(OFF))
|
|
|
-#define SAVED_BYTE(OFF) (((u8 *)(edev->config_space))[BYTE_SWAP(OFF)])
|
|
|
-
|
|
|
eeh_ops->write_config(dn, PCI_CACHE_LINE_SIZE, 1,
|
|
|
SAVED_BYTE(PCI_CACHE_LINE_SIZE));
|
|
|
eeh_ops->write_config(dn, PCI_LATENCY_TIMER, 1,
|
|
@@ -599,6 +716,34 @@ static void *eeh_restore_one_device_bars(void *data, void *flag)
|
|
|
else
|
|
|
cmd &= ~PCI_COMMAND_SERR;
|
|
|
eeh_ops->write_config(dn, PCI_COMMAND, 4, cmd);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * eeh_restore_one_device_bars - Restore the Base Address Registers for one device
|
|
|
+ * @data: EEH device
|
|
|
+ * @flag: Unused
|
|
|
+ *
|
|
|
+ * Loads the PCI configuration space base address registers,
|
|
|
+ * the expansion ROM base address, the latency timer, and etc.
|
|
|
+ * from the saved values in the device node.
|
|
|
+ */
|
|
|
+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 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)
|
|
|
+ eeh_restore_bridge_bars(pdev, edev, dn);
|
|
|
+ else
|
|
|
+ eeh_restore_device_bars(edev, dn);
|
|
|
|
|
|
return NULL;
|
|
|
}
|
|
@@ -635,19 +780,21 @@ struct pci_bus *eeh_pe_bus_get(struct eeh_pe *pe)
|
|
|
struct eeh_dev *edev;
|
|
|
struct pci_dev *pdev;
|
|
|
|
|
|
- eeh_lock();
|
|
|
-
|
|
|
if (pe->type & EEH_PE_PHB) {
|
|
|
bus = pe->phb->bus;
|
|
|
} else if (pe->type & EEH_PE_BUS ||
|
|
|
pe->type & EEH_PE_DEVICE) {
|
|
|
+ if (pe->bus) {
|
|
|
+ bus = pe->bus;
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
edev = list_first_entry(&pe->edevs, struct eeh_dev, list);
|
|
|
pdev = eeh_dev_to_pci_dev(edev);
|
|
|
if (pdev)
|
|
|
bus = pdev->bus;
|
|
|
}
|
|
|
|
|
|
- eeh_unlock();
|
|
|
-
|
|
|
+out:
|
|
|
return bus;
|
|
|
}
|