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

cxl/pci: Retrieve CXL DVSEC memory info

Before CXL 2.0 HDM Decoder Capability mechanisms can be utilized in a
device the driver must determine that the device is ready for CXL.mem
operation and that platform firmware, or some other agent, has
established an active decode via the legacy CXL 1.1 decoder mechanism.

This legacy mechanism is defined in the CXL DVSEC as a set of range
registers and status bits that take time to settle after a reset.

Validate the CXL memory decode setup via the DVSEC and cache it for
later consideration by the cxl_mem driver (to be added). Failure to
validate is not fatal to the cxl_pci driver since that is only providing
CXL command support over PCI.mmio, and might be needed to rectify CXL
DVSEC validation problems.

Any potential ranges that the device is already claiming via DVSEC need
to be reconciled with the dynamic provisioning ranges provided by
platform firmware (like ACPI CEDT.CFMWS). Leave that reconciliation to
the cxl_mem driver.

[djbw: shorten defines]
[djbw: change precise spin wait to generous msleep]

Reported-by: kernel test robot <lkp@intel.com>
Signed-off-by: Ben Widawsky <ben.widawsky@intel.com>
[djbw: clarify changelog]
Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
Link: https://lore.kernel.org/r/164375911821.559935.7375160041663453400.stgit@dwillia2-desk3.amr.corp.intel.com
Signed-off-by: Dan Williams <dan.j.williams@intel.com>

authored by

Ben Widawsky and committed by
Dan Williams
560f7855 06e279e5

