| 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/pci-acpi.h> |
| 11 | #include <linux/msi.h> |
| 12 | #include <linux/bitfield.h> |
| 13 | #include <linux/pci-tph.h> |
| 14 | |
| 15 | #include "pci.h" |
| 16 | |
| 17 | /* System-wide TPH disabled */ |
| 18 | static bool pci_tph_disabled; |
| 19 | |
| 20 | #ifdef CONFIG_ACPI |
| 21 | /* |
| 22 | * The st_info struct defines the Steering Tag (ST) info returned by the |
| 23 | * firmware PCI ACPI _DSM method (rev=0x7, func=0xF, "_DSM to Query Cache |
| 24 | * Locality TPH Features"), as specified in the approved ECN for PCI Firmware |
| 25 | * Spec and available at https://members.pcisig.com/wg/PCI-SIG/document/15470. |
| 26 | * |
| 27 | * @vm_st_valid: 8-bit ST for volatile memory is valid |
| 28 | * @vm_xst_valid: 16-bit extended ST for volatile memory is valid |
| 29 | * @vm_ph_ignore: 1 => PH was and will be ignored, 0 => PH should be supplied |
| 30 | * @vm_st: 8-bit ST for volatile mem |
| 31 | * @vm_xst: 16-bit extended ST for volatile mem |
| 32 | * @pm_st_valid: 8-bit ST for persistent memory is valid |
| 33 | * @pm_xst_valid: 16-bit extended ST for persistent memory is valid |
| 34 | * @pm_ph_ignore: 1 => PH was and will be ignored, 0 => PH should be supplied |
| 35 | * @pm_st: 8-bit ST for persistent mem |
| 36 | * @pm_xst: 16-bit extended ST for persistent mem |
| 37 | */ |
| 38 | union st_info { |
| 39 | struct { |
| 40 | u64 vm_st_valid : 1; |
| 41 | u64 vm_xst_valid : 1; |
| 42 | u64 vm_ph_ignore : 1; |
| 43 | u64 rsvd1 : 5; |
| 44 | u64 vm_st : 8; |
| 45 | u64 vm_xst : 16; |
| 46 | u64 pm_st_valid : 1; |
| 47 | u64 pm_xst_valid : 1; |
| 48 | u64 pm_ph_ignore : 1; |
| 49 | u64 rsvd2 : 5; |
| 50 | u64 pm_st : 8; |
| 51 | u64 pm_xst : 16; |
| 52 | }; |
| 53 | u64 value; |
| 54 | }; |
| 55 | |
| 56 | static u16 (enum tph_mem_type mem_type, u8 req_type, |
| 57 | union st_info *info) |
| 58 | { |
| 59 | switch (req_type) { |
| 60 | case PCI_TPH_REQ_TPH_ONLY: /* 8-bit tag */ |
| 61 | switch (mem_type) { |
| 62 | case TPH_MEM_TYPE_VM: |
| 63 | if (info->vm_st_valid) |
| 64 | return info->vm_st; |
| 65 | break; |
| 66 | case TPH_MEM_TYPE_PM: |
| 67 | if (info->pm_st_valid) |
| 68 | return info->pm_st; |
| 69 | break; |
| 70 | } |
| 71 | break; |
| 72 | case PCI_TPH_REQ_EXT_TPH: /* 16-bit tag */ |
| 73 | switch (mem_type) { |
| 74 | case TPH_MEM_TYPE_VM: |
| 75 | if (info->vm_xst_valid) |
| 76 | return info->vm_xst; |
| 77 | break; |
| 78 | case TPH_MEM_TYPE_PM: |
| 79 | if (info->pm_xst_valid) |
| 80 | return info->pm_xst; |
| 81 | break; |
| 82 | } |
| 83 | break; |
| 84 | default: |
| 85 | return 0; |
| 86 | } |
| 87 | |
| 88 | return 0; |
| 89 | } |
| 90 | |
| 91 | #define TPH_ST_DSM_FUNC_INDEX 0xF |
| 92 | static acpi_status tph_invoke_dsm(acpi_handle handle, u32 cpu_uid, |
| 93 | union st_info *st_out) |
| 94 | { |
| 95 | union acpi_object arg3[3], in_obj, *out_obj; |
| 96 | |
| 97 | if (!acpi_check_dsm(handle, guid: &pci_acpi_dsm_guid, rev: 7, |
| 98 | BIT(TPH_ST_DSM_FUNC_INDEX))) |
| 99 | return AE_ERROR; |
| 100 | |
| 101 | /* DWORD: feature ID (0 for processor cache ST query) */ |
| 102 | arg3[0].integer.type = ACPI_TYPE_INTEGER; |
| 103 | arg3[0].integer.value = 0; |
| 104 | |
| 105 | /* DWORD: target UID */ |
| 106 | arg3[1].integer.type = ACPI_TYPE_INTEGER; |
| 107 | arg3[1].integer.value = cpu_uid; |
| 108 | |
| 109 | /* QWORD: properties, all 0's */ |
| 110 | arg3[2].integer.type = ACPI_TYPE_INTEGER; |
| 111 | arg3[2].integer.value = 0; |
| 112 | |
| 113 | in_obj.type = ACPI_TYPE_PACKAGE; |
| 114 | in_obj.package.count = ARRAY_SIZE(arg3); |
| 115 | in_obj.package.elements = arg3; |
| 116 | |
| 117 | out_obj = acpi_evaluate_dsm(handle, guid: &pci_acpi_dsm_guid, rev: 7, |
| 118 | TPH_ST_DSM_FUNC_INDEX, argv4: &in_obj); |
| 119 | if (!out_obj) |
| 120 | return AE_ERROR; |
| 121 | |
| 122 | if (out_obj->type != ACPI_TYPE_BUFFER) { |
| 123 | ACPI_FREE(out_obj); |
| 124 | return AE_ERROR; |
| 125 | } |
| 126 | |
| 127 | st_out->value = *((u64 *)(out_obj->buffer.pointer)); |
| 128 | |
| 129 | ACPI_FREE(out_obj); |
| 130 | |
| 131 | return AE_OK; |
| 132 | } |
| 133 | #endif |
| 134 | |
| 135 | /* Update the TPH Requester Enable field of TPH Control Register */ |
| 136 | static void set_ctrl_reg_req_en(struct pci_dev *pdev, u8 req_type) |
| 137 | { |
| 138 | u32 reg; |
| 139 | |
| 140 | pci_read_config_dword(dev: pdev, where: pdev->tph_cap + PCI_TPH_CTRL, val: ®); |
| 141 | |
| 142 | reg &= ~PCI_TPH_CTRL_REQ_EN_MASK; |
| 143 | reg |= FIELD_PREP(PCI_TPH_CTRL_REQ_EN_MASK, req_type); |
| 144 | |
| 145 | pci_write_config_dword(dev: pdev, where: pdev->tph_cap + PCI_TPH_CTRL, val: reg); |
| 146 | } |
| 147 | |
| 148 | static u8 get_st_modes(struct pci_dev *pdev) |
| 149 | { |
| 150 | u32 reg; |
| 151 | |
| 152 | pci_read_config_dword(dev: pdev, where: pdev->tph_cap + PCI_TPH_CAP, val: ®); |
| 153 | reg &= PCI_TPH_CAP_ST_NS | PCI_TPH_CAP_ST_IV | PCI_TPH_CAP_ST_DS; |
| 154 | |
| 155 | return reg; |
| 156 | } |
| 157 | |
| 158 | static u32 get_st_table_loc(struct pci_dev *pdev) |
| 159 | { |
| 160 | u32 reg; |
| 161 | |
| 162 | pci_read_config_dword(dev: pdev, where: pdev->tph_cap + PCI_TPH_CAP, val: ®); |
| 163 | |
| 164 | return FIELD_GET(PCI_TPH_CAP_LOC_MASK, reg); |
| 165 | } |
| 166 | |
| 167 | /* |
| 168 | * Return the size of ST table. If ST table is not in TPH Requester Extended |
| 169 | * Capability space, return 0. Otherwise return the ST Table Size + 1. |
| 170 | */ |
| 171 | static u16 get_st_table_size(struct pci_dev *pdev) |
| 172 | { |
| 173 | u32 reg; |
| 174 | u32 loc; |
| 175 | |
| 176 | /* Check ST table location first */ |
| 177 | loc = get_st_table_loc(pdev); |
| 178 | |
| 179 | /* Convert loc to match with PCI_TPH_LOC_* defined in pci_regs.h */ |
| 180 | loc = FIELD_PREP(PCI_TPH_CAP_LOC_MASK, loc); |
| 181 | if (loc != PCI_TPH_LOC_CAP) |
| 182 | return 0; |
| 183 | |
| 184 | pci_read_config_dword(dev: pdev, where: pdev->tph_cap + PCI_TPH_CAP, val: ®); |
| 185 | |
| 186 | return FIELD_GET(PCI_TPH_CAP_ST_MASK, reg) + 1; |
| 187 | } |
| 188 | |
| 189 | /* Return device's Root Port completer capability */ |
| 190 | static u8 get_rp_completer_type(struct pci_dev *pdev) |
| 191 | { |
| 192 | struct pci_dev *rp; |
| 193 | u32 reg; |
| 194 | int ret; |
| 195 | |
| 196 | rp = pcie_find_root_port(dev: pdev); |
| 197 | if (!rp) |
| 198 | return 0; |
| 199 | |
| 200 | ret = pcie_capability_read_dword(dev: rp, PCI_EXP_DEVCAP2, val: ®); |
| 201 | if (ret) |
| 202 | return 0; |
| 203 | |
| 204 | return FIELD_GET(PCI_EXP_DEVCAP2_TPH_COMP_MASK, reg); |
| 205 | } |
| 206 | |
| 207 | /* Write tag to ST table - Return 0 if OK, otherwise -errno */ |
| 208 | static int write_tag_to_st_table(struct pci_dev *pdev, int index, u16 tag) |
| 209 | { |
| 210 | int st_table_size; |
| 211 | int offset; |
| 212 | |
| 213 | /* Check if index is out of bound */ |
| 214 | st_table_size = get_st_table_size(pdev); |
| 215 | if (index >= st_table_size) |
| 216 | return -ENXIO; |
| 217 | |
| 218 | offset = pdev->tph_cap + PCI_TPH_BASE_SIZEOF + index * sizeof(u16); |
| 219 | |
| 220 | return pci_write_config_word(dev: pdev, where: offset, val: tag); |
| 221 | } |
| 222 | |
| 223 | /** |
| 224 | * pcie_tph_get_cpu_st() - Retrieve Steering Tag for a target memory associated |
| 225 | * with a specific CPU |
| 226 | * @pdev: PCI device |
| 227 | * @mem_type: target memory type (volatile or persistent RAM) |
| 228 | * @cpu_uid: associated CPU id |
| 229 | * @tag: Steering Tag to be returned |
| 230 | * |
| 231 | * Return the Steering Tag for a target memory that is associated with a |
| 232 | * specific CPU as indicated by cpu_uid. |
| 233 | * |
| 234 | * Return: 0 if success, otherwise negative value (-errno) |
| 235 | */ |
| 236 | int pcie_tph_get_cpu_st(struct pci_dev *pdev, enum tph_mem_type mem_type, |
| 237 | unsigned int cpu_uid, u16 *tag) |
| 238 | { |
| 239 | #ifdef CONFIG_ACPI |
| 240 | struct pci_dev *rp; |
| 241 | acpi_handle rp_acpi_handle; |
| 242 | union st_info info; |
| 243 | |
| 244 | rp = pcie_find_root_port(dev: pdev); |
| 245 | if (!rp || !rp->bus || !rp->bus->bridge) |
| 246 | return -ENODEV; |
| 247 | |
| 248 | rp_acpi_handle = ACPI_HANDLE(rp->bus->bridge); |
| 249 | |
| 250 | if (tph_invoke_dsm(handle: rp_acpi_handle, cpu_uid, st_out: &info) != AE_OK) { |
| 251 | *tag = 0; |
| 252 | return -EINVAL; |
| 253 | } |
| 254 | |
| 255 | *tag = tph_extract_tag(mem_type, req_type: pdev->tph_req_type, info: &info); |
| 256 | |
| 257 | pci_dbg(pdev, "get steering tag: mem_type=%s, cpu_uid=%d, tag=%#04x\n" , |
| 258 | (mem_type == TPH_MEM_TYPE_VM) ? "volatile" : "persistent" , |
| 259 | cpu_uid, *tag); |
| 260 | |
| 261 | return 0; |
| 262 | #else |
| 263 | return -ENODEV; |
| 264 | #endif |
| 265 | } |
| 266 | EXPORT_SYMBOL(pcie_tph_get_cpu_st); |
| 267 | |
| 268 | /** |
| 269 | * pcie_tph_set_st_entry() - Set Steering Tag in the ST table entry |
| 270 | * @pdev: PCI device |
| 271 | * @index: ST table entry index |
| 272 | * @tag: Steering Tag to be written |
| 273 | * |
| 274 | * Figure out the proper location of ST table, either in the MSI-X table or |
| 275 | * in the TPH Extended Capability space, and write the Steering Tag into |
| 276 | * the ST entry pointed by index. |
| 277 | * |
| 278 | * Return: 0 if success, otherwise negative value (-errno) |
| 279 | */ |
| 280 | int pcie_tph_set_st_entry(struct pci_dev *pdev, unsigned int index, u16 tag) |
| 281 | { |
| 282 | u32 loc; |
| 283 | int err = 0; |
| 284 | |
| 285 | if (!pdev->tph_cap) |
| 286 | return -EINVAL; |
| 287 | |
| 288 | if (!pdev->tph_enabled) |
| 289 | return -EINVAL; |
| 290 | |
| 291 | /* No need to write tag if device is in "No ST Mode" */ |
| 292 | if (pdev->tph_mode == PCI_TPH_ST_NS_MODE) |
| 293 | return 0; |
| 294 | |
| 295 | /* |
| 296 | * Disable TPH before updating ST to avoid potential instability as |
| 297 | * cautioned in PCIe r6.2, sec 6.17.3, "ST Modes of Operation" |
| 298 | */ |
| 299 | set_ctrl_reg_req_en(pdev, PCI_TPH_REQ_DISABLE); |
| 300 | |
| 301 | loc = get_st_table_loc(pdev); |
| 302 | /* Convert loc to match with PCI_TPH_LOC_* */ |
| 303 | loc = FIELD_PREP(PCI_TPH_CAP_LOC_MASK, loc); |
| 304 | |
| 305 | switch (loc) { |
| 306 | case PCI_TPH_LOC_MSIX: |
| 307 | err = pci_msix_write_tph_tag(pdev, index, tag); |
| 308 | break; |
| 309 | case PCI_TPH_LOC_CAP: |
| 310 | err = write_tag_to_st_table(pdev, index, tag); |
| 311 | break; |
| 312 | default: |
| 313 | err = -EINVAL; |
| 314 | } |
| 315 | |
| 316 | if (err) { |
| 317 | pcie_disable_tph(pdev); |
| 318 | return err; |
| 319 | } |
| 320 | |
| 321 | set_ctrl_reg_req_en(pdev, req_type: pdev->tph_req_type); |
| 322 | |
| 323 | pci_dbg(pdev, "set steering tag: %s table, index=%d, tag=%#04x\n" , |
| 324 | (loc == PCI_TPH_LOC_MSIX) ? "MSI-X" : "ST" , index, tag); |
| 325 | |
| 326 | return 0; |
| 327 | } |
| 328 | EXPORT_SYMBOL(pcie_tph_set_st_entry); |
| 329 | |
| 330 | /** |
| 331 | * pcie_disable_tph - Turn off TPH support for device |
| 332 | * @pdev: PCI device |
| 333 | * |
| 334 | * Return: none |
| 335 | */ |
| 336 | void pcie_disable_tph(struct pci_dev *pdev) |
| 337 | { |
| 338 | if (!pdev->tph_cap) |
| 339 | return; |
| 340 | |
| 341 | if (!pdev->tph_enabled) |
| 342 | return; |
| 343 | |
| 344 | pci_write_config_dword(dev: pdev, where: pdev->tph_cap + PCI_TPH_CTRL, val: 0); |
| 345 | |
| 346 | pdev->tph_mode = 0; |
| 347 | pdev->tph_req_type = 0; |
| 348 | pdev->tph_enabled = 0; |
| 349 | } |
| 350 | EXPORT_SYMBOL(pcie_disable_tph); |
| 351 | |
| 352 | /** |
| 353 | * pcie_enable_tph - Enable TPH support for device using a specific ST mode |
| 354 | * @pdev: PCI device |
| 355 | * @mode: ST mode to enable. Current supported modes include: |
| 356 | * |
| 357 | * - PCI_TPH_ST_NS_MODE: NO ST Mode |
| 358 | * - PCI_TPH_ST_IV_MODE: Interrupt Vector Mode |
| 359 | * - PCI_TPH_ST_DS_MODE: Device Specific Mode |
| 360 | * |
| 361 | * Check whether the mode is actually supported by the device before enabling |
| 362 | * and return an error if not. Additionally determine what types of requests, |
| 363 | * TPH or extended TPH, can be issued by the device based on its TPH requester |
| 364 | * capability and the Root Port's completer capability. |
| 365 | * |
| 366 | * Return: 0 on success, otherwise negative value (-errno) |
| 367 | */ |
| 368 | int pcie_enable_tph(struct pci_dev *pdev, int mode) |
| 369 | { |
| 370 | u32 reg; |
| 371 | u8 dev_modes; |
| 372 | u8 rp_req_type; |
| 373 | |
| 374 | /* Honor "notph" kernel parameter */ |
| 375 | if (pci_tph_disabled) |
| 376 | return -EINVAL; |
| 377 | |
| 378 | if (!pdev->tph_cap) |
| 379 | return -EINVAL; |
| 380 | |
| 381 | if (pdev->tph_enabled) |
| 382 | return -EBUSY; |
| 383 | |
| 384 | /* Sanitize and check ST mode compatibility */ |
| 385 | mode &= PCI_TPH_CTRL_MODE_SEL_MASK; |
| 386 | dev_modes = get_st_modes(pdev); |
| 387 | if (!((1 << mode) & dev_modes)) |
| 388 | return -EINVAL; |
| 389 | |
| 390 | pdev->tph_mode = mode; |
| 391 | |
| 392 | /* Get req_type supported by device and its Root Port */ |
| 393 | pci_read_config_dword(dev: pdev, where: pdev->tph_cap + PCI_TPH_CAP, val: ®); |
| 394 | if (FIELD_GET(PCI_TPH_CAP_EXT_TPH, reg)) |
| 395 | pdev->tph_req_type = PCI_TPH_REQ_EXT_TPH; |
| 396 | else |
| 397 | pdev->tph_req_type = PCI_TPH_REQ_TPH_ONLY; |
| 398 | |
| 399 | rp_req_type = get_rp_completer_type(pdev); |
| 400 | |
| 401 | /* Final req_type is the smallest value of two */ |
| 402 | pdev->tph_req_type = min(pdev->tph_req_type, rp_req_type); |
| 403 | |
| 404 | if (pdev->tph_req_type == PCI_TPH_REQ_DISABLE) |
| 405 | return -EINVAL; |
| 406 | |
| 407 | /* Write them into TPH control register */ |
| 408 | pci_read_config_dword(dev: pdev, where: pdev->tph_cap + PCI_TPH_CTRL, val: ®); |
| 409 | |
| 410 | reg &= ~PCI_TPH_CTRL_MODE_SEL_MASK; |
| 411 | reg |= FIELD_PREP(PCI_TPH_CTRL_MODE_SEL_MASK, pdev->tph_mode); |
| 412 | |
| 413 | reg &= ~PCI_TPH_CTRL_REQ_EN_MASK; |
| 414 | reg |= FIELD_PREP(PCI_TPH_CTRL_REQ_EN_MASK, pdev->tph_req_type); |
| 415 | |
| 416 | pci_write_config_dword(dev: pdev, where: pdev->tph_cap + PCI_TPH_CTRL, val: reg); |
| 417 | |
| 418 | pdev->tph_enabled = 1; |
| 419 | |
| 420 | return 0; |
| 421 | } |
| 422 | EXPORT_SYMBOL(pcie_enable_tph); |
| 423 | |
| 424 | void pci_restore_tph_state(struct pci_dev *pdev) |
| 425 | { |
| 426 | struct pci_cap_saved_state *save_state; |
| 427 | int num_entries, i, offset; |
| 428 | u16 *st_entry; |
| 429 | u32 *cap; |
| 430 | |
| 431 | if (!pdev->tph_cap) |
| 432 | return; |
| 433 | |
| 434 | if (!pdev->tph_enabled) |
| 435 | return; |
| 436 | |
| 437 | save_state = pci_find_saved_ext_cap(dev: pdev, PCI_EXT_CAP_ID_TPH); |
| 438 | if (!save_state) |
| 439 | return; |
| 440 | |
| 441 | /* Restore control register and all ST entries */ |
| 442 | cap = &save_state->cap.data[0]; |
| 443 | pci_write_config_dword(dev: pdev, where: pdev->tph_cap + PCI_TPH_CTRL, val: *cap++); |
| 444 | st_entry = (u16 *)cap; |
| 445 | offset = PCI_TPH_BASE_SIZEOF; |
| 446 | num_entries = get_st_table_size(pdev); |
| 447 | for (i = 0; i < num_entries; i++) { |
| 448 | pci_write_config_word(dev: pdev, where: pdev->tph_cap + offset, |
| 449 | val: *st_entry++); |
| 450 | offset += sizeof(u16); |
| 451 | } |
| 452 | } |
| 453 | |
| 454 | void pci_save_tph_state(struct pci_dev *pdev) |
| 455 | { |
| 456 | struct pci_cap_saved_state *save_state; |
| 457 | int num_entries, i, offset; |
| 458 | u16 *st_entry; |
| 459 | u32 *cap; |
| 460 | |
| 461 | if (!pdev->tph_cap) |
| 462 | return; |
| 463 | |
| 464 | if (!pdev->tph_enabled) |
| 465 | return; |
| 466 | |
| 467 | save_state = pci_find_saved_ext_cap(dev: pdev, PCI_EXT_CAP_ID_TPH); |
| 468 | if (!save_state) |
| 469 | return; |
| 470 | |
| 471 | /* Save control register */ |
| 472 | cap = &save_state->cap.data[0]; |
| 473 | pci_read_config_dword(dev: pdev, where: pdev->tph_cap + PCI_TPH_CTRL, val: cap++); |
| 474 | |
| 475 | /* Save all ST entries in extended capability structure */ |
| 476 | st_entry = (u16 *)cap; |
| 477 | offset = PCI_TPH_BASE_SIZEOF; |
| 478 | num_entries = get_st_table_size(pdev); |
| 479 | for (i = 0; i < num_entries; i++) { |
| 480 | pci_read_config_word(dev: pdev, where: pdev->tph_cap + offset, |
| 481 | val: st_entry++); |
| 482 | offset += sizeof(u16); |
| 483 | } |
| 484 | } |
| 485 | |
| 486 | void pci_no_tph(void) |
| 487 | { |
| 488 | pci_tph_disabled = true; |
| 489 | |
| 490 | pr_info("PCIe TPH is disabled\n" ); |
| 491 | } |
| 492 | |
| 493 | void pci_tph_init(struct pci_dev *pdev) |
| 494 | { |
| 495 | int num_entries; |
| 496 | u32 save_size; |
| 497 | |
| 498 | pdev->tph_cap = pci_find_ext_capability(dev: pdev, PCI_EXT_CAP_ID_TPH); |
| 499 | if (!pdev->tph_cap) |
| 500 | return; |
| 501 | |
| 502 | num_entries = get_st_table_size(pdev); |
| 503 | save_size = sizeof(u32) + num_entries * sizeof(u16); |
| 504 | pci_add_ext_cap_save_buffer(dev: pdev, PCI_EXT_CAP_ID_TPH, size: save_size); |
| 505 | } |
| 506 | |