1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
|
// 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,
PCIE_AER_CAP_HW_SIZE);
}
}
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)
{
/* A flat copy cannot fill the struct; aer_cap_regs_unpack() places it. */
__le32 raw[PCIE_AER_CAP_HW_SIZE / sizeof(__le32)];
if (!aer_base)
return false;
/*
* Use readl() to guarantee 32-bit accesses; it returns host order, so
* put the registers back little-endian for aer_cap_regs_unpack().
*/
for (int n = 0; n < ARRAY_SIZE(raw); n++)
raw[n] = cpu_to_le32(readl(aer_base + n * sizeof(u32)));
aer_cap_regs_unpack(aer_regs, raw, sizeof(raw));
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)
{
u32 uncor_status = aer_regs->uncor_status & ~aer_regs->uncor_mask;
if (uncor_status) {
*severity = (uncor_status & aer_regs->uncor_severity) ?
AER_FATAL : 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);
if (!port)
return;
/*
* The dport may have been freed since the lookup above, so take the
* lock that pins it and find it again.
*/
guard(device)(cxl_port_dport_host(port));
dport = cxl_pci_find_dport(pdev, port);
if (!dport)
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);
}
|