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

cxl/port: Read CDAT table

The per-device CDAT data provides performance data that is relevant for
mapping which CXL devices can participate in which CXL ranges by QTG
(QoS Throttling Group) (per ECN: CXL 2.0 CEDT CFMWS & QTG_DSM) [1]. The
QTG association specified in the ECN is advisory. Until the
cxl_acpi driver grows support for invoking the QTG _DSM method the CDAT
data is only of interest to userspace that may need it for debug
purposes.

Search the DOE mailboxes available, query CDAT data, cache the data and
make it available via a sysfs binary attribute per endpoint at:

/sys/bus/cxl/devices/endpointX/CDAT

...similar to other ACPI-structured table data in
/sys/firmware/ACPI/tables. The CDAT is relative to 'struct cxl_port'
objects since switches in addition to endpoints can host a CDAT
instance. Switch CDAT support is not implemented.

This does not support table updates at runtime. It will always provide
whatever was there when first cached. It is also the case that table
updates are not expected outside of explicit DPA address map affecting
commands like Set Partition with the immediate flag set. Given that the
driver does not support Set Partition with the immediate flag set there
is no current need for update support.

Link: https://www.computeexpresslink.org/spec-landing [1]
Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
Co-developed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
Signed-off-by: Ira Weiny <ira.weiny@intel.com>
[djbw: drop in-kernel parsing infra for now, and other minor fixups]
Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
Link: https://lore.kernel.org/r/20220719205249.566684-7-ira.weiny@intel.com
Signed-off-by: Dan Williams <dan.j.williams@intel.com>

authored by

Ira Weiny and committed by
Dan Williams
c9700604 9d6794fe

