Index: sys/amd64/vmm/io/ppt.c =================================================================== --- sys/amd64/vmm/io/ppt.c +++ sys/amd64/vmm/io/ppt.c @@ -356,25 +356,12 @@ static void ppt_pci_reset(device_t dev) { - int ps; if (pcie_flr(dev, - max(pcie_get_max_completion_timeout(dev) / 1000, 10), - true)) + max(pcie_get_max_completion_timeout(dev) / 1000, 10), true)) return; - /* - * If FLR fails, attempt a power-management reset by cycling - * the device in/out of D3 state. - * PCI spec says we can only go into D3 state from D0 state. - * Transition from D[12] into D0 before going to D3 state. - */ - ps = pci_get_powerstate(dev); - if (ps != PCI_POWERSTATE_D0 && ps != PCI_POWERSTATE_D3) - pci_set_powerstate(dev, PCI_POWERSTATE_D0); - if (pci_get_powerstate(dev) != PCI_POWERSTATE_D3) - pci_set_powerstate(dev, PCI_POWERSTATE_D3); - pci_set_powerstate(dev, ps); + pci_hard_reset(dev); } int Index: sys/dev/pci/pci.c =================================================================== --- sys/dev/pci/pci.c +++ sys/dev/pci/pci.c @@ -6365,6 +6365,153 @@ return (true); } +/* + * Try link drop and retrain of the downstream port of upstream + * switch, for PCIe. According to the PCIe 3.0 spec 6.6.1, this must + * cause Conventional Hot reset of the device in the slot. + * Alternative, for PCIe, could be the secondary bus reset initiatied + * on the upstream switch PCIR_BRIDGECTL_1, bit 6. + */ +static int +pcie_link_reset(device_t port) +{ + struct pci_devinfo *pdinfo; + devclass_t pci_class; + uint16_t v; + + pci_class = devclass_find("pci"); + if (device_get_devclass(port) != pci_class) + return (EINVAL); + pdinfo = device_get_ivars(port); + if (pdinfo->cfg.pcie.pcie_location == 0) + return (EINVAL); + if (pdinfo->cfg.pcie.pcie_type != PCIEM_TYPE_DOWNSTREAM_PORT && + pdinfo->cfg.pcie.pcie_type != PCIEM_TYPE_ROOT_PORT) + return (ENOENT); + + v = pci_read_config(port, pdinfo->cfg.pcie.pcie_location + + PCIER_LINK_CTL, 2); + v |= PCIEM_LINK_CTL_LINK_DIS; + pci_write_config(port, pdinfo->cfg.pcie.pcie_location + + PCIER_LINK_CTL, v, 2); + pause("pcier1", hz / 50); /* 20 ms */ + v &= ~PCIEM_LINK_CTL_LINK_DIS; + v |= PCIEM_LINK_CTL_RETRAIN_LINK; + pci_write_config(port, pdinfo->cfg.pcie.pcie_location + + PCIER_LINK_CTL, v, 2); + pause("pcier2", hz / 10); /* 100 ms */ + return (0); +} + +/* + * Issue Conventional Hot reset, and if not possible (e.g. device is + * not PCIe or link reset failed), power reset. + */ +void +pci_hard_reset(device_t dev) +{ + device_t pci, port; + devclass_t pci_class; + struct pci_devinfo *dinfo; + int error, ps; + + pci_class = devclass_find("pci"); + if (device_get_devclass(dev) != pci_class) + return; + dinfo = device_get_ivars(dev); + pci_cfg_save(dev, dinfo, 0); + + error = ENOENT; + if (dinfo->cfg.pcie.pcie_location != 0) { + pci = device_get_parent(dev); + if (device_get_devclass(pci) != pci_class) + error = EINVAL; + else { + port = device_get_parent(pci); + error = pcie_link_reset(port); + } + } + + /* If not PCIe or link method failed, try power reset */ + if (error != 0) { + ps = pci_get_powerstate(dev); + if (ps != PCI_POWERSTATE_D0 && ps != PCI_POWERSTATE_D3) + pci_set_powerstate(dev, PCI_POWERSTATE_D0); + pci_set_powerstate(dev, PCI_POWERSTATE_D3); + pci_set_powerstate(dev, ps); + error = ps == pci_get_powerstate(dev) ? 0 : EIO; + } + + if (error == 0) + pci_cfg_restore(dev, dinfo); +} + +/* + * Issue Conventional Hot reset to the device, while detaching drivers + * from the device itself and all sibling PCIe functions in the same + * slot. PCI(e) configuration is saved around the reset. + * + * Typical Conventional reset affects all functions of multi-function + * device. More, drivers accessing functions during the reset get + * very confused, and could corrupt the hardware state when it brought + * up from the cold. + */ +int +pcie_down_port_link_reset(device_t dev) +{ + device_t dev1, bus, pcib, *children; + devclass_t pci_class; + struct pci_devinfo *pdinfo; + int error, i, nchildren; + + error = EINVAL; + children = NULL; + nchildren = 0; + + mtx_lock(&Giant); + pci_class = devclass_find("pci"); + bus = device_get_parent(dev); + if (bus == NULL) + goto done; + pcib = device_get_parent(bus); + if (pcib == NULL || + device_get_devclass(device_get_parent(pcib)) != pci_class) + goto done; + + pdinfo = device_get_ivars(pcib); + if (pdinfo->cfg.pcie.pcie_location == 0) + return (EINVAL); + if (pdinfo->cfg.pcie.pcie_type != PCIEM_TYPE_DOWNSTREAM_PORT && + pdinfo->cfg.pcie.pcie_type != PCIEM_TYPE_ROOT_PORT) + goto done; + + error = device_get_children(bus, &children, &nchildren); + if (error != 0) + goto done; + for (i = 0; i < nchildren; i++) { + dev1 = children[i]; + error = device_quiesce(dev1); + if (error == 0) + error = device_detach(dev1); + pci_save_state(dev1); + } + + pcie_link_reset(pcib); + + for (i = 0; i < nchildren; i++) { + dev1 = children[i]; + pci_restore_state(dev1); + if (device_get_state(dev1) != DS_ATTACHED) + device_probe_and_attach(dev1); + } + error = 0; + +done: + mtx_unlock(&Giant); + free(children, M_TEMP); + return (error); +} + const struct pci_device_table * pci_match_device(device_t child, const struct pci_device_table *id, size_t nelt) { Index: sys/dev/pci/pcivar.h =================================================================== --- sys/dev/pci/pcivar.h +++ sys/dev/pci/pcivar.h @@ -671,6 +671,7 @@ device_t pci_find_pcie_root_port(device_t dev); int pci_get_max_payload(device_t dev); int pci_get_max_read_req(device_t dev); +void pci_hard_reset(device_t dev); void pci_restore_state(device_t dev); void pci_save_state(device_t dev); int pci_set_max_read_req(device_t dev, int size); @@ -679,6 +680,7 @@ uint32_t pcie_adjust_config(device_t dev, int reg, uint32_t mask, uint32_t value, int width); bool pcie_flr(device_t dev, u_int max_delay, bool force); +int pcie_down_port_link_reset(device_t dev); int pcie_get_max_completion_timeout(device_t dev); bool pcie_wait_for_pending_transactions(device_t dev, u_int max_delay);