Skip to content

Commit c970060

Browse files
weiny2djbw
authored andcommitted
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 <[email protected]> Co-developed-by: Jonathan Cameron <[email protected]> Signed-off-by: Ira Weiny <[email protected]> [djbw: drop in-kernel parsing infra for now, and other minor fixups] Reviewed-by: Jonathan Cameron <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Dan Williams <[email protected]>
1 parent 9d6794f commit c970060

File tree

5 files changed

+244
-0
lines changed

5 files changed

+244
-0
lines changed

Documentation/ABI/testing/sysfs-bus-cxl

+10
Original file line numberDiff line numberDiff line change
@@ -164,3 +164,13 @@ Description:
164164
expander memory (type-3). The 'target_type' attribute indicates
165165
the current setting which may dynamically change based on what
166166
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+
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.

drivers/cxl/core/pci.c

+173
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include <linux/device.h>
55
#include <linux/delay.h>
66
#include <linux/pci.h>
7+
#include <linux/pci-doe.h>
78
#include <cxlpci.h>
89
#include <cxlmem.h>
910
#include <cxl.h>
@@ -452,3 +453,175 @@ int cxl_hdm_decode_init(struct cxl_dev_state *cxlds, struct cxl_hdm *cxlhdm)
452453
return 0;
453454
}
454455
EXPORT_SYMBOL_NS_GPL(cxl_hdm_decode_init, CXL);
456+
457+
#define CXL_DOE_TABLE_ACCESS_REQ_CODE 0x000000ff
458+
#define CXL_DOE_TABLE_ACCESS_REQ_CODE_READ 0
459+
#define CXL_DOE_TABLE_ACCESS_TABLE_TYPE 0x0000ff00
460+
#define CXL_DOE_TABLE_ACCESS_TABLE_TYPE_CDATA 0
461+
#define CXL_DOE_TABLE_ACCESS_ENTRY_HANDLE 0xffff0000
462+
#define CXL_DOE_TABLE_ACCESS_LAST_ENTRY 0xffff
463+
#define CXL_DOE_PROTOCOL_TABLE_ACCESS 2
464+
465+
static struct pci_doe_mb *find_cdat_doe(struct device *uport)
466+
{
467+
struct cxl_memdev *cxlmd;
468+
struct cxl_dev_state *cxlds;
469+
unsigned long index;
470+
void *entry;
471+
472+
cxlmd = to_cxl_memdev(uport);
473+
cxlds = cxlmd->cxlds;
474+
475+
xa_for_each(&cxlds->doe_mbs, index, entry) {
476+
struct pci_doe_mb *cur = entry;
477+
478+
if (pci_doe_supports_prot(cur, PCI_DVSEC_VENDOR_ID_CXL,
479+
CXL_DOE_PROTOCOL_TABLE_ACCESS))
480+
return cur;
481+
}
482+
483+
return NULL;
484+
}
485+
486+
#define CDAT_DOE_REQ(entry_handle) \
487+
(FIELD_PREP(CXL_DOE_TABLE_ACCESS_REQ_CODE, \
488+
CXL_DOE_TABLE_ACCESS_REQ_CODE_READ) | \
489+
FIELD_PREP(CXL_DOE_TABLE_ACCESS_TABLE_TYPE, \
490+
CXL_DOE_TABLE_ACCESS_TABLE_TYPE_CDATA) | \
491+
FIELD_PREP(CXL_DOE_TABLE_ACCESS_ENTRY_HANDLE, (entry_handle)))
492+
493+
static void cxl_doe_task_complete(struct pci_doe_task *task)
494+
{
495+
complete(task->private);
496+
}
497+
498+
struct cdat_doe_task {
499+
u32 request_pl;
500+
u32 response_pl[32];
501+
struct completion c;
502+
struct pci_doe_task task;
503+
};
504+
505+
#define DECLARE_CDAT_DOE_TASK(req, cdt) \
506+
struct cdat_doe_task cdt = { \
507+
.c = COMPLETION_INITIALIZER_ONSTACK(cdt.c), \
508+
.request_pl = req, \
509+
.task = { \
510+
.prot.vid = PCI_DVSEC_VENDOR_ID_CXL, \
511+
.prot.type = CXL_DOE_PROTOCOL_TABLE_ACCESS, \
512+
.request_pl = &cdt.request_pl, \
513+
.request_pl_sz = sizeof(cdt.request_pl), \
514+
.response_pl = cdt.response_pl, \
515+
.response_pl_sz = sizeof(cdt.response_pl), \
516+
.complete = cxl_doe_task_complete, \
517+
.private = &cdt.c, \
518+
} \
519+
}
520+
521+
static int cxl_cdat_get_length(struct device *dev,
522+
struct pci_doe_mb *cdat_doe,
523+
size_t *length)
524+
{
525+
DECLARE_CDAT_DOE_TASK(CDAT_DOE_REQ(0), t);
526+
int rc;
527+
528+
rc = pci_doe_submit_task(cdat_doe, &t.task);
529+
if (rc < 0) {
530+
dev_err(dev, "DOE submit failed: %d", rc);
531+
return rc;
532+
}
533+
wait_for_completion(&t.c);
534+
if (t.task.rv < sizeof(u32))
535+
return -EIO;
536+
537+
*length = t.response_pl[1];
538+
dev_dbg(dev, "CDAT length %zu\n", *length);
539+
540+
return 0;
541+
}
542+
543+
static int cxl_cdat_read_table(struct device *dev,
544+
struct pci_doe_mb *cdat_doe,
545+
struct cxl_cdat *cdat)
546+
{
547+
size_t length = cdat->length;
548+
u32 *data = cdat->table;
549+
int entry_handle = 0;
550+
551+
do {
552+
DECLARE_CDAT_DOE_TASK(CDAT_DOE_REQ(entry_handle), t);
553+
size_t entry_dw;
554+
u32 *entry;
555+
int rc;
556+
557+
rc = pci_doe_submit_task(cdat_doe, &t.task);
558+
if (rc < 0) {
559+
dev_err(dev, "DOE submit failed: %d", rc);
560+
return rc;
561+
}
562+
wait_for_completion(&t.c);
563+
/* 1 DW header + 1 DW data min */
564+
if (t.task.rv < (2 * sizeof(u32)))
565+
return -EIO;
566+
567+
/* Get the CXL table access header entry handle */
568+
entry_handle = FIELD_GET(CXL_DOE_TABLE_ACCESS_ENTRY_HANDLE,
569+
t.response_pl[0]);
570+
entry = t.response_pl + 1;
571+
entry_dw = t.task.rv / sizeof(u32);
572+
/* Skip Header */
573+
entry_dw -= 1;
574+
entry_dw = min(length / sizeof(u32), entry_dw);
575+
/* Prevent length < 1 DW from causing a buffer overflow */
576+
if (entry_dw) {
577+
memcpy(data, entry, entry_dw * sizeof(u32));
578+
length -= entry_dw * sizeof(u32);
579+
data += entry_dw;
580+
}
581+
} while (entry_handle != CXL_DOE_TABLE_ACCESS_LAST_ENTRY);
582+
583+
return 0;
584+
}
585+
586+
/**
587+
* read_cdat_data - Read the CDAT data on this port
588+
* @port: Port to read data from
589+
*
590+
* This call will sleep waiting for responses from the DOE mailbox.
591+
*/
592+
void read_cdat_data(struct cxl_port *port)
593+
{
594+
struct pci_doe_mb *cdat_doe;
595+
struct device *dev = &port->dev;
596+
struct device *uport = port->uport;
597+
size_t cdat_length;
598+
int rc;
599+
600+
cdat_doe = find_cdat_doe(uport);
601+
if (!cdat_doe) {
602+
dev_dbg(dev, "No CDAT mailbox\n");
603+
return;
604+
}
605+
606+
port->cdat_available = true;
607+
608+
if (cxl_cdat_get_length(dev, cdat_doe, &cdat_length)) {
609+
dev_dbg(dev, "No CDAT length\n");
610+
return;
611+
}
612+
613+
port->cdat.table = devm_kzalloc(dev, cdat_length, GFP_KERNEL);
614+
if (!port->cdat.table)
615+
return;
616+
617+
port->cdat.length = cdat_length;
618+
rc = cxl_cdat_read_table(dev, cdat_doe, &port->cdat);
619+
if (rc) {
620+
/* Don't leave table data allocated on error */
621+
devm_kfree(dev, port->cdat.table);
622+
port->cdat.table = NULL;
623+
port->cdat.length = 0;
624+
dev_err(dev, "CDAT data read error\n");
625+
}
626+
}
627+
EXPORT_SYMBOL_NS_GPL(read_cdat_data, CXL);

