|
| 1 | +// SPDX-License-Identifier: GPL-2.0 |
| 2 | +/* |
| 3 | + * TPH (TLP Processing Hints) support |
| 4 | + * |
| 5 | + * Copyright (C) 2024 Advanced Micro Devices, Inc. |
| 6 | + * Eric Van Tassell <Eric.VanTassell@amd.com> |
| 7 | + * Wei Huang <wei.huang2@amd.com> |
| 8 | + */ |
| 9 | +#include <linux/pci.h> |
| 10 | +#include <linux/bitfield.h> |
| 11 | +#include <linux/pci-tph.h> |
| 12 | + |
| 13 | +#include "pci.h" |
| 14 | + |
| 15 | +/* System-wide TPH disabled */ |
| 16 | +static bool pci_tph_disabled; |
| 17 | + |
| 18 | +static u8 get_st_modes(struct pci_dev *pdev) |
| 19 | +{ |
| 20 | + u32 reg; |
| 21 | + |
| 22 | + pci_read_config_dword(pdev, pdev->tph_cap + PCI_TPH_CAP, ®); |
| 23 | + reg &= PCI_TPH_CAP_ST_NS | PCI_TPH_CAP_ST_IV | PCI_TPH_CAP_ST_DS; |
| 24 | + |
| 25 | + return reg; |
| 26 | +} |
| 27 | + |
| 28 | +/* Return device's Root Port completer capability */ |
| 29 | +static u8 get_rp_completer_type(struct pci_dev *pdev) |
| 30 | +{ |
| 31 | + struct pci_dev *rp; |
| 32 | + u32 reg; |
| 33 | + int ret; |
| 34 | + |
| 35 | + rp = pcie_find_root_port(pdev); |
| 36 | + if (!rp) |
| 37 | + return 0; |
| 38 | + |
| 39 | + ret = pcie_capability_read_dword(rp, PCI_EXP_DEVCAP2, ®); |
| 40 | + if (ret) |
| 41 | + return 0; |
| 42 | + |
| 43 | + return FIELD_GET(PCI_EXP_DEVCAP2_TPH_COMP_MASK, reg); |
| 44 | +} |
| 45 | + |
| 46 | +/** |
| 47 | + * pcie_disable_tph - Turn off TPH support for device |
| 48 | + * @pdev: PCI device |
| 49 | + * |
| 50 | + * Return: none |
| 51 | + */ |
| 52 | +void pcie_disable_tph(struct pci_dev *pdev) |
| 53 | +{ |
| 54 | + if (!pdev->tph_cap) |
| 55 | + return; |
| 56 | + |
| 57 | + if (!pdev->tph_enabled) |
| 58 | + return; |
| 59 | + |
| 60 | + pci_write_config_dword(pdev, pdev->tph_cap + PCI_TPH_CTRL, 0); |
| 61 | + |
| 62 | + pdev->tph_mode = 0; |
| 63 | + pdev->tph_req_type = 0; |
| 64 | + pdev->tph_enabled = 0; |
| 65 | +} |
| 66 | +EXPORT_SYMBOL(pcie_disable_tph); |
| 67 | + |
| 68 | +/** |
| 69 | + * pcie_enable_tph - Enable TPH support for device using a specific ST mode |
| 70 | + * @pdev: PCI device |
| 71 | + * @mode: ST mode to enable. Current supported modes include: |
| 72 | + * |
| 73 | + * - PCI_TPH_ST_NS_MODE: NO ST Mode |
| 74 | + * - PCI_TPH_ST_IV_MODE: Interrupt Vector Mode |
| 75 | + * - PCI_TPH_ST_DS_MODE: Device Specific Mode |
| 76 | + * |
| 77 | + * Check whether the mode is actually supported by the device before enabling |
| 78 | + * and return an error if not. Additionally determine what types of requests, |
| 79 | + * TPH or extended TPH, can be issued by the device based on its TPH requester |
| 80 | + * capability and the Root Port's completer capability. |
| 81 | + * |
| 82 | + * Return: 0 on success, otherwise negative value (-errno) |
| 83 | + */ |
| 84 | +int pcie_enable_tph(struct pci_dev *pdev, int mode) |
| 85 | +{ |
| 86 | + u32 reg; |
| 87 | + u8 dev_modes; |
| 88 | + u8 rp_req_type; |
| 89 | + |
| 90 | + /* Honor "notph" kernel parameter */ |
| 91 | + if (pci_tph_disabled) |
| 92 | + return -EINVAL; |
| 93 | + |
| 94 | + if (!pdev->tph_cap) |
| 95 | + return -EINVAL; |
| 96 | + |
| 97 | + if (pdev->tph_enabled) |
| 98 | + return -EBUSY; |
| 99 | + |
| 100 | + /* Sanitize and check ST mode compatibility */ |
| 101 | + mode &= PCI_TPH_CTRL_MODE_SEL_MASK; |
| 102 | + dev_modes = get_st_modes(pdev); |
| 103 | + if (!((1 << mode) & dev_modes)) |
| 104 | + return -EINVAL; |
| 105 | + |
| 106 | + pdev->tph_mode = mode; |
| 107 | + |
| 108 | + /* Get req_type supported by device and its Root Port */ |
| 109 | + pci_read_config_dword(pdev, pdev->tph_cap + PCI_TPH_CAP, ®); |
| 110 | + if (FIELD_GET(PCI_TPH_CAP_EXT_TPH, reg)) |
| 111 | + pdev->tph_req_type = PCI_TPH_REQ_EXT_TPH; |
| 112 | + else |
| 113 | + pdev->tph_req_type = PCI_TPH_REQ_TPH_ONLY; |
| 114 | + |
| 115 | + rp_req_type = get_rp_completer_type(pdev); |
| 116 | + |
| 117 | + /* Final req_type is the smallest value of two */ |
| 118 | + pdev->tph_req_type = min(pdev->tph_req_type, rp_req_type); |
| 119 | + |
| 120 | + if (pdev->tph_req_type == PCI_TPH_REQ_DISABLE) |
| 121 | + return -EINVAL; |
| 122 | + |
| 123 | + /* Write them into TPH control register */ |
| 124 | + pci_read_config_dword(pdev, pdev->tph_cap + PCI_TPH_CTRL, ®); |
| 125 | + |
| 126 | + reg &= ~PCI_TPH_CTRL_MODE_SEL_MASK; |
| 127 | + reg |= FIELD_PREP(PCI_TPH_CTRL_MODE_SEL_MASK, pdev->tph_mode); |
| 128 | + |
| 129 | + reg &= ~PCI_TPH_CTRL_REQ_EN_MASK; |
| 130 | + reg |= FIELD_PREP(PCI_TPH_CTRL_REQ_EN_MASK, pdev->tph_req_type); |
| 131 | + |
| 132 | + pci_write_config_dword(pdev, pdev->tph_cap + PCI_TPH_CTRL, reg); |
| 133 | + |
| 134 | + pdev->tph_enabled = 1; |
| 135 | + |
| 136 | + return 0; |
| 137 | +} |
| 138 | +EXPORT_SYMBOL(pcie_enable_tph); |
| 139 | + |
| 140 | +void pci_restore_tph_state(struct pci_dev *pdev) |
| 141 | +{ |
| 142 | + struct pci_cap_saved_state *save_state; |
| 143 | + u32 *cap; |
| 144 | + |
| 145 | + if (!pdev->tph_cap) |
| 146 | + return; |
| 147 | + |
| 148 | + if (!pdev->tph_enabled) |
| 149 | + return; |
| 150 | + |
| 151 | + save_state = pci_find_saved_ext_cap(pdev, PCI_EXT_CAP_ID_TPH); |
| 152 | + if (!save_state) |
| 153 | + return; |
| 154 | + |
| 155 | + /* Restore control register and all ST entries */ |
| 156 | + cap = &save_state->cap.data[0]; |
| 157 | + pci_write_config_dword(pdev, pdev->tph_cap + PCI_TPH_CTRL, *cap++); |
| 158 | +} |
| 159 | + |
| 160 | +void pci_save_tph_state(struct pci_dev *pdev) |
| 161 | +{ |
| 162 | + struct pci_cap_saved_state *save_state; |
| 163 | + u32 *cap; |
| 164 | + |
| 165 | + if (!pdev->tph_cap) |
| 166 | + return; |
| 167 | + |
| 168 | + if (!pdev->tph_enabled) |
| 169 | + return; |
| 170 | + |
| 171 | + save_state = pci_find_saved_ext_cap(pdev, PCI_EXT_CAP_ID_TPH); |
| 172 | + if (!save_state) |
| 173 | + return; |
| 174 | + |
| 175 | + /* Save control register */ |
| 176 | + cap = &save_state->cap.data[0]; |
| 177 | + pci_read_config_dword(pdev, pdev->tph_cap + PCI_TPH_CTRL, cap++); |
| 178 | +} |
| 179 | + |
| 180 | +void pci_no_tph(void) |
| 181 | +{ |
| 182 | + pci_tph_disabled = true; |
| 183 | + |
| 184 | + pr_info("PCIe TPH is disabled\n"); |
| 185 | +} |
| 186 | + |
| 187 | +void pci_tph_init(struct pci_dev *pdev) |
| 188 | +{ |
| 189 | + u32 save_size; |
| 190 | + |
| 191 | + pdev->tph_cap = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_TPH); |
| 192 | + if (!pdev->tph_cap) |
| 193 | + return; |
| 194 | + |
| 195 | + save_size = sizeof(u32); |
| 196 | + pci_add_ext_cap_save_buffer(pdev, PCI_EXT_CAP_ID_TPH, save_size); |
| 197 | +} |
0 commit comments