]>
Commit | Line | Data |
---|---|---|
2e28bc84 OP |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * This file implements the error recovery as a core part of PCIe error | |
4 | * reporting. When a PCIe error is delivered, an error message will be | |
5 | * collected and printed to console, then, an error recovery procedure | |
6 | * will be executed by following the PCI error recovery rules. | |
7 | * | |
8 | * Copyright (C) 2006 Intel Corp. | |
9 | * Tom Long Nguyen ([email protected]) | |
10 | * Zhang Yanmin ([email protected]) | |
11 | */ | |
12 | ||
13 | #include <linux/pci.h> | |
14 | #include <linux/module.h> | |
2e28bc84 OP |
15 | #include <linux/kernel.h> |
16 | #include <linux/errno.h> | |
17 | #include <linux/aer.h> | |
18 | #include "portdrv.h" | |
19 | #include "../pci.h" | |
20 | ||
2e28bc84 OP |
21 | static pci_ers_result_t merge_result(enum pci_ers_result orig, |
22 | enum pci_ers_result new) | |
23 | { | |
24 | if (new == PCI_ERS_RESULT_NO_AER_DRIVER) | |
25 | return PCI_ERS_RESULT_NO_AER_DRIVER; | |
26 | ||
27 | if (new == PCI_ERS_RESULT_NONE) | |
28 | return orig; | |
29 | ||
30 | switch (orig) { | |
31 | case PCI_ERS_RESULT_CAN_RECOVER: | |
32 | case PCI_ERS_RESULT_RECOVERED: | |
33 | orig = new; | |
34 | break; | |
35 | case PCI_ERS_RESULT_DISCONNECT: | |
36 | if (new == PCI_ERS_RESULT_NEED_RESET) | |
37 | orig = PCI_ERS_RESULT_NEED_RESET; | |
38 | break; | |
39 | default: | |
40 | break; | |
41 | } | |
42 | ||
43 | return orig; | |
44 | } | |
45 | ||
542aeb9c KB |
46 | static int report_error_detected(struct pci_dev *dev, |
47 | enum pci_channel_state state, | |
48 | enum pci_ers_result *result) | |
2e28bc84 OP |
49 | { |
50 | pci_ers_result_t vote; | |
51 | const struct pci_error_handlers *err_handler; | |
2e28bc84 OP |
52 | |
53 | device_lock(&dev->dev); | |
a6bd101b KB |
54 | if (!pci_dev_set_io_state(dev, state) || |
55 | !dev->driver || | |
2e28bc84 OP |
56 | !dev->driver->err_handler || |
57 | !dev->driver->err_handler->error_detected) { | |
2e28bc84 | 58 | /* |
bfcb79fc KB |
59 | * If any device in the subtree does not have an error_detected |
60 | * callback, PCI_ERS_RESULT_NO_AER_DRIVER prevents subsequent | |
61 | * error callbacks of "any" device in the subtree, and will | |
62 | * exit in the disconnected error state. | |
2e28bc84 | 63 | */ |
2e28bc84 OP |
64 | if (dev->hdr_type != PCI_HEADER_TYPE_BRIDGE) |
65 | vote = PCI_ERS_RESULT_NO_AER_DRIVER; | |
66 | else | |
67 | vote = PCI_ERS_RESULT_NONE; | |
68 | } else { | |
69 | err_handler = dev->driver->err_handler; | |
542aeb9c | 70 | vote = err_handler->error_detected(dev, state); |
2e28bc84 | 71 | } |
7b42d97e | 72 | pci_uevent_ers(dev, vote); |
542aeb9c | 73 | *result = merge_result(*result, vote); |
2e28bc84 OP |
74 | device_unlock(&dev->dev); |
75 | return 0; | |
76 | } | |
77 | ||
542aeb9c KB |
78 | static int report_frozen_detected(struct pci_dev *dev, void *data) |
79 | { | |
80 | return report_error_detected(dev, pci_channel_io_frozen, data); | |
81 | } | |
82 | ||
83 | static int report_normal_detected(struct pci_dev *dev, void *data) | |
84 | { | |
85 | return report_error_detected(dev, pci_channel_io_normal, data); | |
86 | } | |
87 | ||
2e28bc84 OP |
88 | static int report_mmio_enabled(struct pci_dev *dev, void *data) |
89 | { | |
542aeb9c | 90 | pci_ers_result_t vote, *result = data; |
2e28bc84 | 91 | const struct pci_error_handlers *err_handler; |
2e28bc84 OP |
92 | |
93 | device_lock(&dev->dev); | |
94 | if (!dev->driver || | |
95 | !dev->driver->err_handler || | |
96 | !dev->driver->err_handler->mmio_enabled) | |
97 | goto out; | |
98 | ||
99 | err_handler = dev->driver->err_handler; | |
100 | vote = err_handler->mmio_enabled(dev); | |
542aeb9c | 101 | *result = merge_result(*result, vote); |
2e28bc84 OP |
102 | out: |
103 | device_unlock(&dev->dev); | |
104 | return 0; | |
105 | } | |
106 | ||
107 | static int report_slot_reset(struct pci_dev *dev, void *data) | |
108 | { | |
542aeb9c | 109 | pci_ers_result_t vote, *result = data; |
2e28bc84 | 110 | const struct pci_error_handlers *err_handler; |
2e28bc84 OP |
111 | |
112 | device_lock(&dev->dev); | |
113 | if (!dev->driver || | |
114 | !dev->driver->err_handler || | |
115 | !dev->driver->err_handler->slot_reset) | |
116 | goto out; | |
117 | ||
118 | err_handler = dev->driver->err_handler; | |
119 | vote = err_handler->slot_reset(dev); | |
542aeb9c | 120 | *result = merge_result(*result, vote); |
2e28bc84 OP |
121 | out: |
122 | device_unlock(&dev->dev); | |
123 | return 0; | |
124 | } | |
125 | ||
126 | static int report_resume(struct pci_dev *dev, void *data) | |
127 | { | |
128 | const struct pci_error_handlers *err_handler; | |
129 | ||
130 | device_lock(&dev->dev); | |
a6bd101b KB |
131 | if (!pci_dev_set_io_state(dev, pci_channel_io_normal) || |
132 | !dev->driver || | |
2e28bc84 OP |
133 | !dev->driver->err_handler || |
134 | !dev->driver->err_handler->resume) | |
135 | goto out; | |
136 | ||
137 | err_handler = dev->driver->err_handler; | |
138 | err_handler->resume(dev); | |
2e28bc84 | 139 | out: |
7b42d97e | 140 | pci_uevent_ers(dev, PCI_ERS_RESULT_RECOVERED); |
2e28bc84 OP |
141 | device_unlock(&dev->dev); |
142 | return 0; | |
143 | } | |
144 | ||
145 | /** | |
146 | * default_reset_link - default reset function | |
147 | * @dev: pointer to pci_dev data structure | |
148 | * | |
149 | * Invoked when performing link reset on a Downstream Port or a | |
150 | * Root Port with no aer driver. | |
151 | */ | |
152 | static pci_ers_result_t default_reset_link(struct pci_dev *dev) | |
153 | { | |
18426238 SK |
154 | int rc; |
155 | ||
c4eed62a | 156 | rc = pci_bus_error_reset(dev); |
2e28bc84 | 157 | pci_printk(KERN_DEBUG, dev, "downstream link has been reset\n"); |
18426238 | 158 | return rc ? PCI_ERS_RESULT_DISCONNECT : PCI_ERS_RESULT_RECOVERED; |
2e28bc84 OP |
159 | } |
160 | ||
0b91439d | 161 | static pci_ers_result_t reset_link(struct pci_dev *dev, u32 service) |
2e28bc84 | 162 | { |
2e28bc84 OP |
163 | pci_ers_result_t status; |
164 | struct pcie_port_service_driver *driver = NULL; | |
165 | ||
bfcb79fc | 166 | driver = pcie_port_find_service(dev, service); |
2e28bc84 | 167 | if (driver && driver->reset_link) { |
bfcb79fc KB |
168 | status = driver->reset_link(dev); |
169 | } else if (dev->has_secondary_link) { | |
170 | status = default_reset_link(dev); | |
2e28bc84 OP |
171 | } else { |
172 | pci_printk(KERN_DEBUG, dev, "no link-reset support at upstream device %s\n", | |
bfcb79fc | 173 | pci_name(dev)); |
2e28bc84 OP |
174 | return PCI_ERS_RESULT_DISCONNECT; |
175 | } | |
176 | ||
177 | if (status != PCI_ERS_RESULT_RECOVERED) { | |
178 | pci_printk(KERN_DEBUG, dev, "link reset at upstream device %s failed\n", | |
bfcb79fc | 179 | pci_name(dev)); |
2e28bc84 OP |
180 | return PCI_ERS_RESULT_DISCONNECT; |
181 | } | |
182 | ||
183 | return status; | |
184 | } | |
185 | ||
bdb5ac85 KB |
186 | void pcie_do_recovery(struct pci_dev *dev, enum pci_channel_state state, |
187 | u32 service) | |
2e28bc84 | 188 | { |
542aeb9c KB |
189 | pci_ers_result_t status = PCI_ERS_RESULT_CAN_RECOVER; |
190 | struct pci_bus *bus; | |
2e28bc84 | 191 | |
bfcb79fc KB |
192 | /* |
193 | * Error recovery runs on all subordinates of the first downstream port. | |
194 | * If the downstream port detected the error, it is cleared at the end. | |
195 | */ | |
196 | if (!(pci_pcie_type(dev) == PCI_EXP_TYPE_ROOT_PORT || | |
197 | pci_pcie_type(dev) == PCI_EXP_TYPE_DOWNSTREAM)) | |
198 | dev = dev->bus->self; | |
542aeb9c | 199 | bus = dev->subordinate; |
bfcb79fc | 200 | |
542aeb9c KB |
201 | pci_dbg(dev, "broadcast error_detected message\n"); |
202 | if (state == pci_channel_io_frozen) | |
203 | pci_walk_bus(bus, report_frozen_detected, &status); | |
204 | else | |
205 | pci_walk_bus(bus, report_normal_detected, &status); | |
2e28bc84 | 206 | |
bdb5ac85 KB |
207 | if (state == pci_channel_io_frozen && |
208 | reset_link(dev, service) != PCI_ERS_RESULT_RECOVERED) | |
209 | goto failed; | |
210 | ||
542aeb9c KB |
211 | if (status == PCI_ERS_RESULT_CAN_RECOVER) { |
212 | status = PCI_ERS_RESULT_RECOVERED; | |
213 | pci_dbg(dev, "broadcast mmio_enabled message\n"); | |
214 | pci_walk_bus(bus, report_mmio_enabled, &status); | |
215 | } | |
2e28bc84 OP |
216 | |
217 | if (status == PCI_ERS_RESULT_NEED_RESET) { | |
218 | /* | |
219 | * TODO: Should call platform-specific | |
220 | * functions to reset slot before calling | |
221 | * drivers' slot_reset callbacks? | |
222 | */ | |
542aeb9c KB |
223 | status = PCI_ERS_RESULT_RECOVERED; |
224 | pci_dbg(dev, "broadcast slot_reset message\n"); | |
225 | pci_walk_bus(bus, report_slot_reset, &status); | |
2e28bc84 OP |
226 | } |
227 | ||
228 | if (status != PCI_ERS_RESULT_RECOVERED) | |
229 | goto failed; | |
230 | ||
542aeb9c KB |
231 | pci_dbg(dev, "broadcast resume message\n"); |
232 | pci_walk_bus(bus, report_resume, &status); | |
2e28bc84 | 233 | |
bfcb79fc KB |
234 | pci_aer_clear_device_status(dev); |
235 | pci_cleanup_aer_uncorrect_error_status(dev); | |
2e28bc84 OP |
236 | pci_info(dev, "AER: Device recovery successful\n"); |
237 | return; | |
238 | ||
239 | failed: | |
240 | pci_uevent_ers(dev, PCI_ERS_RESULT_DISCONNECT); | |
241 | ||
242 | /* TODO: Should kernel panic here? */ | |
243 | pci_info(dev, "AER: Device recovery failed\n"); | |
244 | } |