[PATCH V3 3/5] PCI: Save and restore the Device 3 Control register
Vidya Sagar <[email protected]>
| Newsgroups | org.kernel.vger.linux-pci,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
The Device 3 Extended Capability carries the 14-Bit Tag Requester Enable bit, which platform firmware may have programmed before the PCI core takes over. The core neither saves nor restores DEV3_CTL, so its contents are lost on every path that goes through pci_save_state() and pci_restore_state(), e.g. a Secondary Bus Reset, a slot reset or a D3cold resume. Restoring the saved value verbatim is not correct either. 14-Bit Tag Requester Enable is only meaningful while the link operates in Flit Mode; in Non-Flit Mode the upper tag bits are not transmitted on the wire, so a requester that still has it set emits TLPs whose completions it can no longer match. That shows up as a Completion Timeout together with an Unexpected Completion on the very first transaction after the reset. A link that comes back in Non-Flit Mode must therefore come back with 14-Bit Tag Requester Enable cleared. The completer side needs no such handling: a completer reflects the Tag field of the request it answers, so the spec defines no completer enable to fix up. Allocate a save buffer in pci_dev3_init() for every device that exposes the Device 3 Extended Capability and save DEV3_CTL from pci_save_state(). DEV3_STA needs no save buffer of its own because all of its fields (Initial Link Width, Segment Captured and Remote L0p Supported) are read-only status reported by hardware. On restore, sanitize the saved value first: if the device advertises 14-Bit Tag Requester support but Flit Mode is no longer active, as determined from the live LNKSTA2.Flit_Mode and DEV3_STA.Segment Captured, drop PCI_DEV3_CTL_14BIT_TAG_REQ_EN before writing DEV3_CTL back and refresh dev->fm_enabled and bus->flit_mode to match what the hardware now reports. Devices without 14-Bit Tag Requester support, and every other DEV3_CTL bit, are restored unchanged. Signed-off-by: Vidya Sagar <[email protected]> --- V3: * Modified to preserve the original DEV3_CTL value V2: * New patch as a result of splitting the V1 monolithic patch drivers/pci/pci.c | 84 +++++++++++++++++++++++++++++++++++++++++++++ drivers/pci/probe.c | 12 +++++++ 2 files changed, 96 insertions(+) diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index ff6d5d059b21..c59329365ad6 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -1703,6 +1703,85 @@ static void pci_restore_pcie_state(struct pci_dev *dev) pcie_capability_write_word(dev, PCI_EXP_SLTCTL2, cap[i++]); } +static int pci_save_dev3_state(struct pci_dev *dev) +{ + struct pci_cap_saved_state *save_state; + u32 *cap; + int pos; + + pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_DEV3); + if (!pos) + return 0; + + save_state = pci_find_saved_ext_cap(dev, PCI_EXT_CAP_ID_DEV3); + if (!save_state) + return -ENOMEM; + + cap = (u32 *)&save_state->cap.data[0]; + pci_read_config_dword(dev, pos + PCI_DEV3_CTL, &cap[0]); + + return 0; +} + +static void pci_restore_dev3_state(struct pci_dev *dev) +{ + struct pci_cap_saved_state *save_state; + u32 *cap, val, dev3_cap, dev3_sta; + u16 lnksta2 = 0; + bool flit_now; + int pos; + + pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_DEV3); + if (!pos) + return; + + save_state = pci_find_saved_ext_cap(dev, PCI_EXT_CAP_ID_DEV3); + if (!save_state) + return; + + cap = (u32 *)&save_state->cap.data[0]; + val = cap[0]; + + /* + * DEV3_CTL.14-Bit Tag Requester Enable is only meaningful in flit + * mode. On devices that advertise 14-Bit Tag Requester support, + * sanitize the saved value before writing it back, so that callers + * that issue further TLPs through this device after restore see a + * coherent enable state. Every other bit of DEV3_CTL (DMWr + * Requester Enable, DMWr Egress Blocking, L0p Enable, Target Link + * Width and any future addition) is written back unchanged. + * + * Only the value written to hardware is adjusted. The save buffer + * keeps what was saved, so the decision is taken afresh on every + * restore and the originally programmed value is not lost. + */ + pci_read_config_dword(dev, pos + PCI_DEV3_CAP, &dev3_cap); + if (dev3_cap & PCI_DEV3_CAP_14BIT_TAG_REQ) { + /* + * Check both LNKSTA2.Flit_Mode (link-level) and + * DEV3_STA.Segment Captured (end-to-end); both must be + * active for 14-bit tags. Refresh bus->flit_mode and + * dev->fm_enabled in lock-step. + */ + pci_read_config_dword(dev, pos + PCI_DEV3_STA, &dev3_sta); + dev->fm_enabled = !!(dev3_sta & PCI_DEV3_STA_SEGMENT); + + pcie_capability_read_word(dev, PCI_EXP_LNKSTA2, &lnksta2); + flit_now = !!(lnksta2 & PCI_EXP_LNKSTA2_FLIT); + if (dev->bus) + dev->bus->flit_mode = flit_now; + + if ((!dev->fm_enabled || !flit_now) && + (val & PCI_DEV3_CTL_14BIT_TAG_REQ_EN)) { + val &= ~PCI_DEV3_CTL_14BIT_TAG_REQ_EN; + pci_info(dev, "clearing 14-Bit Tag Requester Enable: flit mode no longer active (LNKSTA2=%#06x, DEV3_STA=%#010x)\n", + lnksta2, dev3_sta); + } + } + + pci_write_config_dword(dev, pos + PCI_DEV3_CTL, val); +} + static int pci_save_pcix_state(struct pci_dev *dev) { int pos; @@ -1759,6 +1838,10 @@ int pci_save_state(struct pci_dev *dev) if (i != 0) return i; + i = pci_save_dev3_state(dev); + if (i != 0) + return i; + i = pci_save_pcix_state(dev); if (i != 0) return i; @@ -1826,6 +1909,7 @@ static void pci_restore_config_space(struct pci_dev *pdev) void pci_restore_state(struct pci_dev *dev) { pci_restore_pcie_state(dev); + pci_restore_dev3_state(dev); pci_restore_pasid_state(dev); pci_restore_pri_state(dev); pci_restore_ats_state(dev); diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index 60dd1efe9abb..810114029ee0 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -2334,11 +2334,23 @@ static void pci_dev3_init(struct pci_dev *pdev) { u16 cap = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_DEV3); u32 val = 0; + int err; if (!cap) return; pci_read_config_dword(pdev, cap + PCI_DEV3_STA, &val); pdev->fm_enabled = !!(val & PCI_DEV3_STA_SEGMENT); + + /* + * Save buffer for DEV3_CTL only. Every field in DEV3_STA is + * read-only status reported by hardware, so there is nothing there + * to restore. + */ + err = pci_add_ext_cap_save_buffer(pdev, PCI_EXT_CAP_ID_DEV3, + sizeof(u32)); + if (err) + pci_warn(pdev, + "unable to preallocate Device 3 save buffer\n"); } /** -- 2.43.0