2023-12-21 22:03:13 +00:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-only
|
|
|
|
/* Copyright(c) 2023 Intel Corporation. All rights reserved. */
|
|
|
|
#include <linux/acpi.h>
|
|
|
|
#include <linux/xarray.h>
|
|
|
|
#include <linux/fw_table.h>
|
2023-12-21 22:03:20 +00:00
|
|
|
#include <linux/node.h>
|
|
|
|
#include <linux/overflow.h>
|
2023-12-21 22:03:13 +00:00
|
|
|
#include "cxlpci.h"
|
|
|
|
#include "cxl.h"
|
|
|
|
|
|
|
|
struct dsmas_entry {
|
|
|
|
struct range dpa_range;
|
|
|
|
u8 handle;
|
2023-12-21 22:03:20 +00:00
|
|
|
struct access_coordinate coord;
|
2023-12-21 22:03:13 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static int cdat_dsmas_handler(union acpi_subtable_headers *header, void *arg,
|
|
|
|
const unsigned long end)
|
|
|
|
{
|
|
|
|
struct acpi_cdat_header *hdr = &header->cdat;
|
|
|
|
struct acpi_cdat_dsmas *dsmas;
|
|
|
|
int size = sizeof(*hdr) + sizeof(*dsmas);
|
|
|
|
struct xarray *dsmas_xa = arg;
|
|
|
|
struct dsmas_entry *dent;
|
|
|
|
u16 len;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
len = le16_to_cpu((__force __le16)hdr->length);
|
|
|
|
if (len != size || (unsigned long)hdr + len > end) {
|
|
|
|
pr_warn("Malformed DSMAS table length: (%u:%u)\n", size, len);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Skip common header */
|
|
|
|
dsmas = (struct acpi_cdat_dsmas *)(hdr + 1);
|
|
|
|
|
|
|
|
dent = kzalloc(sizeof(*dent), GFP_KERNEL);
|
|
|
|
if (!dent)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
dent->handle = dsmas->dsmad_handle;
|
|
|
|
dent->dpa_range.start = le64_to_cpu((__force __le64)dsmas->dpa_base_address);
|
|
|
|
dent->dpa_range.end = le64_to_cpu((__force __le64)dsmas->dpa_base_address) +
|
|
|
|
le64_to_cpu((__force __le64)dsmas->dpa_length) - 1;
|
|
|
|
|
|
|
|
rc = xa_insert(dsmas_xa, dent->handle, dent, GFP_KERNEL);
|
|
|
|
if (rc) {
|
|
|
|
kfree(dent);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2023-12-21 22:03:20 +00:00
|
|
|
static void cxl_access_coordinate_set(struct access_coordinate *coord,
|
|
|
|
int access, unsigned int val)
|
|
|
|
{
|
|
|
|
switch (access) {
|
|
|
|
case ACPI_HMAT_ACCESS_LATENCY:
|
|
|
|
coord->read_latency = val;
|
|
|
|
coord->write_latency = val;
|
|
|
|
break;
|
|
|
|
case ACPI_HMAT_READ_LATENCY:
|
|
|
|
coord->read_latency = val;
|
|
|
|
break;
|
|
|
|
case ACPI_HMAT_WRITE_LATENCY:
|
|
|
|
coord->write_latency = val;
|
|
|
|
break;
|
|
|
|
case ACPI_HMAT_ACCESS_BANDWIDTH:
|
|
|
|
coord->read_bandwidth = val;
|
|
|
|
coord->write_bandwidth = val;
|
|
|
|
break;
|
|
|
|
case ACPI_HMAT_READ_BANDWIDTH:
|
|
|
|
coord->read_bandwidth = val;
|
|
|
|
break;
|
|
|
|
case ACPI_HMAT_WRITE_BANDWIDTH:
|
|
|
|
coord->write_bandwidth = val;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int cdat_dslbis_handler(union acpi_subtable_headers *header, void *arg,
|
|
|
|
const unsigned long end)
|
|
|
|
{
|
|
|
|
struct acpi_cdat_header *hdr = &header->cdat;
|
|
|
|
struct acpi_cdat_dslbis *dslbis;
|
|
|
|
int size = sizeof(*hdr) + sizeof(*dslbis);
|
|
|
|
struct xarray *dsmas_xa = arg;
|
|
|
|
struct dsmas_entry *dent;
|
|
|
|
__le64 le_base;
|
|
|
|
__le16 le_val;
|
|
|
|
u64 val;
|
|
|
|
u16 len;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
len = le16_to_cpu((__force __le16)hdr->length);
|
|
|
|
if (len != size || (unsigned long)hdr + len > end) {
|
|
|
|
pr_warn("Malformed DSLBIS table length: (%u:%u)\n", size, len);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Skip common header */
|
|
|
|
dslbis = (struct acpi_cdat_dslbis *)(hdr + 1);
|
|
|
|
|
|
|
|
/* Skip unrecognized data type */
|
|
|
|
if (dslbis->data_type > ACPI_HMAT_WRITE_BANDWIDTH)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* Not a memory type, skip */
|
|
|
|
if ((dslbis->flags & ACPI_HMAT_MEMORY_HIERARCHY) != ACPI_HMAT_MEMORY)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
dent = xa_load(dsmas_xa, dslbis->handle);
|
|
|
|
if (!dent) {
|
|
|
|
pr_warn("No matching DSMAS entry for DSLBIS entry.\n");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
le_base = (__force __le64)dslbis->entry_base_unit;
|
|
|
|
le_val = (__force __le16)dslbis->entry[0];
|
|
|
|
rc = check_mul_overflow(le64_to_cpu(le_base),
|
|
|
|
le16_to_cpu(le_val), &val);
|
|
|
|
if (rc)
|
|
|
|
pr_warn("DSLBIS value overflowed.\n");
|
|
|
|
|
|
|
|
cxl_access_coordinate_set(&dent->coord, dslbis->data_type, val);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int cdat_table_parse_output(int rc)
|
|
|
|
{
|
|
|
|
if (rc < 0)
|
|
|
|
return rc;
|
|
|
|
if (rc == 0)
|
|
|
|
return -ENOENT;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2023-12-21 22:03:13 +00:00
|
|
|
static int cxl_cdat_endpoint_process(struct cxl_port *port,
|
|
|
|
struct xarray *dsmas_xa)
|
|
|
|
{
|
2023-12-21 22:03:20 +00:00
|
|
|
int rc;
|
|
|
|
|
|
|
|
rc = cdat_table_parse(ACPI_CDAT_TYPE_DSMAS, cdat_dsmas_handler,
|
|
|
|
dsmas_xa, port->cdat.table);
|
|
|
|
rc = cdat_table_parse_output(rc);
|
|
|
|
if (rc)
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
rc = cdat_table_parse(ACPI_CDAT_TYPE_DSLBIS, cdat_dslbis_handler,
|
|
|
|
dsmas_xa, port->cdat.table);
|
|
|
|
return cdat_table_parse_output(rc);
|
2023-12-21 22:03:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void discard_dsmas(struct xarray *xa)
|
|
|
|
{
|
|
|
|
unsigned long index;
|
|
|
|
void *ent;
|
|
|
|
|
|
|
|
xa_for_each(xa, index, ent) {
|
|
|
|
xa_erase(xa, index);
|
|
|
|
kfree(ent);
|
|
|
|
}
|
|
|
|
xa_destroy(xa);
|
|
|
|
}
|
|
|
|
DEFINE_FREE(dsmas, struct xarray *, if (_T) discard_dsmas(_T))
|
|
|
|
|
|
|
|
void cxl_endpoint_parse_cdat(struct cxl_port *port)
|
|
|
|
{
|
|
|
|
struct xarray __dsmas_xa;
|
|
|
|
struct xarray *dsmas_xa __free(dsmas) = &__dsmas_xa;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
xa_init(&__dsmas_xa);
|
|
|
|
if (!port->cdat.table)
|
|
|
|
return;
|
|
|
|
|
|
|
|
rc = cxl_cdat_endpoint_process(port, dsmas_xa);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_dbg(&port->dev, "Failed to parse CDAT: %d\n", rc);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Performance data processing */
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_NS_GPL(cxl_endpoint_parse_cdat, CXL);
|
|
|
|
|
|
|
|
MODULE_IMPORT_NS(CXL);
|