drivers/cxl/cxl.h

+7
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,8 @@ struct cxl_nvdimm {
289289
* @component_reg_phys: component register capability base address (optional)
290290
* @dead: last ep has been removed, force port re-creation
291291
* @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
292294
*/
293295
struct cxl_port {
294296
struct device dev;
@@ -301,6 +303,11 @@ struct cxl_port {
301303
resource_size_t component_reg_phys;
302304
bool dead;
303305
unsigned int depth;
306+
struct cxl_cdat {
307+
void *table;
308+
size_t length;
309+
} cdat;
310+
bool cdat_available;
304311
};
305312

306313
/**

drivers/cxl/cxlpci.h

+1
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,5 @@ static inline resource_size_t cxl_regmap_to_base(struct pci_dev *pdev,
7474
int devm_cxl_port_enumerate_dports(struct cxl_port *port);
7575
struct cxl_dev_state;
7676
int cxl_hdm_decode_init(struct cxl_dev_state *cxlds, struct cxl_hdm *cxlhdm);
77+
void read_cdat_data(struct cxl_port *port);
7778
#endif /* __CXL_PCI_H__ */

drivers/cxl/port.c

+53
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ static int cxl_port_probe(struct device *dev)
5353
struct cxl_memdev *cxlmd = to_cxl_memdev(port->uport);
5454
struct cxl_dev_state *cxlds = cxlmd->cxlds;
5555

56+
/* Cache the data early to ensure is_visible() works */
57+
read_cdat_data(port);
58+
5659
get_device(&cxlmd->dev);
5760
rc = devm_add_action_or_reset(dev, schedule_detach, cxlmd);
5861
if (rc)
@@ -78,10 +81,60 @@ static int cxl_port_probe(struct device *dev)
7881
return 0;
7982
}
8083

84+
static ssize_t CDAT_read(struct file *filp, struct kobject *kobj,
85+
struct bin_attribute *bin_attr, char *buf,
86+
loff_t offset, size_t count)
87+
{
88+
struct device *dev = kobj_to_dev(kobj);
89+
struct cxl_port *port = to_cxl_port(dev);
90+
91+
if (!port->cdat_available)
92+
return -ENXIO;
93+
94+
if (!port->cdat.table)
95+
return 0;
96+
97+
return memory_read_from_buffer(buf, count, &offset,
98+
port->cdat.table,
99+
port->cdat.length);
100+
}
101+
102+
static BIN_ATTR_ADMIN_RO(CDAT, 0);
103+
104+
static umode_t cxl_port_bin_attr_is_visible(struct kobject *kobj,
105+
struct bin_attribute *attr, int i)
106+
{
107+
struct device *dev = kobj_to_dev(kobj);
108+
struct cxl_port *port = to_cxl_port(dev);
109+
110+
if ((attr == &bin_attr_CDAT) && port->cdat_available)
111+
return attr->attr.mode;
112+
113+
return 0;
114+
}
115+
116+
static struct bin_attribute *cxl_cdat_bin_attributes[] = {
117+
&bin_attr_CDAT,
118+
NULL,
119+
};
120+
121+
static struct attribute_group cxl_cdat_attribute_group = {
122+
.bin_attrs = cxl_cdat_bin_attributes,
123+
.is_bin_visible = cxl_port_bin_attr_is_visible,
124+
};
125+
126+
static const struct attribute_group *cxl_port_attribute_groups[] = {
127+
&cxl_cdat_attribute_group,
128+
NULL,
129+
};
130+
81131
static struct cxl_driver cxl_port_driver = {
82132
.name = "cxl_port",
83133
.probe = cxl_port_probe,
84134
.id = CXL_DEVICE_PORT,
135+
.drv = {
136+
.dev_groups = cxl_port_attribute_groups,
137+
},
85138
};
86139

87140
module_cxl_driver(cxl_port_driver);

0 commit comments

Comments
 (0)