|
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>
|
@@ -452,3 +453,175 @@ int cxl_hdm_decode_init(struct cxl_dev_state *cxlds, struct cxl_hdm *cxlhdm)
|
452 | 453 | return 0;
|
453 | 454 | }
|
454 | 455 | 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); |
0 commit comments