Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

PCI/TPH: Add Steering Tag support

Add pcie_tph_get_cpu_st() to allow a caller to retrieve Steering Tags for a
target memory associated with a specific CPU. The ST tag is retrieved by
invoking PCI ACPI "_DSM to Query Cache Locality TPH Features" method
(rev=0x7, func=0xF) of the device's Root Port device.

Add pcie_tph_set_st_entry() to update the device's Steering Tags. The tags
will be written into the device's MSI-X table or the ST table located in
the TPH Extended Capability space.

Co-developed-by: Eric Van Tassell <Eric.VanTassell@amd.com>
Link: https://lore.kernel.org/r/20241002165954.128085-3-wei.huang2@amd.com
Signed-off-by: Eric Van Tassell <Eric.VanTassell@amd.com>
Signed-off-by: Wei Huang <wei.huang2@amd.com>
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
Reviewed-by: Ajit Khaparde <ajit.khaparde@broadcom.com>
Reviewed-by: Somnath Kotur <somnath.kotur@broadcom.com>
Reviewed-by: Andy Gospodarek <andrew.gospodarek@broadcom.com>

authored by

Wei Huang and committed by
Bjorn Helgaas
d2e8a348 f69767a1

+374 -1
+351 -1
drivers/pci/tph.c
··· 7 7 * Wei Huang <wei.huang2@amd.com> 8 8 */ 9 9 #include <linux/pci.h> 10 + #include <linux/pci-acpi.h> 11 + #include <linux/msi.h> 10 12 #include <linux/bitfield.h> 11 13 #include <linux/pci-tph.h> 12 14 ··· 16 14 17 15 /* System-wide TPH disabled */ 18 16 static bool pci_tph_disabled; 17 + 18 + #ifdef CONFIG_ACPI 19 + /* 20 + * The st_info struct defines the Steering Tag (ST) info returned by the 21 + * firmware PCI ACPI _DSM method (rev=0x7, func=0xF, "_DSM to Query Cache 22 + * Locality TPH Features"), as specified in the approved ECN for PCI Firmware 23 + * Spec and available at https://members.pcisig.com/wg/PCI-SIG/document/15470. 24 + * 25 + * @vm_st_valid: 8-bit ST for volatile memory is valid 26 + * @vm_xst_valid: 16-bit extended ST for volatile memory is valid 27 + * @vm_ph_ignore: 1 => PH was and will be ignored, 0 => PH should be supplied 28 + * @vm_st: 8-bit ST for volatile mem 29 + * @vm_xst: 16-bit extended ST for volatile mem 30 + * @pm_st_valid: 8-bit ST for persistent memory is valid 31 + * @pm_xst_valid: 16-bit extended ST for persistent memory is valid 32 + * @pm_ph_ignore: 1 => PH was and will be ignored, 0 => PH should be supplied 33 + * @pm_st: 8-bit ST for persistent mem 34 + * @pm_xst: 16-bit extended ST for persistent mem 35 + */ 36 + union st_info { 37 + struct { 38 + u64 vm_st_valid : 1; 39 + u64 vm_xst_valid : 1; 40 + u64 vm_ph_ignore : 1; 41 + u64 rsvd1 : 5; 42 + u64 vm_st : 8; 43 + u64 vm_xst : 16; 44 + u64 pm_st_valid : 1; 45 + u64 pm_xst_valid : 1; 46 + u64 pm_ph_ignore : 1; 47 + u64 rsvd2 : 5; 48 + u64 pm_st : 8; 49 + u64 pm_xst : 16; 50 + }; 51 + u64 value; 52 + }; 53 + 54 + static u16 tph_extract_tag(enum tph_mem_type mem_type, u8 req_type, 55 + union st_info *info) 56 + { 57 + switch (req_type) { 58 + case PCI_TPH_REQ_TPH_ONLY: /* 8-bit tag */ 59 + switch (mem_type) { 60 + case TPH_MEM_TYPE_VM: 61 + if (info->vm_st_valid) 62 + return info->vm_st; 63 + break; 64 + case TPH_MEM_TYPE_PM: 65 + if (info->pm_st_valid) 66 + return info->pm_st; 67 + break; 68 + } 69 + break; 70 + case PCI_TPH_REQ_EXT_TPH: /* 16-bit tag */ 71 + switch (mem_type) { 72 + case TPH_MEM_TYPE_VM: 73 + if (info->vm_xst_valid) 74 + return info->vm_xst; 75 + break; 76 + case TPH_MEM_TYPE_PM: 77 + if (info->pm_xst_valid) 78 + return info->pm_xst; 79 + break; 80 + } 81 + break; 82 + default: 83 + return 0; 84 + } 85 + 86 + return 0; 87 + } 88 + 89 + #define TPH_ST_DSM_FUNC_INDEX 0xF 90 + static acpi_status tph_invoke_dsm(acpi_handle handle, u32 cpu_uid, 91 + union st_info *st_out) 92 + { 93 + union acpi_object arg3[3], in_obj, *out_obj; 94 + 95 + if (!acpi_check_dsm(handle, &pci_acpi_dsm_guid, 7, 96 + BIT(TPH_ST_DSM_FUNC_INDEX))) 97 + return AE_ERROR; 98 + 99 + /* DWORD: feature ID (0 for processor cache ST query) */ 100 + arg3[0].integer.type = ACPI_TYPE_INTEGER; 101 + arg3[0].integer.value = 0; 102 + 103 + /* DWORD: target UID */ 104 + arg3[1].integer.type = ACPI_TYPE_INTEGER; 105 + arg3[1].integer.value = cpu_uid; 106 + 107 + /* QWORD: properties, all 0's */ 108 + arg3[2].integer.type = ACPI_TYPE_INTEGER; 109 + arg3[2].integer.value = 0; 110 + 111 + in_obj.type = ACPI_TYPE_PACKAGE; 112 + in_obj.package.count = ARRAY_SIZE(arg3); 113 + in_obj.package.elements = arg3; 114 + 115 + out_obj = acpi_evaluate_dsm(handle, &pci_acpi_dsm_guid, 7, 116 + TPH_ST_DSM_FUNC_INDEX, &in_obj); 117 + if (!out_obj) 118 + return AE_ERROR; 119 + 120 + if (out_obj->type != ACPI_TYPE_BUFFER) { 121 + ACPI_FREE(out_obj); 122 + return AE_ERROR; 123 + } 124 + 125 + st_out->value = *((u64 *)(out_obj->buffer.pointer)); 126 + 127 + ACPI_FREE(out_obj); 128 + 129 + return AE_OK; 130 + } 131 + #endif 132 + 133 + /* Update the TPH Requester Enable field of TPH Control Register */ 134 + static void set_ctrl_reg_req_en(struct pci_dev *pdev, u8 req_type) 135 + { 136 + u32 reg; 137 + 138 + pci_read_config_dword(pdev, pdev->tph_cap + PCI_TPH_CTRL, &reg); 139 + 140 + reg &= ~PCI_TPH_CTRL_REQ_EN_MASK; 141 + reg |= FIELD_PREP(PCI_TPH_CTRL_REQ_EN_MASK, req_type); 142 + 143 + pci_write_config_dword(pdev, pdev->tph_cap + PCI_TPH_CTRL, reg); 144 + } 19 145 20 146 static u8 get_st_modes(struct pci_dev *pdev) 21 147 { ··· 153 23 reg &= PCI_TPH_CAP_ST_NS | PCI_TPH_CAP_ST_IV | PCI_TPH_CAP_ST_DS; 154 24 155 25 return reg; 26 + } 27 + 28 + static u32 get_st_table_loc(struct pci_dev *pdev) 29 + { 30 + u32 reg; 31 + 32 + pci_read_config_dword(pdev, pdev->tph_cap + PCI_TPH_CAP, &reg); 33 + 34 + return FIELD_GET(PCI_TPH_CAP_LOC_MASK, reg); 35 + } 36 + 37 + /* 38 + * Return the size of ST table. If ST table is not in TPH Requester Extended 39 + * Capability space, return 0. Otherwise return the ST Table Size + 1. 40 + */ 41 + static u16 get_st_table_size(struct pci_dev *pdev) 42 + { 43 + u32 reg; 44 + u32 loc; 45 + 46 + /* Check ST table location first */ 47 + loc = get_st_table_loc(pdev); 48 + 49 + /* Convert loc to match with PCI_TPH_LOC_* defined in pci_regs.h */ 50 + loc = FIELD_PREP(PCI_TPH_CAP_LOC_MASK, loc); 51 + if (loc != PCI_TPH_LOC_CAP) 52 + return 0; 53 + 54 + pci_read_config_dword(pdev, pdev->tph_cap + PCI_TPH_CAP, &reg); 55 + 56 + return FIELD_GET(PCI_TPH_CAP_ST_MASK, reg) + 1; 156 57 } 157 58 158 59 /* Return device's Root Port completer capability */ ··· 203 42 204 43 return FIELD_GET(PCI_EXP_DEVCAP2_TPH_COMP_MASK, reg); 205 44 } 45 + 46 + /* Write ST to MSI-X vector control reg - Return 0 if OK, otherwise -errno */ 47 + static int write_tag_to_msix(struct pci_dev *pdev, int msix_idx, u16 tag) 48 + { 49 + #ifdef CONFIG_PCI_MSI 50 + struct msi_desc *msi_desc = NULL; 51 + void __iomem *vec_ctrl; 52 + u32 val; 53 + int err = 0; 54 + 55 + msi_lock_descs(&pdev->dev); 56 + 57 + /* Find the msi_desc entry with matching msix_idx */ 58 + msi_for_each_desc(msi_desc, &pdev->dev, MSI_DESC_ASSOCIATED) { 59 + if (msi_desc->msi_index == msix_idx) 60 + break; 61 + } 62 + 63 + if (!msi_desc) { 64 + err = -ENXIO; 65 + goto err_out; 66 + } 67 + 68 + /* Get the vector control register (offset 0xc) pointed by msix_idx */ 69 + vec_ctrl = pdev->msix_base + msix_idx * PCI_MSIX_ENTRY_SIZE; 70 + vec_ctrl += PCI_MSIX_ENTRY_VECTOR_CTRL; 71 + 72 + val = readl(vec_ctrl); 73 + val &= ~PCI_MSIX_ENTRY_CTRL_ST; 74 + val |= FIELD_PREP(PCI_MSIX_ENTRY_CTRL_ST, tag); 75 + writel(val, vec_ctrl); 76 + 77 + /* Read back to flush the update */ 78 + val = readl(vec_ctrl); 79 + 80 + err_out: 81 + msi_unlock_descs(&pdev->dev); 82 + return err; 83 + #else 84 + return -ENODEV; 85 + #endif 86 + } 87 + 88 + /* Write tag to ST table - Return 0 if OK, otherwise -errno */ 89 + static int write_tag_to_st_table(struct pci_dev *pdev, int index, u16 tag) 90 + { 91 + int st_table_size; 92 + int offset; 93 + 94 + /* Check if index is out of bound */ 95 + st_table_size = get_st_table_size(pdev); 96 + if (index >= st_table_size) 97 + return -ENXIO; 98 + 99 + offset = pdev->tph_cap + PCI_TPH_BASE_SIZEOF + index * sizeof(u16); 100 + 101 + return pci_write_config_word(pdev, offset, tag); 102 + } 103 + 104 + /** 105 + * pcie_tph_get_cpu_st() - Retrieve Steering Tag for a target memory associated 106 + * with a specific CPU 107 + * @pdev: PCI device 108 + * @mem_type: target memory type (volatile or persistent RAM) 109 + * @cpu_uid: associated CPU id 110 + * @tag: Steering Tag to be returned 111 + * 112 + * Return the Steering Tag for a target memory that is associated with a 113 + * specific CPU as indicated by cpu_uid. 114 + * 115 + * Return: 0 if success, otherwise negative value (-errno) 116 + */ 117 + int pcie_tph_get_cpu_st(struct pci_dev *pdev, enum tph_mem_type mem_type, 118 + unsigned int cpu_uid, u16 *tag) 119 + { 120 + #ifdef CONFIG_ACPI 121 + struct pci_dev *rp; 122 + acpi_handle rp_acpi_handle; 123 + union st_info info; 124 + 125 + rp = pcie_find_root_port(pdev); 126 + if (!rp || !rp->bus || !rp->bus->bridge) 127 + return -ENODEV; 128 + 129 + rp_acpi_handle = ACPI_HANDLE(rp->bus->bridge); 130 + 131 + if (tph_invoke_dsm(rp_acpi_handle, cpu_uid, &info) != AE_OK) { 132 + *tag = 0; 133 + return -EINVAL; 134 + } 135 + 136 + *tag = tph_extract_tag(mem_type, pdev->tph_req_type, &info); 137 + 138 + pci_dbg(pdev, "get steering tag: mem_type=%s, cpu_uid=%d, tag=%#04x\n", 139 + (mem_type == TPH_MEM_TYPE_VM) ? "volatile" : "persistent", 140 + cpu_uid, *tag); 141 + 142 + return 0; 143 + #else 144 + return -ENODEV; 145 + #endif 146 + } 147 + EXPORT_SYMBOL(pcie_tph_get_cpu_st); 148 + 149 + /** 150 + * pcie_tph_set_st_entry() - Set Steering Tag in the ST table entry 151 + * @pdev: PCI device 152 + * @index: ST table entry index 153 + * @tag: Steering Tag to be written 154 + * 155 + * Figure out the proper location of ST table, either in the MSI-X table or 156 + * in the TPH Extended Capability space, and write the Steering Tag into 157 + * the ST entry pointed by index. 158 + * 159 + * Return: 0 if success, otherwise negative value (-errno) 160 + */ 161 + int pcie_tph_set_st_entry(struct pci_dev *pdev, unsigned int index, u16 tag) 162 + { 163 + u32 loc; 164 + int err = 0; 165 + 166 + if (!pdev->tph_cap) 167 + return -EINVAL; 168 + 169 + if (!pdev->tph_enabled) 170 + return -EINVAL; 171 + 172 + /* No need to write tag if device is in "No ST Mode" */ 173 + if (pdev->tph_mode == PCI_TPH_ST_NS_MODE) 174 + return 0; 175 + 176 + /* 177 + * Disable TPH before updating ST to avoid potential instability as 178 + * cautioned in PCIe r6.2, sec 6.17.3, "ST Modes of Operation" 179 + */ 180 + set_ctrl_reg_req_en(pdev, PCI_TPH_REQ_DISABLE); 181 + 182 + loc = get_st_table_loc(pdev); 183 + /* Convert loc to match with PCI_TPH_LOC_* */ 184 + loc = FIELD_PREP(PCI_TPH_CAP_LOC_MASK, loc); 185 + 186 + switch (loc) { 187 + case PCI_TPH_LOC_MSIX: 188 + err = write_tag_to_msix(pdev, index, tag); 189 + break; 190 + case PCI_TPH_LOC_CAP: 191 + err = write_tag_to_st_table(pdev, index, tag); 192 + break; 193 + default: 194 + err = -EINVAL; 195 + } 196 + 197 + if (err) { 198 + pcie_disable_tph(pdev); 199 + return err; 200 + } 201 + 202 + set_ctrl_reg_req_en(pdev, pdev->tph_mode); 203 + 204 + pci_dbg(pdev, "set steering tag: %s table, index=%d, tag=%#04x\n", 205 + (loc == PCI_TPH_LOC_MSIX) ? "MSI-X" : "ST", index, tag); 206 + 207 + return 0; 208 + } 209 + EXPORT_SYMBOL(pcie_tph_set_st_entry); 206 210 207 211 /** 208 212 * pcie_disable_tph - Turn off TPH support for device ··· 466 140 void pci_restore_tph_state(struct pci_dev *pdev) 467 141 { 468 142 struct pci_cap_saved_state *save_state; 143 + int num_entries, i, offset; 144 + u16 *st_entry; 469 145 u32 *cap; 470 146 471 147 if (!pdev->tph_cap) ··· 483 155 /* Restore control register and all ST entries */ 484 156 cap = &save_state->cap.data[0]; 485 157 pci_write_config_dword(pdev, pdev->tph_cap + PCI_TPH_CTRL, *cap++); 158 + st_entry = (u16 *)cap; 159 + offset = PCI_TPH_BASE_SIZEOF; 160 + num_entries = get_st_table_size(pdev); 161 + for (i = 0; i < num_entries; i++) { 162 + pci_write_config_word(pdev, pdev->tph_cap + offset, 163 + *st_entry++); 164 + offset += sizeof(u16); 165 + } 486 166 } 487 167 488 168 void pci_save_tph_state(struct pci_dev *pdev) 489 169 { 490 170 struct pci_cap_saved_state *save_state; 171 + int num_entries, i, offset; 172 + u16 *st_entry; 491 173 u32 *cap; 492 174 493 175 if (!pdev->tph_cap) ··· 513 175 /* Save control register */ 514 176 cap = &save_state->cap.data[0]; 515 177 pci_read_config_dword(pdev, pdev->tph_cap + PCI_TPH_CTRL, cap++); 178 + 179 + /* Save all ST entries in extended capability structure */ 180 + st_entry = (u16 *)cap; 181 + offset = PCI_TPH_BASE_SIZEOF; 182 + num_entries = get_st_table_size(pdev); 183 + for (i = 0; i < num_entries; i++) { 184 + pci_read_config_word(pdev, pdev->tph_cap + offset, 185 + st_entry++); 186 + offset += sizeof(u16); 187 + } 516 188 } 517 189 518 190 void pci_no_tph(void) ··· 534 186 535 187 void pci_tph_init(struct pci_dev *pdev) 536 188 { 189 + int num_entries; 537 190 u32 save_size; 538 191 539 192 pdev->tph_cap = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_TPH); 540 193 if (!pdev->tph_cap) 541 194 return; 542 195 543 - save_size = sizeof(u32); 196 + num_entries = get_st_table_size(pdev); 197 + save_size = sizeof(u32) + num_entries * sizeof(u16); 544 198 pci_add_ext_cap_save_buffer(pdev, PCI_EXT_CAP_ID_TPH, save_size); 545 199 }
+23
include/linux/pci-tph.h
··· 9 9 #ifndef LINUX_PCI_TPH_H 10 10 #define LINUX_PCI_TPH_H 11 11 12 + /* 13 + * According to the ECN for PCI Firmware Spec, Steering Tag can be different 14 + * depending on the memory type: Volatile Memory or Persistent Memory. When a 15 + * caller query about a target's Steering Tag, it must provide the target's 16 + * tph_mem_type. ECN link: https://members.pcisig.com/wg/PCI-SIG/document/15470. 17 + */ 18 + enum tph_mem_type { 19 + TPH_MEM_TYPE_VM, /* volatile memory */ 20 + TPH_MEM_TYPE_PM /* persistent memory */ 21 + }; 22 + 12 23 #ifdef CONFIG_PCIE_TPH 24 + int pcie_tph_set_st_entry(struct pci_dev *pdev, 25 + unsigned int index, u16 tag); 26 + int pcie_tph_get_cpu_st(struct pci_dev *dev, 27 + enum tph_mem_type mem_type, 28 + unsigned int cpu_uid, u16 *tag); 13 29 void pcie_disable_tph(struct pci_dev *pdev); 14 30 int pcie_enable_tph(struct pci_dev *pdev, int mode); 15 31 #else 32 + static inline int pcie_tph_set_st_entry(struct pci_dev *pdev, 33 + unsigned int index, u16 tag) 34 + { return -EINVAL; } 35 + static inline int pcie_tph_get_cpu_st(struct pci_dev *dev, 36 + enum tph_mem_type mem_type, 37 + unsigned int cpu_uid, u16 *tag) 38 + { return -EINVAL; } 16 39 static inline void pcie_disable_tph(struct pci_dev *pdev) { } 17 40 static inline int pcie_enable_tph(struct pci_dev *pdev, int mode) 18 41 { return -EINVAL; }