Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+246
View File
@@ -0,0 +1,246 @@
# SPDX-License-Identifier: GPL-2.0-only
menuconfig CXL_BUS
tristate "CXL (Compute Express Link) Devices Support"
depends on PCI
select FW_LOADER
select FW_UPLOAD
select PCI_DOE
select FIRMWARE_TABLE
select NUMA_KEEP_MEMINFO if NUMA_MEMBLKS
select FWCTL if CXL_FEATURES
help
CXL is a bus that is electrically compatible with PCI Express, but
layers three protocols on that signalling (CXL.io, CXL.cache, and
CXL.mem). The CXL.cache protocol allows devices to hold cachelines
locally, the CXL.mem protocol allows devices to be fully coherent
memory targets, the CXL.io protocol is equivalent to PCI Express.
Say 'y' to enable support for the configuration and management of
devices supporting these protocols.
if CXL_BUS
config CXL_PCI
tristate "PCI manageability"
default CXL_BUS
select CXL_MEM
help
The CXL specification defines a "CXL memory device" sub-class in the
PCI "memory controller" base class of devices. Device's identified by
this class code provide support for volatile and / or persistent
memory to be mapped into the system address map (Host-managed Device
Memory (HDM)).
Say 'y/m' to enable a driver that will attach to CXL memory expander
devices enumerated by the memory device class code for configuration
and management primarily via the mailbox interface. See Chapter 2.3
Type 3 CXL Device in the CXL 2.0 specification for more details.
If unsure say 'm'.
config CXL_MEM_RAW_COMMANDS
bool "RAW Command Interface for Memory Devices"
depends on CXL_PCI
help
Enable CXL RAW command interface.
The CXL driver ioctl interface may assign a kernel ioctl command
number for each specification defined opcode. At any given point in
time the number of opcodes that the specification defines and a device
may implement may exceed the kernel's set of associated ioctl function
numbers. The mismatch is either by omission, specification is too new,
or by design. When prototyping new hardware, or developing / debugging
the driver it is useful to be able to submit any possible command to
the hardware, even commands that may crash the kernel due to their
potential impact to memory currently in use by the kernel.
If developing CXL hardware or the driver say Y, otherwise say N.
config CXL_ACPI
tristate "CXL ACPI: Platform Support"
depends on ACPI
depends on ACPI_NUMA
depends on CXL_PMEM || !CXL_PMEM
default CXL_BUS
select ACPI_TABLE_LIB
select ACPI_HMAT
select CXL_PORT
help
Enable support for host managed device memory (HDM) resources
published by a platform's ACPI CXL memory layout description. See
Chapter 9.14.1 CXL Early Discovery Table (CEDT) in the CXL 2.0
specification, and CXL Fixed Memory Window Structures (CEDT.CFMWS)
(https://www.computeexpresslink.org/spec-landing). The CXL core
consumes these resource to publish the root of a cxl_port decode
hierarchy to map regions that represent System RAM, or Persistent
Memory regions to be managed by LIBNVDIMM.
If unsure say 'm'.
config CXL_PMEM
tristate "CXL PMEM: Persistent Memory Support"
depends on LIBNVDIMM
default CXL_BUS
help
In addition to typical memory resources a platform may also advertise
support for persistent memory attached via CXL. This support is
managed via a bridge driver from CXL to the LIBNVDIMM system
subsystem. Say 'y/m' to enable support for enumerating and
provisioning the persistent memory capacity of CXL memory expanders.
If unsure say 'm'.
config CXL_MEM
tristate "CXL: Memory Expansion"
default CXL_BUS
help
The CXL.mem protocol allows a device to act as a provider of "System
RAM" and/or "Persistent Memory" that is fully coherent as if the
memory were attached to the typical CPU memory controller. This is
known as HDM "Host-managed Device Memory".
Say 'y/m' to enable a driver that will attach to CXL.mem devices for
memory expansion and control of HDM. See Chapter 9.13 in the CXL 2.0
specification for a detailed description of HDM.
If unsure say 'm'.
config CXL_FEATURES
bool "CXL: Features"
depends on CXL_PCI
help
Enable support for CXL Features. A CXL device that includes a mailbox
supports commands that allows listing, getting, and setting of
optionally defined features such as memory sparing or post package
sparing. Vendors may define custom features for the device.
If unsure say 'n'
config CXL_EDAC_MEM_FEATURES
bool "CXL: EDAC Memory Features"
depends on EXPERT
depends on CXL_MEM
depends on CXL_FEATURES
depends on EDAC >= CXL_BUS
help
The CXL EDAC memory feature is optional and allows host to
control the EDAC memory features configurations of CXL memory
expander devices.
Say 'y' if you have an expert need to change default settings
of a memory RAS feature established by the platform/device.
Otherwise say 'n'.
config CXL_EDAC_SCRUB
bool "Enable CXL Patrol Scrub Control (Patrol Read)"
depends on CXL_EDAC_MEM_FEATURES
depends on EDAC_SCRUB
help
The CXL EDAC scrub control is optional and allows host to
control the scrub feature configurations of CXL memory expander
devices.
When enabled 'cxl_mem' and 'cxl_region' EDAC devices are
published with memory scrub control attributes as described by
Documentation/ABI/testing/sysfs-edac-scrub.
Say 'y' if you have an expert need to change default settings
of a memory scrub feature established by the platform/device
(e.g. scrub rates for the patrol scrub feature).
Otherwise say 'n'.
config CXL_EDAC_ECS
bool "Enable CXL Error Check Scrub (Repair)"
depends on CXL_EDAC_MEM_FEATURES
depends on EDAC_ECS
help
The CXL EDAC ECS control is optional and allows host to
control the ECS feature configurations of CXL memory expander
devices.
When enabled 'cxl_mem' EDAC devices are published with memory
ECS control attributes as described by
Documentation/ABI/testing/sysfs-edac-ecs.
Say 'y' if you have an expert need to change default settings
of a memory ECS feature established by the platform/device.
Otherwise say 'n'.
config CXL_EDAC_MEM_REPAIR
bool "Enable CXL Memory Repair"
depends on CXL_EDAC_MEM_FEATURES
depends on EDAC_MEM_REPAIR
help
The CXL EDAC memory repair control is optional and allows host
to control the memory repair features (e.g. sparing, PPR)
configurations of CXL memory expander devices.
When enabled, the memory repair feature requires an additional
memory of approximately 43KB to store CXL DRAM and CXL general
media event records.
When enabled 'cxl_mem' EDAC devices are published with memory
repair control attributes as described by
Documentation/ABI/testing/sysfs-edac-memory-repair.
Say 'y' if you have an expert need to change default settings
of a memory repair feature established by the platform/device.
Otherwise say 'n'.
config CXL_PORT
default CXL_BUS
tristate
config CXL_SUSPEND
def_bool y
depends on SUSPEND && CXL_MEM
config CXL_REGION
bool "CXL: Region Support"
default CXL_BUS
# For MAX_PHYSMEM_BITS
depends on SPARSEMEM
select MEMREGION
select GET_FREE_REGION
help
Enable the CXL core to enumerate and provision CXL regions. A CXL
region is defined by one or more CXL expanders that decode a given
system-physical address range. For CXL regions established by
platform-firmware this option enables memory error handling to
identify the devices participating in a given interleaved memory
range. Otherwise, platform-firmware managed CXL is enabled by being
placed in the system address map and does not need a driver.
If unsure say 'y'
config CXL_REGION_INVALIDATION_TEST
bool "CXL: Region Cache Management Bypass (TEST)"
depends on CXL_REGION
help
CXL Region management and security operations potentially invalidate
the content of CPU caches without notifying those caches to
invalidate the affected cachelines. The CXL Region driver attempts
to invalidate caches when those events occur. If that invalidation
fails the region will fail to enable. Reasons for cache
invalidation failure are due to the CPU not providing a cache
invalidation mechanism. For example usage of wbinvd is restricted to
bare metal x86. However, for testing purposes toggling this option
can disable that data integrity safety and proceed with enabling
regions when there might be conflicting contents in the CPU cache.
If unsure, or if this kernel is meant for production environments,
say N.
config CXL_MCE
def_bool y
depends on X86_MCE && MEMORY_FAILURE
config CXL_RAS
def_bool y
depends on ACPI_APEI_GHES && PCIEAER && CXL_BUS
config CXL_ATL
def_bool y
depends on CXL_REGION
depends on ACPI_PRMT && AMD_NB
endif
+21
View File
@@ -0,0 +1,21 @@
# SPDX-License-Identifier: GPL-2.0
# Order is important here for the built-in case:
# - 'core' first for fundamental init
# - 'port' before platform root drivers like 'acpi' so that CXL-root ports
# are immediately enabled
# - 'mem' and 'pmem' before endpoint drivers so that memdevs are
# immediately enabled
# - 'pci' last, also mirrors the hardware enumeration hierarchy
obj-y += core/
obj-$(CONFIG_CXL_PORT) += cxl_port.o
obj-$(CONFIG_CXL_ACPI) += cxl_acpi.o
obj-$(CONFIG_CXL_PMEM) += cxl_pmem.o
obj-$(CONFIG_CXL_MEM) += cxl_mem.o
obj-$(CONFIG_CXL_PCI) += cxl_pci.o
cxl_port-y := port.o
cxl_acpi-y := acpi.o
cxl_pmem-y := pmem.o security.o
cxl_mem-y := mem.o
cxl_pci-y := pci.o
File diff suppressed because it is too large Load Diff
+24
View File
@@ -0,0 +1,24 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_CXL_BUS) += cxl_core.o
obj-$(CONFIG_CXL_SUSPEND) += suspend.o
ccflags-y += -I$(srctree)/drivers/cxl
CFLAGS_trace.o = -DTRACE_INCLUDE_PATH=. -I$(src)
cxl_core-y := port.o
cxl_core-y += pmem.o
cxl_core-y += regs.o
cxl_core-y += memdev.o
cxl_core-y += mbox.o
cxl_core-y += pci.o
cxl_core-y += hdm.o
cxl_core-y += pmu.o
cxl_core-y += cdat.o
cxl_core-$(CONFIG_TRACING) += trace.o
cxl_core-$(CONFIG_CXL_REGION) += region.o region_pmem.o region_dax.o
cxl_core-$(CONFIG_CXL_MCE) += mce.o
cxl_core-$(CONFIG_CXL_FEATURES) += features.o
cxl_core-$(CONFIG_CXL_EDAC_MEM_FEATURES) += edac.o
cxl_core-$(CONFIG_CXL_RAS) += ras.o
cxl_core-$(CONFIG_CXL_RAS) += ras_rch.o
cxl_core-$(CONFIG_CXL_ATL) += atl.o
+211
View File
@@ -0,0 +1,211 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2025 Advanced Micro Devices, Inc.
*/
#include <linux/prmt.h>
#include <linux/pci.h>
#include <linux/acpi.h>
#include <cxlmem.h>
#include "core.h"
/*
* PRM Address Translation - CXL DPA to System Physical Address
*
* Reference:
*
* AMD Family 1Ah Models 00h–0Fh and Models 10h–1Fh
* ACPI v6.5 Porting Guide, Publication # 58088
*/
static const guid_t prm_cxl_dpa_spa_guid =
GUID_INIT(0xee41b397, 0x25d4, 0x452c, 0xad, 0x54, 0x48, 0xc6, 0xe3,
0x48, 0x0b, 0x94);
struct prm_cxl_dpa_spa_data {
u64 dpa;
u8 reserved;
u8 devfn;
u8 bus;
u8 segment;
u64 *spa;
} __packed;
static u64 prm_cxl_dpa_spa(struct pci_dev *pci_dev, u64 dpa)
{
struct prm_cxl_dpa_spa_data data;
u64 spa;
int rc;
data = (struct prm_cxl_dpa_spa_data) {
.dpa = dpa,
.devfn = pci_dev->devfn,
.bus = pci_dev->bus->number,
.segment = pci_domain_nr(pci_dev->bus),
.spa = &spa,
};
rc = acpi_call_prm_handler(prm_cxl_dpa_spa_guid, &data);
if (rc) {
pci_dbg(pci_dev, "failed to get SPA for %#llx: %d\n", dpa, rc);
return ULLONG_MAX;
}
pci_dbg(pci_dev, "PRM address translation: DPA -> SPA: %#llx -> %#llx\n", dpa, spa);
return spa;
}
static int cxl_prm_setup_root(struct cxl_root *cxl_root, void *data)
{
struct cxl_region_context *ctx = data;
struct cxl_endpoint_decoder *cxled = ctx->cxled;
struct cxl_decoder *cxld = &cxled->cxld;
struct cxl_memdev *cxlmd = cxled_to_memdev(cxled);
struct range hpa_range = ctx->hpa_range;
struct pci_dev *pci_dev;
u64 spa_len, len;
u64 addr, base_spa, base;
int ways, gran;
/*
* When Normalized Addressing is enabled, the endpoint maintains a 1:1
* mapping between HPA and DPA. If disabled, skip address translation
* and perform only a range check.
*/
if (hpa_range.start != cxled->dpa_res->start)
return 0;
/*
* Endpoints are programmed passthrough in Normalized Addressing mode.
*/
if (ctx->interleave_ways != 1) {
dev_dbg(&cxld->dev, "unexpected interleaving config: ways: %d granularity: %d\n",
ctx->interleave_ways, ctx->interleave_granularity);
return -ENXIO;
}
if (!cxlmd || !dev_is_pci(cxlmd->dev.parent)) {
dev_dbg(&cxld->dev, "No endpoint found: %s, range %#llx-%#llx\n",
dev_name(cxld->dev.parent), hpa_range.start,
hpa_range.end);
return -ENXIO;
}
pci_dev = to_pci_dev(cxlmd->dev.parent);
/* Translate HPA range to SPA. */
base = hpa_range.start;
hpa_range.start = prm_cxl_dpa_spa(pci_dev, hpa_range.start);
hpa_range.end = prm_cxl_dpa_spa(pci_dev, hpa_range.end);
base_spa = hpa_range.start;
if (hpa_range.start == ULLONG_MAX || hpa_range.end == ULLONG_MAX) {
dev_dbg(cxld->dev.parent,
"CXL address translation: Failed to translate HPA range: %#llx-%#llx:%#llx-%#llx(%s)\n",
hpa_range.start, hpa_range.end, ctx->hpa_range.start,
ctx->hpa_range.end, dev_name(&cxld->dev));
return -ENXIO;
}
/*
* Since translated addresses include the interleaving offsets, align
* the range to 256 MB.
*/
hpa_range.start = ALIGN_DOWN(hpa_range.start, SZ_256M);
hpa_range.end = ALIGN(hpa_range.end, SZ_256M) - 1;
len = range_len(&ctx->hpa_range);
spa_len = range_len(&hpa_range);
if (!len || !spa_len || spa_len % len) {
dev_dbg(cxld->dev.parent,
"CXL address translation: HPA range not contiguous: %#llx-%#llx:%#llx-%#llx(%s)\n",
hpa_range.start, hpa_range.end, ctx->hpa_range.start,
ctx->hpa_range.end, dev_name(&cxld->dev));
return -ENXIO;
}
ways = spa_len / len;
gran = SZ_256;
/*
* Determine interleave granularity
*
* Note: The position of the chunk from one interleaving block to the
* next may vary and thus cannot be considered constant. Address offsets
* larger than the interleaving block size cannot be used to calculate
* the granularity.
*/
if (ways > 1) {
while (gran <= SZ_16M) {
addr = prm_cxl_dpa_spa(pci_dev, base + gran);
if (addr != base_spa + gran)
break;
gran <<= 1;
}
}
if (gran > SZ_16M) {
dev_dbg(cxld->dev.parent,
"CXL address translation: Cannot determine granularity: %#llx-%#llx:%#llx-%#llx(%s)\n",
hpa_range.start, hpa_range.end, ctx->hpa_range.start,
ctx->hpa_range.end, dev_name(&cxld->dev));
return -ENXIO;
}
/*
* The current kernel implementation does not support endpoint
* setup with Normalized Addressing. It only translates an
* endpoint's DPA to the SPA range of the host bridge.
* Therefore, the endpoint address range cannot be determined,
* making a non-auto setup impossible. If a decoder requires
* address translation, reprogramming should be disabled and
* the decoder locked.
*
* The BIOS, however, provides all the necessary address
* translation data, which the kernel can use to reconfigure
* endpoint decoders with normalized addresses. Locking the
* decoders in the BIOS would prevent a capable kernel (or
* other operating systems) from shutting down auto-generated
* regions and managing resources dynamically.
*
* Indicate that Normalized Addressing is enabled.
*/
cxld->flags |= CXL_DECODER_F_LOCK;
cxld->flags |= CXL_DECODER_F_NORMALIZED_ADDRESSING;
ctx->hpa_range = hpa_range;
ctx->interleave_ways = ways;
ctx->interleave_granularity = gran;
dev_dbg(&cxld->dev,
"address mapping found for %s (hpa -> spa): %#llx+%#llx -> %#llx+%#llx ways:%d granularity:%d\n",
dev_name(cxlmd->dev.parent), base, len, hpa_range.start,
spa_len, ways, gran);
return 0;
}
void cxl_setup_prm_address_translation(struct cxl_root *cxl_root)
{
struct device *host = cxl_root->port.uport_dev;
u64 spa;
struct prm_cxl_dpa_spa_data data = { .spa = &spa };
int rc;
/*
* Applies only to PCIe Host Bridges which are children of the CXL Root
* Device (HID=“ACPI0017”). Check this and drop cxl_test instances.
*/
if (!acpi_match_device(host->driver->acpi_match_table, host))
return;
/* Check kernel (-EOPNOTSUPP) and firmware support (-ENODEV) */
rc = acpi_call_prm_handler(prm_cxl_dpa_spa_guid, &data);
if (rc == -EOPNOTSUPP || rc == -ENODEV)
return;
cxl_root->ops.translation_setup_root = cxl_prm_setup_root;
}
EXPORT_SYMBOL_NS_GPL(cxl_setup_prm_address_translation, "CXL");
File diff suppressed because it is too large Load Diff
+233
View File
@@ -0,0 +1,233 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright(c) 2020 Intel Corporation. */
#ifndef __CXL_CORE_H__
#define __CXL_CORE_H__
#include <cxl/mailbox.h>
#include <linux/rwsem.h>
extern const struct device_type cxl_nvdimm_bridge_type;
extern const struct device_type cxl_nvdimm_type;
extern const struct device_type cxl_pmu_type;
extern struct attribute_group cxl_base_attribute_group;
enum cxl_detach_mode {
DETACH_ONLY,
DETACH_INVALIDATE,
};
#ifdef CONFIG_CXL_REGION
struct cxl_region_context {
struct cxl_endpoint_decoder *cxled;
struct range hpa_range;
int interleave_ways;
int interleave_granularity;
};
extern struct device_attribute dev_attr_create_pmem_region;
extern struct device_attribute dev_attr_create_ram_region;
extern struct device_attribute dev_attr_delete_region;
extern struct device_attribute dev_attr_region;
extern const struct device_type cxl_pmem_region_type;
extern const struct device_type cxl_dax_region_type;
extern const struct device_type cxl_region_type;
int cxl_decoder_detach(struct cxl_region *cxlr,
struct cxl_endpoint_decoder *cxled, int pos,
enum cxl_detach_mode mode);
#define CXL_REGION_ATTR(x) (&dev_attr_##x.attr)
#define CXL_REGION_TYPE(x) (&cxl_region_type)
#define SET_CXL_REGION_ATTR(x) (&dev_attr_##x.attr),
#define CXL_PMEM_REGION_TYPE(x) (&cxl_pmem_region_type)
#define CXL_DAX_REGION_TYPE(x) (&cxl_dax_region_type)
int cxl_region_init(void);
void cxl_region_exit(void);
int cxl_get_poison_by_endpoint(struct cxl_port *port);
struct cxl_region *cxl_dpa_to_region(const struct cxl_memdev *cxlmd, u64 dpa);
u64 cxl_dpa_to_hpa(struct cxl_region *cxlr, const struct cxl_memdev *cxlmd,
u64 dpa);
int devm_cxl_add_dax_region(struct cxl_region *cxlr);
int devm_cxl_add_pmem_region(struct cxl_region *cxlr);
void kill_regions(struct cxl_root_decoder *cxlrd);
#else
static inline u64 cxl_dpa_to_hpa(struct cxl_region *cxlr,
const struct cxl_memdev *cxlmd, u64 dpa)
{
return ULLONG_MAX;
}
static inline
struct cxl_region *cxl_dpa_to_region(const struct cxl_memdev *cxlmd, u64 dpa)
{
return NULL;
}
static inline int cxl_get_poison_by_endpoint(struct cxl_port *port)
{
return 0;
}
static inline int cxl_decoder_detach(struct cxl_region *cxlr,
struct cxl_endpoint_decoder *cxled,
int pos, enum cxl_detach_mode mode)
{
return 0;
}
static inline int cxl_region_init(void)
{
return 0;
}
static inline void cxl_region_exit(void)
{
}
static inline void kill_regions(struct cxl_root_decoder *cxlrd) { };
#define CXL_REGION_ATTR(x) NULL
#define CXL_REGION_TYPE(x) NULL
#define SET_CXL_REGION_ATTR(x)
#define CXL_PMEM_REGION_TYPE(x) NULL
#define CXL_DAX_REGION_TYPE(x) NULL
#endif
struct cxl_send_command;
struct cxl_mem_query_commands;
int cxl_query_cmd(struct cxl_mailbox *cxl_mbox,
struct cxl_mem_query_commands __user *q);
int cxl_send_cmd(struct cxl_mailbox *cxl_mbox, struct cxl_send_command __user *s);
void __iomem *devm_cxl_iomap_block(struct device *dev, resource_size_t addr,
resource_size_t length);
struct dentry *cxl_debugfs_create_dir(const char *dir);
int cxl_dpa_set_part(struct cxl_endpoint_decoder *cxled,
enum cxl_partition_mode mode);
int cxl_dpa_alloc(struct cxl_endpoint_decoder *cxled, u64 size);
int cxl_dpa_free(struct cxl_endpoint_decoder *cxled);
resource_size_t cxl_dpa_size(struct cxl_endpoint_decoder *cxled);
resource_size_t cxl_dpa_resource_start(struct cxl_endpoint_decoder *cxled);
bool cxl_resource_contains_addr(const struct resource *res, const resource_size_t addr);
enum cxl_rcrb {
CXL_RCRB_DOWNSTREAM,
CXL_RCRB_UPSTREAM,
};
struct cxl_rcrb_info;
resource_size_t __rcrb_to_component(struct device *dev,
struct cxl_rcrb_info *ri,
enum cxl_rcrb which);
u16 cxl_rcrb_to_aer(struct device *dev, resource_size_t rcrb);
#define PCI_RCRB_CAP_LIST_ID_MASK GENMASK(7, 0)
#define PCI_RCRB_CAP_HDR_ID_MASK GENMASK(7, 0)
#define PCI_RCRB_CAP_HDR_NEXT_MASK GENMASK(15, 8)
#define PCI_CAP_EXP_SIZEOF 0x3c
struct cxl_rwsem {
/*
* All changes to HPA (interleave configuration) occur with this
* lock held for write.
*/
struct rw_semaphore region;
/*
* All changes to a device DPA space occur with this lock held
* for write.
*/
struct rw_semaphore dpa;
};
extern struct cxl_rwsem cxl_rwsem;
int cxl_memdev_init(void);
void cxl_memdev_exit(void);
void cxl_mbox_init(void);
enum cxl_poison_trace_type {
CXL_POISON_TRACE_LIST,
CXL_POISON_TRACE_INJECT,
CXL_POISON_TRACE_CLEAR,
};
enum poison_cmd_enabled_bits;
bool cxl_memdev_has_poison_cmd(struct cxl_memdev *cxlmd,
enum poison_cmd_enabled_bits cmd);
long cxl_pci_get_latency(struct pci_dev *pdev);
int cxl_pci_get_bandwidth(struct pci_dev *pdev, struct access_coordinate *c);
int cxl_port_get_switch_dport_bandwidth(struct cxl_port *port,
struct access_coordinate *c);
static inline struct device *port_to_host(struct cxl_port *port)
{
struct cxl_port *parent = is_cxl_root(port) ? NULL :
to_cxl_port(port->dev.parent);
/*
* The host of CXL root port and the first level of ports is
* the platform firmware device, the host of all other ports
* is their parent port.
*/
if (!parent)
return port->uport_dev;
else if (is_cxl_root(parent))
return parent->uport_dev;
else
return &parent->dev;
}
static inline struct device *dport_to_host(struct cxl_dport *dport)
{
struct cxl_port *port = dport->port;
if (is_cxl_root(port))
return port->uport_dev;
return &port->dev;
}
#ifdef CONFIG_CXL_RAS
int cxl_ras_init(void);
void cxl_ras_exit(void);
bool cxl_handle_ras(struct device *dev, void __iomem *ras_base);
void cxl_handle_cor_ras(struct device *dev, void __iomem *ras_base);
void cxl_dport_map_rch_aer(struct cxl_dport *dport);
void cxl_disable_rch_root_ints(struct cxl_dport *dport);
void cxl_handle_rdport_errors(struct cxl_dev_state *cxlds);
void devm_cxl_dport_ras_setup(struct cxl_dport *dport);
#else
static inline int cxl_ras_init(void)
{
return 0;
}
static inline void cxl_ras_exit(void) { }
static inline bool cxl_handle_ras(struct device *dev, void __iomem *ras_base)
{
return false;
}
static inline void cxl_handle_cor_ras(struct device *dev, void __iomem *ras_base) { }
static inline void cxl_dport_map_rch_aer(struct cxl_dport *dport) { }
static inline void cxl_disable_rch_root_ints(struct cxl_dport *dport) { }
static inline void cxl_handle_rdport_errors(struct cxl_dev_state *cxlds) { }
static inline void devm_cxl_dport_ras_setup(struct cxl_dport *dport) { }
#endif /* CONFIG_CXL_RAS */
int cxl_gpf_port_setup(struct cxl_dport *dport);
struct cxl_hdm;
int cxl_hdm_decode_init(struct cxl_dev_state *cxlds, struct cxl_hdm *cxlhdm,
struct cxl_endpoint_dvsec_info *info);
int cxl_port_get_possible_dports(struct cxl_port *port);
#ifdef CONFIG_CXL_FEATURES
struct cxl_feat_entry *
cxl_feature_info(struct cxl_features_state *cxlfs, const uuid_t *uuid);
size_t cxl_get_feature(struct cxl_mailbox *cxl_mbox, const uuid_t *feat_uuid,
enum cxl_get_feat_selection selection,
void *feat_out, size_t feat_out_size, u16 offset,
u16 *return_code);
int cxl_set_feature(struct cxl_mailbox *cxl_mbox, const uuid_t *feat_uuid,
u8 feat_version, const void *feat_data,
size_t feat_data_size, u32 feat_flag, u16 offset,
u16 *return_code);
#endif
resource_size_t cxl_rcd_component_reg_phys(struct device *dev,
struct cxl_dport *dport);
#endif /* __CXL_CORE_H__ */
File diff suppressed because it is too large Load Diff
+706
View File
@@ -0,0 +1,706 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright(c) 2024-2025 Intel Corporation. All rights reserved. */
#include <linux/fwctl.h>
#include <linux/device.h>
#include <cxl/mailbox.h>
#include <cxl/features.h>
#include <uapi/fwctl/cxl.h>
#include "cxl.h"
#include "core.h"
#include "cxlmem.h"
/**
* DOC: cxl features
*
* CXL Features:
* A CXL device that includes a mailbox supports commands that allows
* listing, getting, and setting of optionally defined features such
* as memory sparing or post package sparing. Vendors may define custom
* features for the device.
*/
/* All the features below are exclusive to the kernel */
static const uuid_t cxl_exclusive_feats[] = {
CXL_FEAT_PATROL_SCRUB_UUID,
CXL_FEAT_ECS_UUID,
CXL_FEAT_SPPR_UUID,
CXL_FEAT_HPPR_UUID,
CXL_FEAT_CACHELINE_SPARING_UUID,
CXL_FEAT_ROW_SPARING_UUID,
CXL_FEAT_BANK_SPARING_UUID,
CXL_FEAT_RANK_SPARING_UUID,
};
static bool is_cxl_feature_exclusive_by_uuid(const uuid_t *uuid)
{
for (int i = 0; i < ARRAY_SIZE(cxl_exclusive_feats); i++) {
if (uuid_equal(uuid, &cxl_exclusive_feats[i]))
return true;
}
return false;
}
static bool is_cxl_feature_exclusive(struct cxl_feat_entry *entry)
{
return is_cxl_feature_exclusive_by_uuid(&entry->uuid);
}
struct cxl_features_state *to_cxlfs(struct cxl_dev_state *cxlds)
{
return cxlds->cxlfs;
}
EXPORT_SYMBOL_NS_GPL(to_cxlfs, "CXL");
static int cxl_get_supported_features_count(struct cxl_mailbox *cxl_mbox)
{
struct cxl_mbox_get_sup_feats_out mbox_out;
struct cxl_mbox_get_sup_feats_in mbox_in;
struct cxl_mbox_cmd mbox_cmd;
int rc;
memset(&mbox_in, 0, sizeof(mbox_in));
mbox_in.count = cpu_to_le32(sizeof(mbox_out));
memset(&mbox_out, 0, sizeof(mbox_out));
mbox_cmd = (struct cxl_mbox_cmd) {
.opcode = CXL_MBOX_OP_GET_SUPPORTED_FEATURES,
.size_in = sizeof(mbox_in),
.payload_in = &mbox_in,
.size_out = sizeof(mbox_out),
.payload_out = &mbox_out,
.min_out = sizeof(mbox_out),
};
rc = cxl_internal_send_cmd(cxl_mbox, &mbox_cmd);
if (rc < 0)
return rc;
return le16_to_cpu(mbox_out.supported_feats);
}
static struct cxl_feat_entries *
get_supported_features(struct cxl_features_state *cxlfs)
{
int remain_feats, max_size, max_feats, start, rc, hdr_size;
struct cxl_mailbox *cxl_mbox = &cxlfs->cxlds->cxl_mbox;
int feat_size = sizeof(struct cxl_feat_entry);
struct cxl_mbox_get_sup_feats_in mbox_in;
struct cxl_feat_entry *entry;
struct cxl_mbox_cmd mbox_cmd;
int user_feats = 0;
int count;
count = cxl_get_supported_features_count(cxl_mbox);
if (count <= 0)
return NULL;
struct cxl_feat_entries *entries __free(kvfree) =
kvmalloc_flex(*entries, ent, count);
if (!entries)
return NULL;
struct cxl_mbox_get_sup_feats_out *mbox_out __free(kvfree) =
kvmalloc(cxl_mbox->payload_size, GFP_KERNEL);
if (!mbox_out)
return NULL;
hdr_size = struct_size(mbox_out, ents, 0);
max_size = cxl_mbox->payload_size - hdr_size;
/* max feat entries that can fit in mailbox max payload size */
max_feats = max_size / feat_size;
entry = entries->ent;
start = 0;
remain_feats = count;
do {
int retrieved, alloc_size, copy_feats;
int num_entries;
if (remain_feats > max_feats) {
alloc_size = struct_size(mbox_out, ents, max_feats);
remain_feats = remain_feats - max_feats;
copy_feats = max_feats;
} else {
alloc_size = struct_size(mbox_out, ents, remain_feats);
copy_feats = remain_feats;
remain_feats = 0;
}
memset(&mbox_in, 0, sizeof(mbox_in));
mbox_in.count = cpu_to_le32(alloc_size);
mbox_in.start_idx = cpu_to_le16(start);
memset(mbox_out, 0, alloc_size);
mbox_cmd = (struct cxl_mbox_cmd) {
.opcode = CXL_MBOX_OP_GET_SUPPORTED_FEATURES,
.size_in = sizeof(mbox_in),
.payload_in = &mbox_in,
.size_out = alloc_size,
.payload_out = mbox_out,
.min_out = hdr_size,
};
rc = cxl_internal_send_cmd(cxl_mbox, &mbox_cmd);
if (rc < 0)
return NULL;
if (mbox_cmd.size_out <= hdr_size)
return NULL;
/*
* Make sure retrieved out buffer is multiple of feature
* entries.
*/
retrieved = mbox_cmd.size_out - hdr_size;
if (retrieved % feat_size)
return NULL;
num_entries = le16_to_cpu(mbox_out->num_entries);
/*
* If the reported output entries * defined entry size !=
* retrieved output bytes, then the output package is incorrect.
*/
if (num_entries * feat_size != retrieved)
return NULL;
memcpy(entry, mbox_out->ents, retrieved);
for (int i = 0; i < num_entries; i++) {
if (!is_cxl_feature_exclusive(entry + i))
user_feats++;
}
entry += num_entries;
/*
* If the number of output entries is less than expected, add the
* remaining entries to the next batch.
*/
remain_feats += copy_feats - num_entries;
start += num_entries;
} while (remain_feats);
entries->num_features = count;
entries->num_user_features = user_feats;
return no_free_ptr(entries);
}
static void free_cxlfs(void *_cxlfs)
{
struct cxl_features_state *cxlfs = _cxlfs;
struct cxl_dev_state *cxlds = cxlfs->cxlds;
cxlds->cxlfs = NULL;
kvfree(cxlfs->entries);
kfree(cxlfs);
}
/**
* devm_cxl_setup_features() - Allocate and initialize features context
* @cxlds: CXL device context
*
* Return 0 on success or -errno on failure.
*/
int devm_cxl_setup_features(struct cxl_dev_state *cxlds)
{
struct cxl_mailbox *cxl_mbox = &cxlds->cxl_mbox;
if (cxl_mbox->feat_cap < CXL_FEATURES_RO)
return -ENODEV;
struct cxl_features_state *cxlfs __free(kfree) =
kzalloc_obj(*cxlfs);
if (!cxlfs)
return -ENOMEM;
cxlfs->cxlds = cxlds;
cxlfs->entries = get_supported_features(cxlfs);
if (!cxlfs->entries)
return -ENOMEM;
cxlds->cxlfs = cxlfs;
return devm_add_action_or_reset(cxlds->dev, free_cxlfs, no_free_ptr(cxlfs));
}
EXPORT_SYMBOL_NS_GPL(devm_cxl_setup_features, "CXL");
size_t cxl_get_feature(struct cxl_mailbox *cxl_mbox, const uuid_t *feat_uuid,
enum cxl_get_feat_selection selection,
void *feat_out, size_t feat_out_size, u16 offset,
u16 *return_code)
{
size_t data_to_rd_size, size_out;
struct cxl_mbox_get_feat_in pi;
struct cxl_mbox_cmd mbox_cmd;
size_t data_rcvd_size = 0;
int rc;
if (return_code)
*return_code = CXL_MBOX_CMD_RC_INPUT;
if (!feat_out || !feat_out_size)
return 0;
size_out = min(feat_out_size, cxl_mbox->payload_size);
uuid_copy(&pi.uuid, feat_uuid);
pi.selection = selection;
do {
data_to_rd_size = min(feat_out_size - data_rcvd_size,
cxl_mbox->payload_size);
pi.offset = cpu_to_le16(offset + data_rcvd_size);
pi.count = cpu_to_le16(data_to_rd_size);
mbox_cmd = (struct cxl_mbox_cmd) {
.opcode = CXL_MBOX_OP_GET_FEATURE,
.size_in = sizeof(pi),
.payload_in = &pi,
.size_out = size_out,
.payload_out = feat_out + data_rcvd_size,
.min_out = data_to_rd_size,
};
rc = cxl_internal_send_cmd(cxl_mbox, &mbox_cmd);
if (rc < 0 || !mbox_cmd.size_out) {
if (return_code)
*return_code = mbox_cmd.return_code;
return 0;
}
data_rcvd_size += mbox_cmd.size_out;
} while (data_rcvd_size < feat_out_size);
if (return_code)
*return_code = CXL_MBOX_CMD_RC_SUCCESS;
return data_rcvd_size;
}
/*
* FEAT_DATA_MIN_PAYLOAD_SIZE - min extra number of bytes should be
* available in the mailbox for storing the actual feature data so that
* the feature data transfer would work as expected.
*/
#define FEAT_DATA_MIN_PAYLOAD_SIZE 10
int cxl_set_feature(struct cxl_mailbox *cxl_mbox,
const uuid_t *feat_uuid, u8 feat_version,
const void *feat_data, size_t feat_data_size,
u32 feat_flag, u16 offset, u16 *return_code)
{
size_t data_in_size, data_sent_size = 0;
struct cxl_mbox_cmd mbox_cmd;
size_t hdr_size;
if (return_code)
*return_code = CXL_MBOX_CMD_RC_INPUT;
struct cxl_mbox_set_feat_in *pi __free(kfree) =
kzalloc(cxl_mbox->payload_size, GFP_KERNEL);
if (!pi)
return -ENOMEM;
uuid_copy(&pi->uuid, feat_uuid);
pi->version = feat_version;
feat_flag &= ~CXL_SET_FEAT_FLAG_DATA_TRANSFER_MASK;
feat_flag |= CXL_SET_FEAT_FLAG_DATA_SAVED_ACROSS_RESET;
hdr_size = sizeof(pi->hdr);
/*
* Check minimum mbox payload size is available for
* the feature data transfer.
*/
if (hdr_size + FEAT_DATA_MIN_PAYLOAD_SIZE > cxl_mbox->payload_size)
return -ENOMEM;
if (hdr_size + feat_data_size <= cxl_mbox->payload_size) {
pi->flags = cpu_to_le32(feat_flag |
CXL_SET_FEAT_FLAG_FULL_DATA_TRANSFER);
data_in_size = feat_data_size;
} else {
pi->flags = cpu_to_le32(feat_flag |
CXL_SET_FEAT_FLAG_INITIATE_DATA_TRANSFER);
data_in_size = cxl_mbox->payload_size - hdr_size;
}
do {
int rc;
pi->offset = cpu_to_le16(offset + data_sent_size);
memcpy(pi->feat_data, feat_data + data_sent_size, data_in_size);
mbox_cmd = (struct cxl_mbox_cmd) {
.opcode = CXL_MBOX_OP_SET_FEATURE,
.size_in = hdr_size + data_in_size,
.payload_in = pi,
};
rc = cxl_internal_send_cmd(cxl_mbox, &mbox_cmd);
if (rc < 0) {
if (return_code)
*return_code = mbox_cmd.return_code;
return rc;
}
data_sent_size += data_in_size;
if (data_sent_size >= feat_data_size) {
if (return_code)
*return_code = CXL_MBOX_CMD_RC_SUCCESS;
return 0;
}
if ((feat_data_size - data_sent_size) <= (cxl_mbox->payload_size - hdr_size)) {
data_in_size = feat_data_size - data_sent_size;
pi->flags = cpu_to_le32(feat_flag |
CXL_SET_FEAT_FLAG_FINISH_DATA_TRANSFER);
} else {
pi->flags = cpu_to_le32(feat_flag |
CXL_SET_FEAT_FLAG_CONTINUE_DATA_TRANSFER);
}
} while (true);
}
/* FWCTL support */
static inline struct cxl_memdev *fwctl_to_memdev(struct fwctl_device *fwctl_dev)
{
return to_cxl_memdev(fwctl_dev->dev.parent);
}
static int cxlctl_open_uctx(struct fwctl_uctx *uctx)
{
return 0;
}
static void cxlctl_close_uctx(struct fwctl_uctx *uctx)
{
}
struct cxl_feat_entry *
cxl_feature_info(struct cxl_features_state *cxlfs,
const uuid_t *uuid)
{
struct cxl_feat_entry *feat;
if (!cxlfs || !cxlfs->entries)
return ERR_PTR(-EOPNOTSUPP);
for (int i = 0; i < cxlfs->entries->num_features; i++) {
feat = &cxlfs->entries->ent[i];
if (uuid_equal(uuid, &feat->uuid))
return feat;
}
return ERR_PTR(-EINVAL);
}
static void *cxlctl_get_supported_features(struct cxl_features_state *cxlfs,
const struct fwctl_rpc_cxl *rpc_in,
size_t *out_len)
{
const struct cxl_mbox_get_sup_feats_in *feat_in;
struct cxl_mbox_get_sup_feats_out *feat_out;
struct cxl_feat_entry *pos;
size_t out_size;
int requested;
u32 count;
u16 start;
int i;
if (rpc_in->op_size != sizeof(*feat_in))
return ERR_PTR(-EINVAL);
feat_in = &rpc_in->get_sup_feats_in;
count = le32_to_cpu(feat_in->count);
start = le16_to_cpu(feat_in->start_idx);
requested = count / sizeof(*pos);
/*
* Make sure that the total requested number of entries is not greater
* than the total number of supported features allowed for userspace.
*/
if (start >= cxlfs->entries->num_features)
return ERR_PTR(-EINVAL);
requested = min_t(int, requested, cxlfs->entries->num_features - start);
out_size = sizeof(struct fwctl_rpc_cxl_out) +
struct_size(feat_out, ents, requested);
struct fwctl_rpc_cxl_out *rpc_out __free(kvfree) =
kvzalloc(out_size, GFP_KERNEL);
if (!rpc_out)
return ERR_PTR(-ENOMEM);
rpc_out->size = struct_size(feat_out, ents, requested);
feat_out = &rpc_out->get_sup_feats_out;
feat_out->num_entries = cpu_to_le16(requested);
for (i = start, pos = &feat_out->ents[0];
i < cxlfs->entries->num_features; i++, pos++) {
if (i - start == requested)
break;
memcpy(pos, &cxlfs->entries->ent[i], sizeof(*pos));
/*
* If the feature is exclusive, set the set_feat_size to 0 to
* indicate that the feature is not changeable.
*/
if (is_cxl_feature_exclusive(pos)) {
u32 flags;
pos->set_feat_size = 0;
flags = le32_to_cpu(pos->flags);
flags &= ~CXL_FEATURE_F_CHANGEABLE;
pos->flags = cpu_to_le32(flags);
}
}
feat_out->supported_feats = cpu_to_le16(cxlfs->entries->num_features);
rpc_out->retval = CXL_MBOX_CMD_RC_SUCCESS;
*out_len = out_size;
return no_free_ptr(rpc_out);
}
static void *cxlctl_get_feature(struct cxl_features_state *cxlfs,
const struct fwctl_rpc_cxl *rpc_in,
size_t *out_len)
{
struct cxl_mailbox *cxl_mbox = &cxlfs->cxlds->cxl_mbox;
const struct cxl_mbox_get_feat_in *feat_in;
u16 offset, count, return_code;
size_t out_size = *out_len;
if (rpc_in->op_size != sizeof(*feat_in))
return ERR_PTR(-EINVAL);
feat_in = &rpc_in->get_feat_in;
offset = le16_to_cpu(feat_in->offset);
count = le16_to_cpu(feat_in->count);
if (!count)
return ERR_PTR(-EINVAL);
struct fwctl_rpc_cxl_out *rpc_out __free(kvfree) =
kvzalloc(out_size, GFP_KERNEL);
if (!rpc_out)
return ERR_PTR(-ENOMEM);
out_size = cxl_get_feature(cxl_mbox, &feat_in->uuid,
feat_in->selection, rpc_out->payload,
count, offset, &return_code);
*out_len = sizeof(struct fwctl_rpc_cxl_out);
if (!out_size) {
rpc_out->size = 0;
rpc_out->retval = return_code;
return no_free_ptr(rpc_out);
}
rpc_out->size = out_size;
rpc_out->retval = CXL_MBOX_CMD_RC_SUCCESS;
*out_len += out_size;
return no_free_ptr(rpc_out);
}
static void *cxlctl_set_feature(struct cxl_features_state *cxlfs,
const struct fwctl_rpc_cxl *rpc_in,
size_t *out_len)
{
struct cxl_mailbox *cxl_mbox = &cxlfs->cxlds->cxl_mbox;
const struct cxl_mbox_set_feat_in *feat_in;
size_t out_size, data_size;
u16 offset, return_code;
u32 flags;
int rc;
if (rpc_in->op_size <= sizeof(feat_in->hdr))
return ERR_PTR(-EINVAL);
feat_in = &rpc_in->set_feat_in;
if (is_cxl_feature_exclusive_by_uuid(&feat_in->uuid))
return ERR_PTR(-EPERM);
offset = le16_to_cpu(feat_in->offset);
flags = le32_to_cpu(feat_in->flags);
out_size = *out_len;
struct fwctl_rpc_cxl_out *rpc_out __free(kvfree) =
kvzalloc(out_size, GFP_KERNEL);
if (!rpc_out)
return ERR_PTR(-ENOMEM);
rpc_out->size = 0;
data_size = rpc_in->op_size - sizeof(feat_in->hdr);
rc = cxl_set_feature(cxl_mbox, &feat_in->uuid,
feat_in->version, feat_in->feat_data,
data_size, flags, offset, &return_code);
*out_len = sizeof(*rpc_out);
if (rc) {
rpc_out->retval = return_code;
return no_free_ptr(rpc_out);
}
rpc_out->retval = CXL_MBOX_CMD_RC_SUCCESS;
return no_free_ptr(rpc_out);
}
static bool cxlctl_validate_set_features(struct cxl_features_state *cxlfs,
const struct fwctl_rpc_cxl *rpc_in,
enum fwctl_rpc_scope scope)
{
u16 effects, imm_mask, reset_mask;
struct cxl_feat_entry *feat;
u32 flags;
if (rpc_in->op_size < sizeof(uuid_t))
return false;
feat = cxl_feature_info(cxlfs, &rpc_in->set_feat_in.uuid);
if (IS_ERR(feat))
return false;
/* Ensure that the attribute is changeable */
flags = le32_to_cpu(feat->flags);
if (!(flags & CXL_FEATURE_F_CHANGEABLE))
return false;
effects = le16_to_cpu(feat->effects);
/*
* Reserved bits are set, rejecting since the effects is not
* comprehended by the driver.
*/
if (effects & CXL_CMD_EFFECTS_RESERVED) {
dev_warn_once(cxlfs->cxlds->dev,
"Reserved bits set in the Feature effects field!\n");
return false;
}
/* Currently no user background command support */
if (effects & CXL_CMD_BACKGROUND)
return false;
/* Effects cause immediate change, highest security scope is needed */
imm_mask = CXL_CMD_CONFIG_CHANGE_IMMEDIATE |
CXL_CMD_DATA_CHANGE_IMMEDIATE |
CXL_CMD_POLICY_CHANGE_IMMEDIATE |
CXL_CMD_LOG_CHANGE_IMMEDIATE;
reset_mask = CXL_CMD_CONFIG_CHANGE_COLD_RESET |
CXL_CMD_CONFIG_CHANGE_CONV_RESET |
CXL_CMD_CONFIG_CHANGE_CXL_RESET;
/* If no immediate or reset effect set, The hardware has a bug */
if (!(effects & imm_mask) && !(effects & reset_mask))
return false;
/*
* If the Feature setting causes immediate configuration change
* then we need the full write permission policy.
*/
if (effects & imm_mask && scope >= FWCTL_RPC_DEBUG_WRITE_FULL)
return true;
/*
* If the Feature setting only causes configuration change
* after a reset, then the lesser level of write permission
* policy is ok.
*/
if (!(effects & imm_mask) && scope >= FWCTL_RPC_DEBUG_WRITE)
return true;
return false;
}
static bool cxlctl_validate_hw_command(struct cxl_features_state *cxlfs,
const struct fwctl_rpc_cxl *rpc_in,
enum fwctl_rpc_scope scope,
u16 opcode)
{
struct cxl_mailbox *cxl_mbox = &cxlfs->cxlds->cxl_mbox;
switch (opcode) {
case CXL_MBOX_OP_GET_SUPPORTED_FEATURES:
case CXL_MBOX_OP_GET_FEATURE:
return cxl_mbox->feat_cap >= CXL_FEATURES_RO;
case CXL_MBOX_OP_SET_FEATURE:
if (cxl_mbox->feat_cap < CXL_FEATURES_RW)
return false;
return cxlctl_validate_set_features(cxlfs, rpc_in, scope);
default:
return false;
}
}
static void *cxlctl_handle_commands(struct cxl_features_state *cxlfs,
const struct fwctl_rpc_cxl *rpc_in,
size_t *out_len, u16 opcode)
{
switch (opcode) {
case CXL_MBOX_OP_GET_SUPPORTED_FEATURES:
return cxlctl_get_supported_features(cxlfs, rpc_in, out_len);
case CXL_MBOX_OP_GET_FEATURE:
return cxlctl_get_feature(cxlfs, rpc_in, out_len);
case CXL_MBOX_OP_SET_FEATURE:
return cxlctl_set_feature(cxlfs, rpc_in, out_len);
default:
return ERR_PTR(-EOPNOTSUPP);
}
}
static void *cxlctl_fw_rpc(struct fwctl_uctx *uctx, enum fwctl_rpc_scope scope,
void *in, size_t in_len, size_t *out_len)
{
struct fwctl_device *fwctl_dev = uctx->fwctl;
struct cxl_memdev *cxlmd = fwctl_to_memdev(fwctl_dev);
struct cxl_features_state *cxlfs = to_cxlfs(cxlmd->cxlds);
const struct fwctl_rpc_cxl *rpc_in = in;
u16 opcode = rpc_in->opcode;
if (!cxlctl_validate_hw_command(cxlfs, rpc_in, scope, opcode))
return ERR_PTR(-EINVAL);
return cxlctl_handle_commands(cxlfs, rpc_in, out_len, opcode);
}
static const struct fwctl_ops cxlctl_ops = {
.device_type = FWCTL_DEVICE_TYPE_CXL,
.uctx_size = sizeof(struct fwctl_uctx),
.open_uctx = cxlctl_open_uctx,
.close_uctx = cxlctl_close_uctx,
.fw_rpc = cxlctl_fw_rpc,
};
DEFINE_FREE(free_fwctl_dev, struct fwctl_device *, if (_T) fwctl_put(_T))
static void free_memdev_fwctl(void *_fwctl_dev)
{
struct fwctl_device *fwctl_dev = _fwctl_dev;
fwctl_unregister(fwctl_dev);
fwctl_put(fwctl_dev);
}
int devm_cxl_setup_fwctl(struct device *host, struct cxl_memdev *cxlmd)
{
struct cxl_dev_state *cxlds = cxlmd->cxlds;
struct cxl_features_state *cxlfs;
int rc;
cxlfs = to_cxlfs(cxlds);
if (!cxlfs)
return -ENODEV;
/* No need to setup FWCTL if there are no user allowed features found */
if (!cxlfs->entries->num_user_features)
return -ENODEV;
struct fwctl_device *fwctl_dev __free(free_fwctl_dev) =
_fwctl_alloc_device(&cxlmd->dev, &cxlctl_ops, sizeof(*fwctl_dev));
if (!fwctl_dev)
return -ENOMEM;
rc = fwctl_register(fwctl_dev);
if (rc)
return rc;
return devm_add_action_or_reset(host, free_memdev_fwctl,
no_free_ptr(fwctl_dev));
}
EXPORT_SYMBOL_NS_GPL(devm_cxl_setup_fwctl, "CXL");
MODULE_IMPORT_NS("FWCTL");
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+65
View File
@@ -0,0 +1,65 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright(c) 2024 Intel Corporation. All rights reserved. */
#include <linux/mm.h>
#include <linux/notifier.h>
#include <linux/set_memory.h>
#include <asm/mce.h>
#include <cxlmem.h>
#include "mce.h"
static int cxl_handle_mce(struct notifier_block *nb, unsigned long val,
void *data)
{
struct cxl_memdev_state *mds = container_of(nb, struct cxl_memdev_state,
mce_notifier);
struct cxl_memdev *cxlmd = mds->cxlds.cxlmd;
struct cxl_port *endpoint = cxlmd->endpoint;
struct mce *mce = data;
u64 spa, spa_alias;
unsigned long pfn;
if (!mce || !mce_usable_address(mce))
return NOTIFY_DONE;
if (!endpoint)
return NOTIFY_DONE;
spa = mce->addr & MCI_ADDR_PHYSADDR;
pfn = spa >> PAGE_SHIFT;
if (!pfn_valid(pfn))
return NOTIFY_DONE;
spa_alias = cxl_port_get_spa_cache_alias(endpoint, spa);
if (spa_alias == ~0ULL)
return NOTIFY_DONE;
pfn = spa_alias >> PAGE_SHIFT;
/*
* Take down the aliased memory page. The original memory page flagged
* by the MCE will be taken cared of by the standard MCE handler.
*/
dev_emerg(mds->cxlds.dev, "Offlining aliased SPA address0: %#llx\n",
spa_alias);
if (!memory_failure(pfn, 0))
set_mce_nospec(pfn);
return NOTIFY_OK;
}
static void cxl_unregister_mce_notifier(void *mce_notifier)
{
mce_unregister_decode_chain(mce_notifier);
}
int devm_cxl_register_mce_notifier(struct device *dev,
struct notifier_block *mce_notifier)
{
mce_notifier->notifier_call = cxl_handle_mce;
mce_notifier->priority = MCE_PRIO_UC;
mce_register_decode_chain(mce_notifier);
return devm_add_action_or_reset(dev, cxl_unregister_mce_notifier,
mce_notifier);
}
+20
View File
@@ -0,0 +1,20 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright(c) 2024 Intel Corporation. All rights reserved. */
#ifndef _CXL_CORE_MCE_H_
#define _CXL_CORE_MCE_H_
#include <linux/notifier.h>
#ifdef CONFIG_CXL_MCE
int devm_cxl_register_mce_notifier(struct device *dev,
struct notifier_block *mce_notifier);
#else
static inline int
devm_cxl_register_mce_notifier(struct device *dev,
struct notifier_block *mce_notifier)
{
return -EOPNOTSUPP;
}
#endif
#endif
File diff suppressed because it is too large Load Diff
+928
View File
@@ -0,0 +1,928 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright(c) 2021 Intel Corporation. All rights reserved. */
#include <linux/units.h>
#include <linux/io-64-nonatomic-lo-hi.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/pci.h>
#include <linux/pci-doe.h>
#include <linux/aer.h>
#include <cxlpci.h>
#include <cxlmem.h>
#include <cxl.h>
#include "core.h"
#include "trace.h"
/**
* DOC: cxl core pci
*
* Compute Express Link protocols are layered on top of PCIe. CXL core provides
* a set of helpers for CXL interactions which occur via PCIe.
*/
static unsigned short media_ready_timeout = 60;
module_param(media_ready_timeout, ushort, 0644);
MODULE_PARM_DESC(media_ready_timeout, "seconds to wait for media ready");
static int pci_get_port_num(struct pci_dev *pdev)
{
u32 lnkcap;
int type;
type = pci_pcie_type(pdev);
if (type != PCI_EXP_TYPE_DOWNSTREAM && type != PCI_EXP_TYPE_ROOT_PORT)
return -EINVAL;
if (pci_read_config_dword(pdev, pci_pcie_cap(pdev) + PCI_EXP_LNKCAP,
&lnkcap))
return -ENXIO;
return FIELD_GET(PCI_EXP_LNKCAP_PN, lnkcap);
}
/**
* devm_cxl_add_dport_by_dev - allocate a dport by dport device
* @port: cxl_port that hosts the dport
* @dport_dev: 'struct device' of the dport
*
* Returns the allocated dport on success or ERR_PTR() of -errno on error
*/
struct cxl_dport *devm_cxl_add_dport_by_dev(struct cxl_port *port,
struct device *dport_dev)
{
struct cxl_register_map map;
struct pci_dev *pdev;
int port_num, rc;
if (!dev_is_pci(dport_dev))
return ERR_PTR(-EINVAL);
pdev = to_pci_dev(dport_dev);
port_num = pci_get_port_num(pdev);
if (port_num < 0)
return ERR_PTR(port_num);
rc = cxl_find_regblock(pdev, CXL_REGLOC_RBI_COMPONENT, &map);
if (rc)
return ERR_PTR(rc);
device_lock_assert(&port->dev);
return devm_cxl_add_dport(port, dport_dev, port_num, map.resource);
}
EXPORT_SYMBOL_NS_GPL(devm_cxl_add_dport_by_dev, "CXL");
static int cxl_dvsec_mem_range_valid(struct cxl_dev_state *cxlds, int id)
{
struct pci_dev *pdev = to_pci_dev(cxlds->dev);
int d = cxlds->cxl_dvsec;
bool valid = false;
int rc, i;
u32 temp;
if (id > CXL_DVSEC_RANGE_MAX)
return -EINVAL;
/* Check MEM INFO VALID bit first, give up after 1s */
i = 1;
do {
rc = pci_read_config_dword(pdev,
d + PCI_DVSEC_CXL_RANGE_SIZE_LOW(id),
&temp);
if (rc)
return pcibios_err_to_errno(rc);
valid = FIELD_GET(PCI_DVSEC_CXL_MEM_INFO_VALID, temp);
if (valid)
break;
msleep(1000);
} while (i--);
if (!valid) {
dev_err(&pdev->dev,
"Timeout awaiting memory range %d valid after 1s.\n",
id);
return -ETIMEDOUT;
}
return 0;
}
static int cxl_dvsec_mem_range_active(struct cxl_dev_state *cxlds, int id)
{
struct pci_dev *pdev = to_pci_dev(cxlds->dev);
int d = cxlds->cxl_dvsec;
bool active = false;
int rc, i;
u32 temp;
if (id > CXL_DVSEC_RANGE_MAX)
return -EINVAL;
/* Check MEM ACTIVE bit, up to 60s timeout by default */
for (i = media_ready_timeout; i; i--) {
rc = pci_read_config_dword(
pdev, d + PCI_DVSEC_CXL_RANGE_SIZE_LOW(id), &temp);
if (rc)
return pcibios_err_to_errno(rc);
active = FIELD_GET(PCI_DVSEC_CXL_MEM_ACTIVE, temp);
if (active)
break;
msleep(1000);
}
if (!active) {
dev_err(&pdev->dev,
"timeout awaiting memory active after %d seconds\n",
media_ready_timeout);
return -ETIMEDOUT;
}
return 0;
}
/*
* Wait up to @media_ready_timeout for the device to report memory
* active.
*/
int cxl_await_media_ready(struct cxl_dev_state *cxlds)
{
struct pci_dev *pdev = to_pci_dev(cxlds->dev);
int d = cxlds->cxl_dvsec;
int rc, i, hdm_count;
u64 md_status;
u16 cap;
rc = pci_read_config_word(pdev,
d + PCI_DVSEC_CXL_CAP, &cap);
if (rc)
return pcibios_err_to_errno(rc);
hdm_count = FIELD_GET(PCI_DVSEC_CXL_HDM_COUNT, cap);
for (i = 0; i < hdm_count; i++) {
rc = cxl_dvsec_mem_range_valid(cxlds, i);
if (rc)
return rc;
}
for (i = 0; i < hdm_count; i++) {
rc = cxl_dvsec_mem_range_active(cxlds, i);
if (rc)
return rc;
}
md_status = readq(cxlds->regs.memdev + CXLMDEV_STATUS_OFFSET);
if (!CXLMDEV_READY(md_status))
return -EIO;
return 0;
}
EXPORT_SYMBOL_NS_GPL(cxl_await_media_ready, "CXL");
static int cxl_set_mem_enable(struct cxl_dev_state *cxlds, u16 val)
{
struct pci_dev *pdev = to_pci_dev(cxlds->dev);
int d = cxlds->cxl_dvsec;
u16 ctrl;
int rc;
rc = pci_read_config_word(pdev, d + PCI_DVSEC_CXL_CTRL, &ctrl);
if (rc)
return pcibios_err_to_errno(rc);
if ((ctrl & PCI_DVSEC_CXL_MEM_ENABLE) == val)
return 1;
ctrl &= ~PCI_DVSEC_CXL_MEM_ENABLE;
ctrl |= val;
rc = pci_write_config_word(pdev, d + PCI_DVSEC_CXL_CTRL, ctrl);
if (rc)
return pcibios_err_to_errno(rc);
return 0;
}
static void clear_mem_enable(void *cxlds)
{
cxl_set_mem_enable(cxlds, 0);
}
static int devm_cxl_enable_mem(struct device *host, struct cxl_dev_state *cxlds)
{
int rc;
rc = cxl_set_mem_enable(cxlds, PCI_DVSEC_CXL_MEM_ENABLE);
if (rc < 0)
return rc;
if (rc > 0)
return 0;
return devm_add_action_or_reset(host, clear_mem_enable, cxlds);
}
/* require dvsec ranges to be covered by a locked platform window */
static int dvsec_range_allowed(struct device *dev, const void *arg)
{
const struct range *dev_range = arg;
struct cxl_decoder *cxld;
if (!is_root_decoder(dev))
return 0;
cxld = to_cxl_decoder(dev);
if (!(cxld->flags & CXL_DECODER_F_RAM))
return 0;
return range_contains(&cxld->hpa_range, dev_range);
}
static void disable_hdm(void *_cxlhdm)
{
u32 global_ctrl;
struct cxl_hdm *cxlhdm = _cxlhdm;
void __iomem *hdm = cxlhdm->regs.hdm_decoder;
global_ctrl = readl(hdm + CXL_HDM_DECODER_CTRL_OFFSET);
writel(global_ctrl & ~CXL_HDM_DECODER_ENABLE,
hdm + CXL_HDM_DECODER_CTRL_OFFSET);
}
static int devm_cxl_enable_hdm(struct device *host, struct cxl_hdm *cxlhdm)
{
void __iomem *hdm = cxlhdm->regs.hdm_decoder;
u32 global_ctrl;
global_ctrl = readl(hdm + CXL_HDM_DECODER_CTRL_OFFSET);
writel(global_ctrl | CXL_HDM_DECODER_ENABLE,
hdm + CXL_HDM_DECODER_CTRL_OFFSET);
return devm_add_action_or_reset(host, disable_hdm, cxlhdm);
}
int cxl_dvsec_rr_decode(struct cxl_dev_state *cxlds,
struct cxl_endpoint_dvsec_info *info)
{
struct pci_dev *pdev = to_pci_dev(cxlds->dev);
struct device *dev = cxlds->dev;
int hdm_count, rc, i, ranges = 0;
int d = cxlds->cxl_dvsec;
u16 cap, ctrl;
if (!d) {
dev_dbg(dev, "No DVSEC Capability\n");
return -ENXIO;
}
rc = pci_read_config_word(pdev, d + PCI_DVSEC_CXL_CAP, &cap);
if (rc)
return pcibios_err_to_errno(rc);
if (!(cap & PCI_DVSEC_CXL_MEM_CAPABLE)) {
dev_dbg(dev, "Not MEM Capable\n");
return -ENXIO;
}
/*
* It is not allowed by spec for MEM.capable to be set and have 0 legacy
* HDM decoders (values > 2 are also undefined as of CXL 2.0). As this
* driver is for a spec defined class code which must be CXL.mem
* capable, there is no point in continuing to enable CXL.mem.
*/
hdm_count = FIELD_GET(PCI_DVSEC_CXL_HDM_COUNT, cap);
if (!hdm_count || hdm_count > 2)
return -EINVAL;
/*
* The current DVSEC values are moot if the memory capability is
* disabled, and they will remain moot after the HDM Decoder
* capability is enabled.
*/
rc = pci_read_config_word(pdev, d + PCI_DVSEC_CXL_CTRL, &ctrl);
if (rc)
return pcibios_err_to_errno(rc);
info->mem_enabled = FIELD_GET(PCI_DVSEC_CXL_MEM_ENABLE, ctrl);
if (!info->mem_enabled)
return 0;
for (i = 0; i < hdm_count; i++) {
u64 base, size;
u32 temp;
rc = cxl_dvsec_mem_range_valid(cxlds, i);
if (rc)
return rc;
rc = pci_read_config_dword(
pdev, d + PCI_DVSEC_CXL_RANGE_SIZE_HIGH(i), &temp);
if (rc)
return pcibios_err_to_errno(rc);
size = (u64)temp << 32;
rc = pci_read_config_dword(
pdev, d + PCI_DVSEC_CXL_RANGE_SIZE_LOW(i), &temp);
if (rc)
return pcibios_err_to_errno(rc);
size |= temp & PCI_DVSEC_CXL_MEM_SIZE_LOW;
if (!size) {
continue;
}
rc = pci_read_config_dword(
pdev, d + PCI_DVSEC_CXL_RANGE_BASE_HIGH(i), &temp);
if (rc)
return pcibios_err_to_errno(rc);
base = (u64)temp << 32;
rc = pci_read_config_dword(
pdev, d + PCI_DVSEC_CXL_RANGE_BASE_LOW(i), &temp);
if (rc)
return pcibios_err_to_errno(rc);
base |= temp & PCI_DVSEC_CXL_MEM_BASE_LOW;
info->dvsec_range[ranges++] = (struct range) {
.start = base,
.end = base + size - 1
};
}
info->ranges = ranges;
return 0;
}
EXPORT_SYMBOL_NS_GPL(cxl_dvsec_rr_decode, "CXL");
/**
* cxl_hdm_decode_init() - Setup HDM decoding for the endpoint
* @cxlds: Device state
* @cxlhdm: Mapped HDM decoder Capability
* @info: Cached DVSEC range registers info
*
* Try to enable the endpoint's HDM Decoder Capability
*/
int cxl_hdm_decode_init(struct cxl_dev_state *cxlds, struct cxl_hdm *cxlhdm,
struct cxl_endpoint_dvsec_info *info)
{
void __iomem *hdm = cxlhdm->regs.hdm_decoder;
struct cxl_port *port = cxlhdm->port;
struct device *dev = cxlds->dev;
struct cxl_port *root;
int i, rc, allowed;
u32 global_ctrl = 0;
if (hdm)
global_ctrl = readl(hdm + CXL_HDM_DECODER_CTRL_OFFSET);
/*
* If the HDM Decoder Capability is already enabled then assume
* that some other agent like platform firmware set it up.
*/
if (global_ctrl & CXL_HDM_DECODER_ENABLE || (!hdm && info->mem_enabled))
return devm_cxl_enable_mem(&port->dev, cxlds);
/*
* If the HDM Decoder Capability does not exist and DVSEC was
* not setup, the DVSEC based emulation cannot be used.
*/
if (!hdm)
return -ENODEV;
/* The HDM Decoder Capability exists but is globally disabled. */
/*
* If the DVSEC CXL Range registers are not enabled, just
* enable and use the HDM Decoder Capability registers.
*/
if (!info->mem_enabled) {
rc = devm_cxl_enable_hdm(&port->dev, cxlhdm);
if (rc)
return rc;
return devm_cxl_enable_mem(&port->dev, cxlds);
}
/*
* Per CXL 2.0 Section 8.1.3.8.3 and 8.1.3.8.4 DVSEC CXL Range 1 Base
* [High,Low] when HDM operation is enabled the range register values
* are ignored by the device, but the spec also recommends matching the
* DVSEC Range 1,2 to HDM Decoder Range 0,1. So, non-zero info->ranges
* are expected even though Linux does not require or maintain that
* match. Check if at least one DVSEC range is enabled and allowed by
* the platform. That is, the DVSEC range must be covered by a locked
* platform window (CFMWS). Fail otherwise as the endpoint's decoders
* cannot be used.
*/
root = to_cxl_port(port->dev.parent);
while (!is_cxl_root(root) && is_cxl_port(root->dev.parent))
root = to_cxl_port(root->dev.parent);
if (!is_cxl_root(root)) {
dev_err(dev, "Failed to acquire root port for HDM enable\n");
return -ENODEV;
}
for (i = 0, allowed = 0; i < info->ranges; i++) {
struct device *cxld_dev;
cxld_dev = device_find_child(&root->dev, &info->dvsec_range[i],
dvsec_range_allowed);
if (!cxld_dev) {
dev_dbg(dev, "DVSEC Range%d denied by platform\n", i);
continue;
}
dev_dbg(dev, "DVSEC Range%d allowed by platform\n", i);
put_device(cxld_dev);
allowed++;
}
if (!allowed) {
dev_err(dev, "Range register decodes outside platform defined CXL ranges.\n");
return -ENXIO;
}
return 0;
}
EXPORT_SYMBOL_NS_GPL(cxl_hdm_decode_init, "CXL");
#define CXL_DOE_TABLE_ACCESS_REQ_CODE 0x000000ff
#define CXL_DOE_TABLE_ACCESS_REQ_CODE_READ 0
#define CXL_DOE_TABLE_ACCESS_TABLE_TYPE 0x0000ff00
#define CXL_DOE_TABLE_ACCESS_TABLE_TYPE_CDATA 0
#define CXL_DOE_TABLE_ACCESS_ENTRY_HANDLE 0xffff0000
#define CXL_DOE_TABLE_ACCESS_LAST_ENTRY 0xffff
#define CXL_DOE_PROTOCOL_TABLE_ACCESS 2
#define CDAT_DOE_REQ(entry_handle) cpu_to_le32 \
(FIELD_PREP(CXL_DOE_TABLE_ACCESS_REQ_CODE, \
CXL_DOE_TABLE_ACCESS_REQ_CODE_READ) | \
FIELD_PREP(CXL_DOE_TABLE_ACCESS_TABLE_TYPE, \
CXL_DOE_TABLE_ACCESS_TABLE_TYPE_CDATA) | \
FIELD_PREP(CXL_DOE_TABLE_ACCESS_ENTRY_HANDLE, (entry_handle)))
static int cxl_cdat_get_length(struct device *dev,
struct pci_doe_mb *doe_mb,
size_t *length)
{
__le32 request = CDAT_DOE_REQ(0);
__le32 response[2];
int rc;
rc = pci_doe(doe_mb, PCI_VENDOR_ID_CXL,
CXL_DOE_PROTOCOL_TABLE_ACCESS,
&request, sizeof(request),
&response, sizeof(response));
if (rc < 0) {
dev_err(dev, "DOE failed: %d", rc);
return rc;
}
if (rc < sizeof(response))
return -EIO;
*length = le32_to_cpu(response[1]);
dev_dbg(dev, "CDAT length %zu\n", *length);
return 0;
}
static int cxl_cdat_read_table(struct device *dev,
struct pci_doe_mb *doe_mb,
struct cdat_doe_rsp *rsp, size_t *length)
{
size_t received, remaining = *length;
unsigned int entry_handle = 0;
union cdat_data *data;
__le32 saved_dw = 0;
do {
__le32 request = CDAT_DOE_REQ(entry_handle);
int rc;
rc = pci_doe(doe_mb, PCI_VENDOR_ID_CXL,
CXL_DOE_PROTOCOL_TABLE_ACCESS,
&request, sizeof(request),
rsp, sizeof(*rsp) + remaining);
if (rc < 0) {
dev_err(dev, "DOE failed: %d", rc);
return rc;
}
if (rc < sizeof(*rsp))
return -EIO;
data = (union cdat_data *)rsp->data;
received = rc - sizeof(*rsp);
if (entry_handle == 0) {
if (received != sizeof(data->header))
return -EIO;
} else {
if (received < sizeof(data->entry) ||
received != le16_to_cpu(data->entry.length))
return -EIO;
}
/* Get the CXL table access header entry handle */
entry_handle = FIELD_GET(CXL_DOE_TABLE_ACCESS_ENTRY_HANDLE,
le32_to_cpu(rsp->doe_header));
/*
* Table Access Response Header overwrote the last DW of
* previous entry, so restore that DW
*/
rsp->doe_header = saved_dw;
remaining -= received;
rsp = (void *)rsp + received;
saved_dw = rsp->doe_header;
} while (entry_handle != CXL_DOE_TABLE_ACCESS_LAST_ENTRY);
/* Length in CDAT header may exceed concatenation of CDAT entries */
*length -= remaining;
return 0;
}
static unsigned char cdat_checksum(void *buf, size_t size)
{
unsigned char sum, *data = buf;
size_t i;
for (sum = 0, i = 0; i < size; i++)
sum += data[i];
return sum;
}
/**
* read_cdat_data - Read the CDAT data on this port
* @port: Port to read data from
*
* This call will sleep waiting for responses from the DOE mailbox.
*/
void read_cdat_data(struct cxl_port *port)
{
struct device *uport = port->uport_dev;
struct device *dev = &port->dev;
struct pci_doe_mb *doe_mb;
struct pci_dev *pdev = NULL;
struct cxl_memdev *cxlmd;
struct cdat_doe_rsp *buf;
size_t table_length, length;
int rc;
if (is_cxl_memdev(uport)) {
struct device *host;
cxlmd = to_cxl_memdev(uport);
host = cxlmd->dev.parent;
if (dev_is_pci(host))
pdev = to_pci_dev(host);
} else if (dev_is_pci(uport)) {
pdev = to_pci_dev(uport);
}
if (!pdev)
return;
doe_mb = pci_find_doe_mailbox(pdev, PCI_VENDOR_ID_CXL,
CXL_DOE_PROTOCOL_TABLE_ACCESS);
if (!doe_mb) {
dev_dbg(dev, "No CDAT mailbox\n");
return;
}
port->cdat_available = true;
if (cxl_cdat_get_length(dev, doe_mb, &length)) {
dev_dbg(dev, "No CDAT length\n");
return;
}
/*
* The begin of the CDAT buffer needs space for additional 4
* bytes for the DOE header. Table data starts afterwards.
*/
buf = devm_kzalloc(dev, sizeof(*buf) + length, GFP_KERNEL);
if (!buf)
goto err;
table_length = length;
rc = cxl_cdat_read_table(dev, doe_mb, buf, &length);
if (rc)
goto err;
if (table_length != length)
dev_warn(dev, "Malformed CDAT table length (%zu:%zu), discarding trailing data\n",
table_length, length);
if (cdat_checksum(buf->data, length))
goto err;
port->cdat.table = buf->data;
port->cdat.length = length;
return;
err:
/* Don't leave table data allocated on error */
devm_kfree(dev, buf);
dev_err(dev, "Failed to read/validate CDAT.\n");
}
EXPORT_SYMBOL_NS_GPL(read_cdat_data, "CXL");
static int cxl_flit_size(struct pci_dev *pdev)
{
if (cxl_pci_flit_256(pdev))
return 256;
return 68;
}
/**
* cxl_pci_get_latency - calculate the link latency for the PCIe link
* @pdev: PCI device
*
* return: calculated latency or 0 for no latency
*
* CXL Memory Device SW Guide v1.0 2.11.4 Link latency calculation
* Link latency = LinkPropagationLatency + FlitLatency + RetimerLatency
* LinkProgationLatency is negligible, so 0 will be used
* RetimerLatency is assumed to be negligible and 0 will be used
* FlitLatency = FlitSize / LinkBandwidth
* FlitSize is defined by spec. CXL rev3.0 4.2.1.
* 68B flit is used up to 32GT/s. >32GT/s, 256B flit size is used.
* The FlitLatency is converted to picoseconds.
*/
long cxl_pci_get_latency(struct pci_dev *pdev)
{
long bw;
bw = pcie_link_speed_mbps(pdev);
if (bw < 0)
return 0;
bw /= BITS_PER_BYTE;
return cxl_flit_size(pdev) * MEGA / bw;
}
static int __cxl_endpoint_decoder_reset_detected(struct device *dev, void *data)
{
struct cxl_port *port = data;
struct cxl_decoder *cxld;
struct cxl_hdm *cxlhdm;
void __iomem *hdm;
u32 ctrl;
if (!is_endpoint_decoder(dev))
return 0;
cxld = to_cxl_decoder(dev);
if ((cxld->flags & CXL_DECODER_F_ENABLE) == 0)
return 0;
cxlhdm = dev_get_drvdata(&port->dev);
hdm = cxlhdm->regs.hdm_decoder;
ctrl = readl(hdm + CXL_HDM_DECODER0_CTRL_OFFSET(cxld->id));
return !FIELD_GET(CXL_HDM_DECODER0_CTRL_COMMITTED, ctrl);
}
bool cxl_endpoint_decoder_reset_detected(struct cxl_port *port)
{
return device_for_each_child(&port->dev, port,
__cxl_endpoint_decoder_reset_detected);
}
EXPORT_SYMBOL_NS_GPL(cxl_endpoint_decoder_reset_detected, "CXL");
static int cxl_rcrb_get_comp_regs(struct pci_dev *pdev,
struct cxl_register_map *map,
struct cxl_dport *dport)
{
resource_size_t component_reg_phys;
*map = (struct cxl_register_map) {
.host = &pdev->dev,
.resource = CXL_RESOURCE_NONE,
};
component_reg_phys = cxl_rcd_component_reg_phys(&pdev->dev, dport);
if (component_reg_phys == CXL_RESOURCE_NONE)
return -ENXIO;
map->resource = component_reg_phys;
map->reg_type = CXL_REGLOC_RBI_COMPONENT;
map->max_size = CXL_COMPONENT_REG_BLOCK_SIZE;
return 0;
}
int cxl_pci_setup_regs(struct pci_dev *pdev, enum cxl_regloc_type type,
struct cxl_register_map *map)
{
int rc;
rc = cxl_find_regblock(pdev, type, map);
/*
* If the Register Locator DVSEC does not exist, check if it
* is an RCH and try to extract the Component Registers from
* an RCRB.
*/
if (rc && type == CXL_REGLOC_RBI_COMPONENT && is_cxl_restricted(pdev)) {
struct cxl_dport *dport;
struct cxl_port *port __free(put_cxl_port) =
cxl_pci_find_port(pdev, &dport);
if (!port)
return -EPROBE_DEFER;
rc = cxl_rcrb_get_comp_regs(pdev, map, dport);
if (rc)
return rc;
rc = cxl_dport_map_rcd_linkcap(pdev, dport);
if (rc)
return rc;
} else if (rc) {
return rc;
}
return cxl_setup_regs(map);
}
EXPORT_SYMBOL_NS_GPL(cxl_pci_setup_regs, "CXL");
int cxl_pci_get_bandwidth(struct pci_dev *pdev, struct access_coordinate *c)
{
int speed, bw;
u16 lnksta;
u32 width;
speed = pcie_link_speed_mbps(pdev);
if (speed < 0)
return speed;
speed /= BITS_PER_BYTE;
pcie_capability_read_word(pdev, PCI_EXP_LNKSTA, &lnksta);
width = FIELD_GET(PCI_EXP_LNKSTA_NLW, lnksta);
bw = speed * width;
for (int i = 0; i < ACCESS_COORDINATE_MAX; i++) {
c[i].read_bandwidth = bw;
c[i].write_bandwidth = bw;
}
return 0;
}
/*
* Set max timeout such that platforms will optimize GPF flow to avoid
* the implied worst-case scenario delays. On a sane platform, all
* devices should always complete GPF within the energy budget of
* the GPF flow. The kernel does not have enough information to pick
* anything better than "maximize timeouts and hope it works".
*
* A misbehaving device could block forward progress of GPF for all
* the other devices, exhausting the energy budget of the platform.
* However, the spec seems to assume that moving on from slow to respond
* devices is a virtue. It is not possible to know that, in actuality,
* the slow to respond device is *the* most critical device in the
* system to wait.
*/
#define GPF_TIMEOUT_BASE_MAX 2
#define GPF_TIMEOUT_SCALE_MAX 7 /* 10 seconds */
u16 cxl_gpf_get_dvsec(struct device *dev)
{
struct pci_dev *pdev;
bool is_port = true;
u16 dvsec;
if (!dev_is_pci(dev))
return 0;
pdev = to_pci_dev(dev);
if (pci_pcie_type(pdev) == PCI_EXP_TYPE_ENDPOINT)
is_port = false;
dvsec = pci_find_dvsec_capability(pdev, PCI_VENDOR_ID_CXL,
is_port ? PCI_DVSEC_CXL_PORT_GPF : PCI_DVSEC_CXL_DEVICE_GPF);
if (!dvsec)
dev_warn(dev, "%s GPF DVSEC not present\n",
is_port ? "Port" : "Device");
return dvsec;
}
EXPORT_SYMBOL_NS_GPL(cxl_gpf_get_dvsec, "CXL");
static int update_gpf_port_dvsec(struct pci_dev *pdev, int dvsec, int phase)
{
u64 base, scale;
int rc, offset;
u16 ctrl;
switch (phase) {
case 1:
offset = PCI_DVSEC_CXL_PORT_GPF_PHASE_1_CONTROL;
base = PCI_DVSEC_CXL_PORT_GPF_PHASE_1_TMO_BASE;
scale = PCI_DVSEC_CXL_PORT_GPF_PHASE_1_TMO_SCALE;
break;
case 2:
offset = PCI_DVSEC_CXL_PORT_GPF_PHASE_2_CONTROL;
base = PCI_DVSEC_CXL_PORT_GPF_PHASE_2_TMO_BASE;
scale = PCI_DVSEC_CXL_PORT_GPF_PHASE_2_TMO_SCALE;
break;
default:
return -EINVAL;
}
rc = pci_read_config_word(pdev, dvsec + offset, &ctrl);
if (rc)
return rc;
if (FIELD_GET(base, ctrl) == GPF_TIMEOUT_BASE_MAX &&
FIELD_GET(scale, ctrl) == GPF_TIMEOUT_SCALE_MAX)
return 0;
ctrl = FIELD_PREP(base, GPF_TIMEOUT_BASE_MAX);
ctrl |= FIELD_PREP(scale, GPF_TIMEOUT_SCALE_MAX);
rc = pci_write_config_word(pdev, dvsec + offset, ctrl);
if (!rc)
pci_dbg(pdev, "Port GPF phase %d timeout: %d0 secs\n",
phase, GPF_TIMEOUT_BASE_MAX);
return rc;
}
int cxl_gpf_port_setup(struct cxl_dport *dport)
{
if (!dport)
return -EINVAL;
if (!dport->gpf_dvsec) {
struct pci_dev *pdev;
int dvsec;
dvsec = cxl_gpf_get_dvsec(dport->dport_dev);
if (!dvsec)
return -EINVAL;
dport->gpf_dvsec = dvsec;
pdev = to_pci_dev(dport->dport_dev);
update_gpf_port_dvsec(pdev, dport->gpf_dvsec, 1);
update_gpf_port_dvsec(pdev, dport->gpf_dvsec, 2);
}
return 0;
}
struct cxl_walk_context {
struct pci_bus *bus;
struct cxl_port *port;
int type;
int error;
int count;
};
static int count_dports(struct pci_dev *pdev, void *data)
{
struct cxl_walk_context *ctx = data;
int type = pci_pcie_type(pdev);
if (pdev->bus != ctx->bus)
return 0;
if (!pci_is_pcie(pdev))
return 0;
if (type != ctx->type)
return 0;
ctx->count++;
return 0;
}
int cxl_port_get_possible_dports(struct cxl_port *port)
{
struct pci_bus *bus = cxl_port_to_pci_bus(port);
struct cxl_walk_context ctx;
int type;
if (!bus) {
dev_err(&port->dev, "No PCI bus found for port %s\n",
dev_name(&port->dev));
return -ENXIO;
}
if (pci_is_root_bus(bus))
type = PCI_EXP_TYPE_ROOT_PORT;
else
type = PCI_EXP_TYPE_DOWNSTREAM;
ctx = (struct cxl_walk_context) {
.bus = bus,
.type = type,
};
pci_walk_bus(bus, count_dports, &ctx);
return ctx.count;
}
+310
View File
@@ -0,0 +1,310 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright(c) 2020 Intel Corporation. */
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/idr.h>
#include <cxlmem.h>
#include <cxl.h>
#include "core.h"
/**
* DOC: cxl pmem
*
* The core CXL PMEM infrastructure supports persistent memory
* provisioning and serves as a bridge to the LIBNVDIMM subsystem. A CXL
* 'bridge' device is added at the root of a CXL device topology if
* platform firmware advertises at least one persistent memory capable
* CXL window. That root-level bridge corresponds to a LIBNVDIMM 'bus'
* device. Then for each cxl_memdev in the CXL device topology a bridge
* device is added to host a LIBNVDIMM dimm object. When these bridges
* are registered native LIBNVDIMM uapis are translated to CXL
* operations, for example, namespace label access commands.
*/
static DEFINE_IDA(cxl_nvdimm_bridge_ida);
static void cxl_nvdimm_bridge_release(struct device *dev)
{
struct cxl_nvdimm_bridge *cxl_nvb = to_cxl_nvdimm_bridge(dev);
ida_free(&cxl_nvdimm_bridge_ida, cxl_nvb->id);
kfree(cxl_nvb);
}
static const struct attribute_group *cxl_nvdimm_bridge_attribute_groups[] = {
&cxl_base_attribute_group,
NULL,
};
const struct device_type cxl_nvdimm_bridge_type = {
.name = "cxl_nvdimm_bridge",
.release = cxl_nvdimm_bridge_release,
.groups = cxl_nvdimm_bridge_attribute_groups,
};
struct cxl_nvdimm_bridge *to_cxl_nvdimm_bridge(struct device *dev)
{
if (dev_WARN_ONCE(dev, dev->type != &cxl_nvdimm_bridge_type,
"not a cxl_nvdimm_bridge device\n"))
return NULL;
return container_of(dev, struct cxl_nvdimm_bridge, dev);
}
EXPORT_SYMBOL_NS_GPL(to_cxl_nvdimm_bridge, "CXL");
/**
* cxl_find_nvdimm_bridge() - find a bridge device relative to a port
* @port: any descendant port of an nvdimm-bridge associated
* root-cxl-port
*/
struct cxl_nvdimm_bridge *cxl_find_nvdimm_bridge(struct cxl_port *port)
{
struct cxl_root *cxl_root __free(put_cxl_root) = find_cxl_root(port);
struct device *dev;
if (!cxl_root)
return NULL;
dev = device_find_child(&cxl_root->port.dev,
&cxl_nvdimm_bridge_type,
device_match_type);
if (!dev)
return NULL;
return to_cxl_nvdimm_bridge(dev);
}
EXPORT_SYMBOL_NS_GPL(cxl_find_nvdimm_bridge, "CXL");
static struct lock_class_key cxl_nvdimm_bridge_key;
static struct cxl_nvdimm_bridge *cxl_nvdimm_bridge_alloc(struct cxl_port *port)
{
struct cxl_nvdimm_bridge *cxl_nvb;
struct device *dev;
int rc;
cxl_nvb = kzalloc_obj(*cxl_nvb);
if (!cxl_nvb)
return ERR_PTR(-ENOMEM);
rc = ida_alloc(&cxl_nvdimm_bridge_ida, GFP_KERNEL);
if (rc < 0)
goto err;
cxl_nvb->id = rc;
dev = &cxl_nvb->dev;
cxl_nvb->port = port;
device_initialize(dev);
lockdep_set_class(&dev->mutex, &cxl_nvdimm_bridge_key);
device_set_pm_not_required(dev);
dev->parent = &port->dev;
dev->bus = &cxl_bus_type;
dev->type = &cxl_nvdimm_bridge_type;
return cxl_nvb;
err:
kfree(cxl_nvb);
return ERR_PTR(rc);
}
static void unregister_nvb(void *_cxl_nvb)
{
struct cxl_nvdimm_bridge *cxl_nvb = _cxl_nvb;
device_unregister(&cxl_nvb->dev);
}
static bool cxl_nvdimm_bridge_failed_attach(struct cxl_nvdimm_bridge *cxl_nvb)
{
struct device *dev = &cxl_nvb->dev;
guard(device)(dev);
/* If the device has no driver, then it failed to attach. */
return dev->driver == NULL;
}
struct cxl_nvdimm_bridge *__devm_cxl_add_nvdimm_bridge(struct device *host,
struct cxl_port *port)
{
struct cxl_nvdimm_bridge *cxl_nvb;
struct device *dev;
int rc;
if (!IS_ENABLED(CONFIG_CXL_PMEM))
return ERR_PTR(-ENXIO);
cxl_nvb = cxl_nvdimm_bridge_alloc(port);
if (IS_ERR(cxl_nvb))
return cxl_nvb;
dev = &cxl_nvb->dev;
rc = dev_set_name(dev, "nvdimm-bridge%d", cxl_nvb->id);
if (rc)
goto err;
rc = device_add(dev);
if (rc)
goto err;
if (cxl_nvdimm_bridge_failed_attach(cxl_nvb)) {
unregister_nvb(cxl_nvb);
return ERR_PTR(-ENODEV);
}
rc = devm_add_action_or_reset(host, unregister_nvb, cxl_nvb);
if (rc)
return ERR_PTR(rc);
return cxl_nvb;
err:
put_device(dev);
return ERR_PTR(rc);
}
EXPORT_SYMBOL_FOR_MODULES(__devm_cxl_add_nvdimm_bridge, "cxl_pmem");
static void cxl_nvdimm_release(struct device *dev)
{
struct cxl_nvdimm *cxl_nvd = to_cxl_nvdimm(dev);
kfree(cxl_nvd);
}
static const struct attribute_group *cxl_nvdimm_attribute_groups[] = {
&cxl_base_attribute_group,
NULL,
};
const struct device_type cxl_nvdimm_type = {
.name = "cxl_nvdimm",
.release = cxl_nvdimm_release,
.groups = cxl_nvdimm_attribute_groups,
};
bool is_cxl_nvdimm(struct device *dev)
{
return dev->type == &cxl_nvdimm_type;
}
EXPORT_SYMBOL_NS_GPL(is_cxl_nvdimm, "CXL");
struct cxl_nvdimm *to_cxl_nvdimm(struct device *dev)
{
if (dev_WARN_ONCE(dev, !is_cxl_nvdimm(dev),
"not a cxl_nvdimm device\n"))
return NULL;
return container_of(dev, struct cxl_nvdimm, dev);
}
EXPORT_SYMBOL_NS_GPL(to_cxl_nvdimm, "CXL");
static struct lock_class_key cxl_nvdimm_key;
static struct cxl_nvdimm *cxl_nvdimm_alloc(struct cxl_nvdimm_bridge *cxl_nvb,
struct cxl_memdev *cxlmd)
{
struct cxl_nvdimm *cxl_nvd;
struct device *dev;
cxl_nvd = kzalloc_obj(*cxl_nvd);
if (!cxl_nvd)
return ERR_PTR(-ENOMEM);
dev = &cxl_nvd->dev;
cxl_nvd->cxlmd = cxlmd;
cxlmd->cxl_nvd = cxl_nvd;
device_initialize(dev);
lockdep_set_class(&dev->mutex, &cxl_nvdimm_key);
device_set_pm_not_required(dev);
dev->parent = &cxlmd->dev;
dev->bus = &cxl_bus_type;
dev->type = &cxl_nvdimm_type;
/*
* A "%llx" string is 17-bytes vs dimm_id that is max
* NVDIMM_KEY_DESC_LEN
*/
BUILD_BUG_ON(sizeof(cxl_nvd->dev_id) < 17 ||
sizeof(cxl_nvd->dev_id) > NVDIMM_KEY_DESC_LEN);
sprintf(cxl_nvd->dev_id, "%llx", cxlmd->cxlds->serial);
return cxl_nvd;
}
static void cxlmd_release_nvdimm(void *_cxlmd)
{
struct cxl_memdev *cxlmd = _cxlmd;
struct cxl_nvdimm *cxl_nvd = cxlmd->cxl_nvd;
struct cxl_nvdimm_bridge *cxl_nvb = cxlmd->cxl_nvb;
cxl_nvd->cxlmd = NULL;
cxlmd->cxl_nvd = NULL;
cxlmd->cxl_nvb = NULL;
device_unregister(&cxl_nvd->dev);
put_device(&cxl_nvb->dev);
}
/**
* devm_cxl_add_nvdimm() - add a bridge between a cxl_memdev and an nvdimm
* @host: host device for devm operations
* @port: any port in the CXL topology to find the nvdimm-bridge device
* @cxlmd: parent of the to be created cxl_nvdimm device
*
* Return: 0 on success negative error code on failure.
*/
int devm_cxl_add_nvdimm(struct device *host, struct cxl_port *port,
struct cxl_memdev *cxlmd)
{
struct cxl_nvdimm_bridge *cxl_nvb;
struct cxl_nvdimm *cxl_nvd;
struct device *dev;
int rc;
cxl_nvb = cxl_find_nvdimm_bridge(port);
if (!cxl_nvb)
return -ENODEV;
/*
* Take the uport_dev lock to guard against race of nvdimm_bus object.
* cxl_acpi_probe() registers the nvdimm_bus and is done under the
* root port uport_dev lock.
*
* Take the cxl_nvb device lock to ensure that cxl_nvb driver is in a
* consistent state. And the driver registers nvdimm_bus.
*/
guard(device)(cxl_nvb->port->uport_dev);
guard(device)(&cxl_nvb->dev);
if (!cxl_nvb->nvdimm_bus) {
rc = -ENODEV;
goto err_alloc;
}
cxl_nvd = cxl_nvdimm_alloc(cxl_nvb, cxlmd);
if (IS_ERR(cxl_nvd)) {
rc = PTR_ERR(cxl_nvd);
goto err_alloc;
}
cxlmd->cxl_nvb = cxl_nvb;
dev = &cxl_nvd->dev;
rc = dev_set_name(dev, "pmem%d", cxlmd->id);
if (rc)
goto err;
rc = device_add(dev);
if (rc)
goto err;
dev_dbg(host, "register %s\n", dev_name(dev));
/* @cxlmd carries a reference on @cxl_nvb until cxlmd_release_nvdimm */
return devm_add_action_or_reset(host, cxlmd_release_nvdimm, cxlmd);
err:
put_device(dev);
err_alloc:
cxlmd->cxl_nvb = NULL;
cxlmd->cxl_nvd = NULL;
put_device(&cxl_nvb->dev);
return rc;
}
EXPORT_SYMBOL_NS_GPL(devm_cxl_add_nvdimm, "CXL");
+68
View File
@@ -0,0 +1,68 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright(c) 2023 Huawei. All rights reserved. */
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/idr.h>
#include <cxlmem.h>
#include <pmu.h>
#include <cxl.h>
#include "core.h"
static void cxl_pmu_release(struct device *dev)
{
struct cxl_pmu *pmu = to_cxl_pmu(dev);
kfree(pmu);
}
const struct device_type cxl_pmu_type = {
.name = "cxl_pmu",
.release = cxl_pmu_release,
};
static void remove_dev(void *dev)
{
device_unregister(dev);
}
int devm_cxl_pmu_add(struct device *parent, struct cxl_pmu_regs *regs,
int assoc_id, int index, enum cxl_pmu_type type)
{
struct cxl_pmu *pmu;
struct device *dev;
int rc;
pmu = kzalloc_obj(*pmu);
if (!pmu)
return -ENOMEM;
pmu->assoc_id = assoc_id;
pmu->index = index;
pmu->type = type;
pmu->base = regs->pmu;
dev = &pmu->dev;
device_initialize(dev);
device_set_pm_not_required(dev);
dev->parent = parent;
dev->bus = &cxl_bus_type;
dev->type = &cxl_pmu_type;
switch (pmu->type) {
case CXL_PMU_MEMDEV:
rc = dev_set_name(dev, "pmu_mem%d.%d", assoc_id, index);
break;
}
if (rc)
goto err;
rc = device_add(dev);
if (rc)
goto err;
return devm_add_action_or_reset(parent, remove_dev, dev);
err:
put_device(&pmu->dev);
return rc;
}
EXPORT_SYMBOL_NS_GPL(devm_cxl_pmu_add, "CXL");
File diff suppressed because it is too large Load Diff
+338
View File
@@ -0,0 +1,338 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright(c) 2025 AMD Corporation. All rights reserved. */
#include <linux/pci.h>
#include <linux/aer.h>
#include <cxl/event.h>
#include <cxlmem.h>
#include <cxlpci.h>
#include "trace.h"
/* Check that UCE header definition is maintained to keep ABI intact */
static_assert(CXL_HEADERLOG_TRACE_SIZE_U32 == 128,
"rasdaemon ABI requires exactly 128 u32s");
static void cxl_cper_trace_corr_port_prot_err(struct pci_dev *pdev,
struct cxl_ras_capability_regs ras_cap)
{
u32 status = ras_cap.cor_status & ~ras_cap.cor_mask;
trace_cxl_port_aer_correctable_error(&pdev->dev, status);
}
static void cxl_cper_trace_uncorr_port_prot_err(struct pci_dev *pdev,
struct cxl_ras_capability_regs ras_cap)
{
u32 hl[CXL_HEADERLOG_TRACE_SIZE_U32] = {};
u32 status = ras_cap.uncor_status & ~ras_cap.uncor_mask;
u32 fe;
if (hweight32(status) > 1)
fe = BIT(FIELD_GET(CXL_RAS_CAP_CONTROL_FE_MASK,
ras_cap.cap_control));
else
fe = status;
memcpy(hl, ras_cap.header_log, CXL_HEADERLOG_SIZE);
trace_cxl_port_aer_uncorrectable_error(&pdev->dev, status, fe, hl);
}
static void cxl_cper_trace_corr_prot_err(struct cxl_memdev *cxlmd,
struct cxl_ras_capability_regs ras_cap)
{
u32 status = ras_cap.cor_status & ~ras_cap.cor_mask;
trace_cxl_aer_correctable_error(cxlmd, status);
}
static void
cxl_cper_trace_uncorr_prot_err(struct cxl_memdev *cxlmd,
struct cxl_ras_capability_regs ras_cap)
{
u32 hl[CXL_HEADERLOG_TRACE_SIZE_U32] = {};
u32 status = ras_cap.uncor_status & ~ras_cap.uncor_mask;
u32 fe;
if (hweight32(status) > 1)
fe = BIT(FIELD_GET(CXL_RAS_CAP_CONTROL_FE_MASK,
ras_cap.cap_control));
else
fe = status;
/*
* ras_cap.header_log[] holds CXL_HEADERLOG_SIZE_U32 (16) hardware
* dwords. Copy them into the front of a zero-filled
* CXL_HEADERLOG_TRACE_SIZE_U32 (128) u32 staging buffer so the trace
* event memcpy sees a full 512-byte source and the userspace ABI
* (rasdaemon) is preserved.
*/
memcpy(hl, ras_cap.header_log, CXL_HEADERLOG_SIZE);
trace_cxl_aer_uncorrectable_error(cxlmd, status, fe, hl);
}
static int match_memdev_by_parent(struct device *dev, const void *uport)
{
if (is_cxl_memdev(dev) && dev->parent == uport)
return 1;
return 0;
}
void cxl_cper_handle_prot_err(struct cxl_cper_prot_err_work_data *data)
{
unsigned int devfn = PCI_DEVFN(data->prot_err.agent_addr.device,
data->prot_err.agent_addr.function);
struct pci_dev *pdev __free(pci_dev_put) =
pci_get_domain_bus_and_slot(data->prot_err.agent_addr.segment,
data->prot_err.agent_addr.bus,
devfn);
struct cxl_memdev *cxlmd;
int port_type;
if (!pdev)
return;
port_type = pci_pcie_type(pdev);
if (port_type == PCI_EXP_TYPE_ROOT_PORT ||
port_type == PCI_EXP_TYPE_DOWNSTREAM ||
port_type == PCI_EXP_TYPE_UPSTREAM) {
if (data->severity == AER_CORRECTABLE)
cxl_cper_trace_corr_port_prot_err(pdev, data->ras_cap);
else
cxl_cper_trace_uncorr_port_prot_err(pdev, data->ras_cap);
return;
}
guard(device)(&pdev->dev);
if (!pdev->dev.driver)
return;
struct device *mem_dev __free(put_device) = bus_find_device(
&cxl_bus_type, NULL, pdev, match_memdev_by_parent);
if (!mem_dev)
return;
cxlmd = to_cxl_memdev(mem_dev);
if (data->severity == AER_CORRECTABLE)
cxl_cper_trace_corr_prot_err(cxlmd, data->ras_cap);
else
cxl_cper_trace_uncorr_prot_err(cxlmd, data->ras_cap);
}
EXPORT_SYMBOL_GPL(cxl_cper_handle_prot_err);
static void cxl_cper_prot_err_work_fn(struct work_struct *work)
{
struct cxl_cper_prot_err_work_data wd;
while (cxl_cper_prot_err_kfifo_get(&wd))
cxl_cper_handle_prot_err(&wd);
}
static DECLARE_WORK(cxl_cper_prot_err_work, cxl_cper_prot_err_work_fn);
int cxl_ras_init(void)
{
return cxl_cper_register_prot_err_work(&cxl_cper_prot_err_work);
}
void cxl_ras_exit(void)
{
cxl_cper_unregister_prot_err_work(&cxl_cper_prot_err_work);
cancel_work_sync(&cxl_cper_prot_err_work);
}
static void cxl_dport_map_ras(struct cxl_dport *dport)
{
struct cxl_register_map *map = &dport->reg_map;
struct device *dev = dport->dport_dev;
if (!map->component_map.ras.valid)
dev_dbg(dev, "RAS registers not found\n");
else if (cxl_map_component_regs(map, &dport->regs.component,
BIT(CXL_CM_CAP_CAP_ID_RAS)))
dev_dbg(dev, "Failed to map RAS capability.\n");
}
/**
* devm_cxl_dport_ras_setup - Setup CXL RAS report on this dport
* @dport: the cxl_dport that needs to be initialized
*/
void devm_cxl_dport_ras_setup(struct cxl_dport *dport)
{
dport->reg_map.host = dport_to_host(dport);
cxl_dport_map_ras(dport);
}
void devm_cxl_dport_rch_ras_setup(struct cxl_dport *dport)
{
struct pci_host_bridge *host_bridge;
if (!dev_is_pci(dport->dport_dev))
return;
devm_cxl_dport_ras_setup(dport);
host_bridge = to_pci_host_bridge(dport->dport_dev);
if (!host_bridge->native_aer)
return;
cxl_dport_map_rch_aer(dport);
cxl_disable_rch_root_ints(dport);
}
EXPORT_SYMBOL_NS_GPL(devm_cxl_dport_rch_ras_setup, "CXL");
void devm_cxl_port_ras_setup(struct cxl_port *port)
{
struct cxl_register_map *map = &port->reg_map;
if (!map->component_map.ras.valid) {
dev_dbg(&port->dev, "RAS registers not found\n");
return;
}
map->host = &port->dev;
if (cxl_map_component_regs(map, &port->regs,
BIT(CXL_CM_CAP_CAP_ID_RAS)))
dev_dbg(&port->dev, "Failed to map RAS capability\n");
}
EXPORT_SYMBOL_NS_GPL(devm_cxl_port_ras_setup, "CXL");
void cxl_handle_cor_ras(struct device *dev, void __iomem *ras_base)
{
void __iomem *addr;
u32 status;
if (!ras_base)
return;
addr = ras_base + CXL_RAS_CORRECTABLE_STATUS_OFFSET;
status = readl(addr);
if (status & CXL_RAS_CORRECTABLE_STATUS_MASK) {
writel(status & CXL_RAS_CORRECTABLE_STATUS_MASK, addr);
trace_cxl_aer_correctable_error(to_cxl_memdev(dev), status);
}
}
/* CXL spec rev3.0 8.2.4.16.1 */
static void header_log_copy(void __iomem *ras_base, u32 *log)
{
void __iomem *addr;
u32 *log_addr;
int i;
addr = ras_base + CXL_RAS_HEADER_LOG_OFFSET;
log_addr = log;
for (i = 0; i < CXL_HEADERLOG_SIZE_U32; i++) {
*log_addr = readl(addr);
log_addr++;
addr += sizeof(u32);
}
}
/*
* Log the state of the RAS status registers and prepare them to log the
* next error status. Return 1 if reset needed.
*/
bool cxl_handle_ras(struct device *dev, void __iomem *ras_base)
{
u32 hl[CXL_HEADERLOG_TRACE_SIZE_U32] = {};
void __iomem *addr;
u32 status;
u32 fe;
if (!ras_base)
return false;
addr = ras_base + CXL_RAS_UNCORRECTABLE_STATUS_OFFSET;
status = readl(addr);
if (!(status & CXL_RAS_UNCORRECTABLE_STATUS_MASK))
return false;
/* If multiple errors, log header points to first error from ctrl reg */
if (hweight32(status) > 1) {
void __iomem *rcc_addr =
ras_base + CXL_RAS_CAP_CONTROL_OFFSET;
fe = BIT(FIELD_GET(CXL_RAS_CAP_CONTROL_FE_MASK,
readl(rcc_addr)));
} else {
fe = status;
}
header_log_copy(ras_base, hl);
trace_cxl_aer_uncorrectable_error(to_cxl_memdev(dev), status, fe, hl);
writel(status & CXL_RAS_UNCORRECTABLE_STATUS_MASK, addr);
return true;
}
void cxl_cor_error_detected(struct pci_dev *pdev)
{
struct cxl_dev_state *cxlds = pci_get_drvdata(pdev);
struct cxl_memdev *cxlmd = cxlds->cxlmd;
struct device *dev = &cxlds->cxlmd->dev;
scoped_guard(device, dev) {
if (!dev->driver) {
dev_warn(&pdev->dev,
"%s: memdev disabled, abort error handling\n",
dev_name(dev));
return;
}
if (cxlds->rcd)
cxl_handle_rdport_errors(cxlds);
cxl_handle_cor_ras(&cxlds->cxlmd->dev, cxlmd->endpoint->regs.ras);
}
}
EXPORT_SYMBOL_NS_GPL(cxl_cor_error_detected, "CXL");
pci_ers_result_t cxl_error_detected(struct pci_dev *pdev,
pci_channel_state_t state)
{
struct cxl_dev_state *cxlds = pci_get_drvdata(pdev);
struct cxl_memdev *cxlmd = cxlds->cxlmd;
struct device *dev = &cxlmd->dev;
bool ue;
scoped_guard(device, dev) {
if (!dev->driver) {
dev_warn(&pdev->dev,
"%s: memdev disabled, abort error handling\n",
dev_name(dev));
return PCI_ERS_RESULT_DISCONNECT;
}
if (cxlds->rcd)
cxl_handle_rdport_errors(cxlds);
/*
* A frozen channel indicates an impending reset which is fatal to
* CXL.mem operation, and will likely crash the system. On the off
* chance the situation is recoverable dump the status of the RAS
* capability registers and bounce the active state of the memdev.
*/
ue = cxl_handle_ras(&cxlds->cxlmd->dev, cxlmd->endpoint->regs.ras);
}
switch (state) {
case pci_channel_io_normal:
if (ue) {
device_release_driver(dev);
return PCI_ERS_RESULT_NEED_RESET;
}
return PCI_ERS_RESULT_CAN_RECOVER;
case pci_channel_io_frozen:
dev_warn(&pdev->dev,
"%s: frozen state error detected, disable CXL.mem\n",
dev_name(dev));
device_release_driver(dev);
return PCI_ERS_RESULT_NEED_RESET;
case pci_channel_io_perm_failure:
dev_warn(&pdev->dev,
"failure state error detected, request disconnect\n");
return PCI_ERS_RESULT_DISCONNECT;
}
return PCI_ERS_RESULT_NEED_RESET;
}
EXPORT_SYMBOL_NS_GPL(cxl_error_detected, "CXL");
+121
View File
@@ -0,0 +1,121 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright(c) 2025 AMD Corporation. All rights reserved. */
#include <linux/types.h>
#include <linux/aer.h>
#include "cxl.h"
#include "core.h"
#include "cxlmem.h"
void cxl_dport_map_rch_aer(struct cxl_dport *dport)
{
resource_size_t aer_phys;
struct device *host;
u16 aer_cap;
aer_cap = cxl_rcrb_to_aer(dport->dport_dev, dport->rcrb.base);
if (aer_cap) {
host = dport->reg_map.host;
aer_phys = aer_cap + dport->rcrb.base;
dport->regs.dport_aer =
devm_cxl_iomap_block(host, aer_phys,
sizeof(struct aer_capability_regs));
}
}
void cxl_disable_rch_root_ints(struct cxl_dport *dport)
{
void __iomem *aer_base = dport->regs.dport_aer;
u32 aer_cmd_mask, aer_cmd;
if (!aer_base)
return;
/*
* Disable RCH root port command interrupts.
* CXL 3.0 12.2.1.1 - RCH Downstream Port-detected Errors
*
* This sequence may not be necessary. CXL spec states disabling
* the root cmd register's interrupts is required. But, PCI spec
* shows these are disabled by default on reset.
*/
aer_cmd_mask = (PCI_ERR_ROOT_CMD_COR_EN |
PCI_ERR_ROOT_CMD_NONFATAL_EN |
PCI_ERR_ROOT_CMD_FATAL_EN);
aer_cmd = readl(aer_base + PCI_ERR_ROOT_COMMAND);
aer_cmd &= ~aer_cmd_mask;
writel(aer_cmd, aer_base + PCI_ERR_ROOT_COMMAND);
}
/*
* Copy the AER capability registers using 32 bit read accesses.
* This is necessary because RCRB AER capability is MMIO mapped. Clear the
* status after copying.
*
* @aer_base: base address of AER capability block in RCRB
* @aer_regs: destination for copying AER capability
*/
static bool cxl_rch_get_aer_info(void __iomem *aer_base,
struct aer_capability_regs *aer_regs)
{
int read_cnt = sizeof(struct aer_capability_regs) / sizeof(u32);
u32 *aer_regs_buf = (u32 *)aer_regs;
int n;
if (!aer_base)
return false;
/* Use readl() to guarantee 32-bit accesses */
for (n = 0; n < read_cnt; n++)
aer_regs_buf[n] = readl(aer_base + n * sizeof(u32));
writel(aer_regs->uncor_status, aer_base + PCI_ERR_UNCOR_STATUS);
writel(aer_regs->cor_status, aer_base + PCI_ERR_COR_STATUS);
return true;
}
/* Get AER severity. Return false if there is no error. */
static bool cxl_rch_get_aer_severity(struct aer_capability_regs *aer_regs,
int *severity)
{
if (aer_regs->uncor_status & ~aer_regs->uncor_mask) {
if (aer_regs->uncor_status & PCI_ERR_ROOT_FATAL_RCV)
*severity = AER_FATAL;
else
*severity = AER_NONFATAL;
return true;
}
if (aer_regs->cor_status & ~aer_regs->cor_mask) {
*severity = AER_CORRECTABLE;
return true;
}
return false;
}
void cxl_handle_rdport_errors(struct cxl_dev_state *cxlds)
{
struct pci_dev *pdev = to_pci_dev(cxlds->dev);
struct aer_capability_regs aer_regs;
struct cxl_dport *dport;
int severity;
struct cxl_port *port __free(put_cxl_port) =
cxl_pci_find_port(pdev, &dport);
if (!port)
return;
if (!cxl_rch_get_aer_info(dport->regs.dport_aer, &aer_regs))
return;
if (!cxl_rch_get_aer_severity(&aer_regs, &severity))
return;
pci_print_aer(pdev, severity, &aer_regs);
if (severity == AER_CORRECTABLE)
cxl_handle_cor_ras(&cxlds->cxlmd->dev, dport->regs.ras);
else
cxl_handle_ras(&cxlds->cxlmd->dev, dport->regs.ras);
}
File diff suppressed because it is too large Load Diff
+106
View File
@@ -0,0 +1,106 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright(c) 2022 Intel Corporation. All rights reserved.
* Copyright(c) 2026 Meta Technologies Inc. All rights reserved.
*/
#include <linux/device.h>
#include <linux/slab.h>
#include <cxlmem.h>
#include <cxl.h>
#include "core.h"
static void cxl_dax_region_release(struct device *dev)
{
struct cxl_dax_region *cxlr_dax = to_cxl_dax_region(dev);
kfree(cxlr_dax);
}
static const struct attribute_group *cxl_dax_region_attribute_groups[] = {
&cxl_base_attribute_group,
NULL
};
const struct device_type cxl_dax_region_type = {
.name = "cxl_dax_region",
.release = cxl_dax_region_release,
.groups = cxl_dax_region_attribute_groups,
};
static bool is_cxl_dax_region(struct device *dev)
{
return dev->type == &cxl_dax_region_type;
}
struct cxl_dax_region *to_cxl_dax_region(struct device *dev)
{
if (dev_WARN_ONCE(dev, !is_cxl_dax_region(dev),
"not a cxl_dax_region device\n"))
return NULL;
return container_of(dev, struct cxl_dax_region, dev);
}
EXPORT_SYMBOL_NS_GPL(to_cxl_dax_region, "CXL");
static struct lock_class_key cxl_dax_region_key;
static struct cxl_dax_region *cxl_dax_region_alloc(struct cxl_region *cxlr)
{
struct cxl_region_params *p = &cxlr->params;
struct cxl_dax_region *cxlr_dax;
struct device *dev;
guard(rwsem_read)(&cxl_rwsem.region);
if (p->state != CXL_CONFIG_COMMIT)
return ERR_PTR(-ENXIO);
cxlr_dax = kzalloc_obj(*cxlr_dax);
if (!cxlr_dax)
return ERR_PTR(-ENOMEM);
cxlr_dax->hpa_range.start = p->res->start;
cxlr_dax->hpa_range.end = p->res->end;
dev = &cxlr_dax->dev;
cxlr_dax->cxlr = cxlr;
device_initialize(dev);
lockdep_set_class(&dev->mutex, &cxl_dax_region_key);
device_set_pm_not_required(dev);
dev->parent = &cxlr->dev;
dev->bus = &cxl_bus_type;
dev->type = &cxl_dax_region_type;
return cxlr_dax;
}
static void cxlr_dax_unregister(void *_cxlr_dax)
{
struct cxl_dax_region *cxlr_dax = _cxlr_dax;
device_unregister(&cxlr_dax->dev);
}
int devm_cxl_add_dax_region(struct cxl_region *cxlr)
{
struct device *dev;
int rc;
struct cxl_dax_region *cxlr_dax __free(put_cxl_dax_region) =
cxl_dax_region_alloc(cxlr);
if (IS_ERR(cxlr_dax))
return PTR_ERR(cxlr_dax);
dev = &cxlr_dax->dev;
rc = dev_set_name(dev, "dax_region%d", cxlr->id);
if (rc)
return rc;
rc = device_add(dev);
if (rc)
return rc;
dev_dbg(&cxlr->dev, "%s: register %s\n", dev_name(dev->parent),
dev_name(dev));
return devm_add_action_or_reset(&cxlr->dev, cxlr_dax_unregister,
no_free_ptr(cxlr_dax));
}
+191
View File
@@ -0,0 +1,191 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright(c) 2022 Intel Corporation. All rights reserved. */
#include <linux/device.h>
#include <linux/slab.h>
#include <cxlmem.h>
#include <cxl.h>
#include "core.h"
static void cxl_pmem_region_release(struct device *dev)
{
struct cxl_pmem_region *cxlr_pmem = to_cxl_pmem_region(dev);
int i;
for (i = 0; i < cxlr_pmem->nr_mappings; i++) {
struct cxl_memdev *cxlmd = cxlr_pmem->mapping[i].cxlmd;
put_device(&cxlmd->dev);
}
kfree(cxlr_pmem);
}
static const struct attribute_group *cxl_pmem_region_attribute_groups[] = {
&cxl_base_attribute_group,
NULL
};
const struct device_type cxl_pmem_region_type = {
.name = "cxl_pmem_region",
.release = cxl_pmem_region_release,
.groups = cxl_pmem_region_attribute_groups,
};
bool is_cxl_pmem_region(struct device *dev)
{
return dev->type == &cxl_pmem_region_type;
}
EXPORT_SYMBOL_NS_GPL(is_cxl_pmem_region, "CXL");
struct cxl_pmem_region *to_cxl_pmem_region(struct device *dev)
{
if (dev_WARN_ONCE(dev, !is_cxl_pmem_region(dev),
"not a cxl_pmem_region device\n"))
return NULL;
return container_of(dev, struct cxl_pmem_region, dev);
}
EXPORT_SYMBOL_NS_GPL(to_cxl_pmem_region, "CXL");
static struct lock_class_key cxl_pmem_region_key;
static int cxl_pmem_region_alloc(struct cxl_region *cxlr)
{
struct cxl_region_params *p = &cxlr->params;
struct cxl_nvdimm_bridge *cxl_nvb;
struct device *dev;
int i;
guard(rwsem_read)(&cxl_rwsem.region);
if (p->state != CXL_CONFIG_COMMIT)
return -ENXIO;
struct cxl_pmem_region *cxlr_pmem __free(kfree) =
kzalloc_flex(*cxlr_pmem, mapping, p->nr_targets);
if (!cxlr_pmem)
return -ENOMEM;
cxlr_pmem->hpa_range.start = p->res->start;
cxlr_pmem->hpa_range.end = p->res->end;
/* Snapshot the region configuration underneath the cxl_rwsem.region */
cxlr_pmem->nr_mappings = p->nr_targets;
for (i = 0; i < p->nr_targets; i++) {
struct cxl_endpoint_decoder *cxled = p->targets[i];
struct cxl_memdev *cxlmd = cxled_to_memdev(cxled);
struct cxl_pmem_region_mapping *m = &cxlr_pmem->mapping[i];
/*
* Regions never span CXL root devices, so by definition the
* bridge for one device is the same for all.
*/
if (i == 0) {
cxl_nvb = cxl_find_nvdimm_bridge(cxlmd->endpoint);
if (!cxl_nvb)
return -ENODEV;
cxlr->cxl_nvb = cxl_nvb;
}
m->cxlmd = cxlmd;
get_device(&cxlmd->dev);
m->start = cxled->dpa_res->start;
m->size = resource_size(cxled->dpa_res);
m->position = i;
}
dev = &cxlr_pmem->dev;
device_initialize(dev);
lockdep_set_class(&dev->mutex, &cxl_pmem_region_key);
device_set_pm_not_required(dev);
dev->parent = &cxlr->dev;
dev->bus = &cxl_bus_type;
dev->type = &cxl_pmem_region_type;
cxlr_pmem->cxlr = cxlr;
cxlr->cxlr_pmem = no_free_ptr(cxlr_pmem);
return 0;
}
static void cxlr_pmem_unregister(void *_cxlr_pmem)
{
struct cxl_pmem_region *cxlr_pmem = _cxlr_pmem;
struct cxl_region *cxlr = cxlr_pmem->cxlr;
struct cxl_nvdimm_bridge *cxl_nvb = cxlr->cxl_nvb;
/*
* Either the bridge is in ->remove() context under the device_lock(),
* or cxlr_release_nvdimm() is cancelling the bridge's release action
* for @cxlr_pmem and doing it itself (while manually holding the bridge
* lock).
*/
device_lock_assert(&cxl_nvb->dev);
cxlr->cxlr_pmem = NULL;
cxlr_pmem->cxlr = NULL;
device_unregister(&cxlr_pmem->dev);
}
static void cxlr_release_nvdimm(void *_cxlr)
{
struct cxl_region *cxlr = _cxlr;
struct cxl_nvdimm_bridge *cxl_nvb = cxlr->cxl_nvb;
scoped_guard(device, &cxl_nvb->dev) {
if (cxlr->cxlr_pmem)
devm_release_action(&cxl_nvb->dev, cxlr_pmem_unregister,
cxlr->cxlr_pmem);
}
cxlr->cxl_nvb = NULL;
put_device(&cxl_nvb->dev);
}
/**
* devm_cxl_add_pmem_region() - add a cxl_region-to-nd_region bridge
* @cxlr: parent CXL region for this pmem region bridge device
*
* Return: 0 on success negative error code on failure.
*/
int devm_cxl_add_pmem_region(struct cxl_region *cxlr)
{
struct cxl_pmem_region *cxlr_pmem;
struct cxl_nvdimm_bridge *cxl_nvb;
struct device *dev;
int rc;
rc = cxl_pmem_region_alloc(cxlr);
if (rc)
return rc;
cxlr_pmem = cxlr->cxlr_pmem;
cxl_nvb = cxlr->cxl_nvb;
dev = &cxlr_pmem->dev;
rc = dev_set_name(dev, "pmem_region%d", cxlr->id);
if (rc)
goto err;
rc = device_add(dev);
if (rc)
goto err;
dev_dbg(&cxlr->dev, "%s: register %s\n", dev_name(dev->parent),
dev_name(dev));
scoped_guard(device, &cxl_nvb->dev) {
if (cxl_nvb->dev.driver)
rc = devm_add_action_or_reset(&cxl_nvb->dev,
cxlr_pmem_unregister,
cxlr_pmem);
else
rc = -ENXIO;
}
if (rc)
goto err_bridge;
/* @cxlr carries a reference on @cxl_nvb until cxlr_release_nvdimm */
return devm_add_action_or_reset(&cxlr->dev, cxlr_release_nvdimm, cxlr);
err:
put_device(dev);
err_bridge:
put_device(&cxl_nvb->dev);
cxlr->cxl_nvb = NULL;
return rc;
}
+643
View File
@@ -0,0 +1,643 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright(c) 2020 Intel Corporation. */
#include <linux/io-64-nonatomic-lo-hi.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/pci.h>
#include <cxlmem.h>
#include <cxlpci.h>
#include <pmu.h>
#include "core.h"
/**
* DOC: cxl registers
*
* CXL device capabilities are enumerated by PCI DVSEC (Designated
* Vendor-specific) and / or descriptors provided by platform firmware.
* They can be defined as a set like the device and component registers
* mandated by CXL Section 8.1.12.2 Memory Device PCIe Capabilities and
* Extended Capabilities, or they can be individual capabilities
* appended to bridged and endpoint devices.
*
* Provide common infrastructure for enumerating and mapping these
* discrete capabilities.
*/
/**
* cxl_probe_component_regs() - Detect CXL Component register blocks
* @dev: Host device of the @base mapping
* @base: Mapping containing the HDM Decoder Capability Header
* @map: Map object describing the register block information found
*
* See CXL 2.0 8.2.4 Component Register Layout and Definition
* See CXL 2.0 8.2.5.5 CXL Device Register Interface
*
* Probe for component register information and return it in map object.
*/
void cxl_probe_component_regs(struct device *dev, void __iomem *base,
struct cxl_component_reg_map *map)
{
int cap, cap_count;
u32 cap_array;
*map = (struct cxl_component_reg_map) { 0 };
/*
* CXL.cache and CXL.mem registers are at offset 0x1000 as defined in
* CXL 2.0 8.2.4 Table 141.
*/
base += CXL_CM_OFFSET;
cap_array = readl(base + CXL_CM_CAP_HDR_OFFSET);
if (FIELD_GET(CXL_CM_CAP_HDR_ID_MASK, cap_array) != CM_CAP_HDR_CAP_ID) {
dev_dbg(dev,
"Couldn't locate the CXL.cache and CXL.mem capability array header.\n");
return;
}
/* It's assumed that future versions will be backward compatible */
cap_count = FIELD_GET(CXL_CM_CAP_HDR_ARRAY_SIZE_MASK, cap_array);
for (cap = 1; cap <= cap_count; cap++) {
void __iomem *register_block;
struct cxl_reg_map *rmap;
u16 cap_id, offset;
u32 length, hdr;
hdr = readl(base + cap * 0x4);
cap_id = FIELD_GET(CXL_CM_CAP_HDR_ID_MASK, hdr);
offset = FIELD_GET(CXL_CM_CAP_PTR_MASK, hdr);
register_block = base + offset;
hdr = readl(register_block);
rmap = NULL;
switch (cap_id) {
case CXL_CM_CAP_CAP_ID_HDM: {
int decoder_cnt;
dev_dbg(dev, "found HDM decoder capability (0x%x)\n",
offset);
decoder_cnt = cxl_hdm_decoder_count(hdr);
length = 0x20 * decoder_cnt + 0x10;
rmap = &map->hdm_decoder;
break;
}
case CXL_CM_CAP_CAP_ID_RAS:
dev_dbg(dev, "found RAS capability (0x%x)\n",
offset);
length = CXL_RAS_CAPABILITY_LENGTH;
rmap = &map->ras;
break;
default:
dev_dbg(dev, "Unknown CM cap ID: %d (0x%x)\n", cap_id,
offset);
break;
}
if (!rmap)
continue;
rmap->valid = true;
rmap->id = cap_id;
rmap->offset = CXL_CM_OFFSET + offset;
rmap->size = length;
}
}
EXPORT_SYMBOL_NS_GPL(cxl_probe_component_regs, "CXL");
/**
* cxl_probe_device_regs() - Detect CXL Device register blocks
* @dev: Host device of the @base mapping
* @base: Mapping of CXL 2.0 8.2.8 CXL Device Register Interface
* @map: Map object describing the register block information found
*
* Probe for device register information and return it in map object.
*/
void cxl_probe_device_regs(struct device *dev, void __iomem *base,
struct cxl_device_reg_map *map)
{
int cap, cap_count;
u64 cap_array;
*map = (struct cxl_device_reg_map){ 0 };
cap_array = readq(base + CXLDEV_CAP_ARRAY_OFFSET);
if (FIELD_GET(CXLDEV_CAP_ARRAY_ID_MASK, cap_array) !=
CXLDEV_CAP_ARRAY_CAP_ID)
return;
cap_count = FIELD_GET(CXLDEV_CAP_ARRAY_COUNT_MASK, cap_array);
for (cap = 1; cap <= cap_count; cap++) {
struct cxl_reg_map *rmap;
u32 offset, length;
u16 cap_id;
cap_id = FIELD_GET(CXLDEV_CAP_HDR_CAP_ID_MASK,
readl(base + cap * 0x10));
offset = readl(base + cap * 0x10 + 0x4);
length = readl(base + cap * 0x10 + 0x8);
rmap = NULL;
switch (cap_id) {
case CXLDEV_CAP_CAP_ID_DEVICE_STATUS:
dev_dbg(dev, "found Status capability (0x%x)\n", offset);
rmap = &map->status;
break;
case CXLDEV_CAP_CAP_ID_PRIMARY_MAILBOX:
dev_dbg(dev, "found Mailbox capability (0x%x)\n", offset);
rmap = &map->mbox;
break;
case CXLDEV_CAP_CAP_ID_SECONDARY_MAILBOX:
dev_dbg(dev, "found Secondary Mailbox capability (0x%x)\n", offset);
break;
case CXLDEV_CAP_CAP_ID_MEMDEV:
dev_dbg(dev, "found Memory Device capability (0x%x)\n", offset);
rmap = &map->memdev;
break;
default:
if (cap_id >= 0x8000)
dev_dbg(dev, "Vendor cap ID: %#x offset: %#x\n", cap_id, offset);
else
dev_dbg(dev, "Unknown cap ID: %#x offset: %#x\n", cap_id, offset);
break;
}
if (!rmap)
continue;
rmap->valid = true;
rmap->id = cap_id;
rmap->offset = offset;
rmap->size = length;
}
}
EXPORT_SYMBOL_NS_GPL(cxl_probe_device_regs, "CXL");
void __iomem *devm_cxl_iomap_block(struct device *dev, resource_size_t addr,
resource_size_t length)
{
void __iomem *ret_val;
struct resource *res;
if (WARN_ON_ONCE(addr == CXL_RESOURCE_NONE))
return NULL;
res = devm_request_mem_region(dev, addr, length, dev_name(dev));
if (!res) {
resource_size_t end = addr + length - 1;
dev_err(dev, "Failed to request region %pa-%pa\n", &addr, &end);
return NULL;
}
ret_val = devm_ioremap(dev, addr, length);
if (!ret_val)
dev_err(dev, "Failed to map region %pr\n", res);
return ret_val;
}
int cxl_map_component_regs(const struct cxl_register_map *map,
struct cxl_component_regs *regs,
unsigned long map_mask)
{
struct device *host = map->host;
struct mapinfo {
const struct cxl_reg_map *rmap;
void __iomem **addr;
} mapinfo[] = {
{ &map->component_map.hdm_decoder, &regs->hdm_decoder },
{ &map->component_map.ras, &regs->ras },
};
int i;
for (i = 0; i < ARRAY_SIZE(mapinfo); i++) {
struct mapinfo *mi = &mapinfo[i];
resource_size_t addr;
resource_size_t length;
if (!mi->rmap->valid)
continue;
if (!test_bit(mi->rmap->id, &map_mask))
continue;
addr = map->resource + mi->rmap->offset;
length = mi->rmap->size;
*(mi->addr) = devm_cxl_iomap_block(host, addr, length);
if (!*(mi->addr))
return -ENOMEM;
}
return 0;
}
EXPORT_SYMBOL_NS_GPL(cxl_map_component_regs, "CXL");
int cxl_map_device_regs(const struct cxl_register_map *map,
struct cxl_device_regs *regs)
{
struct device *host = map->host;
resource_size_t phys_addr = map->resource;
struct mapinfo {
const struct cxl_reg_map *rmap;
void __iomem **addr;
} mapinfo[] = {
{ &map->device_map.status, &regs->status, },
{ &map->device_map.mbox, &regs->mbox, },
{ &map->device_map.memdev, &regs->memdev, },
};
int i;
for (i = 0; i < ARRAY_SIZE(mapinfo); i++) {
struct mapinfo *mi = &mapinfo[i];
resource_size_t length;
resource_size_t addr;
if (!mi->rmap->valid)
continue;
addr = phys_addr + mi->rmap->offset;
length = mi->rmap->size;
*(mi->addr) = devm_cxl_iomap_block(host, addr, length);
if (!*(mi->addr))
return -ENOMEM;
}
return 0;
}
EXPORT_SYMBOL_NS_GPL(cxl_map_device_regs, "CXL");
static bool cxl_decode_regblock(struct pci_dev *pdev, u32 reg_lo, u32 reg_hi,
struct cxl_register_map *map)
{
u8 reg_type = FIELD_GET(PCI_DVSEC_CXL_REG_LOCATOR_BLOCK_ID, reg_lo);
int bar = FIELD_GET(PCI_DVSEC_CXL_REG_LOCATOR_BIR, reg_lo);
u64 offset = ((u64)reg_hi << 32) |
(reg_lo & PCI_DVSEC_CXL_REG_LOCATOR_BLOCK_OFF_LOW);
if (offset > pci_resource_len(pdev, bar)) {
dev_warn(&pdev->dev,
"BAR%d: %pr: too small (offset: %pa, type: %d)\n", bar,
&pdev->resource[bar], &offset, reg_type);
return false;
}
map->reg_type = reg_type;
map->resource = pci_resource_start(pdev, bar) + offset;
map->max_size = pci_resource_len(pdev, bar) - offset;
return true;
}
/*
* __cxl_find_regblock_instance() - Locate a register block or count instances by type / index
* Use CXL_INSTANCES_COUNT for @index if counting instances.
*
* __cxl_find_regblock_instance() may return:
* 0 - if register block enumerated.
* >= 0 - if counting instances.
* < 0 - error code otherwise.
*/
static int __cxl_find_regblock_instance(struct pci_dev *pdev, enum cxl_regloc_type type,
struct cxl_register_map *map, int index)
{
u32 regloc_size, regblocks;
int instance = 0;
int regloc, i;
*map = (struct cxl_register_map) {
.host = &pdev->dev,
.resource = CXL_RESOURCE_NONE,
};
regloc = pci_find_dvsec_capability(pdev, PCI_VENDOR_ID_CXL,
PCI_DVSEC_CXL_REG_LOCATOR);
if (!regloc)
return -ENXIO;
pci_read_config_dword(pdev, regloc + PCI_DVSEC_HEADER1, &regloc_size);
regloc_size = PCI_DVSEC_HEADER1_LEN(regloc_size);
regloc += PCI_DVSEC_CXL_REG_LOCATOR_BLOCK1;
regblocks = (regloc_size - PCI_DVSEC_CXL_REG_LOCATOR_BLOCK1) / 8;
for (i = 0; i < regblocks; i++, regloc += 8) {
u32 reg_lo, reg_hi;
pci_read_config_dword(pdev, regloc, &reg_lo);
pci_read_config_dword(pdev, regloc + 4, &reg_hi);
if (!cxl_decode_regblock(pdev, reg_lo, reg_hi, map))
continue;
if (map->reg_type == type) {
if (index == instance)
return 0;
instance++;
}
}
map->resource = CXL_RESOURCE_NONE;
if (index == CXL_INSTANCES_COUNT)
return instance;
return -ENODEV;
}
/**
* cxl_find_regblock_instance() - Locate a register block by type / index
* @pdev: The CXL PCI device to enumerate.
* @type: Register Block Indicator id
* @map: Enumeration output, clobbered on error
* @index: Index into which particular instance of a regblock wanted in the
* order found in register locator DVSEC.
*
* Return: 0 if register block enumerated, negative error code otherwise
*
* A CXL DVSEC may point to one or more register blocks, search for them
* by @type and @index.
*/
int cxl_find_regblock_instance(struct pci_dev *pdev, enum cxl_regloc_type type,
struct cxl_register_map *map, unsigned int index)
{
return __cxl_find_regblock_instance(pdev, type, map, index);
}
EXPORT_SYMBOL_NS_GPL(cxl_find_regblock_instance, "CXL");
/**
* cxl_find_regblock() - Locate register blocks by type
* @pdev: The CXL PCI device to enumerate.
* @type: Register Block Indicator id
* @map: Enumeration output, clobbered on error
*
* Return: 0 if register block enumerated, negative error code otherwise
*
* A CXL DVSEC may point to one or more register blocks, search for them
* by @type.
*/
int cxl_find_regblock(struct pci_dev *pdev, enum cxl_regloc_type type,
struct cxl_register_map *map)
{
return __cxl_find_regblock_instance(pdev, type, map, 0);
}
EXPORT_SYMBOL_NS_GPL(cxl_find_regblock, "CXL");
/**
* cxl_count_regblock() - Count instances of a given regblock type.
* @pdev: The CXL PCI device to enumerate.
* @type: Register Block Indicator id
*
* Some regblocks may be repeated. Count how many instances.
*
* Return: non-negative count of matching regblocks, negative error code otherwise.
*/
int cxl_count_regblock(struct pci_dev *pdev, enum cxl_regloc_type type)
{
struct cxl_register_map map;
return __cxl_find_regblock_instance(pdev, type, &map, CXL_INSTANCES_COUNT);
}
EXPORT_SYMBOL_NS_GPL(cxl_count_regblock, "CXL");
int cxl_map_pmu_regs(struct cxl_register_map *map, struct cxl_pmu_regs *regs)
{
struct device *dev = map->host;
resource_size_t phys_addr;
phys_addr = map->resource;
regs->pmu = devm_cxl_iomap_block(dev, phys_addr, CXL_PMU_REGMAP_SIZE);
if (!regs->pmu)
return -ENOMEM;
return 0;
}
EXPORT_SYMBOL_NS_GPL(cxl_map_pmu_regs, "CXL");
static int cxl_map_regblock(struct cxl_register_map *map)
{
struct device *host = map->host;
map->base = ioremap(map->resource, map->max_size);
if (!map->base) {
dev_err(host, "failed to map registers\n");
return -ENOMEM;
}
dev_dbg(host, "Mapped CXL Memory Device resource %pa\n", &map->resource);
return 0;
}
static void cxl_unmap_regblock(struct cxl_register_map *map)
{
iounmap(map->base);
map->base = NULL;
}
static int cxl_probe_regs(struct cxl_register_map *map)
{
struct cxl_component_reg_map *comp_map;
struct cxl_device_reg_map *dev_map;
struct device *host = map->host;
void __iomem *base = map->base;
switch (map->reg_type) {
case CXL_REGLOC_RBI_COMPONENT:
comp_map = &map->component_map;
cxl_probe_component_regs(host, base, comp_map);
dev_dbg(host, "Set up component registers\n");
break;
case CXL_REGLOC_RBI_MEMDEV:
dev_map = &map->device_map;
cxl_probe_device_regs(host, base, dev_map);
if (!dev_map->status.valid || !dev_map->mbox.valid ||
!dev_map->memdev.valid) {
dev_err(host, "registers not found: %s%s%s\n",
!dev_map->status.valid ? "status " : "",
!dev_map->mbox.valid ? "mbox " : "",
!dev_map->memdev.valid ? "memdev " : "");
return -ENXIO;
}
dev_dbg(host, "Probing device registers...\n");
break;
default:
break;
}
return 0;
}
int cxl_setup_regs(struct cxl_register_map *map)
{
int rc;
rc = cxl_map_regblock(map);
if (rc)
return rc;
rc = cxl_probe_regs(map);
cxl_unmap_regblock(map);
return rc;
}
EXPORT_SYMBOL_NS_GPL(cxl_setup_regs, "CXL");
u16 cxl_rcrb_to_aer(struct device *dev, resource_size_t rcrb)
{
void __iomem *addr;
u16 offset = 0;
u32 cap_hdr;
if (WARN_ON_ONCE(rcrb == CXL_RESOURCE_NONE))
return 0;
if (!request_mem_region(rcrb, SZ_4K, dev_name(dev)))
return 0;
addr = ioremap(rcrb, SZ_4K);
if (!addr)
goto out;
cap_hdr = readl(addr + offset);
while (PCI_EXT_CAP_ID(cap_hdr) != PCI_EXT_CAP_ID_ERR) {
offset = PCI_EXT_CAP_NEXT(cap_hdr);
/* Offset 0 terminates capability list. */
if (!offset)
break;
cap_hdr = readl(addr + offset);
}
if (offset)
dev_dbg(dev, "found AER extended capability (0x%x)\n", offset);
iounmap(addr);
out:
release_mem_region(rcrb, SZ_4K);
return offset;
}
static resource_size_t cxl_rcrb_to_linkcap(struct device *dev, struct cxl_dport *dport)
{
resource_size_t rcrb = dport->rcrb.base;
void __iomem *addr;
u32 cap_hdr;
u16 offset;
if (!request_mem_region(rcrb, SZ_4K, "CXL RCRB"))
return CXL_RESOURCE_NONE;
addr = ioremap(rcrb, SZ_4K);
if (!addr) {
dev_err(dev, "Failed to map region %pr\n", addr);
release_mem_region(rcrb, SZ_4K);
return CXL_RESOURCE_NONE;
}
offset = FIELD_GET(PCI_RCRB_CAP_LIST_ID_MASK, readw(addr + PCI_CAPABILITY_LIST));
cap_hdr = readl(addr + offset);
while ((FIELD_GET(PCI_RCRB_CAP_HDR_ID_MASK, cap_hdr)) != PCI_CAP_ID_EXP) {
offset = FIELD_GET(PCI_RCRB_CAP_HDR_NEXT_MASK, cap_hdr);
if (offset == 0 || offset > SZ_4K) {
offset = 0;
break;
}
cap_hdr = readl(addr + offset);
}
iounmap(addr);
release_mem_region(rcrb, SZ_4K);
if (!offset)
return CXL_RESOURCE_NONE;
return offset;
}
int cxl_dport_map_rcd_linkcap(struct pci_dev *pdev, struct cxl_dport *dport)
{
void __iomem *dport_pcie_cap = NULL;
resource_size_t pos;
struct cxl_rcrb_info *ri;
ri = &dport->rcrb;
pos = cxl_rcrb_to_linkcap(&pdev->dev, dport);
if (pos == CXL_RESOURCE_NONE)
return -ENXIO;
dport_pcie_cap = devm_cxl_iomap_block(&pdev->dev,
ri->base + pos,
PCI_CAP_EXP_SIZEOF);
dport->regs.rcd_pcie_cap = dport_pcie_cap;
return 0;
}
EXPORT_SYMBOL_NS_GPL(cxl_dport_map_rcd_linkcap, "CXL");
resource_size_t __rcrb_to_component(struct device *dev, struct cxl_rcrb_info *ri,
enum cxl_rcrb which)
{
resource_size_t component_reg_phys;
resource_size_t rcrb = ri->base;
void __iomem *addr;
u32 bar0, bar1;
u32 id;
if (which == CXL_RCRB_UPSTREAM)
rcrb += SZ_4K;
/*
* RCRB's BAR[0..1] point to component block containing CXL
* subsystem component registers. MEMBAR extraction follows
* the PCI Base spec here, esp. 64 bit extraction and memory
* ranges alignment (6.0, 7.5.1.2.1).
*/
if (!request_mem_region(rcrb, SZ_4K, "CXL RCRB"))
return CXL_RESOURCE_NONE;
addr = ioremap(rcrb, SZ_4K);
if (!addr) {
dev_err(dev, "Failed to map region %pr\n", addr);
release_mem_region(rcrb, SZ_4K);
return CXL_RESOURCE_NONE;
}
id = readl(addr + PCI_VENDOR_ID);
bar0 = readl(addr + PCI_BASE_ADDRESS_0);
bar1 = readl(addr + PCI_BASE_ADDRESS_1);
iounmap(addr);
release_mem_region(rcrb, SZ_4K);
/*
* Sanity check, see CXL 3.0 Figure 9-8 CXL Device that Does Not
* Remap Upstream Port and Component Registers
*/
if (id == U32_MAX) {
if (which == CXL_RCRB_DOWNSTREAM)
dev_err(dev, "Failed to access Downstream Port RCRB\n");
return CXL_RESOURCE_NONE;
}
/* The RCRB is a Memory Window, and the MEM_TYPE_1M bit is obsolete */
if (bar0 & (PCI_BASE_ADDRESS_MEM_TYPE_1M | PCI_BASE_ADDRESS_SPACE_IO))
return CXL_RESOURCE_NONE;
component_reg_phys = bar0 & PCI_BASE_ADDRESS_MEM_MASK;
if (bar0 & PCI_BASE_ADDRESS_MEM_TYPE_64)
component_reg_phys |= ((u64)bar1) << 32;
if (!component_reg_phys)
return CXL_RESOURCE_NONE;
/* MEMBAR is block size (64k) aligned. */
if (!IS_ALIGNED(component_reg_phys, CXL_COMPONENT_REG_BLOCK_SIZE))
return CXL_RESOURCE_NONE;
return component_reg_phys;
}
resource_size_t cxl_rcd_component_reg_phys(struct device *dev,
struct cxl_dport *dport)
{
if (!dport->rch)
return CXL_RESOURCE_NONE;
return __rcrb_to_component(dev, &dport->rcrb, CXL_RCRB_UPSTREAM);
}
+24
View File
@@ -0,0 +1,24 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright(c) 2022 Intel Corporation. All rights reserved. */
#include <linux/atomic.h>
#include <linux/export.h>
#include "cxlmem.h"
static atomic_t mem_active;
bool cxl_mem_active(void)
{
return atomic_read(&mem_active) != 0;
}
void cxl_mem_active_inc(void)
{
atomic_inc(&mem_active);
}
EXPORT_SYMBOL_NS_GPL(cxl_mem_active_inc, "CXL");
void cxl_mem_active_dec(void)
{
atomic_dec(&mem_active);
}
EXPORT_SYMBOL_NS_GPL(cxl_mem_active_dec, "CXL");
+8
View File
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright(c) 2022 Intel Corporation. All rights reserved. */
#include <cxl.h>
#include "core.h"
#define CREATE_TRACE_POINTS
#include "trace.h"
File diff suppressed because it is too large Load Diff
+932
View File
@@ -0,0 +1,932 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright(c) 2020 Intel Corporation. */
#ifndef __CXL_H__
#define __CXL_H__
#include <linux/libnvdimm.h>
#include <linux/bitfield.h>
#include <linux/notifier.h>
#include <linux/bitops.h>
#include <linux/log2.h>
#include <linux/node.h>
#include <linux/io.h>
#include <linux/range.h>
#include <cxl/cxl.h>
extern const struct nvdimm_security_ops *cxl_security_ops;
/**
* DOC: cxl objects
*
* The CXL core objects like ports, decoders, and regions are shared
* between the subsystem drivers cxl_acpi, cxl_pci, and core drivers
* (port-driver, region-driver, nvdimm object-drivers... etc).
*/
/* CXL 2.0 8.2.4 CXL Component Register Layout and Definition */
#define CXL_COMPONENT_REG_BLOCK_SIZE SZ_64K
/* CXL 2.0 8.2.5 CXL.cache and CXL.mem Registers*/
#define CXL_CM_OFFSET 0x1000
#define CXL_CM_CAP_HDR_OFFSET 0x0
#define CXL_CM_CAP_HDR_ID_MASK GENMASK(15, 0)
#define CM_CAP_HDR_CAP_ID 1
#define CXL_CM_CAP_HDR_VERSION_MASK GENMASK(19, 16)
#define CM_CAP_HDR_CAP_VERSION 1
#define CXL_CM_CAP_HDR_CACHE_MEM_VERSION_MASK GENMASK(23, 20)
#define CM_CAP_HDR_CACHE_MEM_VERSION 1
#define CXL_CM_CAP_HDR_ARRAY_SIZE_MASK GENMASK(31, 24)
#define CXL_CM_CAP_PTR_MASK GENMASK(31, 20)
#define CXL_CM_CAP_CAP_ID_RAS 0x2
#define CXL_CM_CAP_CAP_ID_HDM 0x5
#define CXL_CM_CAP_CAP_HDM_VERSION 1
/* HDM decoders CXL 2.0 8.2.5.12 CXL HDM Decoder Capability Structure */
#define CXL_HDM_DECODER_CAP_OFFSET 0x0
#define CXL_HDM_DECODER_COUNT_MASK GENMASK(3, 0)
#define CXL_HDM_DECODER_TARGET_COUNT_MASK GENMASK(7, 4)
#define CXL_HDM_DECODER_INTERLEAVE_11_8 BIT(8)
#define CXL_HDM_DECODER_INTERLEAVE_14_12 BIT(9)
#define CXL_HDM_DECODER_INTERLEAVE_3_6_12_WAY BIT(11)
#define CXL_HDM_DECODER_INTERLEAVE_16_WAY BIT(12)
#define CXL_HDM_DECODER_CTRL_OFFSET 0x4
#define CXL_HDM_DECODER_ENABLE BIT(1)
#define CXL_HDM_DECODER0_BASE_LOW_OFFSET(i) (0x20 * (i) + 0x10)
#define CXL_HDM_DECODER0_BASE_HIGH_OFFSET(i) (0x20 * (i) + 0x14)
#define CXL_HDM_DECODER0_SIZE_LOW_OFFSET(i) (0x20 * (i) + 0x18)
#define CXL_HDM_DECODER0_SIZE_HIGH_OFFSET(i) (0x20 * (i) + 0x1c)
#define CXL_HDM_DECODER0_CTRL_OFFSET(i) (0x20 * (i) + 0x20)
#define CXL_HDM_DECODER0_CTRL_IG_MASK GENMASK(3, 0)
#define CXL_HDM_DECODER0_CTRL_IW_MASK GENMASK(7, 4)
#define CXL_HDM_DECODER0_CTRL_LOCK BIT(8)
#define CXL_HDM_DECODER0_CTRL_COMMIT BIT(9)
#define CXL_HDM_DECODER0_CTRL_COMMITTED BIT(10)
#define CXL_HDM_DECODER0_CTRL_COMMIT_ERROR BIT(11)
#define CXL_HDM_DECODER0_CTRL_HOSTONLY BIT(12)
#define CXL_HDM_DECODER0_TL_LOW(i) (0x20 * (i) + 0x24)
#define CXL_HDM_DECODER0_TL_HIGH(i) (0x20 * (i) + 0x28)
#define CXL_HDM_DECODER0_SKIP_LOW(i) CXL_HDM_DECODER0_TL_LOW(i)
#define CXL_HDM_DECODER0_SKIP_HIGH(i) CXL_HDM_DECODER0_TL_HIGH(i)
/* HDM decoder control register constants CXL 3.0 8.2.5.19.7 */
#define CXL_DECODER_MIN_GRANULARITY 256
#define CXL_DECODER_MAX_ENCODED_IG 6
static inline int cxl_hdm_decoder_count(u32 cap_hdr)
{
int val = FIELD_GET(CXL_HDM_DECODER_COUNT_MASK, cap_hdr);
switch (val) {
case 0:
return 1;
case 1 ... 8:
return val * 2;
case 9 ... 12:
return (val - 4) * 4;
default:
return -ENXIO;
}
}
/* Encode defined in CXL 2.0 8.2.5.12.7 HDM Decoder Control Register */
static inline int eig_to_granularity(u16 eig, int *granularity)
{
if (eig > CXL_DECODER_MAX_ENCODED_IG)
return -EINVAL;
*granularity = CXL_DECODER_MIN_GRANULARITY << eig;
return 0;
}
/* Encode defined in CXL ECN "3, 6, 12 and 16-way memory Interleaving" */
static inline int eiw_to_ways(u8 eiw, int *ways)
{
switch (eiw) {
case 0 ... 4:
*ways = 1 << eiw;
break;
case 8 ... 10:
*ways = 3 << (eiw - 8);
break;
default:
return -EINVAL;
}
return 0;
}
static inline int granularity_to_eig(int granularity, u16 *eig)
{
*eig = 0;
if (granularity > SZ_16K || granularity < CXL_DECODER_MIN_GRANULARITY ||
!is_power_of_2(granularity))
return -EINVAL;
*eig = ilog2(granularity) - 8;
return 0;
}
static inline int ways_to_eiw(unsigned int ways, u8 *eiw)
{
*eiw = 0;
if (ways > 16)
return -EINVAL;
if (is_power_of_2(ways)) {
*eiw = ilog2(ways);
return 0;
}
if (ways % 3)
return -EINVAL;
ways /= 3;
if (!is_power_of_2(ways))
return -EINVAL;
*eiw = ilog2(ways) + 8;
return 0;
}
/* RAS Registers CXL 2.0 8.2.5.9 CXL RAS Capability Structure */
#define CXL_RAS_UNCORRECTABLE_STATUS_OFFSET 0x0
#define CXL_RAS_UNCORRECTABLE_STATUS_MASK (GENMASK(16, 14) | GENMASK(11, 0))
#define CXL_RAS_UNCORRECTABLE_MASK_OFFSET 0x4
#define CXL_RAS_UNCORRECTABLE_MASK_MASK (GENMASK(16, 14) | GENMASK(11, 0))
#define CXL_RAS_UNCORRECTABLE_MASK_F256B_MASK BIT(8)
#define CXL_RAS_UNCORRECTABLE_SEVERITY_OFFSET 0x8
#define CXL_RAS_UNCORRECTABLE_SEVERITY_MASK (GENMASK(16, 14) | GENMASK(11, 0))
#define CXL_RAS_CORRECTABLE_STATUS_OFFSET 0xC
#define CXL_RAS_CORRECTABLE_STATUS_MASK GENMASK(6, 0)
#define CXL_RAS_CORRECTABLE_MASK_OFFSET 0x10
#define CXL_RAS_CORRECTABLE_MASK_MASK GENMASK(6, 0)
#define CXL_RAS_CAP_CONTROL_OFFSET 0x14
#define CXL_RAS_CAP_CONTROL_FE_MASK GENMASK(5, 0)
#define CXL_RAS_HEADER_LOG_OFFSET 0x18
#define CXL_RAS_CAPABILITY_LENGTH 0x58
#define CXL_HEADERLOG_SIZE SZ_64
#define CXL_HEADERLOG_SIZE_U32 (CXL_HEADERLOG_SIZE / sizeof(u32))
/*
* The RAS UCE trace event header array was originally sized at SZ_512/sizeof(u32)
* = 128 u32s due to a bug. Userspace tools (rasdaemon) have grown a dependency
* on that 512-byte layout. Keep the trace array at 128 u32s to preserve the
* ABI; only CXL_HEADERLOG_SIZE_U32 (16) dwords are valid hardware data, the
* remainder are zero-filled.
*/
#define CXL_HEADERLOG_TRACE_SIZE SZ_512
#define CXL_HEADERLOG_TRACE_SIZE_U32 (CXL_HEADERLOG_TRACE_SIZE / sizeof(u32))
/* CXL 2.0 8.2.8.1 Device Capabilities Array Register */
#define CXLDEV_CAP_ARRAY_OFFSET 0x0
#define CXLDEV_CAP_ARRAY_CAP_ID 0
#define CXLDEV_CAP_ARRAY_ID_MASK GENMASK_ULL(15, 0)
#define CXLDEV_CAP_ARRAY_COUNT_MASK GENMASK_ULL(47, 32)
/* CXL 2.0 8.2.8.2 CXL Device Capability Header Register */
#define CXLDEV_CAP_HDR_CAP_ID_MASK GENMASK(15, 0)
/* CXL 2.0 8.2.8.2.1 CXL Device Capabilities */
#define CXLDEV_CAP_CAP_ID_DEVICE_STATUS 0x1
#define CXLDEV_CAP_CAP_ID_PRIMARY_MAILBOX 0x2
#define CXLDEV_CAP_CAP_ID_SECONDARY_MAILBOX 0x3
#define CXLDEV_CAP_CAP_ID_MEMDEV 0x4000
/* CXL 3.0 8.2.8.3.1 Event Status Register */
#define CXLDEV_DEV_EVENT_STATUS_OFFSET 0x00
#define CXLDEV_EVENT_STATUS_INFO BIT(0)
#define CXLDEV_EVENT_STATUS_WARN BIT(1)
#define CXLDEV_EVENT_STATUS_FAIL BIT(2)
#define CXLDEV_EVENT_STATUS_FATAL BIT(3)
#define CXLDEV_EVENT_STATUS_ALL (CXLDEV_EVENT_STATUS_INFO | \
CXLDEV_EVENT_STATUS_WARN | \
CXLDEV_EVENT_STATUS_FAIL | \
CXLDEV_EVENT_STATUS_FATAL)
/* CXL rev 3.0 section 8.2.9.2.4; Table 8-52 */
#define CXLDEV_EVENT_INT_MODE_MASK GENMASK(1, 0)
#define CXLDEV_EVENT_INT_MSGNUM_MASK GENMASK(7, 4)
/* CXL 2.0 8.2.8.4 Mailbox Registers */
#define CXLDEV_MBOX_CAPS_OFFSET 0x00
#define CXLDEV_MBOX_CAP_PAYLOAD_SIZE_MASK GENMASK(4, 0)
#define CXLDEV_MBOX_CAP_BG_CMD_IRQ BIT(6)
#define CXLDEV_MBOX_CAP_IRQ_MSGNUM_MASK GENMASK(10, 7)
#define CXLDEV_MBOX_CTRL_OFFSET 0x04
#define CXLDEV_MBOX_CTRL_DOORBELL BIT(0)
#define CXLDEV_MBOX_CTRL_BG_CMD_IRQ BIT(2)
#define CXLDEV_MBOX_CMD_OFFSET 0x08
#define CXLDEV_MBOX_CMD_COMMAND_OPCODE_MASK GENMASK_ULL(15, 0)
#define CXLDEV_MBOX_CMD_PAYLOAD_LENGTH_MASK GENMASK_ULL(36, 16)
#define CXLDEV_MBOX_STATUS_OFFSET 0x10
#define CXLDEV_MBOX_STATUS_BG_CMD BIT(0)
#define CXLDEV_MBOX_STATUS_RET_CODE_MASK GENMASK_ULL(47, 32)
#define CXLDEV_MBOX_BG_CMD_STATUS_OFFSET 0x18
#define CXLDEV_MBOX_BG_CMD_COMMAND_OPCODE_MASK GENMASK_ULL(15, 0)
#define CXLDEV_MBOX_BG_CMD_COMMAND_PCT_MASK GENMASK_ULL(22, 16)
#define CXLDEV_MBOX_BG_CMD_COMMAND_RC_MASK GENMASK_ULL(47, 32)
#define CXLDEV_MBOX_BG_CMD_COMMAND_VENDOR_MASK GENMASK_ULL(63, 48)
#define CXLDEV_MBOX_PAYLOAD_OFFSET 0x20
void cxl_probe_component_regs(struct device *dev, void __iomem *base,
struct cxl_component_reg_map *map);
void cxl_probe_device_regs(struct device *dev, void __iomem *base,
struct cxl_device_reg_map *map);
int cxl_map_component_regs(const struct cxl_register_map *map,
struct cxl_component_regs *regs,
unsigned long map_mask);
int cxl_map_device_regs(const struct cxl_register_map *map,
struct cxl_device_regs *regs);
int cxl_map_pmu_regs(struct cxl_register_map *map, struct cxl_pmu_regs *regs);
#define CXL_INSTANCES_COUNT -1
enum cxl_regloc_type;
int cxl_count_regblock(struct pci_dev *pdev, enum cxl_regloc_type type);
int cxl_find_regblock_instance(struct pci_dev *pdev, enum cxl_regloc_type type,
struct cxl_register_map *map, unsigned int index);
int cxl_find_regblock(struct pci_dev *pdev, enum cxl_regloc_type type,
struct cxl_register_map *map);
int cxl_setup_regs(struct cxl_register_map *map);
struct cxl_dport;
int cxl_dport_map_rcd_linkcap(struct pci_dev *pdev, struct cxl_dport *dport);
#define CXL_RESOURCE_NONE ((resource_size_t) -1)
#define CXL_TARGET_STRLEN 20
/*
* cxl_decoder flags that define the type of memory / devices this
* decoder supports as well as configuration lock status See "CXL 2.0
* 8.2.5.12.7 CXL HDM Decoder 0 Control Register" for details.
* Additionally indicate whether decoder settings were autodetected,
* user customized.
*/
#define CXL_DECODER_F_RAM BIT(0)
#define CXL_DECODER_F_PMEM BIT(1)
#define CXL_DECODER_F_TYPE2 BIT(2)
#define CXL_DECODER_F_TYPE3 BIT(3)
#define CXL_DECODER_F_LOCK BIT(4)
#define CXL_DECODER_F_ENABLE BIT(5)
#define CXL_DECODER_F_NORMALIZED_ADDRESSING BIT(6)
#define CXL_DECODER_F_RESET_MASK (CXL_DECODER_F_ENABLE | CXL_DECODER_F_LOCK)
enum cxl_decoder_type {
CXL_DECODER_DEVMEM = 2,
CXL_DECODER_HOSTONLYMEM = 3,
};
/*
* Current specification goes up to 8, double that seems a reasonable
* software max for the foreseeable future
*/
#define CXL_DECODER_MAX_INTERLEAVE 16
#define CXL_QOS_CLASS_INVALID -1
/**
* struct cxl_decoder - Common CXL HDM Decoder Attributes
* @dev: this decoder's device
* @id: kernel device name id
* @hpa_range: Host physical address range mapped by this decoder
* @interleave_ways: number of cxl_dports in this decode
* @interleave_granularity: data stride per dport
* @target_type: accelerator vs expander (type2 vs type3) selector
* @region: currently assigned region for this decoder
* @flags: memory type capabilities and locking
* @target_map: cached copy of hardware port-id list, available at init
* before all @dport objects have been instantiated. While
* dport id is 8bit, CFMWS interleave targets are 32bits.
* @commit: device/decoder-type specific callback to commit settings to hw
* @reset: device/decoder-type specific callback to reset hw settings
*/
struct cxl_decoder {
struct device dev;
int id;
struct range hpa_range;
int interleave_ways;
int interleave_granularity;
enum cxl_decoder_type target_type;
struct cxl_region *region;
unsigned long flags;
u32 target_map[CXL_DECODER_MAX_INTERLEAVE];
int (*commit)(struct cxl_decoder *cxld);
void (*reset)(struct cxl_decoder *cxld);
};
/*
* Track whether this decoder is free for userspace provisioning, reserved for
* region autodiscovery, whether it is started connecting (awaiting other
* peers), or has completed auto assembly.
*/
enum cxl_decoder_state {
CXL_DECODER_STATE_MANUAL,
CXL_DECODER_STATE_AUTO,
CXL_DECODER_STATE_AUTO_STAGED,
};
/**
* struct cxl_endpoint_decoder - Endpoint / SPA to DPA decoder
* @cxld: base cxl_decoder_object
* @dpa_res: actively claimed DPA span of this decoder
* @skip: offset into @dpa_res where @cxld.hpa_range maps
* @state: autodiscovery state
* @part: partition index this decoder maps
* @pos: interleave position in @cxld.region
*/
struct cxl_endpoint_decoder {
struct cxl_decoder cxld;
struct resource *dpa_res;
resource_size_t skip;
enum cxl_decoder_state state;
int part;
int pos;
};
/**
* struct cxl_switch_decoder - Switch specific CXL HDM Decoder
* @cxld: base cxl_decoder object
* @nr_targets: number of elements in @target
* @target: active ordered target list in current decoder configuration
*
* The 'switch' decoder type represents the decoder instances of cxl_port's that
* route from the root of a CXL memory decode topology to the endpoints. They
* come in two flavors, root-level decoders, statically defined by platform
* firmware, and mid-level decoders, where interleave-granularity,
* interleave-width, and the target list are mutable.
*/
struct cxl_switch_decoder {
struct cxl_decoder cxld;
int nr_targets;
struct cxl_dport *target[];
};
struct cxl_root_decoder;
/**
* struct cxl_rd_ops - CXL root decoder callback operations
* @hpa_to_spa: Convert host physical address to system physical address
* @spa_to_hpa: Convert system physical address to host physical address
*/
struct cxl_rd_ops {
u64 (*hpa_to_spa)(struct cxl_root_decoder *cxlrd, u64 hpa);
u64 (*spa_to_hpa)(struct cxl_root_decoder *cxlrd, u64 spa);
};
/**
* struct cxl_root_decoder - Static platform CXL address decoder
* @res: host / parent resource for region allocations
* @cache_size: extended linear cache size if exists, otherwise zero.
* @region_id: region id for next region provisioning event
* @platform_data: platform specific configuration data
* @regions_lock: sync region discovery, construction, and deletion
* @regions: regions to remove at root decoder destruct time
* @dead: root decoder dead to region creation
* @qos_class: QoS performance class cookie
* @ops: CXL root decoder operations
* @cxlsd: base cxl switch decoder
*/
struct cxl_root_decoder {
struct resource *res;
resource_size_t cache_size;
atomic_t region_id;
void *platform_data;
struct mutex regions_lock;
struct xarray regions;
bool dead;
int qos_class;
struct cxl_rd_ops ops;
struct cxl_switch_decoder cxlsd;
};
/*
* enum cxl_config_state - State machine for region configuration
* @CXL_CONFIG_IDLE: Any sysfs attribute can be written freely
* @CXL_CONFIG_INTERLEAVE_ACTIVE: region size has been set, no more
* changes to interleave_ways or interleave_granularity
* @CXL_CONFIG_ACTIVE: All targets have been added the region is now
* active
* @CXL_CONFIG_RESET_PENDING: see commit_store()
* @CXL_CONFIG_COMMIT: Soft-config has been committed to hardware
*/
enum cxl_config_state {
CXL_CONFIG_IDLE,
CXL_CONFIG_INTERLEAVE_ACTIVE,
CXL_CONFIG_ACTIVE,
CXL_CONFIG_RESET_PENDING,
CXL_CONFIG_COMMIT,
};
/**
* struct cxl_region_params - region settings
* @state: allow the driver to lockdown further parameter changes
* @uuid: unique id for persistent regions
* @interleave_ways: number of endpoints in the region
* @interleave_granularity: capacity each endpoint contributes to a stripe
* @res: allocated iomem capacity for this region
* @targets: active ordered targets in current decoder configuration
* @nr_targets: number of targets
* @cache_size: extended linear cache size if exists, otherwise zero.
*
* State transitions are protected by cxl_rwsem.region
*/
struct cxl_region_params {
enum cxl_config_state state;
uuid_t uuid;
int interleave_ways;
int interleave_granularity;
struct resource *res;
struct cxl_endpoint_decoder *targets[CXL_DECODER_MAX_INTERLEAVE];
int nr_targets;
resource_size_t cache_size;
};
/*
* Indicate whether this region has been assembled by autodetection or
* userspace assembly. Prevent endpoint decoders outside of automatic
* detection from being added to the region.
*/
#define CXL_REGION_F_AUTO 0
/*
* Require that a committed region successfully complete a teardown once
* any of its associated decoders have been torn down. This maintains
* the commit state for the region since there are committed decoders,
* but blocks cxl_region_probe().
*/
#define CXL_REGION_F_NEEDS_RESET 1
/*
* Indicate whether this region is locked due to 1 or more decoders that have
* been locked. The approach of all or nothing is taken with regard to the
* locked attribute. CXL_REGION_F_NEEDS_RESET should not be set if this flag is
* set.
*/
#define CXL_REGION_F_LOCK 2
/*
* Indicate Normalized Addressing. Use it to disable SPA conversion if
* HPA != SPA and an address translation callback handler does not
* exist. Flag is needed by AMD Zen5 platforms.
*/
#define CXL_REGION_F_NORMALIZED_ADDRESSING 3
/**
* struct cxl_region - CXL region
* @dev: This region's device
* @id: This region's id. Id is globally unique across all regions
* @cxlrd: Region's root decoder
* @hpa_range: Address range occupied by the region
* @mode: Operational mode of the mapped capacity
* @type: Endpoint decoder target type
* @cxl_nvb: nvdimm bridge for coordinating @cxlr_pmem setup / shutdown
* @cxlr_pmem: (for pmem regions) cached copy of the nvdimm bridge
* @flags: Region state flags
* @params: active + config params for the region
* @coord: QoS access coordinates for the region
* @node_notifier: notifier for setting the access coordinates to node
* @adist_notifier: notifier for calculating the abstract distance of node
*/
struct cxl_region {
struct device dev;
int id;
struct cxl_root_decoder *cxlrd;
struct range hpa_range;
enum cxl_partition_mode mode;
enum cxl_decoder_type type;
struct cxl_nvdimm_bridge *cxl_nvb;
struct cxl_pmem_region *cxlr_pmem;
unsigned long flags;
struct cxl_region_params params;
struct access_coordinate coord[ACCESS_COORDINATE_MAX];
struct notifier_block node_notifier;
struct notifier_block adist_notifier;
};
struct cxl_nvdimm_bridge {
int id;
struct device dev;
struct cxl_port *port;
struct nvdimm_bus *nvdimm_bus;
struct nvdimm_bus_descriptor nd_desc;
};
#define CXL_DEV_ID_LEN 19
enum {
CXL_NVD_F_INVALIDATED = 0,
};
struct cxl_nvdimm {
struct device dev;
struct cxl_memdev *cxlmd;
u8 dev_id[CXL_DEV_ID_LEN]; /* for nvdimm, string of 'serial' */
u64 dirty_shutdowns;
unsigned long flags;
};
struct cxl_pmem_region_mapping {
struct cxl_memdev *cxlmd;
struct cxl_nvdimm *cxl_nvd;
u64 start;
u64 size;
int position;
};
struct cxl_pmem_region {
struct device dev;
struct cxl_region *cxlr;
struct nd_region *nd_region;
struct range hpa_range;
int nr_mappings;
struct cxl_pmem_region_mapping mapping[];
};
struct cxl_dax_region {
struct device dev;
struct cxl_region *cxlr;
struct range hpa_range;
};
/**
* struct cxl_port - logical collection of upstream port devices and
* downstream port devices to construct a CXL memory
* decode hierarchy.
* @dev: this port's device
* @uport_dev: PCI or platform device implementing the upstream port capability
* @host_bridge: Shortcut to the platform attach point for this port
* @id: id for port device-name
* @dports: cxl_dport instances referenced by decoders
* @endpoints: cxl_ep instances, endpoints that are a descendant of this port
* @regions: cxl_region_ref instances, regions mapped by this port
* @parent_dport: dport that points to this port in the parent
* @decoder_ida: allocator for decoder ids
* @reg_map: component and ras register mapping parameters
* @regs: mapped component registers
* @nr_dports: number of entries in @dports
* @hdm_end: track last allocated HDM decoder instance for allocation ordering
* @commit_end: cursor to track highest committed decoder for commit ordering
* @dead: last ep has been removed, force port re-creation
* @depth: How deep this port is relative to the root. depth 0 is the root.
* @cdat: Cached CDAT data
* @cdat_available: Should a CDAT attribute be available in sysfs
* @pci_latency: Upstream latency in picoseconds
* @component_reg_phys: Physical address of component register
*/
struct cxl_port {
struct device dev;
struct device *uport_dev;
struct device *host_bridge;
int id;
struct xarray dports;
struct xarray endpoints;
struct xarray regions;
struct cxl_dport *parent_dport;
struct ida decoder_ida;
struct cxl_register_map reg_map;
struct cxl_component_regs regs;
int nr_dports;
int hdm_end;
int commit_end;
bool dead;
unsigned int depth;
struct cxl_cdat {
void *table;
size_t length;
} cdat;
bool cdat_available;
long pci_latency;
resource_size_t component_reg_phys;
};
struct cxl_root;
struct cxl_root_ops {
int (*qos_class)(struct cxl_root *cxl_root,
struct access_coordinate *coord, int entries,
int *qos_class);
int (*translation_setup_root)(struct cxl_root *cxl_root, void *data);
};
/**
* struct cxl_root - logical collection of root cxl_port items
*
* @port: cxl_port member
* @ops: cxl root operations
*/
struct cxl_root {
struct cxl_port port;
struct cxl_root_ops ops;
};
static inline struct cxl_root *
to_cxl_root(const struct cxl_port *port)
{
return container_of(port, struct cxl_root, port);
}
static inline struct cxl_dport *
cxl_find_dport_by_dev(struct cxl_port *port, const struct device *dport_dev)
{
return xa_load(&port->dports, (unsigned long)dport_dev);
}
struct cxl_rcrb_info {
resource_size_t base;
u16 aer_cap;
};
/**
* struct cxl_dport - CXL downstream port
* @dport_dev: PCI bridge or firmware device representing the downstream link
* @reg_map: component and ras register mapping parameters
* @port_id: unique hardware identifier for dport in decoder target list
* @rcrb: Data about the Root Complex Register Block layout
* @rch: Indicate whether this dport was enumerated in RCH or VH mode
* @port: reference to cxl_port that contains this downstream port
* @regs: Dport parsed register blocks
* @coord: access coordinates (bandwidth and latency performance attributes)
* @link_latency: calculated PCIe downstream latency
* @gpf_dvsec: Cached GPF port DVSEC
*/
struct cxl_dport {
struct device *dport_dev;
struct cxl_register_map reg_map;
int port_id;
struct cxl_rcrb_info rcrb;
bool rch;
struct cxl_port *port;
struct cxl_regs regs;
struct access_coordinate coord[ACCESS_COORDINATE_MAX];
long link_latency;
int gpf_dvsec;
};
/**
* struct cxl_ep - track an endpoint's interest in a port
* @ep: device that hosts a generic CXL endpoint (expander or accelerator)
* @dport: which dport routes to this endpoint on @port
* @next: cxl switch port across the link attached to @dport NULL if
* attached to an endpoint
*/
struct cxl_ep {
struct device *ep;
struct cxl_dport *dport;
struct cxl_port *next;
};
/**
* struct cxl_region_ref - track a region's interest in a port
* @port: point in topology to install this reference
* @decoder: decoder assigned for @region in @port
* @region: region for this reference
* @endpoints: cxl_ep references for region members beneath @port
* @nr_targets_set: track how many targets have been programmed during setup
* @nr_eps: number of endpoints beneath @port
* @nr_targets: number of distinct targets needed to reach @nr_eps
*/
struct cxl_region_ref {
struct cxl_port *port;
struct cxl_decoder *decoder;
struct cxl_region *region;
struct xarray endpoints;
int nr_targets_set;
int nr_eps;
int nr_targets;
};
/*
* The platform firmware device hosting the root is also the top of the
* CXL port topology. All other CXL ports have another CXL port as their
* parent and their ->uport_dev / host device is out-of-line of the port
* ancestry.
*/
static inline bool is_cxl_root(struct cxl_port *port)
{
return port->uport_dev == port->dev.parent;
}
/* Address translation functions exported to cxl_translate test module only */
int cxl_validate_translation_params(u8 eiw, u16 eig, int pos);
u64 cxl_calculate_hpa_offset(u64 dpa_offset, int pos, u8 eiw, u16 eig);
u64 cxl_calculate_dpa_offset(u64 hpa_offset, u8 eiw, u16 eig);
int cxl_calculate_position(u64 hpa_offset, u8 eiw, u16 eig);
struct cxl_cxims_data {
int nr_maps;
u64 xormaps[] __counted_by(nr_maps);
};
#if IS_ENABLED(CONFIG_CXL_ACPI)
u64 cxl_do_xormap_calc(struct cxl_cxims_data *cximsd, u64 addr, int hbiw);
#else
static inline u64 cxl_do_xormap_calc(struct cxl_cxims_data *cximsd, u64 addr, int hbiw)
{
return ULLONG_MAX;
}
#endif
int cxl_num_decoders_committed(struct cxl_port *port);
bool is_cxl_port(const struct device *dev);
struct cxl_port *to_cxl_port(const struct device *dev);
struct cxl_port *parent_port_of(struct cxl_port *port);
void cxl_port_commit_reap(struct cxl_decoder *cxld);
struct pci_bus;
int devm_cxl_register_pci_bus(struct device *host, struct device *uport_dev,
struct pci_bus *bus);
struct pci_bus *cxl_port_to_pci_bus(struct cxl_port *port);
struct cxl_port *devm_cxl_add_port(struct device *host,
struct device *uport_dev,
resource_size_t component_reg_phys,
struct cxl_dport *parent_dport);
struct cxl_root *devm_cxl_add_root(struct device *host);
int devm_cxl_add_endpoint(struct device *host, struct cxl_memdev *cxlmd,
struct cxl_dport *parent_dport);
struct cxl_root *find_cxl_root(struct cxl_port *port);
DEFINE_FREE(put_cxl_root, struct cxl_root *, if (_T) put_device(&_T->port.dev))
DEFINE_FREE(put_cxl_port, struct cxl_port *, if (!IS_ERR_OR_NULL(_T)) put_device(&_T->dev))
DEFINE_FREE(put_cxl_root_decoder, struct cxl_root_decoder *, if (!IS_ERR_OR_NULL(_T)) put_device(&_T->cxlsd.cxld.dev))
DEFINE_FREE(put_cxl_region, struct cxl_region *, if (!IS_ERR_OR_NULL(_T)) put_device(&_T->dev))
DEFINE_FREE(put_cxl_dax_region, struct cxl_dax_region *, if (!IS_ERR_OR_NULL(_T)) put_device(&_T->dev))
int devm_cxl_enumerate_ports(struct cxl_memdev *cxlmd);
void cxl_bus_rescan(void);
void cxl_bus_drain(void);
struct cxl_port *cxl_pci_find_port(struct pci_dev *pdev,
struct cxl_dport **dport);
struct cxl_port *cxl_mem_find_port(struct cxl_memdev *cxlmd,
struct cxl_dport **dport);
bool schedule_cxl_memdev_detach(struct cxl_memdev *cxlmd);
struct cxl_dport *devm_cxl_add_dport(struct cxl_port *port,
struct device *dport, int port_id,
resource_size_t component_reg_phys);
struct cxl_dport *devm_cxl_add_rch_dport(struct cxl_port *port,
struct device *dport_dev, int port_id,
resource_size_t rcrb);
#ifdef CONFIG_CXL_ATL
void cxl_setup_prm_address_translation(struct cxl_root *cxl_root);
#else
static inline
void cxl_setup_prm_address_translation(struct cxl_root *cxl_root) {}
#endif
struct cxl_decoder *to_cxl_decoder(struct device *dev);
struct cxl_root_decoder *to_cxl_root_decoder(struct device *dev);
struct cxl_switch_decoder *to_cxl_switch_decoder(struct device *dev);
struct cxl_endpoint_decoder *to_cxl_endpoint_decoder(struct device *dev);
bool is_root_decoder(struct device *dev);
bool is_switch_decoder(struct device *dev);
bool is_endpoint_decoder(struct device *dev);
struct cxl_root_decoder *cxl_root_decoder_alloc(struct cxl_port *port,
unsigned int nr_targets);
struct cxl_switch_decoder *cxl_switch_decoder_alloc(struct cxl_port *port,
unsigned int nr_targets);
int cxl_decoder_add(struct cxl_decoder *cxld);
struct cxl_endpoint_decoder *cxl_endpoint_decoder_alloc(struct cxl_port *port);
int cxl_decoder_add_locked(struct cxl_decoder *cxld);
int cxl_decoder_autoremove(struct device *host, struct cxl_decoder *cxld);
static inline int cxl_root_decoder_autoremove(struct device *host,
struct cxl_root_decoder *cxlrd)
{
return cxl_decoder_autoremove(host, &cxlrd->cxlsd.cxld);
}
int cxl_endpoint_autoremove(struct cxl_memdev *cxlmd, struct cxl_port *endpoint);
/**
* struct cxl_endpoint_dvsec_info - Cached DVSEC info
* @mem_enabled: cached value of mem_enabled in the DVSEC at init time
* @ranges: Number of active HDM ranges this device uses.
* @port: endpoint port associated with this info instance
* @dvsec_range: cached attributes of the ranges in the DVSEC, PCIE_DEVICE
*/
struct cxl_endpoint_dvsec_info {
bool mem_enabled;
int ranges;
struct cxl_port *port;
struct range dvsec_range[2];
};
int devm_cxl_switch_port_decoders_setup(struct cxl_port *port);
int devm_cxl_endpoint_decoders_setup(struct cxl_port *port);
void cxl_port_update_decoder_targets(struct cxl_port *port,
struct cxl_dport *dport);
int cxl_port_setup_regs(struct cxl_port *port,
resource_size_t component_reg_phys);
struct cxl_dev_state;
int cxl_dvsec_rr_decode(struct cxl_dev_state *cxlds,
struct cxl_endpoint_dvsec_info *info);
bool is_cxl_region(struct device *dev);
extern const struct bus_type cxl_bus_type;
/*
* Note, add_dport() is expressly for the cxl_port driver. TODO: investigate a
* type-safe driver model where probe()/remove() take the type of object implied
* by @id and the add_dport() op only defined for the CXL_DEVICE_PORT driver
* template.
*/
struct cxl_driver {
const char *name;
int (*probe)(struct device *dev);
void (*remove)(struct device *dev);
struct cxl_dport *(*add_dport)(struct cxl_port *port,
struct device *dport_dev);
struct device_driver drv;
int id;
};
#define to_cxl_drv(__drv) container_of_const(__drv, struct cxl_driver, drv)
int __cxl_driver_register(struct cxl_driver *cxl_drv, struct module *owner,
const char *modname);
#define cxl_driver_register(x) __cxl_driver_register(x, THIS_MODULE, KBUILD_MODNAME)
void cxl_driver_unregister(struct cxl_driver *cxl_drv);
#define module_cxl_driver(__cxl_driver) \
module_driver(__cxl_driver, cxl_driver_register, cxl_driver_unregister)
#define CXL_DEVICE_NVDIMM_BRIDGE 1
#define CXL_DEVICE_NVDIMM 2
#define CXL_DEVICE_PORT 3
#define CXL_DEVICE_ROOT 4
#define CXL_DEVICE_MEMORY_EXPANDER 5
#define CXL_DEVICE_REGION 6
#define CXL_DEVICE_PMEM_REGION 7
#define CXL_DEVICE_DAX_REGION 8
#define CXL_DEVICE_PMU 9
#define MODULE_ALIAS_CXL(type) MODULE_ALIAS("cxl:t" __stringify(type) "*")
#define CXL_MODALIAS_FMT "cxl:t%d"
struct cxl_nvdimm_bridge *to_cxl_nvdimm_bridge(struct device *dev);
struct cxl_nvdimm_bridge *devm_cxl_add_nvdimm_bridge(struct device *host,
struct cxl_port *port);
struct cxl_nvdimm_bridge *__devm_cxl_add_nvdimm_bridge(struct device *host,
struct cxl_port *port);
struct cxl_nvdimm *to_cxl_nvdimm(struct device *dev);
bool is_cxl_nvdimm(struct device *dev);
int devm_cxl_add_nvdimm(struct device *host, struct cxl_port *port,
struct cxl_memdev *cxlmd);
struct cxl_nvdimm_bridge *cxl_find_nvdimm_bridge(struct cxl_port *port);
#ifdef CONFIG_CXL_REGION
bool is_cxl_pmem_region(struct device *dev);
struct cxl_pmem_region *to_cxl_pmem_region(struct device *dev);
int cxl_add_to_region(struct cxl_endpoint_decoder *cxled);
struct cxl_dax_region *to_cxl_dax_region(struct device *dev);
u64 cxl_port_get_spa_cache_alias(struct cxl_port *endpoint, u64 spa);
bool cxl_region_contains_resource(const struct resource *res);
#else
static inline bool is_cxl_pmem_region(struct device *dev)
{
return false;
}
static inline struct cxl_pmem_region *to_cxl_pmem_region(struct device *dev)
{
return NULL;
}
static inline int cxl_add_to_region(struct cxl_endpoint_decoder *cxled)
{
return 0;
}
static inline struct cxl_dax_region *to_cxl_dax_region(struct device *dev)
{
return NULL;
}
static inline u64 cxl_port_get_spa_cache_alias(struct cxl_port *endpoint,
u64 spa)
{
return 0;
}
static inline bool cxl_region_contains_resource(const struct resource *res)
{
return false;
}
#endif
void cxl_endpoint_parse_cdat(struct cxl_port *port);
void cxl_switch_parse_cdat(struct cxl_dport *dport);
int cxl_endpoint_get_perf_coordinates(struct cxl_port *port,
struct access_coordinate *coord);
void cxl_region_perf_data_calculate(struct cxl_region *cxlr,
struct cxl_endpoint_decoder *cxled);
void cxl_region_shared_upstream_bandwidth_update(struct cxl_region *cxlr);
void cxl_memdev_update_perf(struct cxl_memdev *cxlmd);
void cxl_coordinates_combine(struct access_coordinate *out,
struct access_coordinate *c1,
struct access_coordinate *c2);
bool cxl_endpoint_decoder_reset_detected(struct cxl_port *port);
struct cxl_dport *devm_cxl_add_dport_by_dev(struct cxl_port *port,
struct device *dport_dev);
/*
* Unit test builds overrides this to __weak, find the 'strong' version
* of these symbols in tools/testing/cxl/.
*/
#ifndef __mock
#define __mock static
#endif
u16 cxl_gpf_get_dvsec(struct device *dev);
#endif /* __CXL_H__ */
+876
View File
@@ -0,0 +1,876 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright(c) 2020-2021 Intel Corporation. */
#ifndef __CXL_MEM_H__
#define __CXL_MEM_H__
#include <uapi/linux/cxl_mem.h>
#include <linux/pci.h>
#include <linux/cdev.h>
#include <linux/uuid.h>
#include <linux/node.h>
#include <cxl/event.h>
#include <cxl/mailbox.h>
#include "cxl.h"
/* CXL 2.0 8.2.8.5.1.1 Memory Device Status Register */
#define CXLMDEV_STATUS_OFFSET 0x0
#define CXLMDEV_DEV_FATAL BIT(0)
#define CXLMDEV_FW_HALT BIT(1)
#define CXLMDEV_STATUS_MEDIA_STATUS_MASK GENMASK(3, 2)
#define CXLMDEV_MS_NOT_READY 0
#define CXLMDEV_MS_READY 1
#define CXLMDEV_MS_ERROR 2
#define CXLMDEV_MS_DISABLED 3
#define CXLMDEV_READY(status) \
(FIELD_GET(CXLMDEV_STATUS_MEDIA_STATUS_MASK, status) == \
CXLMDEV_MS_READY)
#define CXLMDEV_MBOX_IF_READY BIT(4)
#define CXLMDEV_RESET_NEEDED_MASK GENMASK(7, 5)
#define CXLMDEV_RESET_NEEDED_NOT 0
#define CXLMDEV_RESET_NEEDED_COLD 1
#define CXLMDEV_RESET_NEEDED_WARM 2
#define CXLMDEV_RESET_NEEDED_HOT 3
#define CXLMDEV_RESET_NEEDED_CXL 4
#define CXLMDEV_RESET_NEEDED(status) \
(FIELD_GET(CXLMDEV_RESET_NEEDED_MASK, status) != \
CXLMDEV_RESET_NEEDED_NOT)
/**
* struct cxl_memdev - CXL bus object representing a Type-3 Memory Device
* @dev: driver core device object
* @cdev: char dev core object for ioctl operations
* @cxlds: The device state backing this device
* @detach_work: active memdev lost a port in its ancestry
* @cxl_nvb: coordinate removal of @cxl_nvd if present
* @cxl_nvd: optional bridge to an nvdimm if the device supports pmem
* @endpoint: connection to the CXL port topology for this memory device
* @attach: creator of this memdev depends on CXL link attach to operate
* @id: id number of this memdev instance.
* @depth: endpoint port depth
* @scrub_cycle: current scrub cycle set for this device
* @scrub_region_id: id number of a backed region (if any) for which current scrub cycle set
* @err_rec_array: List of xarrarys to store the memdev error records to
* check attributes for a memory repair operation are from
* current boot.
*/
struct cxl_memdev {
struct device dev;
struct cdev cdev;
struct cxl_dev_state *cxlds;
struct work_struct detach_work;
struct cxl_nvdimm_bridge *cxl_nvb;
struct cxl_nvdimm *cxl_nvd;
struct cxl_port *endpoint;
const struct cxl_memdev_attach *attach;
int id;
int depth;
u8 scrub_cycle;
int scrub_region_id;
struct cxl_mem_err_rec *err_rec_array;
};
static inline struct cxl_memdev *to_cxl_memdev(struct device *dev)
{
return container_of(dev, struct cxl_memdev, dev);
}
static inline struct cxl_port *cxled_to_port(struct cxl_endpoint_decoder *cxled)
{
return to_cxl_port(cxled->cxld.dev.parent);
}
static inline struct cxl_port *cxlrd_to_port(struct cxl_root_decoder *cxlrd)
{
return to_cxl_port(cxlrd->cxlsd.cxld.dev.parent);
}
static inline struct cxl_memdev *
cxled_to_memdev(struct cxl_endpoint_decoder *cxled)
{
struct cxl_port *port = to_cxl_port(cxled->cxld.dev.parent);
return to_cxl_memdev(port->uport_dev);
}
bool is_cxl_memdev(const struct device *dev);
static inline bool is_cxl_endpoint(struct cxl_port *port)
{
return is_cxl_memdev(port->uport_dev);
}
struct cxl_memdev_attach {
int (*probe)(struct cxl_memdev *cxlmd);
};
/**
* struct cxl_attach_region - coordinate mapping a region at memdev registration
* @attach: common core attachment descriptor
* @hpa_range: physical address range of the region
*
* For the common simple case of a CXL device with private (non-general purpose
* / "accelerator") memory, enumerate firmware instantiated region, or
* instantiate a region for the device's capacity. Destroy the region on detach.
*/
struct cxl_attach_region {
struct cxl_memdev_attach attach;
struct range hpa_range;
};
#ifdef CONFIG_CXL_REGION
int cxl_memdev_attach_region(struct cxl_memdev *cxlmd);
#else
static inline int cxl_memdev_attach_region(struct cxl_memdev *cxlmd)
{
return -EOPNOTSUPP;
}
#endif
struct cxl_memdev *devm_cxl_add_classdev(struct cxl_dev_state *cxlds);
struct cxl_memdev *__devm_cxl_add_memdev(struct cxl_dev_state *cxlds,
const struct cxl_memdev_attach *attach);
int devm_cxl_sanitize_setup_notifier(struct device *host,
struct cxl_memdev *cxlmd);
struct cxl_memdev_state;
int devm_cxl_setup_fw_upload(struct device *host, struct cxl_memdev_state *mds);
int devm_cxl_dpa_reserve(struct cxl_endpoint_decoder *cxled,
resource_size_t base, resource_size_t len,
resource_size_t skipped);
struct cxl_dpa_info {
u64 size;
struct cxl_dpa_part_info {
struct range range;
enum cxl_partition_mode mode;
} part[CXL_NR_PARTITIONS_MAX];
int nr_partitions;
};
int cxl_dpa_setup(struct cxl_dev_state *cxlds, const struct cxl_dpa_info *info);
static inline struct cxl_ep *cxl_ep_load(struct cxl_port *port,
struct cxl_memdev *cxlmd)
{
if (!port)
return NULL;
return xa_load(&port->endpoints, (unsigned long)&cxlmd->dev);
}
/*
* Per CXL 3.0 Section 8.2.8.4.5.1
*/
#define CMD_CMD_RC_TABLE \
C(SUCCESS, 0, NULL), \
C(BACKGROUND, -ENXIO, "background cmd started successfully"), \
C(INPUT, -ENXIO, "cmd input was invalid"), \
C(UNSUPPORTED, -ENXIO, "cmd is not supported"), \
C(INTERNAL, -ENXIO, "internal device error"), \
C(RETRY, -ENXIO, "temporary error, retry once"), \
C(BUSY, -ENXIO, "ongoing background operation"), \
C(MEDIADISABLED, -ENXIO, "media access is disabled"), \
C(FWINPROGRESS, -ENXIO, "one FW package can be transferred at a time"), \
C(FWOOO, -ENXIO, "FW package content was transferred out of order"), \
C(FWAUTH, -ENXIO, "FW package authentication failed"), \
C(FWSLOT, -ENXIO, "FW slot is not supported for requested operation"), \
C(FWROLLBACK, -ENXIO, "rolled back to the previous active FW"), \
C(FWRESET, -ENXIO, "FW failed to activate, needs cold reset"), \
C(HANDLE, -ENXIO, "one or more Event Record Handles were invalid"), \
C(PADDR, -EFAULT, "physical address specified is invalid"), \
C(POISONLMT, -EBUSY, "poison injection limit has been reached"), \
C(MEDIAFAILURE, -ENXIO, "permanent issue with the media"), \
C(ABORT, -ENXIO, "background cmd was aborted by device"), \
C(SECURITY, -ENXIO, "not valid in the current security state"), \
C(PASSPHRASE, -ENXIO, "phrase doesn't match current set passphrase"), \
C(MBUNSUPPORTED, -ENXIO, "unsupported on the mailbox it was issued on"),\
C(PAYLOADLEN, -ENXIO, "invalid payload length"), \
C(LOG, -ENXIO, "invalid or unsupported log page"), \
C(INTERRUPTED, -ENXIO, "asynchronous event occured"), \
C(FEATUREVERSION, -ENXIO, "unsupported feature version"), \
C(FEATURESELVALUE, -ENXIO, "unsupported feature selection value"), \
C(FEATURETRANSFERIP, -ENXIO, "feature transfer in progress"), \
C(FEATURETRANSFEROOO, -ENXIO, "feature transfer out of order"), \
C(RESOURCEEXHAUSTED, -ENXIO, "resources are exhausted"), \
C(EXTLIST, -ENXIO, "invalid Extent List"), \
#undef C
#define C(a, b, c) CXL_MBOX_CMD_RC_##a
enum { CMD_CMD_RC_TABLE };
#undef C
#define C(a, b, c) { b, c }
struct cxl_mbox_cmd_rc {
int err;
const char *desc;
};
static const
struct cxl_mbox_cmd_rc cxl_mbox_cmd_rctable[] ={ CMD_CMD_RC_TABLE };
#undef C
static inline const char *cxl_mbox_cmd_rc2str(struct cxl_mbox_cmd *mbox_cmd)
{
return cxl_mbox_cmd_rctable[mbox_cmd->return_code].desc;
}
static inline int cxl_mbox_cmd_rc2errno(struct cxl_mbox_cmd *mbox_cmd)
{
return cxl_mbox_cmd_rctable[mbox_cmd->return_code].err;
}
/*
* CXL 2.0 - Memory capacity multiplier
* See Section 8.2.9.5
*
* Volatile, Persistent, and Partition capacities are specified to be in
* multiples of 256MB - define a multiplier to convert to/from bytes.
*/
#define CXL_CAPACITY_MULTIPLIER SZ_256M
/*
* Event Interrupt Policy
*
* CXL rev 3.0 section 8.2.9.2.4; Table 8-52
*/
enum cxl_event_int_mode {
CXL_INT_NONE = 0x00,
CXL_INT_MSI_MSIX = 0x01,
CXL_INT_FW = 0x02
};
struct cxl_event_interrupt_policy {
u8 info_settings;
u8 warn_settings;
u8 failure_settings;
u8 fatal_settings;
} __packed;
/**
* struct cxl_event_state - Event log driver state
*
* @buf: Buffer to receive event data
* @log_lock: Serialize event_buf and log use
*/
struct cxl_event_state {
struct cxl_get_event_payload *buf;
struct mutex log_lock;
};
/* Device enabled poison commands */
enum poison_cmd_enabled_bits {
CXL_POISON_ENABLED_LIST,
CXL_POISON_ENABLED_INJECT,
CXL_POISON_ENABLED_CLEAR,
CXL_POISON_ENABLED_SCAN_CAPS,
CXL_POISON_ENABLED_SCAN_MEDIA,
CXL_POISON_ENABLED_SCAN_RESULTS,
CXL_POISON_ENABLED_MAX
};
/* Device enabled security commands */
enum security_cmd_enabled_bits {
CXL_SEC_ENABLED_SANITIZE,
CXL_SEC_ENABLED_SECURE_ERASE,
CXL_SEC_ENABLED_GET_SECURITY_STATE,
CXL_SEC_ENABLED_SET_PASSPHRASE,
CXL_SEC_ENABLED_DISABLE_PASSPHRASE,
CXL_SEC_ENABLED_UNLOCK,
CXL_SEC_ENABLED_FREEZE_SECURITY,
CXL_SEC_ENABLED_PASSPHRASE_SECURE_ERASE,
CXL_SEC_ENABLED_MAX
};
/**
* struct cxl_poison_state - Driver poison state info
*
* @max_errors: Maximum media error records held in device cache
* @enabled_cmds: All poison commands enabled in the CEL
* @list_out: The poison list payload returned by device
* @mutex: Protect reads of the poison list
*
* Reads of the poison list are synchronized to ensure that a reader
* does not get an incomplete list because their request overlapped
* (was interrupted or preceded by) another read request of the same
* DPA range. CXL Spec 3.0 Section 8.2.9.8.4.1
*/
struct cxl_poison_state {
u32 max_errors;
DECLARE_BITMAP(enabled_cmds, CXL_POISON_ENABLED_MAX);
struct cxl_mbox_poison_out *list_out;
struct mutex mutex; /* Protect reads of poison list */
};
/*
* Get FW Info
* CXL rev 3.0 section 8.2.9.3.1; Table 8-56
*/
struct cxl_mbox_get_fw_info {
u8 num_slots;
u8 slot_info;
u8 activation_cap;
u8 reserved[13];
char slot_1_revision[16];
char slot_2_revision[16];
char slot_3_revision[16];
char slot_4_revision[16];
} __packed;
#define CXL_FW_INFO_SLOT_INFO_CUR_MASK GENMASK(2, 0)
#define CXL_FW_INFO_SLOT_INFO_NEXT_MASK GENMASK(5, 3)
#define CXL_FW_INFO_SLOT_INFO_NEXT_SHIFT 3
#define CXL_FW_INFO_ACTIVATION_CAP_HAS_LIVE_ACTIVATE BIT(0)
/*
* Transfer FW Input Payload
* CXL rev 3.0 section 8.2.9.3.2; Table 8-57
*/
struct cxl_mbox_transfer_fw {
u8 action;
u8 slot;
u8 reserved[2];
__le32 offset;
u8 reserved2[0x78];
u8 data[];
} __packed;
#define CXL_FW_TRANSFER_ACTION_FULL 0x0
#define CXL_FW_TRANSFER_ACTION_INITIATE 0x1
#define CXL_FW_TRANSFER_ACTION_CONTINUE 0x2
#define CXL_FW_TRANSFER_ACTION_END 0x3
#define CXL_FW_TRANSFER_ACTION_ABORT 0x4
/*
* CXL rev 3.0 section 8.2.9.3.2 mandates 128-byte alignment for FW packages
* and for each part transferred in a Transfer FW command.
*/
#define CXL_FW_TRANSFER_ALIGNMENT 128
/*
* Activate FW Input Payload
* CXL rev 3.0 section 8.2.9.3.3; Table 8-58
*/
struct cxl_mbox_activate_fw {
u8 action;
u8 slot;
} __packed;
#define CXL_FW_ACTIVATE_ONLINE 0x0
#define CXL_FW_ACTIVATE_OFFLINE 0x1
/* FW state bits */
#define CXL_FW_STATE_BITS 32
#define CXL_FW_CANCEL 0
/**
* struct cxl_fw_state - Firmware upload / activation state
*
* @state: fw_uploader state bitmask
* @oneshot: whether the fw upload fits in a single transfer
* @num_slots: Number of FW slots available
* @cur_slot: Slot number currently active
* @next_slot: Slot number for the new firmware
*/
struct cxl_fw_state {
DECLARE_BITMAP(state, CXL_FW_STATE_BITS);
bool oneshot;
int num_slots;
int cur_slot;
int next_slot;
};
/**
* struct cxl_security_state - Device security state
*
* @state: state of last security operation
* @enabled_cmds: All security commands enabled in the CEL
* @poll_tmo_secs: polling timeout
* @sanitize_active: sanitize completion pending
* @poll_dwork: polling work item
* @sanitize_node: sanitation sysfs file to notify
*/
struct cxl_security_state {
unsigned long state;
DECLARE_BITMAP(enabled_cmds, CXL_SEC_ENABLED_MAX);
int poll_tmo_secs;
bool sanitize_active;
struct delayed_work poll_dwork;
struct kernfs_node *sanitize_node;
};
static inline resource_size_t cxl_pmem_size(struct cxl_dev_state *cxlds)
{
/*
* Static PMEM may be at partition index 0 when there is no static RAM
* capacity.
*/
for (int i = 0; i < cxlds->nr_partitions; i++)
if (cxlds->part[i].mode == CXL_PARTMODE_PMEM)
return resource_size(&cxlds->part[i].res);
return 0;
}
static inline struct cxl_dev_state *mbox_to_cxlds(struct cxl_mailbox *cxl_mbox)
{
return dev_get_drvdata(cxl_mbox->host);
}
/**
* struct cxl_memdev_state - Generic Type-3 Memory Device Class driver data
*
* CXL 8.1.12.1 PCI Header - Class Code Register Memory Device defines
* common memory device functionality like the presence of a mailbox and
* the functionality related to that like Identify Memory Device and Get
* Partition Info
* @cxlds: Core driver state common across Type-2 and Type-3 devices
* @lsa_size: Size of Label Storage Area
* (CXL 2.0 8.2.9.5.1.1 Identify Memory Device)
* @firmware_version: Firmware version for the memory device.
* @total_bytes: sum of all possible capacities
* @volatile_only_bytes: hard volatile capacity
* @persistent_only_bytes: hard persistent capacity
* @partition_align_bytes: alignment size for partition-able capacity
* @active_volatile_bytes: sum of hard + soft volatile
* @active_persistent_bytes: sum of hard + soft persistent
* @event: event log driver state
* @poison: poison driver state info
* @security: security driver state info
* @fw: firmware upload / activation state
* @mce_notifier: MCE notifier
*
* See CXL 3.0 8.2.9.8.2 Capacity Configuration and Label Storage for
* details on capacity parameters.
*/
struct cxl_memdev_state {
struct cxl_dev_state cxlds;
size_t lsa_size;
char firmware_version[0x10];
u64 total_bytes;
u64 volatile_only_bytes;
u64 persistent_only_bytes;
u64 partition_align_bytes;
u64 active_volatile_bytes;
u64 active_persistent_bytes;
struct cxl_event_state event;
struct cxl_poison_state poison;
struct cxl_security_state security;
struct cxl_fw_state fw;
struct notifier_block mce_notifier;
};
static inline struct cxl_memdev_state *
to_cxl_memdev_state(struct cxl_dev_state *cxlds)
{
if (cxlds->type != CXL_DEVTYPE_CLASSMEM)
return NULL;
return container_of(cxlds, struct cxl_memdev_state, cxlds);
}
enum cxl_opcode {
CXL_MBOX_OP_INVALID = 0x0000,
CXL_MBOX_OP_RAW = CXL_MBOX_OP_INVALID,
CXL_MBOX_OP_GET_EVENT_RECORD = 0x0100,
CXL_MBOX_OP_CLEAR_EVENT_RECORD = 0x0101,
CXL_MBOX_OP_GET_EVT_INT_POLICY = 0x0102,
CXL_MBOX_OP_SET_EVT_INT_POLICY = 0x0103,
CXL_MBOX_OP_GET_FW_INFO = 0x0200,
CXL_MBOX_OP_TRANSFER_FW = 0x0201,
CXL_MBOX_OP_ACTIVATE_FW = 0x0202,
CXL_MBOX_OP_GET_TIMESTAMP = 0x0300,
CXL_MBOX_OP_SET_TIMESTAMP = 0x0301,
CXL_MBOX_OP_GET_SUPPORTED_LOGS = 0x0400,
CXL_MBOX_OP_GET_LOG = 0x0401,
CXL_MBOX_OP_GET_LOG_CAPS = 0x0402,
CXL_MBOX_OP_CLEAR_LOG = 0x0403,
CXL_MBOX_OP_GET_SUP_LOG_SUBLIST = 0x0405,
CXL_MBOX_OP_GET_SUPPORTED_FEATURES = 0x0500,
CXL_MBOX_OP_GET_FEATURE = 0x0501,
CXL_MBOX_OP_SET_FEATURE = 0x0502,
CXL_MBOX_OP_DO_MAINTENANCE = 0x0600,
CXL_MBOX_OP_IDENTIFY = 0x4000,
CXL_MBOX_OP_GET_PARTITION_INFO = 0x4100,
CXL_MBOX_OP_SET_PARTITION_INFO = 0x4101,
CXL_MBOX_OP_GET_LSA = 0x4102,
CXL_MBOX_OP_SET_LSA = 0x4103,
CXL_MBOX_OP_GET_HEALTH_INFO = 0x4200,
CXL_MBOX_OP_GET_ALERT_CONFIG = 0x4201,
CXL_MBOX_OP_SET_ALERT_CONFIG = 0x4202,
CXL_MBOX_OP_GET_SHUTDOWN_STATE = 0x4203,
CXL_MBOX_OP_SET_SHUTDOWN_STATE = 0x4204,
CXL_MBOX_OP_GET_POISON = 0x4300,
CXL_MBOX_OP_INJECT_POISON = 0x4301,
CXL_MBOX_OP_CLEAR_POISON = 0x4302,
CXL_MBOX_OP_GET_SCAN_MEDIA_CAPS = 0x4303,
CXL_MBOX_OP_SCAN_MEDIA = 0x4304,
CXL_MBOX_OP_GET_SCAN_MEDIA = 0x4305,
CXL_MBOX_OP_SANITIZE = 0x4400,
CXL_MBOX_OP_SECURE_ERASE = 0x4401,
CXL_MBOX_OP_GET_SECURITY_STATE = 0x4500,
CXL_MBOX_OP_SET_PASSPHRASE = 0x4501,
CXL_MBOX_OP_DISABLE_PASSPHRASE = 0x4502,
CXL_MBOX_OP_UNLOCK = 0x4503,
CXL_MBOX_OP_FREEZE_SECURITY = 0x4504,
CXL_MBOX_OP_PASSPHRASE_SECURE_ERASE = 0x4505,
CXL_MBOX_OP_MAX = 0x10000
};
#define DEFINE_CXL_CEL_UUID \
UUID_INIT(0xda9c0b5, 0xbf41, 0x4b78, 0x8f, 0x79, 0x96, 0xb1, 0x62, \
0x3b, 0x3f, 0x17)
#define DEFINE_CXL_VENDOR_DEBUG_UUID \
UUID_INIT(0x5e1819d9, 0x11a9, 0x400c, 0x81, 0x1f, 0xd6, 0x07, 0x19, \
0x40, 0x3d, 0x86)
struct cxl_mbox_get_supported_logs {
__le16 entries;
u8 rsvd[6];
struct cxl_gsl_entry {
uuid_t uuid;
__le32 size;
} __packed entry[];
} __packed;
struct cxl_cel_entry {
__le16 opcode;
__le16 effect;
} __packed;
struct cxl_mbox_get_log {
uuid_t uuid;
__le32 offset;
__le32 length;
} __packed;
/* See CXL 2.0 Table 175 Identify Memory Device Output Payload */
struct cxl_mbox_identify {
char fw_revision[0x10];
__le64 total_capacity;
__le64 volatile_capacity;
__le64 persistent_capacity;
__le64 partition_align;
__le16 info_event_log_size;
__le16 warning_event_log_size;
__le16 failure_event_log_size;
__le16 fatal_event_log_size;
__le32 lsa_size;
u8 poison_list_max_mer[3];
__le16 inject_poison_limit;
u8 poison_caps;
u8 qos_telemetry_caps;
} __packed;
/*
* General Media Event Record UUID
* CXL rev 3.0 Section 8.2.9.2.1.1; Table 8-43
*/
#define CXL_EVENT_GEN_MEDIA_UUID \
UUID_INIT(0xfbcd0a77, 0xc260, 0x417f, 0x85, 0xa9, 0x08, 0x8b, 0x16, \
0x21, 0xeb, 0xa6)
/*
* DRAM Event Record UUID
* CXL rev 3.0 section 8.2.9.2.1.2; Table 8-44
*/
#define CXL_EVENT_DRAM_UUID \
UUID_INIT(0x601dcbb3, 0x9c06, 0x4eab, 0xb8, 0xaf, 0x4e, 0x9b, 0xfb, \
0x5c, 0x96, 0x24)
/*
* Memory Module Event Record UUID
* CXL rev 3.0 section 8.2.9.2.1.3; Table 8-45
*/
#define CXL_EVENT_MEM_MODULE_UUID \
UUID_INIT(0xfe927475, 0xdd59, 0x4339, 0xa5, 0x86, 0x79, 0xba, 0xb1, \
0x13, 0xb7, 0x74)
/*
* Memory Sparing Event Record UUID
* CXL rev 3.2 section 8.2.10.2.1.4: Table 8-60
*/
#define CXL_EVENT_MEM_SPARING_UUID \
UUID_INIT(0xe71f3a40, 0x2d29, 0x4092, 0x8a, 0x39, 0x4d, 0x1c, 0x96, \
0x6c, 0x7c, 0x65)
/*
* Get Event Records output payload
* CXL rev 3.0 section 8.2.9.2.2; Table 8-50
*/
#define CXL_GET_EVENT_FLAG_OVERFLOW BIT(0)
#define CXL_GET_EVENT_FLAG_MORE_RECORDS BIT(1)
struct cxl_get_event_payload {
u8 flags;
u8 reserved1;
__le16 overflow_err_count;
__le64 first_overflow_timestamp;
__le64 last_overflow_timestamp;
__le16 record_count;
u8 reserved2[10];
struct cxl_event_record_raw records[];
} __packed;
/*
* CXL rev 3.0 section 8.2.9.2.2; Table 8-49
*/
enum cxl_event_log_type {
CXL_EVENT_TYPE_INFO = 0x00,
CXL_EVENT_TYPE_WARN,
CXL_EVENT_TYPE_FAIL,
CXL_EVENT_TYPE_FATAL,
CXL_EVENT_TYPE_MAX
};
/*
* Clear Event Records input payload
* CXL rev 3.0 section 8.2.9.2.3; Table 8-51
*/
struct cxl_mbox_clear_event_payload {
u8 event_log; /* enum cxl_event_log_type */
u8 clear_flags;
u8 nr_recs;
u8 reserved[3];
__le16 handles[];
} __packed;
#define CXL_CLEAR_EVENT_MAX_HANDLES U8_MAX
struct cxl_mbox_get_partition_info {
__le64 active_volatile_cap;
__le64 active_persistent_cap;
__le64 next_volatile_cap;
__le64 next_persistent_cap;
} __packed;
struct cxl_mbox_get_lsa {
__le32 offset;
__le32 length;
} __packed;
struct cxl_mbox_set_lsa {
__le32 offset;
__le32 reserved;
u8 data[];
} __packed;
struct cxl_mbox_set_partition_info {
__le64 volatile_capacity;
u8 flags;
} __packed;
#define CXL_SET_PARTITION_IMMEDIATE_FLAG BIT(0)
/* Get Health Info Output Payload CXL 3.2 Spec 8.2.10.9.3.1 Table 8-148 */
struct cxl_mbox_get_health_info_out {
u8 health_status;
u8 media_status;
u8 additional_status;
u8 life_used;
__le16 device_temperature;
__le32 dirty_shutdown_cnt;
__le32 corrected_volatile_error_cnt;
__le32 corrected_persistent_error_cnt;
} __packed;
/* Set Shutdown State Input Payload CXL 3.2 Spec 8.2.10.9.3.5 Table 8-152 */
struct cxl_mbox_set_shutdown_state_in {
u8 state;
} __packed;
/* Set Timestamp CXL 3.0 Spec 8.2.9.4.2 */
struct cxl_mbox_set_timestamp_in {
__le64 timestamp;
} __packed;
/* Get Poison List CXL 3.0 Spec 8.2.9.8.4.1 */
struct cxl_mbox_poison_in {
__le64 offset;
__le64 length;
} __packed;
struct cxl_mbox_poison_out {
u8 flags;
u8 rsvd1;
__le64 overflow_ts;
__le16 count;
u8 rsvd2[20];
struct cxl_poison_record {
__le64 address;
__le32 length;
__le32 rsvd;
} __packed record[];
} __packed;
/*
* Get Poison List address field encodes the starting
* address of poison, and the source of the poison.
*/
#define CXL_POISON_START_MASK GENMASK_ULL(63, 6)
#define CXL_POISON_SOURCE_MASK GENMASK(2, 0)
/* Get Poison List record length is in units of 64 bytes */
#define CXL_POISON_LEN_MULT 64
/* Kernel defined maximum for a list of poison errors */
#define CXL_POISON_LIST_MAX 1024
/* Get Poison List: Payload out flags */
#define CXL_POISON_FLAG_MORE BIT(0)
#define CXL_POISON_FLAG_OVERFLOW BIT(1)
#define CXL_POISON_FLAG_SCANNING BIT(2)
/* Get Poison List: Poison Source */
#define CXL_POISON_SOURCE_UNKNOWN 0
#define CXL_POISON_SOURCE_EXTERNAL 1
#define CXL_POISON_SOURCE_INTERNAL 2
#define CXL_POISON_SOURCE_INJECTED 3
#define CXL_POISON_SOURCE_VENDOR 7
/* Inject & Clear Poison CXL 3.0 Spec 8.2.9.8.4.2/3 */
struct cxl_mbox_inject_poison {
__le64 address;
};
/* Clear Poison CXL 3.0 Spec 8.2.9.8.4.3 */
struct cxl_mbox_clear_poison {
__le64 address;
u8 write_data[CXL_POISON_LEN_MULT];
} __packed;
/**
* struct cxl_mem_command - Driver representation of a memory device command
* @info: Command information as it exists for the UAPI
* @opcode: The actual bits used for the mailbox protocol
* @flags: Set of flags effecting driver behavior.
*
* * %CXL_CMD_FLAG_FORCE_ENABLE: In cases of error, commands with this flag
* will be enabled by the driver regardless of what hardware may have
* advertised.
*
* The cxl_mem_command is the driver's internal representation of commands that
* are supported by the driver. Some of these commands may not be supported by
* the hardware. The driver will use @info to validate the fields passed in by
* the user then submit the @opcode to the hardware.
*
* See struct cxl_command_info.
*/
struct cxl_mem_command {
struct cxl_command_info info;
enum cxl_opcode opcode;
u32 flags;
#define CXL_CMD_FLAG_FORCE_ENABLE BIT(0)
};
#define CXL_PMEM_SEC_STATE_USER_PASS_SET 0x01
#define CXL_PMEM_SEC_STATE_MASTER_PASS_SET 0x02
#define CXL_PMEM_SEC_STATE_LOCKED 0x04
#define CXL_PMEM_SEC_STATE_FROZEN 0x08
#define CXL_PMEM_SEC_STATE_USER_PLIMIT 0x10
#define CXL_PMEM_SEC_STATE_MASTER_PLIMIT 0x20
/* set passphrase input payload */
struct cxl_set_pass {
u8 type;
u8 reserved[31];
/* CXL field using NVDIMM define, same length */
u8 old_pass[NVDIMM_PASSPHRASE_LEN];
u8 new_pass[NVDIMM_PASSPHRASE_LEN];
} __packed;
/* disable passphrase input payload */
struct cxl_disable_pass {
u8 type;
u8 reserved[31];
u8 pass[NVDIMM_PASSPHRASE_LEN];
} __packed;
/* passphrase secure erase payload */
struct cxl_pass_erase {
u8 type;
u8 reserved[31];
u8 pass[NVDIMM_PASSPHRASE_LEN];
} __packed;
enum {
CXL_PMEM_SEC_PASS_MASTER = 0,
CXL_PMEM_SEC_PASS_USER,
};
int cxl_internal_send_cmd(struct cxl_mailbox *cxl_mbox,
struct cxl_mbox_cmd *cmd);
int cxl_dev_state_identify(struct cxl_memdev_state *mds);
int cxl_await_media_ready(struct cxl_dev_state *cxlds);
int cxl_enumerate_cmds(struct cxl_memdev_state *mds);
int cxl_mem_dpa_fetch(struct cxl_memdev_state *mds, struct cxl_dpa_info *info);
struct cxl_memdev_state *cxl_memdev_state_create(struct device *dev, u64 serial,
u16 dvsec);
void set_exclusive_cxl_commands(struct cxl_memdev_state *mds,
unsigned long *cmds);
void clear_exclusive_cxl_commands(struct cxl_memdev_state *mds,
unsigned long *cmds);
void cxl_mem_get_event_records(struct cxl_memdev_state *mds, u32 status);
void cxl_event_trace_record(struct cxl_memdev *cxlmd,
enum cxl_event_log_type type,
enum cxl_event_type event_type,
const uuid_t *uuid, union cxl_event *evt);
int cxl_get_dirty_count(struct cxl_memdev_state *mds, u32 *count);
int cxl_arm_dirty_shutdown(struct cxl_memdev_state *mds);
int cxl_set_timestamp(struct cxl_memdev_state *mds);
int cxl_poison_state_init(struct cxl_memdev_state *mds);
int cxl_mem_get_poison(struct cxl_memdev *cxlmd, u64 offset, u64 len,
struct cxl_region *cxlr);
int cxl_trigger_poison_list(struct cxl_memdev *cxlmd);
int cxl_inject_poison(struct cxl_memdev *cxlmd, u64 dpa);
int cxl_clear_poison(struct cxl_memdev *cxlmd, u64 dpa);
int cxl_inject_poison_locked(struct cxl_memdev *cxlmd, u64 dpa);
int cxl_clear_poison_locked(struct cxl_memdev *cxlmd, u64 dpa);
#ifdef CONFIG_CXL_EDAC_MEM_FEATURES
int devm_cxl_memdev_edac_register(struct cxl_memdev *cxlmd);
int devm_cxl_region_edac_register(struct cxl_region *cxlr);
int cxl_store_rec_gen_media(struct cxl_memdev *cxlmd, union cxl_event *evt);
int cxl_store_rec_dram(struct cxl_memdev *cxlmd, union cxl_event *evt);
#else
static inline int devm_cxl_memdev_edac_register(struct cxl_memdev *cxlmd)
{ return 0; }
static inline int devm_cxl_region_edac_register(struct cxl_region *cxlr)
{ return 0; }
static inline int cxl_store_rec_gen_media(struct cxl_memdev *cxlmd,
union cxl_event *evt)
{ return 0; }
static inline int cxl_store_rec_dram(struct cxl_memdev *cxlmd,
union cxl_event *evt)
{ return 0; }
#endif
#ifdef CONFIG_CXL_SUSPEND
void cxl_mem_active_inc(void);
void cxl_mem_active_dec(void);
#else
static inline void cxl_mem_active_inc(void)
{
}
static inline void cxl_mem_active_dec(void)
{
}
#endif
int cxl_mem_sanitize(struct cxl_memdev *cxlmd, u16 cmd);
/**
* struct cxl_hdm - HDM Decoder registers and cached / decoded capabilities
* @regs: mapped registers, see devm_cxl_setup_hdm()
* @decoder_count: number of decoders for this port
* @target_count: for switch decoders, max downstream port targets
* @interleave_mask: interleave granularity capability, see check_interleave_cap()
* @iw_cap_mask: bitmask of supported interleave ways, see check_interleave_cap()
* @port: mapped cxl_port, see devm_cxl_setup_hdm()
*/
struct cxl_hdm {
struct cxl_component_regs regs;
int decoder_count;
unsigned int target_count;
unsigned int interleave_mask;
unsigned long iw_cap_mask;
struct cxl_port *port;
};
struct seq_file;
struct dentry *cxl_debugfs_create_dir(const char *dir);
void cxl_dpa_debug(struct seq_file *file, struct cxl_dev_state *cxlds);
#endif /* __CXL_MEM_H__ */
+117
View File
@@ -0,0 +1,117 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright(c) 2020 Intel Corporation. All rights reserved. */
#ifndef __CXL_PCI_H__
#define __CXL_PCI_H__
#include <linux/pci.h>
#include "cxl.h"
#define CXL_MEMORY_PROGIF 0x10
/*
* NOTE: Currently all the functions which are enabled for CXL require their
* vectors to be in the first 16. Use this as the default max.
*/
#define CXL_PCI_DEFAULT_MAX_VECTORS 16
/* Register Block Identifier (RBI) */
enum cxl_regloc_type {
CXL_REGLOC_RBI_EMPTY = 0,
CXL_REGLOC_RBI_COMPONENT,
CXL_REGLOC_RBI_VIRT,
CXL_REGLOC_RBI_MEMDEV,
CXL_REGLOC_RBI_PMU,
CXL_REGLOC_RBI_TYPES
};
/*
* Table Access DOE, CDAT Read Entry Response
*
* Spec refs:
*
* CXL 3.1 8.1.11, Table 8-14: Read Entry Response
* CDAT Specification 1.03: 2 CDAT Data Structures
*/
struct cdat_header {
__le32 length;
u8 revision;
u8 checksum;
u8 reserved[6];
__le32 sequence;
} __packed;
struct cdat_entry_header {
u8 type;
u8 reserved;
__le16 length;
} __packed;
/*
* The DOE CDAT read response contains a CDAT read entry (either the
* CDAT header or a structure).
*/
union cdat_data {
struct cdat_header header;
struct cdat_entry_header entry;
} __packed;
/* There is an additional CDAT response header of 4 bytes. */
struct cdat_doe_rsp {
__le32 doe_header;
u8 data[];
} __packed;
/*
* CXL v3.0 6.2.3 Table 6-4
* The table indicates that if PCIe Flit Mode is set, then CXL is in 256B flits
* mode, otherwise it's 68B flits mode.
*/
static inline bool cxl_pci_flit_256(struct pci_dev *pdev)
{
u16 lnksta2;
pcie_capability_read_word(pdev, PCI_EXP_LNKSTA2, &lnksta2);
return lnksta2 & PCI_EXP_LNKSTA2_FLIT;
}
/*
* Assume that the caller has already validated that @pdev has CXL
* capabilities, any RCiEP with CXL capabilities is treated as a
* Restricted CXL Device (RCD) and finds upstream port and endpoint
* registers in a Root Complex Register Block (RCRB).
*/
static inline bool is_cxl_restricted(struct pci_dev *pdev)
{
return pci_pcie_type(pdev) == PCI_EXP_TYPE_RC_END;
}
struct cxl_dev_state;
void read_cdat_data(struct cxl_port *port);
#ifdef CONFIG_CXL_RAS
void cxl_cor_error_detected(struct pci_dev *pdev);
pci_ers_result_t cxl_error_detected(struct pci_dev *pdev,
pci_channel_state_t state);
void devm_cxl_dport_rch_ras_setup(struct cxl_dport *dport);
void devm_cxl_port_ras_setup(struct cxl_port *port);
#else
static inline void cxl_cor_error_detected(struct pci_dev *pdev) { }
static inline pci_ers_result_t cxl_error_detected(struct pci_dev *pdev,
pci_channel_state_t state)
{
return PCI_ERS_RESULT_NONE;
}
static inline void devm_cxl_dport_rch_ras_setup(struct cxl_dport *dport)
{
}
static inline void devm_cxl_port_ras_setup(struct cxl_port *port)
{
}
#endif
int cxl_pci_setup_regs(struct pci_dev *pdev, enum cxl_regloc_type type,
struct cxl_register_map *map);
#endif /* __CXL_PCI_H__ */
+315
View File
@@ -0,0 +1,315 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright(c) 2022 Intel Corporation. All rights reserved. */
#include <linux/debugfs.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/pci.h>
#include "cxlmem.h"
#include "cxlpci.h"
/**
* DOC: cxl mem
*
* CXL memory endpoint devices and switches are CXL capable devices that are
* participating in CXL.mem protocol. Their functionality builds on top of the
* CXL.io protocol that allows enumerating and configuring components via
* standard PCI mechanisms.
*
* The cxl_mem driver owns kicking off the enumeration of this CXL.mem
* capability. With the detection of a CXL capable endpoint, the driver will
* walk up to find the platform specific port it is connected to, and determine
* if there are intervening switches in the path. If there are switches, a
* secondary action is to enumerate those (implemented in cxl_core). Finally the
* cxl_mem driver adds the device it is bound to as a CXL endpoint-port for use
* in higher level operations.
*/
static void enable_suspend(void *data)
{
cxl_mem_active_dec();
}
static void remove_debugfs(void *dentry)
{
debugfs_remove_recursive(dentry);
}
static int cxl_mem_dpa_show(struct seq_file *file, void *data)
{
struct device *dev = file->private;
struct cxl_memdev *cxlmd = to_cxl_memdev(dev);
cxl_dpa_debug(file, cxlmd->cxlds);
return 0;
}
static int cxl_debugfs_poison_inject(void *data, u64 dpa)
{
struct cxl_memdev *cxlmd = data;
int rc;
ACQUIRE(device_intr, devlock)(&cxlmd->dev);
if ((rc = ACQUIRE_ERR(device_intr, &devlock)))
return rc;
return cxl_inject_poison(cxlmd, dpa);
}
DEFINE_DEBUGFS_ATTRIBUTE(cxl_poison_inject_fops, NULL,
cxl_debugfs_poison_inject, "%llx\n");
static int cxl_debugfs_poison_clear(void *data, u64 dpa)
{
struct cxl_memdev *cxlmd = data;
int rc;
ACQUIRE(device_intr, devlock)(&cxlmd->dev);
if ((rc = ACQUIRE_ERR(device_intr, &devlock)))
return rc;
return cxl_clear_poison(cxlmd, dpa);
}
DEFINE_DEBUGFS_ATTRIBUTE(cxl_poison_clear_fops, NULL,
cxl_debugfs_poison_clear, "%llx\n");
static void cxl_memdev_poison_enable(struct cxl_memdev_state *mds,
struct cxl_memdev *cxlmd,
struct dentry *dentry)
{
/*
* Avoid poison debugfs for DEVMEM aka accelerators as they rely on
* cxl_memdev_state.
*/
if (!mds)
return;
if (test_bit(CXL_POISON_ENABLED_INJECT, mds->poison.enabled_cmds))
debugfs_create_file("inject_poison", 0200, dentry, cxlmd,
&cxl_poison_inject_fops);
if (test_bit(CXL_POISON_ENABLED_CLEAR, mds->poison.enabled_cmds))
debugfs_create_file("clear_poison", 0200, dentry, cxlmd,
&cxl_poison_clear_fops);
}
static int cxl_mem_probe(struct device *dev)
{
struct cxl_memdev *cxlmd = to_cxl_memdev(dev);
struct cxl_memdev_state *mds = to_cxl_memdev_state(cxlmd->cxlds);
struct cxl_dev_state *cxlds = cxlmd->cxlds;
struct device *endpoint_parent;
struct cxl_dport *dport;
struct dentry *dentry;
int rc;
if (!cxlds->media_ready)
return -EBUSY;
/*
* Someone is trying to reattach this device after it lost its port
* connection (an endpoint port previously registered by this memdev was
* disabled). This racy check is ok because if the port is still gone,
* no harm done, and if the port hierarchy comes back it will re-trigger
* this probe. Port rescan and memdev detach work share the same
* single-threaded workqueue.
*/
if (work_pending(&cxlmd->detach_work))
return -EBUSY;
dentry = cxl_debugfs_create_dir(dev_name(dev));
debugfs_create_devm_seqfile(dev, "dpamem", dentry, cxl_mem_dpa_show);
cxl_memdev_poison_enable(mds, cxlmd, dentry);
rc = devm_add_action_or_reset(dev, remove_debugfs, dentry);
if (rc)
return rc;
rc = devm_cxl_enumerate_ports(cxlmd);
if (rc)
return rc;
struct cxl_port *parent_port __free(put_cxl_port) =
cxl_mem_find_port(cxlmd, &dport);
if (!parent_port) {
dev_err(dev, "CXL port topology not found\n");
return -ENXIO;
}
if (cxl_pmem_size(cxlds) && IS_ENABLED(CONFIG_CXL_PMEM)) {
rc = devm_cxl_add_nvdimm(dev, parent_port, cxlmd);
if (rc) {
if (rc == -ENODEV)
dev_info(dev, "PMEM disabled by platform\n");
return rc;
}
}
if (dport->rch)
endpoint_parent = parent_port->uport_dev;
else
endpoint_parent = &parent_port->dev;
scoped_guard(device, endpoint_parent) {
if (!endpoint_parent->driver) {
dev_err(dev, "CXL port topology %s not enabled\n",
dev_name(endpoint_parent));
return -ENXIO;
}
rc = devm_cxl_add_endpoint(endpoint_parent, cxlmd, dport);
if (rc)
return rc;
}
if (cxlmd->attach) {
rc = cxlmd->attach->probe(cxlmd);
if (rc)
return rc;
}
rc = devm_cxl_memdev_edac_register(cxlmd);
if (rc)
dev_dbg(dev, "CXL memdev EDAC registration failed rc=%d\n", rc);
/*
* The kernel may be operating out of CXL memory on this device,
* there is no spec defined way to determine whether this device
* preserves contents over suspend, and there is no simple way
* to arrange for the suspend image to avoid CXL memory which
* would setup a circular dependency between PCI resume and save
* state restoration.
*
* TODO: support suspend when all the regions this device is
* hosting are locked and covered by the system address map,
* i.e. platform firmware owns restoring the HDM configuration
* that it locked.
*/
cxl_mem_active_inc();
return devm_add_action_or_reset(dev, enable_suspend, NULL);
}
/**
* devm_cxl_add_classdev - Add a CXL memory class-code device
* @cxlds: CXL device state to associate with the memdev
*
* Upon return the device will have had a chance to attach to the
* cxl_mem driver, but may fail to attach if the CXL topology is not ready
* (hardware CXL link down, or software platform CXL root not attached).
*
* The parent of the resulting device and the devm context for allocations is
* @cxlds->dev.
*/
struct cxl_memdev *devm_cxl_add_classdev(struct cxl_dev_state *cxlds)
{
return __devm_cxl_add_memdev(cxlds, NULL);
}
EXPORT_SYMBOL_NS_GPL(devm_cxl_add_classdev, "CXL");
/**
* devm_cxl_probe_mem - Add a CXL memory device and probe its region
* @cxlds: CXL device state to associate with the memdev
* @hpa_range: CXL.mem physical address range result
*
* Upon return the device will have had a chance to attach to the
* cxl_mem driver, but may fail to attach if the CXL topology is not ready
* (hardware CXL link down, or software platform CXL root not attached).
*
* Failure to probe the memdev and/or setup a region for the memdev
* results in this function failing.
*
* The parent of the resulting device and the devm context for allocations is
* @cxlds->dev.
*/
struct cxl_memdev *devm_cxl_probe_mem(struct cxl_dev_state *cxlds,
struct range *hpa_range)
{
struct cxl_attach_region *attach =
devm_kmalloc(cxlds->dev, sizeof(*attach), GFP_KERNEL);
struct cxl_memdev *cxlmd;
if (!attach)
return ERR_PTR(-ENOMEM);
*attach = (struct cxl_attach_region) {
.attach = {
.probe = cxl_memdev_attach_region,
},
.hpa_range = { 0, -1 },
};
cxlmd = __devm_cxl_add_memdev(cxlds, &attach->attach);
*hpa_range = attach->hpa_range;
return cxlmd;
}
EXPORT_SYMBOL_NS_GPL(devm_cxl_probe_mem, "CXL");
static ssize_t trigger_poison_list_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len)
{
bool trigger;
int rc;
if (kstrtobool(buf, &trigger) || !trigger)
return -EINVAL;
rc = cxl_trigger_poison_list(to_cxl_memdev(dev));
return rc ? rc : len;
}
static DEVICE_ATTR_WO(trigger_poison_list);
static bool cxl_poison_attr_visible(struct kobject *kobj, struct attribute *a)
{
struct device *dev = kobj_to_dev(kobj);
struct cxl_memdev *cxlmd = to_cxl_memdev(dev);
struct cxl_memdev_state *mds = to_cxl_memdev_state(cxlmd->cxlds);
if (!mds ||
!test_bit(CXL_POISON_ENABLED_LIST, mds->poison.enabled_cmds))
return false;
return true;
}
static umode_t cxl_mem_visible(struct kobject *kobj, struct attribute *a, int n)
{
if (a == &dev_attr_trigger_poison_list.attr &&
!cxl_poison_attr_visible(kobj, a))
return 0;
return a->mode;
}
static struct attribute *cxl_mem_attrs[] = {
&dev_attr_trigger_poison_list.attr,
NULL
};
static struct attribute_group cxl_mem_group = {
.attrs = cxl_mem_attrs,
.is_visible = cxl_mem_visible,
};
__ATTRIBUTE_GROUPS(cxl_mem);
static struct cxl_driver cxl_mem_driver = {
.name = "cxl_mem",
.probe = cxl_mem_probe,
.id = CXL_DEVICE_MEMORY_EXPANDER,
.drv = {
.probe_type = PROBE_FORCE_SYNCHRONOUS,
.dev_groups = cxl_mem_groups,
},
};
module_cxl_driver(cxl_mem_driver);
MODULE_DESCRIPTION("CXL: Memory Expansion");
MODULE_LICENSE("GPL v2");
MODULE_IMPORT_NS("CXL");
MODULE_ALIAS_CXL(CXL_DEVICE_MEMORY_EXPANDER);
File diff suppressed because it is too large Load Diff
+562
View File
@@ -0,0 +1,562 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright(c) 2021 Intel Corporation. All rights reserved. */
#include <linux/libnvdimm.h>
#include <linux/unaligned.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/ndctl.h>
#include <linux/async.h>
#include <linux/slab.h>
#include <linux/nd.h>
#include "cxlmem.h"
#include "cxl.h"
static __read_mostly DECLARE_BITMAP(exclusive_cmds, CXL_MEM_COMMAND_ID_MAX);
/**
* devm_cxl_add_nvdimm_bridge() - add the root of a LIBNVDIMM topology
* @host: platform firmware root device
* @port: CXL port at the root of a CXL topology
*
* Return: bridge device that can host cxl_nvdimm objects
*/
struct cxl_nvdimm_bridge *devm_cxl_add_nvdimm_bridge(struct device *host,
struct cxl_port *port)
{
return __devm_cxl_add_nvdimm_bridge(host, port);
}
EXPORT_SYMBOL_NS_GPL(devm_cxl_add_nvdimm_bridge, "CXL");
static void clear_exclusive(void *mds)
{
clear_exclusive_cxl_commands(mds, exclusive_cmds);
}
static void unregister_nvdimm(void *nvdimm)
{
nvdimm_delete(nvdimm);
}
static ssize_t provider_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct nvdimm *nvdimm = to_nvdimm(dev);
struct cxl_nvdimm *cxl_nvd = nvdimm_provider_data(nvdimm);
return sysfs_emit(buf, "%s\n", dev_name(&cxl_nvd->dev));
}
static DEVICE_ATTR_RO(provider);
static ssize_t id_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct nvdimm *nvdimm = to_nvdimm(dev);
struct cxl_nvdimm *cxl_nvd = nvdimm_provider_data(nvdimm);
struct cxl_dev_state *cxlds = cxl_nvd->cxlmd->cxlds;
return sysfs_emit(buf, "%lld\n", cxlds->serial);
}
static DEVICE_ATTR_RO(id);
static ssize_t dirty_shutdown_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct nvdimm *nvdimm = to_nvdimm(dev);
struct cxl_nvdimm *cxl_nvd = nvdimm_provider_data(nvdimm);
return sysfs_emit(buf, "%llu\n", cxl_nvd->dirty_shutdowns);
}
static DEVICE_ATTR_RO(dirty_shutdown);
static struct attribute *cxl_dimm_attributes[] = {
&dev_attr_id.attr,
&dev_attr_provider.attr,
&dev_attr_dirty_shutdown.attr,
NULL
};
#define CXL_INVALID_DIRTY_SHUTDOWN_COUNT ULLONG_MAX
static umode_t cxl_dimm_visible(struct kobject *kobj,
struct attribute *a, int n)
{
if (a == &dev_attr_dirty_shutdown.attr) {
struct device *dev = kobj_to_dev(kobj);
struct nvdimm *nvdimm = to_nvdimm(dev);
struct cxl_nvdimm *cxl_nvd = nvdimm_provider_data(nvdimm);
if (cxl_nvd->dirty_shutdowns ==
CXL_INVALID_DIRTY_SHUTDOWN_COUNT)
return 0;
}
return a->mode;
}
static const struct attribute_group cxl_dimm_attribute_group = {
.name = "cxl",
.attrs = cxl_dimm_attributes,
.is_visible = cxl_dimm_visible
};
static const struct attribute_group *cxl_dimm_attribute_groups[] = {
&cxl_dimm_attribute_group,
NULL
};
static void cxl_nvdimm_arm_dirty_shutdown_tracking(struct cxl_nvdimm *cxl_nvd)
{
struct cxl_memdev *cxlmd = cxl_nvd->cxlmd;
struct cxl_dev_state *cxlds = cxlmd->cxlds;
struct cxl_memdev_state *mds = to_cxl_memdev_state(cxlds);
struct device *dev = &cxl_nvd->dev;
u32 count;
/*
* Dirty tracking is enabled and exposed to the user, only when:
* - dirty shutdown on the device can be set, and,
* - the device has a Device GPF DVSEC (albeit unused), and,
* - the Get Health Info cmd can retrieve the device's dirty count.
*/
cxl_nvd->dirty_shutdowns = CXL_INVALID_DIRTY_SHUTDOWN_COUNT;
if (cxl_arm_dirty_shutdown(mds)) {
dev_warn(dev, "GPF: could not set dirty shutdown state\n");
return;
}
if (!cxl_gpf_get_dvsec(cxlds->dev))
return;
if (cxl_get_dirty_count(mds, &count)) {
dev_warn(dev, "GPF: could not retrieve dirty count\n");
return;
}
cxl_nvd->dirty_shutdowns = count;
}
static int cxl_nvdimm_probe(struct device *dev)
{
struct cxl_nvdimm *cxl_nvd = to_cxl_nvdimm(dev);
struct cxl_memdev *cxlmd = cxl_nvd->cxlmd;
struct cxl_nvdimm_bridge *cxl_nvb = cxlmd->cxl_nvb;
struct cxl_memdev_state *mds = to_cxl_memdev_state(cxlmd->cxlds);
unsigned long flags = 0, cmd_mask = 0;
struct nvdimm *nvdimm;
int rc;
if (test_bit(CXL_NVD_F_INVALIDATED, &cxl_nvd->flags))
return -EBUSY;
set_exclusive_cxl_commands(mds, exclusive_cmds);
rc = devm_add_action_or_reset(dev, clear_exclusive, mds);
if (rc)
return rc;
set_bit(NDD_LABELING, &flags);
set_bit(NDD_REGISTER_SYNC, &flags);
set_bit(ND_CMD_GET_CONFIG_SIZE, &cmd_mask);
set_bit(ND_CMD_GET_CONFIG_DATA, &cmd_mask);
set_bit(ND_CMD_SET_CONFIG_DATA, &cmd_mask);
/*
* Set dirty shutdown now, with the expectation that the device
* clear it upon a successful GPF flow. The exception to this
* is upon Viral detection, per CXL 3.2 section 12.4.2.
*/
cxl_nvdimm_arm_dirty_shutdown_tracking(cxl_nvd);
nvdimm = __nvdimm_create(cxl_nvb->nvdimm_bus, cxl_nvd,
cxl_dimm_attribute_groups, flags,
cmd_mask, 0, NULL, cxl_nvd->dev_id,
cxl_security_ops, NULL);
if (!nvdimm)
return -ENOMEM;
dev_set_drvdata(dev, nvdimm);
return devm_add_action_or_reset(dev, unregister_nvdimm, nvdimm);
}
static struct cxl_driver cxl_nvdimm_driver = {
.name = "cxl_nvdimm",
.probe = cxl_nvdimm_probe,
.id = CXL_DEVICE_NVDIMM,
.drv = {
.suppress_bind_attrs = true,
},
};
static int cxl_pmem_get_config_size(struct cxl_memdev_state *mds,
struct nd_cmd_get_config_size *cmd,
unsigned int buf_len)
{
struct cxl_mailbox *cxl_mbox = &mds->cxlds.cxl_mbox;
if (sizeof(*cmd) > buf_len)
return -EINVAL;
*cmd = (struct nd_cmd_get_config_size){
.config_size = mds->lsa_size,
.max_xfer =
cxl_mbox->payload_size - sizeof(struct cxl_mbox_set_lsa),
};
return 0;
}
static int cxl_pmem_get_config_data(struct cxl_memdev_state *mds,
struct nd_cmd_get_config_data_hdr *cmd,
unsigned int buf_len)
{
struct cxl_mailbox *cxl_mbox = &mds->cxlds.cxl_mbox;
struct cxl_mbox_get_lsa get_lsa;
struct cxl_mbox_cmd mbox_cmd;
int rc;
if (sizeof(*cmd) > buf_len)
return -EINVAL;
if (struct_size(cmd, out_buf, cmd->in_length) > buf_len)
return -EINVAL;
get_lsa = (struct cxl_mbox_get_lsa) {
.offset = cpu_to_le32(cmd->in_offset),
.length = cpu_to_le32(cmd->in_length),
};
mbox_cmd = (struct cxl_mbox_cmd) {
.opcode = CXL_MBOX_OP_GET_LSA,
.payload_in = &get_lsa,
.size_in = sizeof(get_lsa),
.size_out = cmd->in_length,
.payload_out = cmd->out_buf,
};
rc = cxl_internal_send_cmd(cxl_mbox, &mbox_cmd);
cmd->status = 0;
return rc;
}
static int cxl_pmem_set_config_data(struct cxl_memdev_state *mds,
struct nd_cmd_set_config_hdr *cmd,
unsigned int buf_len)
{
struct cxl_mailbox *cxl_mbox = &mds->cxlds.cxl_mbox;
struct cxl_mbox_set_lsa *set_lsa;
struct cxl_mbox_cmd mbox_cmd;
int rc;
if (sizeof(*cmd) > buf_len)
return -EINVAL;
/* 4-byte status follows the input data in the payload */
if (size_add(struct_size(cmd, in_buf, cmd->in_length), 4) > buf_len)
return -EINVAL;
set_lsa =
kvzalloc_flex(*set_lsa, data, cmd->in_length);
if (!set_lsa)
return -ENOMEM;
*set_lsa = (struct cxl_mbox_set_lsa) {
.offset = cpu_to_le32(cmd->in_offset),
};
memcpy(set_lsa->data, cmd->in_buf, cmd->in_length);
mbox_cmd = (struct cxl_mbox_cmd) {
.opcode = CXL_MBOX_OP_SET_LSA,
.payload_in = set_lsa,
.size_in = struct_size(set_lsa, data, cmd->in_length),
};
rc = cxl_internal_send_cmd(cxl_mbox, &mbox_cmd);
/*
* Set "firmware" status (4-packed bytes at the end of the input
* payload.
*/
put_unaligned(0, (u32 *) &cmd->in_buf[cmd->in_length]);
kvfree(set_lsa);
return rc;
}
static int cxl_pmem_nvdimm_ctl(struct nvdimm *nvdimm, unsigned int cmd,
void *buf, unsigned int buf_len)
{
struct cxl_nvdimm *cxl_nvd = nvdimm_provider_data(nvdimm);
unsigned long cmd_mask = nvdimm_cmd_mask(nvdimm);
struct cxl_memdev *cxlmd = cxl_nvd->cxlmd;
struct cxl_memdev_state *mds = to_cxl_memdev_state(cxlmd->cxlds);
if (!test_bit(cmd, &cmd_mask))
return -ENOTTY;
switch (cmd) {
case ND_CMD_GET_CONFIG_SIZE:
return cxl_pmem_get_config_size(mds, buf, buf_len);
case ND_CMD_GET_CONFIG_DATA:
return cxl_pmem_get_config_data(mds, buf, buf_len);
case ND_CMD_SET_CONFIG_DATA:
return cxl_pmem_set_config_data(mds, buf, buf_len);
default:
return -ENOTTY;
}
}
static int cxl_pmem_ctl(struct nvdimm_bus_descriptor *nd_desc,
struct nvdimm *nvdimm, unsigned int cmd, void *buf,
unsigned int buf_len, int *cmd_rc)
{
/*
* No firmware response to translate, let the transport error
* code take precedence.
*/
*cmd_rc = 0;
if (!nvdimm)
return -ENOTTY;
return cxl_pmem_nvdimm_ctl(nvdimm, cmd, buf, buf_len);
}
static int detach_nvdimm(struct device *dev, void *data)
{
struct cxl_nvdimm *cxl_nvd;
bool release = false;
if (!is_cxl_nvdimm(dev))
return 0;
scoped_guard(device, dev) {
if (dev->driver) {
cxl_nvd = to_cxl_nvdimm(dev);
if (cxl_nvd->cxlmd && cxl_nvd->cxlmd->cxl_nvb == data) {
release = true;
set_bit(CXL_NVD_F_INVALIDATED, &cxl_nvd->flags);
}
}
}
if (release)
device_release_driver(dev);
return 0;
}
static void unregister_nvdimm_bus(void *_cxl_nvb)
{
struct cxl_nvdimm_bridge *cxl_nvb = _cxl_nvb;
struct nvdimm_bus *nvdimm_bus = cxl_nvb->nvdimm_bus;
bus_for_each_dev(&cxl_bus_type, NULL, cxl_nvb, detach_nvdimm);
cxl_nvb->nvdimm_bus = NULL;
nvdimm_bus_unregister(nvdimm_bus);
}
static int cxl_nvdimm_bridge_probe(struct device *dev)
{
struct cxl_nvdimm_bridge *cxl_nvb = to_cxl_nvdimm_bridge(dev);
cxl_nvb->nd_desc = (struct nvdimm_bus_descriptor) {
.provider_name = "CXL",
.module = THIS_MODULE,
.ndctl = cxl_pmem_ctl,
};
cxl_nvb->nvdimm_bus =
nvdimm_bus_register(&cxl_nvb->dev, &cxl_nvb->nd_desc);
if (!cxl_nvb->nvdimm_bus)
return -ENOMEM;
return devm_add_action_or_reset(dev, unregister_nvdimm_bus, cxl_nvb);
}
static struct cxl_driver cxl_nvdimm_bridge_driver = {
.name = "cxl_nvdimm_bridge",
.probe = cxl_nvdimm_bridge_probe,
.id = CXL_DEVICE_NVDIMM_BRIDGE,
.drv = {
.probe_type = PROBE_FORCE_SYNCHRONOUS,
.suppress_bind_attrs = true,
},
};
static void unregister_nvdimm_region(void *nd_region)
{
nvdimm_region_delete(nd_region);
}
static void cxlr_pmem_remove_resource(void *res)
{
remove_resource(res);
}
struct cxl_pmem_region_info {
u64 offset;
u64 serial;
};
static int cxl_pmem_region_probe(struct device *dev)
{
struct nd_mapping_desc mappings[CXL_DECODER_MAX_INTERLEAVE];
struct cxl_pmem_region *cxlr_pmem = to_cxl_pmem_region(dev);
struct cxl_region *cxlr = cxlr_pmem->cxlr;
struct cxl_nvdimm_bridge *cxl_nvb = cxlr->cxl_nvb;
struct cxl_pmem_region_info *info = NULL;
struct nd_interleave_set *nd_set;
struct nd_region_desc ndr_desc;
struct cxl_nvdimm *cxl_nvd;
struct nvdimm *nvdimm;
struct resource *res;
int rc, i = 0;
memset(&mappings, 0, sizeof(mappings));
memset(&ndr_desc, 0, sizeof(ndr_desc));
res = devm_kzalloc(dev, sizeof(*res), GFP_KERNEL);
if (!res)
return -ENOMEM;
res->name = "Persistent Memory";
res->start = cxlr_pmem->hpa_range.start;
res->end = cxlr_pmem->hpa_range.end;
res->flags = IORESOURCE_MEM;
res->desc = IORES_DESC_PERSISTENT_MEMORY;
rc = insert_resource(&iomem_resource, res);
if (rc)
return rc;
rc = devm_add_action_or_reset(dev, cxlr_pmem_remove_resource, res);
if (rc)
return rc;
ndr_desc.res = res;
ndr_desc.provider_data = cxlr_pmem;
ndr_desc.numa_node = memory_add_physaddr_to_nid(res->start);
ndr_desc.target_node = phys_to_target_node(res->start);
if (ndr_desc.target_node == NUMA_NO_NODE) {
ndr_desc.target_node = ndr_desc.numa_node;
dev_dbg(&cxlr->dev, "changing target node from %d to %d",
NUMA_NO_NODE, ndr_desc.target_node);
}
nd_set = devm_kzalloc(dev, sizeof(*nd_set), GFP_KERNEL);
if (!nd_set)
return -ENOMEM;
ndr_desc.memregion = cxlr->id;
set_bit(ND_REGION_CXL, &ndr_desc.flags);
set_bit(ND_REGION_PERSIST_MEMCTRL, &ndr_desc.flags);
info = kmalloc_objs(*info, cxlr_pmem->nr_mappings);
if (!info)
return -ENOMEM;
for (i = 0; i < cxlr_pmem->nr_mappings; i++) {
struct cxl_pmem_region_mapping *m = &cxlr_pmem->mapping[i];
struct cxl_memdev *cxlmd = m->cxlmd;
struct cxl_dev_state *cxlds = cxlmd->cxlds;
cxl_nvd = cxlmd->cxl_nvd;
nvdimm = dev_get_drvdata(&cxl_nvd->dev);
if (!nvdimm) {
dev_dbg(dev, "[%d]: %s: no nvdimm found\n", i,
dev_name(&cxlmd->dev));
rc = -ENODEV;
goto out_nvd;
}
if (cxlds->serial == 0) {
/* include missing alongside invalid in this error message. */
dev_err(dev, "%s: invalid or missing serial number\n",
dev_name(&cxlmd->dev));
rc = -ENXIO;
goto out_nvd;
}
info[i].serial = cxlds->serial;
info[i].offset = m->start;
m->cxl_nvd = cxl_nvd;
mappings[i] = (struct nd_mapping_desc) {
.nvdimm = nvdimm,
.start = m->start,
.size = m->size,
.position = i,
};
}
ndr_desc.num_mappings = cxlr_pmem->nr_mappings;
ndr_desc.mapping = mappings;
/*
* TODO enable CXL labels which skip the need for 'interleave-set cookie'
*/
nd_set->cookie1 =
nd_fletcher64(info, sizeof(*info) * cxlr_pmem->nr_mappings, 0);
nd_set->cookie2 = nd_set->cookie1;
ndr_desc.nd_set = nd_set;
cxlr_pmem->nd_region =
nvdimm_pmem_region_create(cxl_nvb->nvdimm_bus, &ndr_desc);
if (!cxlr_pmem->nd_region) {
rc = -ENOMEM;
goto out_nvd;
}
rc = devm_add_action_or_reset(dev, unregister_nvdimm_region,
cxlr_pmem->nd_region);
out_nvd:
kfree(info);
return rc;
}
static struct cxl_driver cxl_pmem_region_driver = {
.name = "cxl_pmem_region",
.probe = cxl_pmem_region_probe,
.id = CXL_DEVICE_PMEM_REGION,
.drv = {
.suppress_bind_attrs = true,
},
};
static __init int cxl_pmem_init(void)
{
int rc;
set_bit(CXL_MEM_COMMAND_ID_SET_SHUTDOWN_STATE, exclusive_cmds);
set_bit(CXL_MEM_COMMAND_ID_SET_LSA, exclusive_cmds);
rc = cxl_driver_register(&cxl_nvdimm_bridge_driver);
if (rc)
return rc;
rc = cxl_driver_register(&cxl_nvdimm_driver);
if (rc)
goto err_nvdimm;
rc = cxl_driver_register(&cxl_pmem_region_driver);
if (rc)
goto err_region;
return 0;
err_region:
cxl_driver_unregister(&cxl_nvdimm_driver);
err_nvdimm:
cxl_driver_unregister(&cxl_nvdimm_bridge_driver);
return rc;
}
static __exit void cxl_pmem_exit(void)
{
cxl_driver_unregister(&cxl_pmem_region_driver);
cxl_driver_unregister(&cxl_nvdimm_driver);
cxl_driver_unregister(&cxl_nvdimm_bridge_driver);
}
MODULE_DESCRIPTION("CXL PMEM: Persistent Memory Support");
MODULE_LICENSE("GPL v2");
subsys_initcall(cxl_pmem_init);
module_exit(cxl_pmem_exit);
MODULE_IMPORT_NS("CXL");
MODULE_ALIAS_CXL(CXL_DEVICE_NVDIMM_BRIDGE);
MODULE_ALIAS_CXL(CXL_DEVICE_NVDIMM);
MODULE_ALIAS_CXL(CXL_DEVICE_PMEM_REGION);
+28
View File
@@ -0,0 +1,28 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright(c) 2023 Huawei
* CXL Specification rev 3.0 Setion 8.2.7 (CPMU Register Interface)
*/
#ifndef CXL_PMU_H
#define CXL_PMU_H
#include <linux/device.h>
enum cxl_pmu_type {
CXL_PMU_MEMDEV,
};
#define CXL_PMU_REGMAP_SIZE 0xe00 /* Table 8-32 CXL 3.0 specification */
struct cxl_pmu {
struct device dev;
void __iomem *base;
int assoc_id;
int index;
enum cxl_pmu_type type;
};
#define to_cxl_pmu(dev) container_of(dev, struct cxl_pmu, dev)
struct cxl_pmu_regs;
int devm_cxl_pmu_add(struct device *parent, struct cxl_pmu_regs *regs,
int assoc_id, int idx, enum cxl_pmu_type type);
#endif
+344
View File
@@ -0,0 +1,344 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright(c) 2022 Intel Corporation. All rights reserved. */
#include <linux/aer.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/slab.h>
#include "cxlmem.h"
#include "cxlpci.h"
/**
* DOC: cxl port
*
* The port driver enumerates dport via PCI and scans for HDM
* (Host-managed-Device-Memory) decoder resources via the
* @component_reg_phys value passed in by the agent that registered the
* port. All descendant ports of a CXL root port (described by platform
* firmware) are managed in this drivers context. Each driver instance
* is responsible for tearing down the driver context of immediate
* descendant ports. The locking for this is validated by
* CONFIG_PROVE_LOCKING.
*
* The primary service this driver provides is presenting APIs to other
* drivers to utilize the decoders, and indicating to userspace (via bind
* status) the connectivity of the CXL.mem protocol throughout the
* PCIe topology.
*/
static void schedule_detach(void *cxlmd)
{
schedule_cxl_memdev_detach(cxlmd);
}
static int discover_region(struct device *dev, void *unused)
{
struct cxl_endpoint_decoder *cxled;
int rc;
if (!is_endpoint_decoder(dev))
return 0;
cxled = to_cxl_endpoint_decoder(dev);
if ((cxled->cxld.flags & CXL_DECODER_F_ENABLE) == 0)
return 0;
if (cxled->state != CXL_DECODER_STATE_AUTO)
return 0;
/*
* Region enumeration is opportunistic, if this add-event fails,
* continue to the next endpoint decoder.
*/
rc = cxl_add_to_region(cxled);
if (rc)
dev_dbg(dev, "failed to add to region: %#llx-%#llx\n",
cxled->cxld.hpa_range.start, cxled->cxld.hpa_range.end);
return 0;
}
static int cxl_switch_port_probe(struct cxl_port *port)
{
/* Reset nr_dports for rebind of driver */
port->nr_dports = 0;
/* Cache the data early to ensure is_visible() works */
read_cdat_data(port);
return 0;
}
static int cxl_ras_unmask(struct cxl_port *port)
{
struct pci_dev *pdev;
void __iomem *addr;
u32 orig_val, val, mask;
u16 cap;
int rc;
if (!dev_is_pci(port->uport_dev))
return 0;
pdev = to_pci_dev(port->uport_dev);
if (!port->regs.ras) {
pci_dbg(pdev, "No RAS registers.\n");
return 0;
}
/* BIOS has PCIe AER error control */
if (!pcie_aer_is_native(pdev))
return 0;
rc = pcie_capability_read_word(pdev, PCI_EXP_DEVCTL, &cap);
if (rc)
return rc;
if (cap & PCI_EXP_DEVCTL_URRE) {
addr = port->regs.ras + CXL_RAS_UNCORRECTABLE_MASK_OFFSET;
orig_val = readl(addr);
mask = CXL_RAS_UNCORRECTABLE_MASK_MASK |
CXL_RAS_UNCORRECTABLE_MASK_F256B_MASK;
val = orig_val & ~mask;
writel(val, addr);
pci_dbg(pdev, "Uncorrectable RAS Errors Mask: %#x -> %#x\n",
orig_val, val);
}
if (cap & PCI_EXP_DEVCTL_CERE) {
addr = port->regs.ras + CXL_RAS_CORRECTABLE_MASK_OFFSET;
orig_val = readl(addr);
val = orig_val & ~CXL_RAS_CORRECTABLE_MASK_MASK;
writel(val, addr);
pci_dbg(pdev, "Correctable RAS Errors Mask: %#x -> %#x\n",
orig_val, val);
}
return 0;
}
static int cxl_endpoint_port_probe(struct cxl_port *port)
{
struct cxl_memdev *cxlmd = to_cxl_memdev(port->uport_dev);
struct cxl_dport *dport = port->parent_dport;
int rc;
/* Cache the data early to ensure is_visible() works */
read_cdat_data(port);
cxl_endpoint_parse_cdat(port);
get_device(&cxlmd->dev);
rc = devm_add_action_or_reset(&port->dev, schedule_detach, cxlmd);
if (rc)
return rc;
rc = devm_cxl_endpoint_decoders_setup(port);
if (rc)
return rc;
/*
* With VH (CXL Virtual Host) topology the cxl_port::add_dport() method
* handles RAS setup for downstream ports. With RCH (CXL Restricted CXL
* Host) topologies the downstream port is enumerated early by platform
* firmware, but the RCRB (root complex register block) is not mapped
* until after the cxl_pci driver attaches to the RCIeP (root complex
* integrated endpoint).
*/
if (dport->rch)
devm_cxl_dport_rch_ras_setup(dport);
devm_cxl_port_ras_setup(port);
if (cxl_ras_unmask(port))
dev_dbg(&port->dev, "failed to unmask RAS interrupts\n");
/*
* Now that all endpoint decoders are successfully enumerated, try to
* assemble regions from committed decoders
*/
device_for_each_child(&port->dev, NULL, discover_region);
return 0;
}
static int cxl_port_probe(struct device *dev)
{
struct cxl_port *port = to_cxl_port(dev);
if (is_cxl_endpoint(port))
return cxl_endpoint_port_probe(port);
return cxl_switch_port_probe(port);
}
static ssize_t CDAT_read(struct file *filp, struct kobject *kobj,
const struct bin_attribute *bin_attr, char *buf,
loff_t offset, size_t count)
{
struct device *dev = kobj_to_dev(kobj);
struct cxl_port *port = to_cxl_port(dev);
if (!port->cdat_available)
return -ENXIO;
if (!port->cdat.table)
return 0;
return memory_read_from_buffer(buf, count, &offset,
port->cdat.table,
port->cdat.length);
}
static const BIN_ATTR_ADMIN_RO(CDAT, 0);
static umode_t cxl_port_bin_attr_is_visible(struct kobject *kobj,
const struct bin_attribute *attr, int i)
{
struct device *dev = kobj_to_dev(kobj);
struct cxl_port *port = to_cxl_port(dev);
if ((attr == &bin_attr_CDAT) && port->cdat_available)
return attr->attr.mode;
return 0;
}
static const struct bin_attribute *const cxl_cdat_bin_attributes[] = {
&bin_attr_CDAT,
NULL,
};
static const struct attribute_group cxl_cdat_attribute_group = {
.bin_attrs = cxl_cdat_bin_attributes,
.is_bin_visible = cxl_port_bin_attr_is_visible,
};
static const struct attribute_group *cxl_port_attribute_groups[] = {
&cxl_cdat_attribute_group,
NULL,
};
/* note this implicitly casts the group back to its @port */
DEFINE_FREE(cxl_port_release_dr_group, struct cxl_port *,
if (_T) devres_release_group(&_T->dev, _T))
static struct cxl_dport *cxl_port_add_dport(struct cxl_port *port,
struct device *dport_dev)
{
struct cxl_dport *dport;
int rc;
/* Temp group for all "first dport" and "per dport" setup actions */
void *port_dr_group __free(cxl_port_release_dr_group) =
devres_open_group(&port->dev, port, GFP_KERNEL);
if (!port_dr_group)
return ERR_PTR(-ENOMEM);
if (port->nr_dports == 0) {
/*
* Some host bridges are known to not have component regsisters
* available until a root port has trained CXL. Perform that
* setup now.
*/
rc = cxl_port_setup_regs(port, port->component_reg_phys);
if (rc)
return ERR_PTR(rc);
rc = devm_cxl_switch_port_decoders_setup(port);
if (rc)
return ERR_PTR(rc);
/*
* RAS setup is optional, either driver operation can continue
* on failure, or the device does not implement RAS registers.
*/
devm_cxl_port_ras_setup(port);
}
dport = devm_cxl_add_dport_by_dev(port, dport_dev);
if (IS_ERR(dport))
return dport;
/* This group was only needed for early exit above */
devres_remove_group(&port->dev, no_free_ptr(port_dr_group));
cxl_switch_parse_cdat(dport);
/* New dport added, update the decoder targets */
cxl_port_update_decoder_targets(port, dport);
dev_dbg(&port->dev, "dport%d:%s added\n", dport->port_id,
dev_name(dport_dev));
return dport;
}
static struct cxl_driver cxl_port_driver = {
.name = "cxl_port",
.probe = cxl_port_probe,
.add_dport = cxl_port_add_dport,
.id = CXL_DEVICE_PORT,
.drv = {
.probe_type = PROBE_FORCE_SYNCHRONOUS,
.dev_groups = cxl_port_attribute_groups,
},
};
int devm_cxl_add_endpoint(struct device *host, struct cxl_memdev *cxlmd,
struct cxl_dport *parent_dport)
{
struct cxl_port *parent_port = parent_dport->port;
struct cxl_port *endpoint, *iter, *down;
int rc;
/*
* Now that the path to the root is established record all the
* intervening ports in the chain.
*/
for (iter = parent_port, down = NULL; !is_cxl_root(iter);
down = iter, iter = to_cxl_port(iter->dev.parent)) {
struct cxl_ep *ep;
ep = cxl_ep_load(iter, cxlmd);
ep->next = down;
}
/* Note: endpoint port component registers are derived from @cxlds */
endpoint = devm_cxl_add_port(host, &cxlmd->dev, CXL_RESOURCE_NONE,
parent_dport);
if (IS_ERR(endpoint))
return PTR_ERR(endpoint);
rc = cxl_endpoint_autoremove(cxlmd, endpoint);
if (rc)
return rc;
if (!endpoint->dev.driver) {
dev_err(&cxlmd->dev, "%s failed probe\n",
dev_name(&endpoint->dev));
return -ENXIO;
}
return 0;
}
EXPORT_SYMBOL_FOR_MODULES(devm_cxl_add_endpoint, "cxl_mem");
static int __init cxl_port_init(void)
{
return cxl_driver_register(&cxl_port_driver);
}
/*
* Be ready to immediately enable ports emitted by the platform CXL root
* (e.g. cxl_acpi) when CONFIG_CXL_PORT=y.
*/
subsys_initcall(cxl_port_init);
static void __exit cxl_port_exit(void)
{
cxl_driver_unregister(&cxl_port_driver);
}
module_exit(cxl_port_exit);
MODULE_DESCRIPTION("CXL: Port enumeration and services");
MODULE_LICENSE("GPL v2");
MODULE_IMPORT_NS("CXL");
MODULE_ALIAS_CXL(CXL_DEVICE_PORT);
+206
View File
@@ -0,0 +1,206 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright(c) 2022 Intel Corporation. All rights reserved. */
#include <linux/libnvdimm.h>
#include <linux/unaligned.h>
#include <linux/module.h>
#include <linux/async.h>
#include <linux/slab.h>
#include <linux/memregion.h>
#include "cxlmem.h"
#include "cxl.h"
static unsigned long cxl_pmem_get_security_flags(struct nvdimm *nvdimm,
enum nvdimm_passphrase_type ptype)
{
struct cxl_nvdimm *cxl_nvd = nvdimm_provider_data(nvdimm);
struct cxl_memdev *cxlmd = cxl_nvd->cxlmd;
struct cxl_mailbox *cxl_mbox = &cxlmd->cxlds->cxl_mbox;
struct cxl_memdev_state *mds = to_cxl_memdev_state(cxlmd->cxlds);
unsigned long security_flags = 0;
struct cxl_get_security_output {
__le32 flags;
} out;
struct cxl_mbox_cmd mbox_cmd;
u32 sec_out;
int rc;
mbox_cmd = (struct cxl_mbox_cmd) {
.opcode = CXL_MBOX_OP_GET_SECURITY_STATE,
.size_out = sizeof(out),
.payload_out = &out,
};
rc = cxl_internal_send_cmd(cxl_mbox, &mbox_cmd);
if (rc < 0)
return 0;
sec_out = le32_to_cpu(out.flags);
/* cache security state */
mds->security.state = sec_out;
if (ptype == NVDIMM_MASTER) {
if (sec_out & CXL_PMEM_SEC_STATE_MASTER_PASS_SET)
set_bit(NVDIMM_SECURITY_UNLOCKED, &security_flags);
else
set_bit(NVDIMM_SECURITY_DISABLED, &security_flags);
if (sec_out & CXL_PMEM_SEC_STATE_MASTER_PLIMIT)
set_bit(NVDIMM_SECURITY_FROZEN, &security_flags);
return security_flags;
}
if (sec_out & CXL_PMEM_SEC_STATE_USER_PASS_SET) {
if (sec_out & CXL_PMEM_SEC_STATE_FROZEN ||
sec_out & CXL_PMEM_SEC_STATE_USER_PLIMIT)
set_bit(NVDIMM_SECURITY_FROZEN, &security_flags);
if (sec_out & CXL_PMEM_SEC_STATE_LOCKED)
set_bit(NVDIMM_SECURITY_LOCKED, &security_flags);
else
set_bit(NVDIMM_SECURITY_UNLOCKED, &security_flags);
} else {
set_bit(NVDIMM_SECURITY_DISABLED, &security_flags);
}
return security_flags;
}
static int cxl_pmem_security_change_key(struct nvdimm *nvdimm,
const struct nvdimm_key_data *old_data,
const struct nvdimm_key_data *new_data,
enum nvdimm_passphrase_type ptype)
{
struct cxl_nvdimm *cxl_nvd = nvdimm_provider_data(nvdimm);
struct cxl_memdev *cxlmd = cxl_nvd->cxlmd;
struct cxl_mailbox *cxl_mbox = &cxlmd->cxlds->cxl_mbox;
struct cxl_mbox_cmd mbox_cmd;
struct cxl_set_pass set_pass;
set_pass = (struct cxl_set_pass) {
.type = ptype == NVDIMM_MASTER ? CXL_PMEM_SEC_PASS_MASTER :
CXL_PMEM_SEC_PASS_USER,
};
memcpy(set_pass.old_pass, old_data->data, NVDIMM_PASSPHRASE_LEN);
memcpy(set_pass.new_pass, new_data->data, NVDIMM_PASSPHRASE_LEN);
mbox_cmd = (struct cxl_mbox_cmd) {
.opcode = CXL_MBOX_OP_SET_PASSPHRASE,
.size_in = sizeof(set_pass),
.payload_in = &set_pass,
};
return cxl_internal_send_cmd(cxl_mbox, &mbox_cmd);
}
static int __cxl_pmem_security_disable(struct nvdimm *nvdimm,
const struct nvdimm_key_data *key_data,
enum nvdimm_passphrase_type ptype)
{
struct cxl_nvdimm *cxl_nvd = nvdimm_provider_data(nvdimm);
struct cxl_memdev *cxlmd = cxl_nvd->cxlmd;
struct cxl_mailbox *cxl_mbox = &cxlmd->cxlds->cxl_mbox;
struct cxl_disable_pass dis_pass;
struct cxl_mbox_cmd mbox_cmd;
dis_pass = (struct cxl_disable_pass) {
.type = ptype == NVDIMM_MASTER ? CXL_PMEM_SEC_PASS_MASTER :
CXL_PMEM_SEC_PASS_USER,
};
memcpy(dis_pass.pass, key_data->data, NVDIMM_PASSPHRASE_LEN);
mbox_cmd = (struct cxl_mbox_cmd) {
.opcode = CXL_MBOX_OP_DISABLE_PASSPHRASE,
.size_in = sizeof(dis_pass),
.payload_in = &dis_pass,
};
return cxl_internal_send_cmd(cxl_mbox, &mbox_cmd);
}
static int cxl_pmem_security_disable(struct nvdimm *nvdimm,
const struct nvdimm_key_data *key_data)
{
return __cxl_pmem_security_disable(nvdimm, key_data, NVDIMM_USER);
}
static int cxl_pmem_security_disable_master(struct nvdimm *nvdimm,
const struct nvdimm_key_data *key_data)
{
return __cxl_pmem_security_disable(nvdimm, key_data, NVDIMM_MASTER);
}
static int cxl_pmem_security_freeze(struct nvdimm *nvdimm)
{
struct cxl_nvdimm *cxl_nvd = nvdimm_provider_data(nvdimm);
struct cxl_memdev *cxlmd = cxl_nvd->cxlmd;
struct cxl_mailbox *cxl_mbox = &cxlmd->cxlds->cxl_mbox;
struct cxl_mbox_cmd mbox_cmd = {
.opcode = CXL_MBOX_OP_FREEZE_SECURITY,
};
return cxl_internal_send_cmd(cxl_mbox, &mbox_cmd);
}
static int cxl_pmem_security_unlock(struct nvdimm *nvdimm,
const struct nvdimm_key_data *key_data)
{
struct cxl_nvdimm *cxl_nvd = nvdimm_provider_data(nvdimm);
struct cxl_memdev *cxlmd = cxl_nvd->cxlmd;
struct cxl_mailbox *cxl_mbox = &cxlmd->cxlds->cxl_mbox;
u8 pass[NVDIMM_PASSPHRASE_LEN];
struct cxl_mbox_cmd mbox_cmd;
int rc;
memcpy(pass, key_data->data, NVDIMM_PASSPHRASE_LEN);
mbox_cmd = (struct cxl_mbox_cmd) {
.opcode = CXL_MBOX_OP_UNLOCK,
.size_in = NVDIMM_PASSPHRASE_LEN,
.payload_in = pass,
};
rc = cxl_internal_send_cmd(cxl_mbox, &mbox_cmd);
if (rc < 0)
return rc;
return 0;
}
static int cxl_pmem_security_passphrase_erase(struct nvdimm *nvdimm,
const struct nvdimm_key_data *key,
enum nvdimm_passphrase_type ptype)
{
struct cxl_nvdimm *cxl_nvd = nvdimm_provider_data(nvdimm);
struct cxl_memdev *cxlmd = cxl_nvd->cxlmd;
struct cxl_mailbox *cxl_mbox = &cxlmd->cxlds->cxl_mbox;
struct cxl_mbox_cmd mbox_cmd;
struct cxl_pass_erase erase;
int rc;
erase = (struct cxl_pass_erase) {
.type = ptype == NVDIMM_MASTER ? CXL_PMEM_SEC_PASS_MASTER :
CXL_PMEM_SEC_PASS_USER,
};
memcpy(erase.pass, key->data, NVDIMM_PASSPHRASE_LEN);
mbox_cmd = (struct cxl_mbox_cmd) {
.opcode = CXL_MBOX_OP_PASSPHRASE_SECURE_ERASE,
.size_in = sizeof(erase),
.payload_in = &erase,
};
rc = cxl_internal_send_cmd(cxl_mbox, &mbox_cmd);
if (rc < 0)
return rc;
return 0;
}
static const struct nvdimm_security_ops __cxl_security_ops = {
.get_flags = cxl_pmem_get_security_flags,
.change_key = cxl_pmem_security_change_key,
.disable = cxl_pmem_security_disable,
.freeze = cxl_pmem_security_freeze,
.unlock = cxl_pmem_security_unlock,
.erase = cxl_pmem_security_passphrase_erase,
.disable_master = cxl_pmem_security_disable_master,
};
const struct nvdimm_security_ops *cxl_security_ops = &__cxl_security_ops;