+244
+10
Documentation/ABI/testing/sysfs-bus-cxl
··· 164 164 expander memory (type-3). The 'target_type' attribute indicates 165 165 the current setting which may dynamically change based on what 166 166 memory regions are activated in this decode hierarchy. 167 + 168 + What: /sys/bus/cxl/devices/endpointX/CDAT 169 + Date: July, 2022 170 + KernelVersion: v5.20 171 + Contact: linux-cxl@vger.kernel.org 172 + Description: 173 + (RO) If this sysfs entry is not present no DOE mailbox was 174 + found to support CDAT data. If it is present and the length of 175 + the data is 0 reading the CDAT data failed. Otherwise the CDAT 176 + data is reported.
+173
drivers/cxl/core/pci.c
··· 4 4 #include <linux/device.h> 5 5 #include <linux/delay.h> 6 6 #include <linux/pci.h> 7 + #include <linux/pci-doe.h> 7 8 #include <cxlpci.h> 8 9 #include <cxlmem.h> 9 10 #include <cxl.h> ··· 453 452 return 0; 454 453 } 455 454 EXPORT_SYMBOL_NS_GPL(cxl_hdm_decode_init, CXL); 455 + 456 + #define CXL_DOE_TABLE_ACCESS_REQ_CODE 0x000000ff 457 + #define CXL_DOE_TABLE_ACCESS_REQ_CODE_READ 0 458 + #define CXL_DOE_TABLE_ACCESS_TABLE_TYPE 0x0000ff00 459 + #define CXL_DOE_TABLE_ACCESS_TABLE_TYPE_CDATA 0 460 + #define CXL_DOE_TABLE_ACCESS_ENTRY_HANDLE 0xffff0000 461 + #define CXL_DOE_TABLE_ACCESS_LAST_ENTRY 0xffff 462 + #define CXL_DOE_PROTOCOL_TABLE_ACCESS 2 463 + 464 + static struct pci_doe_mb *find_cdat_doe(struct device *uport) 465 + { 466 + struct cxl_memdev *cxlmd; 467 + struct cxl_dev_state *cxlds; 468 + unsigned long index; 469 + void *entry; 470 + 471 + cxlmd = to_cxl_memdev(uport); 472 + cxlds = cxlmd->cxlds; 473 + 474 + xa_for_each(&cxlds->doe_mbs, index, entry) { 475 + struct pci_doe_mb *cur = entry; 476 + 477 + if (pci_doe_supports_prot(cur, PCI_DVSEC_VENDOR_ID_CXL, 478 + CXL_DOE_PROTOCOL_TABLE_ACCESS)) 479 + return cur; 480 + } 481 + 482 + return NULL; 483 + } 484 + 485 + #define CDAT_DOE_REQ(entry_handle) \ 486 + (FIELD_PREP(CXL_DOE_TABLE_ACCESS_REQ_CODE, \ 487 + CXL_DOE_TABLE_ACCESS_REQ_CODE_READ) | \ 488 + FIELD_PREP(CXL_DOE_TABLE_ACCESS_TABLE_TYPE, \ 489 + CXL_DOE_TABLE_ACCESS_TABLE_TYPE_CDATA) | \ 490 + FIELD_PREP(CXL_DOE_TABLE_ACCESS_ENTRY_HANDLE, (entry_handle))) 491 + 492 + static void cxl_doe_task_complete(struct pci_doe_task *task) 493 + { 494 + complete(task->private); 495 + } 496 + 497 + struct cdat_doe_task { 498 + u32 request_pl; 499 + u32 response_pl[32]; 500 + struct completion c; 501 + struct pci_doe_task task; 502 + }; 503 + 504 + #define DECLARE_CDAT_DOE_TASK(req, cdt) \ 505 + struct cdat_doe_task cdt = { \ 506 + .c = COMPLETION_INITIALIZER_ONSTACK(cdt.c), \ 507 + .request_pl = req, \ 508 + .task = { \ 509 + .prot.vid = PCI_DVSEC_VENDOR_ID_CXL, \ 510 + .prot.type = CXL_DOE_PROTOCOL_TABLE_ACCESS, \ 511 + .request_pl = &cdt.request_pl, \ 512 + .request_pl_sz = sizeof(cdt.request_pl), \ 513 + .response_pl = cdt.response_pl, \ 514 + .response_pl_sz = sizeof(cdt.response_pl), \ 515 + .complete = cxl_doe_task_complete, \ 516 + .private = &cdt.c, \ 517 + } \ 518 + } 519 + 520 + static int cxl_cdat_get_length(struct device *dev, 521 + struct pci_doe_mb *cdat_doe, 522 + size_t *length) 523 + { 524 + DECLARE_CDAT_DOE_TASK(CDAT_DOE_REQ(0), t); 525 + int rc; 526 + 527 + rc = pci_doe_submit_task(cdat_doe, &t.task); 528 + if (rc < 0) { 529 + dev_err(dev, "DOE submit failed: %d", rc); 530 + return rc; 531 + } 532 + wait_for_completion(&t.c); 533 + if (t.task.rv < sizeof(u32)) 534 + return -EIO; 535 + 536 + *length = t.response_pl[1]; 537 + dev_dbg(dev, "CDAT length %zu\n", *length); 538 + 539 + return 0; 540 + } 541 + 542 + static int cxl_cdat_read_table(struct device *dev, 543 + struct pci_doe_mb *cdat_doe, 544 + struct cxl_cdat *cdat) 545 + { 546 + size_t length = cdat->length; 547 + u32 *data = cdat->table; 548 + int entry_handle = 0; 549 + 550 + do { 551 + DECLARE_CDAT_DOE_TASK(CDAT_DOE_REQ(entry_handle), t); 552 + size_t entry_dw; 553 + u32 *entry; 554 + int rc; 555 + 556 + rc = pci_doe_submit_task(cdat_doe, &t.task); 557 + if (rc < 0) { 558 + dev_err(dev, "DOE submit failed: %d", rc); 559 + return rc; 560 + } 561 + wait_for_completion(&t.c); 562 + /* 1 DW header + 1 DW data min */ 563 + if (t.task.rv < (2 * sizeof(u32))) 564 + return -EIO; 565 + 566 + /* Get the CXL table access header entry handle */ 567 + entry_handle = FIELD_GET(CXL_DOE_TABLE_ACCESS_ENTRY_HANDLE, 568 + t.response_pl[0]); 569 + entry = t.response_pl + 1; 570 + entry_dw = t.task.rv / sizeof(u32); 571 + /* Skip Header */ 572 + entry_dw -= 1; 573 + entry_dw = min(length / sizeof(u32), entry_dw); 574 + /* Prevent length < 1 DW from causing a buffer overflow */ 575 + if (entry_dw) { 576 + memcpy(data, entry, entry_dw * sizeof(u32)); 577 + length -= entry_dw * sizeof(u32); 578 + data += entry_dw; 579 + } 580 + } while (entry_handle != CXL_DOE_TABLE_ACCESS_LAST_ENTRY); 581 + 582 + return 0; 583 + } 584 + 585 + /** 586 + * read_cdat_data - Read the CDAT data on this port 587 + * @port: Port to read data from 588 + * 589 + * This call will sleep waiting for responses from the DOE mailbox. 590 + */ 591 + void read_cdat_data(struct cxl_port *port) 592 + { 593 + struct pci_doe_mb *cdat_doe; 594 + struct device *dev = &port->dev; 595 + struct device *uport = port->uport; 596 + size_t cdat_length; 597 + int rc; 598 + 599 + cdat_doe = find_cdat_doe(uport); 600 + if (!cdat_doe) { 601 + dev_dbg(dev, "No CDAT mailbox\n"); 602 + return; 603 + } 604 + 605 + port->cdat_available = true; 606 + 607 + if (cxl_cdat_get_length(dev, cdat_doe, &cdat_length)) { 608 + dev_dbg(dev, "No CDAT length\n"); 609 + return; 610 + } 611 + 612 + port->cdat.table = devm_kzalloc(dev, cdat_length, GFP_KERNEL); 613 + if (!port->cdat.table) 614 + return; 615 + 616 + port->cdat.length = cdat_length; 617 + rc = cxl_cdat_read_table(dev, cdat_doe, &port->cdat); 618 + if (rc) { 619 + /* Don't leave table data allocated on error */ 620 + devm_kfree(dev, port->cdat.table); 621 + port->cdat.table = NULL; 622 + port->cdat.length = 0; 623 + dev_err(dev, "CDAT data read error\n"); 624 + } 625 + } 626 + EXPORT_SYMBOL_NS_GPL(read_cdat_data, CXL);
+7
drivers/cxl/cxl.h
··· 289 289 * @component_reg_phys: component register capability base address (optional) 290 290 * @dead: last ep has been removed, force port re-creation 291 291 * @depth: How deep this port is relative to the root. depth 0 is the root. 292 + * @cdat: Cached CDAT data 293 + * @cdat_available: Should a CDAT attribute be available in sysfs 292 294 */ 293 295 struct cxl_port { 294 296 struct device dev; ··· 303 301 resource_size_t component_reg_phys; 304 302 bool dead; 305 303 unsigned int depth; 304 + struct cxl_cdat { 305 + void *table; 306 + size_t length; 307 + } cdat; 308 + bool cdat_available; 306 309 }; 307 310 308 311 /**
+1
drivers/cxl/cxlpci.h
··· 74 74 int devm_cxl_port_enumerate_dports(struct cxl_port *port); 75 75 struct cxl_dev_state; 76 76 int cxl_hdm_decode_init(struct cxl_dev_state *cxlds, struct cxl_hdm *cxlhdm); 77 + void read_cdat_data(struct cxl_port *port); 77 78 #endif /* __CXL_PCI_H__ */
+53
drivers/cxl/port.c
··· 53 53 struct cxl_memdev *cxlmd = to_cxl_memdev(port->uport); 54 54 struct cxl_dev_state *cxlds = cxlmd->cxlds; 55 55 56 + /* Cache the data early to ensure is_visible() works */ 57 + read_cdat_data(port); 58 + 56 59 get_device(&cxlmd->dev); 57 60 rc = devm_add_action_or_reset(dev, schedule_detach, cxlmd); 58 61 if (rc) ··· 81 78 return 0; 82 79 } 83 80 81 + static ssize_t CDAT_read(struct file *filp, struct kobject *kobj, 82 + struct bin_attribute *bin_attr, char *buf, 83 + loff_t offset, size_t count) 84 + { 85 + struct device *dev = kobj_to_dev(kobj); 86 + struct cxl_port *port = to_cxl_port(dev); 87 + 88 + if (!port->cdat_available) 89 + return -ENXIO; 90 + 91 + if (!port->cdat.table) 92 + return 0; 93 + 94 + return memory_read_from_buffer(buf, count, &offset, 95 + port->cdat.table, 96 + port->cdat.length); 97 + } 98 + 99 + static BIN_ATTR_ADMIN_RO(CDAT, 0); 100 + 101 + static umode_t cxl_port_bin_attr_is_visible(struct kobject *kobj, 102 + struct bin_attribute *attr, int i) 103 + { 104 + struct device *dev = kobj_to_dev(kobj); 105 + struct cxl_port *port = to_cxl_port(dev); 106 + 107 + if ((attr == &bin_attr_CDAT) && port->cdat_available) 108 + return attr->attr.mode; 109 + 110 + return 0; 111 + } 112 + 113 + static struct bin_attribute *cxl_cdat_bin_attributes[] = { 114 + &bin_attr_CDAT, 115 + NULL, 116 + }; 117 + 118 + static struct attribute_group cxl_cdat_attribute_group = { 119 + .bin_attrs = cxl_cdat_bin_attributes, 120 + .is_bin_visible = cxl_port_bin_attr_is_visible, 121 + }; 122 + 123 + static const struct attribute_group *cxl_port_attribute_groups[] = { 124 + &cxl_cdat_attribute_group, 125 + NULL, 126 + }; 127 + 84 128 static struct cxl_driver cxl_port_driver = { 85 129 .name = "cxl_port", 86 130 .probe = cxl_port_probe, 87 131 .id = CXL_DEVICE_PORT, 132 + .drv = { 133 + .dev_groups = cxl_port_attribute_groups, 134 + }, 88 135 }; 89 136 90 137 module_cxl_driver(cxl_port_driver);