2025-03-10 22:38:38 +00:00
|
|
|
// 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 "trace.h"
|
|
|
|
|
2025-03-10 22:38:39 +00:00
|
|
|
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 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;
|
|
|
|
|
|
|
|
trace_cxl_port_aer_uncorrectable_error(&pdev->dev, status, fe,
|
|
|
|
ras_cap.header_log);
|
|
|
|
}
|
|
|
|
|
2025-06-12 12:20:43 -07:00
|
|
|
static void cxl_cper_trace_corr_prot_err(struct cxl_memdev *cxlmd,
|
|
|
|
struct cxl_ras_capability_regs ras_cap)
|
2025-03-10 22:38:38 +00:00
|
|
|
{
|
|
|
|
u32 status = ras_cap.cor_status & ~ras_cap.cor_mask;
|
|
|
|
|
2025-06-12 12:20:43 -07:00
|
|
|
trace_cxl_aer_correctable_error(cxlmd, status);
|
2025-03-10 22:38:38 +00:00
|
|
|
}
|
|
|
|
|
2025-06-12 12:20:43 -07:00
|
|
|
static void
|
|
|
|
cxl_cper_trace_uncorr_prot_err(struct cxl_memdev *cxlmd,
|
|
|
|
struct cxl_ras_capability_regs ras_cap)
|
2025-03-10 22:38:38 +00:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
|
2025-06-12 12:20:43 -07:00
|
|
|
trace_cxl_aer_uncorrectable_error(cxlmd, status, fe,
|
2025-03-10 22:38:38 +00:00
|
|
|
ras_cap.header_log);
|
|
|
|
}
|
|
|
|
|
2025-06-12 12:20:43 -07:00
|
|
|
static int match_memdev_by_parent(struct device *dev, const void *uport)
|
|
|
|
{
|
|
|
|
if (is_cxl_memdev(dev) && dev->parent == uport)
|
|
|
|
return 1;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2025-03-10 22:38:38 +00:00
|
|
|
static 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);
|
2025-06-12 12:20:43 -07:00
|
|
|
struct cxl_memdev *cxlmd;
|
2025-03-10 22:38:39 +00:00
|
|
|
int port_type;
|
2025-03-10 22:38:38 +00:00
|
|
|
|
|
|
|
if (!pdev)
|
|
|
|
return;
|
|
|
|
|
2025-03-10 22:38:39 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2025-06-12 12:20:43 -07:00
|
|
|
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);
|
2025-03-10 22:38:38 +00:00
|
|
|
if (data->severity == AER_CORRECTABLE)
|
2025-06-12 12:20:43 -07:00
|
|
|
cxl_cper_trace_corr_prot_err(cxlmd, data->ras_cap);
|
2025-03-10 22:38:38 +00:00
|
|
|
else
|
2025-06-12 12:20:43 -07:00
|
|
|
cxl_cper_trace_uncorr_prot_err(cxlmd, data->ras_cap);
|
2025-03-10 22:38:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|