+146
+14
drivers/cxl/cxlmem.h
··· 90 90 #define CXL_CAPACITY_MULTIPLIER SZ_256M 91 91 92 92 /** 93 + * struct cxl_endpoint_dvsec_info - Cached DVSEC info 94 + * @mem_enabled: cached value of mem_enabled in the DVSEC, PCIE_DEVICE 95 + * @ranges: Number of active HDM ranges this device uses. 96 + * @dvsec_range: cached attributes of the ranges in the DVSEC, PCIE_DEVICE 97 + */ 98 + struct cxl_endpoint_dvsec_info { 99 + bool mem_enabled; 100 + int ranges; 101 + struct range dvsec_range[2]; 102 + }; 103 + 104 + /** 93 105 * struct cxl_dev_state - The driver device state 94 106 * 95 107 * cxl_dev_state represents the CXL driver/device state. It provides an ··· 130 118 * @next_volatile_bytes: volatile capacity change pending device reset 131 119 * @next_persistent_bytes: persistent capacity change pending device reset 132 120 * @component_reg_phys: register base of component registers 121 + * @info: Cached DVSEC information about the device. 133 122 * @mbox_send: @dev specific transport for transmitting mailbox commands 134 123 * 135 124 * See section 8.2.9.5.2 Capacity Configuration and Label Storage for ··· 162 149 u64 next_persistent_bytes; 163 150 164 151 resource_size_t component_reg_phys; 152 + struct cxl_endpoint_dvsec_info info; 165 153 166 154 int (*mbox_send)(struct cxl_dev_state *cxlds, struct cxl_mbox_cmd *cmd); 167 155 };
+13
drivers/cxl/cxlpci.h
··· 17 17 18 18 /* CXL 2.0 8.1.3: PCIe DVSEC for CXL Device */ 19 19 #define CXL_DVSEC_PCIE_DEVICE 0 20 + #define CXL_DVSEC_CAP_OFFSET 0xA 21 + #define CXL_DVSEC_MEM_CAPABLE BIT(2) 22 + #define CXL_DVSEC_HDM_COUNT_MASK GENMASK(5, 4) 23 + #define CXL_DVSEC_CTRL_OFFSET 0xC 24 + #define CXL_DVSEC_MEM_ENABLE BIT(2) 25 + #define CXL_DVSEC_RANGE_SIZE_HIGH(i) (0x18 + (i * 0x10)) 26 + #define CXL_DVSEC_RANGE_SIZE_LOW(i) (0x1C + (i * 0x10)) 27 + #define CXL_DVSEC_MEM_INFO_VALID BIT(0) 28 + #define CXL_DVSEC_MEM_ACTIVE BIT(1) 29 + #define CXL_DVSEC_MEM_SIZE_LOW_MASK GENMASK(31, 28) 30 + #define CXL_DVSEC_RANGE_BASE_HIGH(i) (0x20 + (i * 0x10)) 31 + #define CXL_DVSEC_RANGE_BASE_LOW(i) (0x24 + (i * 0x10)) 32 + #define CXL_DVSEC_MEM_BASE_LOW_MASK GENMASK(31, 28) 20 33 21 34 /* CXL 2.0 8.1.4: Non-CXL Function Map DVSEC */ 22 35 #define CXL_DVSEC_FUNCTION_MAP 2
+119
drivers/cxl/pci.c
··· 386 386 return rc; 387 387 } 388 388 389 + static int wait_for_valid(struct cxl_dev_state *cxlds) 390 + { 391 + struct pci_dev *pdev = to_pci_dev(cxlds->dev); 392 + int d = cxlds->cxl_dvsec, rc; 393 + u32 val; 394 + 395 + /* 396 + * Memory_Info_Valid: When set, indicates that the CXL Range 1 Size high 397 + * and Size Low registers are valid. Must be set within 1 second of 398 + * deassertion of reset to CXL device. Likely it is already set by the 399 + * time this runs, but otherwise give a 1.5 second timeout in case of 400 + * clock skew. 401 + */ 402 + rc = pci_read_config_dword(pdev, d + CXL_DVSEC_RANGE_SIZE_LOW(0), &val); 403 + if (rc) 404 + return rc; 405 + 406 + if (val & CXL_DVSEC_MEM_INFO_VALID) 407 + return 0; 408 + 409 + msleep(1500); 410 + 411 + rc = pci_read_config_dword(pdev, d + CXL_DVSEC_RANGE_SIZE_LOW(0), &val); 412 + if (rc) 413 + return rc; 414 + 415 + if (val & CXL_DVSEC_MEM_INFO_VALID) 416 + return 0; 417 + 418 + return -ETIMEDOUT; 419 + } 420 + 421 + static int cxl_dvsec_ranges(struct cxl_dev_state *cxlds) 422 + { 423 + struct cxl_endpoint_dvsec_info *info = &cxlds->info; 424 + struct pci_dev *pdev = to_pci_dev(cxlds->dev); 425 + int d = cxlds->cxl_dvsec; 426 + int hdm_count, rc, i; 427 + u16 cap, ctrl; 428 + 429 + if (!d) 430 + return -ENXIO; 431 + 432 + rc = pci_read_config_word(pdev, d + CXL_DVSEC_CAP_OFFSET, &cap); 433 + if (rc) 434 + return rc; 435 + 436 + rc = pci_read_config_word(pdev, d + CXL_DVSEC_CTRL_OFFSET, &ctrl); 437 + if (rc) 438 + return rc; 439 + 440 + if (!(cap & CXL_DVSEC_MEM_CAPABLE)) 441 + return -ENXIO; 442 + 443 + /* 444 + * It is not allowed by spec for MEM.capable to be set and have 0 legacy 445 + * HDM decoders (values > 2 are also undefined as of CXL 2.0). As this 446 + * driver is for a spec defined class code which must be CXL.mem 447 + * capable, there is no point in continuing to enable CXL.mem. 448 + */ 449 + hdm_count = FIELD_GET(CXL_DVSEC_HDM_COUNT_MASK, cap); 450 + if (!hdm_count || hdm_count > 2) 451 + return -EINVAL; 452 + 453 + rc = wait_for_valid(cxlds); 454 + if (rc) 455 + return rc; 456 + 457 + info->mem_enabled = FIELD_GET(CXL_DVSEC_MEM_ENABLE, ctrl); 458 + 459 + for (i = 0; i < hdm_count; i++) { 460 + u64 base, size; 461 + u32 temp; 462 + 463 + rc = pci_read_config_dword( 464 + pdev, d + CXL_DVSEC_RANGE_SIZE_HIGH(i), &temp); 465 + if (rc) 466 + return rc; 467 + 468 + size = (u64)temp << 32; 469 + 470 + rc = pci_read_config_dword( 471 + pdev, d + CXL_DVSEC_RANGE_SIZE_LOW(i), &temp); 472 + if (rc) 473 + return rc; 474 + 475 + size |= temp & CXL_DVSEC_MEM_SIZE_LOW_MASK; 476 + 477 + rc = pci_read_config_dword( 478 + pdev, d + CXL_DVSEC_RANGE_BASE_HIGH(i), &temp); 479 + if (rc) 480 + return rc; 481 + 482 + base = (u64)temp << 32; 483 + 484 + rc = pci_read_config_dword( 485 + pdev, d + CXL_DVSEC_RANGE_BASE_LOW(i), &temp); 486 + if (rc) 487 + return rc; 488 + 489 + base |= temp & CXL_DVSEC_MEM_BASE_LOW_MASK; 490 + 491 + info->dvsec_range[i] = (struct range) { 492 + .start = base, 493 + .end = base + size - 1 494 + }; 495 + 496 + if (size) 497 + info->ranges++; 498 + } 499 + 500 + return 0; 501 + } 502 + 389 503 static int cxl_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) 390 504 { 391 505 struct cxl_register_map map; ··· 562 448 rc = cxl_mem_create_range_info(cxlds); 563 449 if (rc) 564 450 return rc; 451 + 452 + rc = cxl_dvsec_ranges(cxlds); 453 + if (rc) 454 + dev_warn(&pdev->dev, 455 + "Failed to get DVSEC range information (%d)\n", rc); 565 456 566 457 cxlmd = devm_cxl_add_memdev(cxlds); 567 458 if (IS_ERR(cxlmd))