]>
Commit | Line | Data |
---|---|---|
e1e86ee0 | 1 | // SPDX-License-Identifier: GPL-2.0 |
26e51571 KB |
2 | /* |
3 | * PCI Express Downstream Port Containment services driver | |
61612e6d PG |
4 | * Author: Keith Busch <keith.busch@intel.com> |
5 | * | |
26e51571 | 6 | * Copyright (C) 2016 Intel Corp. |
26e51571 KB |
7 | */ |
8 | ||
9 | #include <linux/delay.h> | |
10 | #include <linux/interrupt.h> | |
61612e6d | 11 | #include <linux/init.h> |
26e51571 | 12 | #include <linux/pci.h> |
c37e627f | 13 | |
ef794260 | 14 | #include "portdrv.h" |
89ee9f76 | 15 | #include "../pci.h" |
26e51571 KB |
16 | |
17 | struct dpc_dev { | |
18 | struct pcie_device *dev; | |
14a16d57 | 19 | struct work_struct work; |
aa745eff | 20 | u16 cap_pos; |
be3039a3 | 21 | bool rp_extensions; |
f20c4ea4 | 22 | u32 rp_pio_status; |
e3c44b8d | 23 | u8 rp_log_size; |
f20c4ea4 DL |
24 | }; |
25 | ||
26 | static const char * const rp_pio_error_string[] = { | |
27 | "Configuration Request received UR Completion", /* Bit Position 0 */ | |
28 | "Configuration Request received CA Completion", /* Bit Position 1 */ | |
29 | "Configuration Request Completion Timeout", /* Bit Position 2 */ | |
30 | NULL, | |
31 | NULL, | |
32 | NULL, | |
33 | NULL, | |
34 | NULL, | |
35 | "I/O Request received UR Completion", /* Bit Position 8 */ | |
36 | "I/O Request received CA Completion", /* Bit Position 9 */ | |
37 | "I/O Request Completion Timeout", /* Bit Position 10 */ | |
38 | NULL, | |
39 | NULL, | |
40 | NULL, | |
41 | NULL, | |
42 | NULL, | |
43 | "Memory Request received UR Completion", /* Bit Position 16 */ | |
44 | "Memory Request received CA Completion", /* Bit Position 17 */ | |
45 | "Memory Request Completion Timeout", /* Bit Position 18 */ | |
26e51571 KB |
46 | }; |
47 | ||
abdbf4d6 KB |
48 | static int dpc_wait_rp_inactive(struct dpc_dev *dpc) |
49 | { | |
50 | unsigned long timeout = jiffies + HZ; | |
51 | struct pci_dev *pdev = dpc->dev->port; | |
9e16b8d6 | 52 | struct device *dev = &dpc->dev->device; |
aa745eff | 53 | u16 cap = dpc->cap_pos, status; |
abdbf4d6 | 54 | |
aa745eff | 55 | pci_read_config_word(pdev, cap + PCI_EXP_DPC_STATUS, &status); |
abdbf4d6 KB |
56 | while (status & PCI_EXP_DPC_RP_BUSY && |
57 | !time_after(jiffies, timeout)) { | |
58 | msleep(10); | |
aa745eff | 59 | pci_read_config_word(pdev, cap + PCI_EXP_DPC_STATUS, &status); |
abdbf4d6 KB |
60 | } |
61 | if (status & PCI_EXP_DPC_RP_BUSY) { | |
9e16b8d6 | 62 | dev_warn(dev, "DPC root port still busy\n"); |
abdbf4d6 KB |
63 | return -EBUSY; |
64 | } | |
65 | return 0; | |
66 | } | |
67 | ||
9e16b8d6 | 68 | static void dpc_wait_link_inactive(struct dpc_dev *dpc) |
26e51571 | 69 | { |
9e16b8d6 | 70 | struct pci_dev *pdev = dpc->dev->port; |
26e51571 | 71 | |
9f5a70f1 | 72 | pcie_wait_for_link(pdev, false); |
26e51571 KB |
73 | } |
74 | ||
b09803b5 | 75 | static pci_ers_result_t dpc_reset_link(struct pci_dev *pdev) |
26e51571 | 76 | { |
b09803b5 OP |
77 | struct dpc_dev *dpc; |
78 | struct pcie_device *pciedev; | |
79 | struct device *devdpc; | |
f8d46c89 | 80 | u16 cap; |
b09803b5 OP |
81 | |
82 | /* | |
83 | * DPC disables the Link automatically in hardware, so it has | |
84 | * already been reset by the time we get here. | |
85 | */ | |
86 | devdpc = pcie_port_find_device(pdev, PCIE_PORT_SERVICE_DPC); | |
87 | pciedev = to_pcie_device(devdpc); | |
88 | dpc = get_service_data(pciedev); | |
89 | cap = dpc->cap_pos; | |
90 | ||
91 | /* | |
92 | * Wait until the Link is inactive, then clear DPC Trigger Status | |
93 | * to allow the Port to leave DPC. | |
94 | */ | |
9e16b8d6 | 95 | dpc_wait_link_inactive(dpc); |
b09803b5 | 96 | |
be3039a3 | 97 | if (dpc->rp_extensions && dpc_wait_rp_inactive(dpc)) |
b09803b5 | 98 | return PCI_ERS_RESULT_DISCONNECT; |
be3039a3 | 99 | if (dpc->rp_extensions && dpc->rp_pio_status) { |
aa745eff BH |
100 | pci_write_config_dword(pdev, cap + PCI_EXP_DPC_RP_PIO_STATUS, |
101 | dpc->rp_pio_status); | |
f20c4ea4 DL |
102 | dpc->rp_pio_status = 0; |
103 | } | |
104 | ||
aa745eff | 105 | pci_write_config_word(pdev, cap + PCI_EXP_DPC_STATUS, |
56abbf8a | 106 | PCI_EXP_DPC_STATUS_TRIGGER); |
aa6ca5a9 | 107 | |
b09803b5 OP |
108 | return PCI_ERS_RESULT_RECOVERED; |
109 | } | |
110 | ||
111 | static void dpc_work(struct work_struct *work) | |
112 | { | |
113 | struct dpc_dev *dpc = container_of(work, struct dpc_dev, work); | |
114 | struct pci_dev *pdev = dpc->dev->port; | |
115 | ||
116 | /* We configure DPC so it only triggers on ERR_FATAL */ | |
117 | pcie_do_fatal_recovery(pdev, PCIE_PORT_SERVICE_DPC); | |
26e51571 KB |
118 | } |
119 | ||
716f0f73 | 120 | static void dpc_process_rp_pio_error(struct dpc_dev *dpc) |
f20c4ea4 | 121 | { |
a88b304e | 122 | struct device *dev = &dpc->dev->device; |
f20c4ea4 | 123 | struct pci_dev *pdev = dpc->dev->port; |
f784c41f BH |
124 | u16 cap = dpc->cap_pos, dpc_status, first_error; |
125 | u32 status, mask, sev, syserr, exc, dw0, dw1, dw2, dw3, log, prefix; | |
f20c4ea4 | 126 | int i; |
f20c4ea4 | 127 | |
f784c41f BH |
128 | pci_read_config_dword(pdev, cap + PCI_EXP_DPC_RP_PIO_STATUS, &status); |
129 | pci_read_config_dword(pdev, cap + PCI_EXP_DPC_RP_PIO_MASK, &mask); | |
a88b304e | 130 | dev_err(dev, "rp_pio_status: %#010x, rp_pio_mask: %#010x\n", |
f784c41f | 131 | status, mask); |
f20c4ea4 | 132 | |
f784c41f | 133 | dpc->rp_pio_status = status; |
f5ec5a07 | 134 | |
f784c41f BH |
135 | pci_read_config_dword(pdev, cap + PCI_EXP_DPC_RP_PIO_SEVERITY, &sev); |
136 | pci_read_config_dword(pdev, cap + PCI_EXP_DPC_RP_PIO_SYSERROR, &syserr); | |
137 | pci_read_config_dword(pdev, cap + PCI_EXP_DPC_RP_PIO_EXCEPTION, &exc); | |
a88b304e | 138 | dev_err(dev, "RP PIO severity=%#010x, syserror=%#010x, exception=%#010x\n", |
f784c41f | 139 | sev, syserr, exc); |
f20c4ea4 DL |
140 | |
141 | /* Get First Error Pointer */ | |
a596a7be | 142 | pci_read_config_word(pdev, cap + PCI_EXP_DPC_STATUS, &dpc_status); |
f784c41f | 143 | first_error = (dpc_status & 0x1f00) >> 8; |
f20c4ea4 | 144 | |
f784c41f | 145 | status &= ~mask; |
a88b304e BH |
146 | for (i = 0; i < ARRAY_SIZE(rp_pio_error_string); i++) { |
147 | if (status & (1 << i)) | |
148 | dev_err(dev, "[%2d] %s%s\n", i, rp_pio_error_string[i], | |
f784c41f | 149 | first_error == i ? " (First)" : ""); |
a88b304e BH |
150 | } |
151 | ||
e3c44b8d | 152 | if (dpc->rp_log_size < 4) |
f20c4ea4 | 153 | return; |
aa745eff | 154 | pci_read_config_dword(pdev, cap + PCI_EXP_DPC_RP_PIO_HEADER_LOG, |
f784c41f | 155 | &dw0); |
aa745eff | 156 | pci_read_config_dword(pdev, cap + PCI_EXP_DPC_RP_PIO_HEADER_LOG + 4, |
f784c41f | 157 | &dw1); |
aa745eff | 158 | pci_read_config_dword(pdev, cap + PCI_EXP_DPC_RP_PIO_HEADER_LOG + 8, |
f784c41f | 159 | &dw2); |
aa745eff | 160 | pci_read_config_dword(pdev, cap + PCI_EXP_DPC_RP_PIO_HEADER_LOG + 12, |
f784c41f | 161 | &dw3); |
a88b304e | 162 | dev_err(dev, "TLP Header: %#010x %#010x %#010x %#010x\n", |
f784c41f | 163 | dw0, dw1, dw2, dw3); |
f20c4ea4 | 164 | |
64c3394e BH |
165 | if (dpc->rp_log_size < 5) |
166 | return; | |
f784c41f BH |
167 | pci_read_config_dword(pdev, cap + PCI_EXP_DPC_RP_PIO_IMPSPEC_LOG, &log); |
168 | dev_err(dev, "RP PIO ImpSpec Log %#010x\n", log); | |
64c3394e | 169 | |
a88b304e | 170 | for (i = 0; i < dpc->rp_log_size - 5; i++) { |
f20c4ea4 | 171 | pci_read_config_dword(pdev, |
f784c41f BH |
172 | cap + PCI_EXP_DPC_RP_PIO_TLPPREFIX_LOG, &prefix); |
173 | dev_err(dev, "TLP Prefix Header: dw%d, %#010x\n", i, prefix); | |
a88b304e | 174 | } |
f20c4ea4 DL |
175 | } |
176 | ||
26e51571 KB |
177 | static irqreturn_t dpc_irq(int irq, void *context) |
178 | { | |
179 | struct dpc_dev *dpc = (struct dpc_dev *)context; | |
180 | struct pci_dev *pdev = dpc->dev->port; | |
9e16b8d6 | 181 | struct device *dev = &dpc->dev->device; |
f8d46c89 | 182 | u16 cap = dpc->cap_pos, status, source, reason, ext_reason; |
26e51571 | 183 | |
aa745eff | 184 | pci_read_config_word(pdev, cap + PCI_EXP_DPC_STATUS, &status); |
aa6ca5a9 | 185 | |
f8d46c89 | 186 | if (!(status & PCI_EXP_DPC_STATUS_INTERRUPT) || status == (u16)(~0)) |
aa6ca5a9 AW |
187 | return IRQ_NONE; |
188 | ||
189 | if (!(status & PCI_EXP_DPC_STATUS_TRIGGER)) { | |
aa745eff | 190 | pci_write_config_word(pdev, cap + PCI_EXP_DPC_STATUS, |
aa6ca5a9 AW |
191 | PCI_EXP_DPC_STATUS_INTERRUPT); |
192 | return IRQ_HANDLED; | |
193 | } | |
194 | ||
aa745eff | 195 | pci_read_config_word(pdev, cap + PCI_EXP_DPC_SOURCE_ID, |
26e51571 | 196 | &source); |
26e51571 | 197 | |
9e16b8d6 | 198 | dev_info(dev, "DPC containment event, status:%#06x source:%#06x\n", |
26e51571 KB |
199 | status, source); |
200 | ||
01060e3d BH |
201 | reason = (status & PCI_EXP_DPC_STATUS_TRIGGER_RSN) >> 1; |
202 | ext_reason = (status & PCI_EXP_DPC_STATUS_TRIGGER_RSN_EXT) >> 5; | |
aa6ca5a9 AW |
203 | |
204 | dev_warn(dev, "DPC %s detected, remove downstream devices\n", | |
205 | (reason == 0) ? "unmasked uncorrectable error" : | |
206 | (reason == 1) ? "ERR_NONFATAL" : | |
207 | (reason == 2) ? "ERR_FATAL" : | |
208 | (ext_reason == 0) ? "RP PIO error" : | |
209 | (ext_reason == 1) ? "software trigger" : | |
210 | "reserved error"); | |
211 | /* show RP PIO error detail information */ | |
e68d281c | 212 | if (dpc->rp_extensions && reason == 3 && ext_reason == 0) |
aa6ca5a9 AW |
213 | dpc_process_rp_pio_error(dpc); |
214 | ||
56abbf8a OP |
215 | pci_write_config_word(pdev, cap + PCI_EXP_DPC_STATUS, |
216 | PCI_EXP_DPC_STATUS_INTERRUPT); | |
f8d46c89 KB |
217 | if (status & PCI_EXP_DPC_STATUS_TRIGGER) |
218 | schedule_work(&dpc->work); | |
26e51571 KB |
219 | return IRQ_HANDLED; |
220 | } | |
221 | ||
222 | #define FLAG(x, y) (((x) & (y)) ? '+' : '-') | |
223 | static int dpc_probe(struct pcie_device *dev) | |
224 | { | |
225 | struct dpc_dev *dpc; | |
226 | struct pci_dev *pdev = dev->port; | |
9e16b8d6 | 227 | struct device *device = &dev->device; |
26e51571 KB |
228 | int status; |
229 | u16 ctl, cap; | |
230 | ||
eed85ff4 KB |
231 | if (pcie_aer_get_firmware_first(pdev)) |
232 | return -ENOTSUPP; | |
233 | ||
9e16b8d6 | 234 | dpc = devm_kzalloc(device, sizeof(*dpc), GFP_KERNEL); |
26e51571 KB |
235 | if (!dpc) |
236 | return -ENOMEM; | |
237 | ||
238 | dpc->cap_pos = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_DPC); | |
239 | dpc->dev = dev; | |
b604472d | 240 | INIT_WORK(&dpc->work, dpc_work); |
26e51571 KB |
241 | set_service_data(dev, dpc); |
242 | ||
9e16b8d6 | 243 | status = devm_request_irq(device, dev->irq, dpc_irq, IRQF_SHARED, |
733f3d13 | 244 | "pcie-dpc", dpc); |
26e51571 | 245 | if (status) { |
9e16b8d6 | 246 | dev_warn(device, "request IRQ%d failed: %d\n", dev->irq, |
26e51571 | 247 | status); |
733f3d13 | 248 | return status; |
26e51571 KB |
249 | } |
250 | ||
251 | pci_read_config_word(pdev, dpc->cap_pos + PCI_EXP_DPC_CAP, &cap); | |
252 | pci_read_config_word(pdev, dpc->cap_pos + PCI_EXP_DPC_CTL, &ctl); | |
253 | ||
be3039a3 | 254 | dpc->rp_extensions = (cap & PCI_EXP_DPC_CAP_RP_EXT); |
e3c44b8d BH |
255 | if (dpc->rp_extensions) { |
256 | dpc->rp_log_size = (cap & PCI_EXP_DPC_RP_PIO_LOG_SIZE) >> 8; | |
257 | if (dpc->rp_log_size < 4 || dpc->rp_log_size > 9) { | |
258 | dev_err(device, "RP PIO log size %u is invalid\n", | |
259 | dpc->rp_log_size); | |
260 | dpc->rp_log_size = 0; | |
261 | } | |
262 | } | |
abdbf4d6 | 263 | |
6927868e | 264 | ctl = (ctl & 0xfff4) | PCI_EXP_DPC_CTL_EN_FATAL | PCI_EXP_DPC_CTL_INT_EN; |
26e51571 KB |
265 | pci_write_config_word(pdev, dpc->cap_pos + PCI_EXP_DPC_CTL, ctl); |
266 | ||
9e16b8d6 | 267 | dev_info(device, "DPC error containment capabilities: Int Msg #%d, RPExt%c PoisonedTLP%c SwTrigger%c RP PIO Log %d, DL_ActiveErr%c\n", |
6b9045b3 | 268 | cap & PCI_EXP_DPC_IRQ, FLAG(cap, PCI_EXP_DPC_CAP_RP_EXT), |
26e51571 | 269 | FLAG(cap, PCI_EXP_DPC_CAP_POISONED_TLP), |
e3c44b8d | 270 | FLAG(cap, PCI_EXP_DPC_CAP_SW_TRIGGER), dpc->rp_log_size, |
26e51571 KB |
271 | FLAG(cap, PCI_EXP_DPC_CAP_DL_ACTIVE)); |
272 | return status; | |
26e51571 KB |
273 | } |
274 | ||
275 | static void dpc_remove(struct pcie_device *dev) | |
276 | { | |
277 | struct dpc_dev *dpc = get_service_data(dev); | |
278 | struct pci_dev *pdev = dev->port; | |
279 | u16 ctl; | |
280 | ||
281 | pci_read_config_word(pdev, dpc->cap_pos + PCI_EXP_DPC_CTL, &ctl); | |
6927868e | 282 | ctl &= ~(PCI_EXP_DPC_CTL_EN_FATAL | PCI_EXP_DPC_CTL_INT_EN); |
26e51571 | 283 | pci_write_config_word(pdev, dpc->cap_pos + PCI_EXP_DPC_CTL, ctl); |
26e51571 KB |
284 | } |
285 | ||
286 | static struct pcie_port_service_driver dpcdriver = { | |
287 | .name = "dpc", | |
7e16fd6d | 288 | .port_type = PCIE_ANY_PORT, |
26e51571 KB |
289 | .service = PCIE_PORT_SERVICE_DPC, |
290 | .probe = dpc_probe, | |
291 | .remove = dpc_remove, | |
b09803b5 | 292 | .reset_link = dpc_reset_link, |
26e51571 KB |
293 | }; |
294 | ||
295 | static int __init dpc_service_init(void) | |
296 | { | |
297 | return pcie_port_service_register(&dpcdriver); | |
298 | } | |
61612e6d | 299 | device_initcall(dpc_service_init); |