powerpc/powernv: Fundamental reset on PLX ports

The patch intends to support fundamental reset on PLX downstream
ports. If the PCI device matches any one of the internal table,
which includes PLX vendor ID, bridge device ID, register offset
for fundamental reset and bit, fundamental reset will be done
accordingly. Otherwise, it will fail back to hot reset.

Additional flag (EEH_DEV_FRESET) is introduced to record the last
reset type on the PCI bridge.

Signed-off-by: Gavin Shan <gwshan@linux.vnet.ibm.com>
Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
This commit is contained in:
Gavin Shan 2014-04-24 18:00:27 +10:00 committed by Benjamin Herrenschmidt
parent 35845a7826
commit b2b5efcf20
2 changed files with 97 additions and 18 deletions

View file

@ -109,6 +109,7 @@ struct eeh_pe {
#define EEH_DEV_NO_HANDLER (1 << 8) /* No error handler */
#define EEH_DEV_SYSFS (1 << 9) /* Sysfs created */
#define EEH_DEV_REMOVED (1 << 10) /* Removed permanently */
#define EEH_DEV_FRESET (1 << 11) /* Fundamental reset */
struct eeh_dev {
int mode; /* EEH mode */

View file

@ -477,49 +477,127 @@ static int ioda_eeh_root_reset(struct pci_controller *hose, int option)
return 0;
}
static bool ioda_eeh_is_plx_dnport(struct pci_dev *dev, int *reg,
int *mask, int *len)
{
unsigned short *pid;
unsigned short ids[] = {
0x10b5, 0x8748, 0x0080, 0x0400, /* PLX#8748 */
0x0000, 0x0000, 0x0000, 0x0000, /* End flag */
};
if (!pci_is_pcie(dev))
return false;
if (pci_pcie_type(dev) != PCI_EXP_TYPE_DOWNSTREAM)
return false;
pid = &ids[0];
while (!reg) {
if (pid[0] == 0x0)
break;
if (dev->vendor == pid[0] &&
dev->device == pid[1]) {
*reg = pid[2];
*mask = pid[3];
*len = 2;
return true;
}
}
*reg = PCI_BRIDGE_CONTROL;
*mask = PCI_BRIDGE_CTL_BUS_RESET;
*len = 2;
return false;
}
static int ioda_eeh_bridge_reset(struct pci_dev *dev, int option)
{
struct device_node *dn = pci_device_to_OF_node(dev);
struct eeh_dev *edev = of_node_to_eeh_dev(dn);
int aer = edev ? edev->aer_cap : 0;
u32 ctrl;
int reg, mask, val, len;
bool is_plx_dnport;
pr_debug("%s: Reset PCI bus %04x:%02x with option %d\n",
__func__, pci_domain_nr(dev->bus),
dev->bus->number, option);
is_plx_dnport = ioda_eeh_is_plx_dnport(dev, &reg, &mask, &len);
if (option == EEH_RESET_FUNDAMENTAL)
if (!is_plx_dnport || !edev)
option = EEH_RESET_HOT;
if (option == EEH_RESET_HOT) {
reg = PCI_BRIDGE_CONTROL;
mask = PCI_BRIDGE_CTL_BUS_RESET;
len = 2;
}
if (option == EEH_RESET_DEACTIVATE) {
if (!is_plx_dnport || !edev ||
!(edev->mode & EEH_DEV_FRESET)) {
reg = PCI_BRIDGE_CONTROL;
mask = PCI_BRIDGE_CTL_BUS_RESET;
len = 2;
}
}
switch (option) {
case EEH_RESET_FUNDAMENTAL:
edev->mode |= EEH_DEV_FRESET;
/* Fall through */
case EEH_RESET_HOT:
/* Don't report linkDown event */
if (aer) {
eeh_ops->read_config(dn, aer + PCI_ERR_UNCOR_MASK,
4, &ctrl);
ctrl |= PCI_ERR_UNC_SURPDN;
eeh_ops->write_config(dn, aer + PCI_ERR_UNCOR_MASK,
4, ctrl);
}
/* Mask receiver error */
eeh_ops->read_config(dn, aer + PCI_ERR_COR_MASK,
4, &val);
val |= PCI_ERR_COR_RCVR;
eeh_ops->write_config(dn, aer + PCI_ERR_COR_MASK,
4, val);
eeh_ops->read_config(dn, PCI_BRIDGE_CONTROL, 2, &ctrl);
ctrl |= PCI_BRIDGE_CTL_BUS_RESET;
eeh_ops->write_config(dn, PCI_BRIDGE_CONTROL, 2, ctrl);
/* Mask linkDown */
eeh_ops->read_config(dn, aer + PCI_ERR_UNCOR_MASK,
4, &val);
val |= PCI_ERR_UNC_SURPDN;
eeh_ops->write_config(dn, aer + PCI_ERR_UNCOR_MASK,
4, val);
}
eeh_ops->read_config(dn, reg, len, &val);
val |= mask;
eeh_ops->write_config(dn, reg, len, val);
msleep(EEH_PE_RST_HOLD_TIME);
break;
case EEH_RESET_DEACTIVATE:
eeh_ops->read_config(dn, PCI_BRIDGE_CONTROL, 2, &ctrl);
ctrl &= ~PCI_BRIDGE_CTL_BUS_RESET;
eeh_ops->write_config(dn, PCI_BRIDGE_CONTROL, 2, ctrl);
eeh_ops->read_config(dn, reg, len, &val);
val &= ~mask;
eeh_ops->write_config(dn, reg, len, val);
msleep(EEH_PE_RST_SETTLE_TIME);
/* Continue reporting linkDown event */
if (edev)
edev->mode &= ~EEH_DEV_FRESET;
if (aer) {
/* Clear receive error and enable it */
eeh_ops->write_config(dn, aer + PCI_ERR_COR_STATUS,
4, PCI_ERR_COR_RCVR);
eeh_ops->read_config(dn, aer + PCI_ERR_COR_MASK,
4, &val);
val &= ~PCI_ERR_COR_RCVR;
eeh_ops->write_config(dn, aer + PCI_ERR_COR_MASK,
4, val);
/* Clear linkDown and enable it */
eeh_ops->write_config(dn, aer + PCI_ERR_UNCOR_STATUS,
4, PCI_ERR_UNC_SURPDN);
eeh_ops->read_config(dn, aer + PCI_ERR_UNCOR_MASK,
4, &ctrl);
ctrl &= ~PCI_ERR_UNC_SURPDN;
4, &val);
val &= ~PCI_ERR_UNC_SURPDN;
eeh_ops->write_config(dn, aer + PCI_ERR_UNCOR_MASK,
4, ctrl);
4, val);
}
break;