]>
Commit | Line | Data |
---|---|---|
6c2b374d ZY |
1 | /* |
2 | * drivers/pci/pcie/aer/aerdrv_core.c | |
3 | * | |
4 | * This file is subject to the terms and conditions of the GNU General Public | |
5 | * License. See the file "COPYING" in the main directory of this archive | |
6 | * for more details. | |
7 | * | |
8 | * This file implements the core part of PCI-Express AER. When an pci-express | |
9 | * error is delivered, an error message will be collected and printed to | |
10 | * console, then, an error recovery procedure will be executed by following | |
11 | * the pci error recovery rules. | |
12 | * | |
13 | * Copyright (C) 2006 Intel Corp. | |
14 | * Tom Long Nguyen (tom.l.nguyen@intel.com) | |
15 | * Zhang Yanmin (yanmin.zhang@intel.com) | |
16 | * | |
17 | */ | |
18 | ||
19 | #include <linux/module.h> | |
20 | #include <linux/pci.h> | |
21 | #include <linux/kernel.h> | |
22 | #include <linux/errno.h> | |
23 | #include <linux/pm.h> | |
24 | #include <linux/suspend.h> | |
6c2b374d | 25 | #include <linux/delay.h> |
5a0e3ad6 | 26 | #include <linux/slab.h> |
6c2b374d ZY |
27 | #include "aerdrv.h" |
28 | ||
29 | static int forceload; | |
28eb27cf | 30 | static int nosourceid; |
6c2b374d | 31 | module_param(forceload, bool, 0); |
28eb27cf | 32 | module_param(nosourceid, bool, 0); |
6c2b374d | 33 | |
6c2b374d ZY |
34 | int pci_enable_pcie_error_reporting(struct pci_dev *dev) |
35 | { | |
36 | u16 reg16 = 0; | |
37 | int pos; | |
38 | ||
affb72c3 | 39 | if (pcie_aer_get_firmware_first(dev)) |
05843961 MD |
40 | return -EIO; |
41 | ||
270c66be | 42 | pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ERR); |
6c2b374d ZY |
43 | if (!pos) |
44 | return -EIO; | |
45 | ||
39a53062 | 46 | pos = pci_pcie_cap(dev); |
0927678f JB |
47 | if (!pos) |
48 | return -EIO; | |
49 | ||
caa5afbd HS |
50 | pci_read_config_word(dev, pos + PCI_EXP_DEVCTL, ®16); |
51 | reg16 |= (PCI_EXP_DEVCTL_CERE | | |
6c2b374d ZY |
52 | PCI_EXP_DEVCTL_NFERE | |
53 | PCI_EXP_DEVCTL_FERE | | |
caa5afbd HS |
54 | PCI_EXP_DEVCTL_URRE); |
55 | pci_write_config_word(dev, pos + PCI_EXP_DEVCTL, reg16); | |
c9a91883 | 56 | |
6c2b374d ZY |
57 | return 0; |
58 | } | |
c9a91883 | 59 | EXPORT_SYMBOL_GPL(pci_enable_pcie_error_reporting); |
6c2b374d ZY |
60 | |
61 | int pci_disable_pcie_error_reporting(struct pci_dev *dev) | |
62 | { | |
63 | u16 reg16 = 0; | |
64 | int pos; | |
65 | ||
affb72c3 | 66 | if (pcie_aer_get_firmware_first(dev)) |
05843961 MD |
67 | return -EIO; |
68 | ||
39a53062 | 69 | pos = pci_pcie_cap(dev); |
6c2b374d ZY |
70 | if (!pos) |
71 | return -EIO; | |
72 | ||
caa5afbd HS |
73 | pci_read_config_word(dev, pos + PCI_EXP_DEVCTL, ®16); |
74 | reg16 &= ~(PCI_EXP_DEVCTL_CERE | | |
75 | PCI_EXP_DEVCTL_NFERE | | |
76 | PCI_EXP_DEVCTL_FERE | | |
77 | PCI_EXP_DEVCTL_URRE); | |
78 | pci_write_config_word(dev, pos + PCI_EXP_DEVCTL, reg16); | |
c9a91883 | 79 | |
6c2b374d ZY |
80 | return 0; |
81 | } | |
c9a91883 | 82 | EXPORT_SYMBOL_GPL(pci_disable_pcie_error_reporting); |
6c2b374d ZY |
83 | |
84 | int pci_cleanup_aer_uncorrect_error_status(struct pci_dev *dev) | |
85 | { | |
86 | int pos; | |
6cdfd995 | 87 | u32 status; |
6c2b374d | 88 | |
0927678f | 89 | pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ERR); |
6c2b374d ZY |
90 | if (!pos) |
91 | return -EIO; | |
92 | ||
93 | pci_read_config_dword(dev, pos + PCI_ERR_UNCOR_STATUS, &status); | |
6cdfd995 AP |
94 | if (status) |
95 | pci_write_config_dword(dev, pos + PCI_ERR_UNCOR_STATUS, status); | |
6c2b374d ZY |
96 | |
97 | return 0; | |
98 | } | |
c9a91883 | 99 | EXPORT_SYMBOL_GPL(pci_cleanup_aer_uncorrect_error_status); |
6c2b374d | 100 | |
4a0c096e HS |
101 | /** |
102 | * add_error_device - list device to be handled | |
103 | * @e_info: pointer to error info | |
104 | * @dev: pointer to pci_dev to be added | |
105 | */ | |
3d5505c5 ZY |
106 | static int add_error_device(struct aer_err_info *e_info, struct pci_dev *dev) |
107 | { | |
108 | if (e_info->error_dev_num < AER_MAX_MULTI_ERR_DEVICES) { | |
109 | e_info->dev[e_info->error_dev_num] = dev; | |
110 | e_info->error_dev_num++; | |
4a0c096e | 111 | return 0; |
c9a91883 | 112 | } |
4a0c096e | 113 | return -ENOSPC; |
3d5505c5 ZY |
114 | } |
115 | ||
28eb27cf ZY |
116 | #define PCI_BUS(x) (((x) >> 8) & 0xff) |
117 | ||
c887275e HS |
118 | /** |
119 | * is_error_source - check whether the device is source of reported error | |
120 | * @dev: pointer to pci_dev to be checked | |
121 | * @e_info: pointer to reported error info | |
122 | */ | |
123 | static bool is_error_source(struct pci_dev *dev, struct aer_err_info *e_info) | |
28eb27cf ZY |
124 | { |
125 | int pos; | |
c887275e | 126 | u32 status, mask; |
28eb27cf | 127 | u16 reg16; |
28eb27cf ZY |
128 | |
129 | /* | |
130 | * When bus id is equal to 0, it might be a bad id | |
131 | * reported by root port. | |
132 | */ | |
133 | if (!nosourceid && (PCI_BUS(e_info->id) != 0)) { | |
bd17d474 HS |
134 | /* Device ID match? */ |
135 | if (e_info->id == ((dev->bus->number << 8) | dev->devfn)) | |
c887275e | 136 | return true; |
3d5505c5 | 137 | |
c887275e | 138 | /* Continue id comparing if there is no multiple error */ |
273024de | 139 | if (!e_info->multi_error_valid) |
c887275e | 140 | return false; |
28eb27cf ZY |
141 | } |
142 | ||
143 | /* | |
3d5505c5 ZY |
144 | * When either |
145 | * 1) nosourceid==y; | |
146 | * 2) bus id is equal to 0. Some ports might lose the bus | |
147 | * id of error source id; | |
148 | * 3) There are multiple errors and prior id comparing fails; | |
c887275e | 149 | * We check AER status registers to find possible reporter. |
28eb27cf ZY |
150 | */ |
151 | if (atomic_read(&dev->enable_cnt) == 0) | |
c887275e | 152 | return false; |
39a53062 | 153 | pos = pci_pcie_cap(dev); |
28eb27cf | 154 | if (!pos) |
c887275e HS |
155 | return false; |
156 | ||
28eb27cf | 157 | /* Check if AER is enabled */ |
c887275e | 158 | pci_read_config_word(dev, pos + PCI_EXP_DEVCTL, ®16); |
28eb27cf ZY |
159 | if (!(reg16 & ( |
160 | PCI_EXP_DEVCTL_CERE | | |
161 | PCI_EXP_DEVCTL_NFERE | | |
162 | PCI_EXP_DEVCTL_FERE | | |
163 | PCI_EXP_DEVCTL_URRE))) | |
c887275e | 164 | return false; |
28eb27cf ZY |
165 | pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ERR); |
166 | if (!pos) | |
c887275e | 167 | return false; |
28eb27cf | 168 | |
c887275e | 169 | /* Check if error is recorded */ |
28eb27cf | 170 | if (e_info->severity == AER_CORRECTABLE) { |
0d90c3ac HS |
171 | pci_read_config_dword(dev, pos + PCI_ERR_COR_STATUS, &status); |
172 | pci_read_config_dword(dev, pos + PCI_ERR_COR_MASK, &mask); | |
28eb27cf | 173 | } else { |
0d90c3ac HS |
174 | pci_read_config_dword(dev, pos + PCI_ERR_UNCOR_STATUS, &status); |
175 | pci_read_config_dword(dev, pos + PCI_ERR_UNCOR_MASK, &mask); | |
6c2b374d | 176 | } |
c887275e HS |
177 | if (status & ~mask) |
178 | return true; | |
6c2b374d | 179 | |
c887275e HS |
180 | return false; |
181 | } | |
3d5505c5 | 182 | |
c887275e HS |
183 | static int find_device_iter(struct pci_dev *dev, void *data) |
184 | { | |
185 | struct aer_err_info *e_info = (struct aer_err_info *)data; | |
186 | ||
187 | if (is_error_source(dev, e_info)) { | |
4a0c096e HS |
188 | /* List this device */ |
189 | if (add_error_device(e_info, dev)) { | |
190 | /* We cannot handle more... Stop iteration */ | |
191 | /* TODO: Should print error message here? */ | |
192 | return 1; | |
193 | } | |
c887275e HS |
194 | |
195 | /* If there is only a single error, stop iteration */ | |
196 | if (!e_info->multi_error_valid) | |
197 | return 1; | |
198 | } | |
199 | return 0; | |
6c2b374d ZY |
200 | } |
201 | ||
202 | /** | |
203 | * find_source_device - search through device hierarchy for source device | |
d885c6b7 | 204 | * @parent: pointer to Root Port pci_dev data structure |
98ca3964 | 205 | * @e_info: including detailed error information such like id |
6c2b374d | 206 | * |
98ca3964 HS |
207 | * Return true if found. |
208 | * | |
209 | * Invoked by DPC when error is detected at the Root Port. | |
7c4ec94f HS |
210 | * Caller of this function must set id, severity, and multi_error_valid of |
211 | * struct aer_err_info pointed by @e_info properly. This function must fill | |
212 | * e_info->error_dev_num and e_info->dev[], based on the given information. | |
d885c6b7 | 213 | */ |
98ca3964 | 214 | static bool find_source_device(struct pci_dev *parent, |
28eb27cf | 215 | struct aer_err_info *e_info) |
6c2b374d ZY |
216 | { |
217 | struct pci_dev *dev = parent; | |
28eb27cf | 218 | int result; |
6c2b374d | 219 | |
7c4ec94f HS |
220 | /* Must reset in this function */ |
221 | e_info->error_dev_num = 0; | |
222 | ||
6c2b374d | 223 | /* Is Root Port an agent that sends error message? */ |
28eb27cf ZY |
224 | result = find_device_iter(dev, e_info); |
225 | if (result) | |
98ca3964 | 226 | return true; |
6c2b374d | 227 | |
28eb27cf | 228 | pci_walk_bus(parent->subordinate, find_device_iter, e_info); |
98ca3964 HS |
229 | |
230 | if (!e_info->error_dev_num) { | |
231 | dev_printk(KERN_DEBUG, &parent->dev, | |
232 | "can't find device of ID%04x\n", | |
233 | e_info->id); | |
234 | return false; | |
235 | } | |
236 | return true; | |
6c2b374d ZY |
237 | } |
238 | ||
70298c6e | 239 | static int report_error_detected(struct pci_dev *dev, void *data) |
6c2b374d ZY |
240 | { |
241 | pci_ers_result_t vote; | |
242 | struct pci_error_handlers *err_handler; | |
243 | struct aer_broadcast_data *result_data; | |
244 | result_data = (struct aer_broadcast_data *) data; | |
245 | ||
246 | dev->error_state = result_data->state; | |
247 | ||
248 | if (!dev->driver || | |
249 | !dev->driver->err_handler || | |
250 | !dev->driver->err_handler->error_detected) { | |
251 | if (result_data->state == pci_channel_io_frozen && | |
252 | !(dev->hdr_type & PCI_HEADER_TYPE_BRIDGE)) { | |
253 | /* | |
254 | * In case of fatal recovery, if one of down- | |
255 | * stream device has no driver. We might be | |
256 | * unable to recover because a later insmod | |
257 | * of a driver for this device is unaware of | |
258 | * its hw state. | |
259 | */ | |
531f254e BH |
260 | dev_printk(KERN_DEBUG, &dev->dev, "device has %s\n", |
261 | dev->driver ? | |
262 | "no AER-aware driver" : "no driver"); | |
6c2b374d | 263 | } |
70298c6e | 264 | return 0; |
6c2b374d ZY |
265 | } |
266 | ||
267 | err_handler = dev->driver->err_handler; | |
268 | vote = err_handler->error_detected(dev, result_data->state); | |
269 | result_data->result = merge_result(result_data->result, vote); | |
70298c6e | 270 | return 0; |
6c2b374d ZY |
271 | } |
272 | ||
70298c6e | 273 | static int report_mmio_enabled(struct pci_dev *dev, void *data) |
6c2b374d ZY |
274 | { |
275 | pci_ers_result_t vote; | |
276 | struct pci_error_handlers *err_handler; | |
277 | struct aer_broadcast_data *result_data; | |
278 | result_data = (struct aer_broadcast_data *) data; | |
279 | ||
280 | if (!dev->driver || | |
281 | !dev->driver->err_handler || | |
282 | !dev->driver->err_handler->mmio_enabled) | |
70298c6e | 283 | return 0; |
6c2b374d ZY |
284 | |
285 | err_handler = dev->driver->err_handler; | |
286 | vote = err_handler->mmio_enabled(dev); | |
287 | result_data->result = merge_result(result_data->result, vote); | |
70298c6e | 288 | return 0; |
6c2b374d ZY |
289 | } |
290 | ||
70298c6e | 291 | static int report_slot_reset(struct pci_dev *dev, void *data) |
6c2b374d ZY |
292 | { |
293 | pci_ers_result_t vote; | |
294 | struct pci_error_handlers *err_handler; | |
295 | struct aer_broadcast_data *result_data; | |
296 | result_data = (struct aer_broadcast_data *) data; | |
297 | ||
298 | if (!dev->driver || | |
299 | !dev->driver->err_handler || | |
300 | !dev->driver->err_handler->slot_reset) | |
70298c6e | 301 | return 0; |
6c2b374d ZY |
302 | |
303 | err_handler = dev->driver->err_handler; | |
304 | vote = err_handler->slot_reset(dev); | |
305 | result_data->result = merge_result(result_data->result, vote); | |
70298c6e | 306 | return 0; |
6c2b374d ZY |
307 | } |
308 | ||
70298c6e | 309 | static int report_resume(struct pci_dev *dev, void *data) |
6c2b374d ZY |
310 | { |
311 | struct pci_error_handlers *err_handler; | |
312 | ||
313 | dev->error_state = pci_channel_io_normal; | |
314 | ||
315 | if (!dev->driver || | |
316 | !dev->driver->err_handler || | |
b0b801dd | 317 | !dev->driver->err_handler->resume) |
70298c6e | 318 | return 0; |
6c2b374d ZY |
319 | |
320 | err_handler = dev->driver->err_handler; | |
321 | err_handler->resume(dev); | |
70298c6e | 322 | return 0; |
6c2b374d ZY |
323 | } |
324 | ||
325 | /** | |
326 | * broadcast_error_message - handle message broadcast to downstream drivers | |
d885c6b7 | 327 | * @dev: pointer to from where in a hierarchy message is broadcasted down |
6c2b374d | 328 | * @state: error state |
d885c6b7 RD |
329 | * @error_mesg: message to print |
330 | * @cb: callback to be broadcasted | |
6c2b374d ZY |
331 | * |
332 | * Invoked during error recovery process. Once being invoked, the content | |
333 | * of error severity will be broadcasted to all downstream drivers in a | |
334 | * hierarchy in question. | |
d885c6b7 | 335 | */ |
6c2b374d ZY |
336 | static pci_ers_result_t broadcast_error_message(struct pci_dev *dev, |
337 | enum pci_channel_state state, | |
338 | char *error_mesg, | |
70298c6e | 339 | int (*cb)(struct pci_dev *, void *)) |
6c2b374d ZY |
340 | { |
341 | struct aer_broadcast_data result_data; | |
342 | ||
531f254e | 343 | dev_printk(KERN_DEBUG, &dev->dev, "broadcast %s message\n", error_mesg); |
6c2b374d ZY |
344 | result_data.state = state; |
345 | if (cb == report_error_detected) | |
346 | result_data.result = PCI_ERS_RESULT_CAN_RECOVER; | |
347 | else | |
348 | result_data.result = PCI_ERS_RESULT_RECOVERED; | |
349 | ||
350 | if (dev->hdr_type & PCI_HEADER_TYPE_BRIDGE) { | |
351 | /* | |
352 | * If the error is reported by a bridge, we think this error | |
353 | * is related to the downstream link of the bridge, so we | |
354 | * do error recovery on all subordinates of the bridge instead | |
355 | * of the bridge and clear the error status of the bridge. | |
356 | */ | |
357 | if (cb == report_error_detected) | |
358 | dev->error_state = state; | |
359 | pci_walk_bus(dev->subordinate, cb, &result_data); | |
360 | if (cb == report_resume) { | |
361 | pci_cleanup_aer_uncorrect_error_status(dev); | |
362 | dev->error_state = pci_channel_io_normal; | |
363 | } | |
c9a91883 | 364 | } else { |
6c2b374d ZY |
365 | /* |
366 | * If the error is reported by an end point, we think this | |
367 | * error is related to the upstream link of the end point. | |
368 | */ | |
369 | pci_walk_bus(dev->bus, cb, &result_data); | |
370 | } | |
371 | ||
372 | return result_data.result; | |
373 | } | |
374 | ||
89713422 HS |
375 | /** |
376 | * aer_do_secondary_bus_reset - perform secondary bus reset | |
377 | * @dev: pointer to bridge's pci_dev data structure | |
378 | * | |
379 | * Invoked when performing link reset at Root Port or Downstream Port. | |
380 | */ | |
381 | void aer_do_secondary_bus_reset(struct pci_dev *dev) | |
382 | { | |
383 | u16 p2p_ctrl; | |
384 | ||
385 | /* Assert Secondary Bus Reset */ | |
386 | pci_read_config_word(dev, PCI_BRIDGE_CONTROL, &p2p_ctrl); | |
387 | p2p_ctrl |= PCI_BRIDGE_CTL_BUS_RESET; | |
388 | pci_write_config_word(dev, PCI_BRIDGE_CONTROL, p2p_ctrl); | |
389 | ||
390 | /* | |
391 | * we should send hot reset message for 2ms to allow it time to | |
392 | * propagate to all downstream ports | |
393 | */ | |
394 | msleep(2); | |
395 | ||
396 | /* De-assert Secondary Bus Reset */ | |
397 | p2p_ctrl &= ~PCI_BRIDGE_CTL_BUS_RESET; | |
398 | pci_write_config_word(dev, PCI_BRIDGE_CONTROL, p2p_ctrl); | |
399 | ||
400 | /* | |
401 | * System software must wait for at least 100ms from the end | |
402 | * of a reset of one or more device before it is permitted | |
403 | * to issue Configuration Requests to those devices. | |
404 | */ | |
405 | msleep(200); | |
406 | } | |
407 | ||
408 | /** | |
409 | * default_downstream_reset_link - default reset function for Downstream Port | |
410 | * @dev: pointer to downstream port's pci_dev data structure | |
411 | * | |
412 | * Invoked when performing link reset at Downstream Port w/ no aer driver. | |
413 | */ | |
414 | static pci_ers_result_t default_downstream_reset_link(struct pci_dev *dev) | |
415 | { | |
416 | aer_do_secondary_bus_reset(dev); | |
417 | dev_printk(KERN_DEBUG, &dev->dev, | |
418 | "Downstream Port link has been reset\n"); | |
419 | return PCI_ERS_RESULT_RECOVERED; | |
420 | } | |
421 | ||
6c2b374d ZY |
422 | static int find_aer_service_iter(struct device *device, void *data) |
423 | { | |
517cae38 | 424 | struct pcie_port_service_driver *service_driver, **drv; |
6c2b374d | 425 | |
517cae38 | 426 | drv = (struct pcie_port_service_driver **) data; |
6c2b374d | 427 | |
4f7ccf6a HS |
428 | if (device->bus == &pcie_port_bus_type && device->driver) { |
429 | service_driver = to_service_driver(device->driver); | |
430 | if (service_driver->service == PCIE_PORT_SERVICE_AER) { | |
517cae38 | 431 | *drv = service_driver; |
4f7ccf6a | 432 | return 1; |
6c2b374d ZY |
433 | } |
434 | } | |
435 | ||
436 | return 0; | |
437 | } | |
438 | ||
517cae38 | 439 | static struct pcie_port_service_driver *find_aer_service(struct pci_dev *dev) |
6c2b374d | 440 | { |
517cae38 HS |
441 | struct pcie_port_service_driver *drv = NULL; |
442 | ||
443 | device_for_each_child(&dev->dev, &drv, find_aer_service_iter); | |
444 | ||
445 | return drv; | |
6c2b374d ZY |
446 | } |
447 | ||
448 | static pci_ers_result_t reset_link(struct pcie_device *aerdev, | |
449 | struct pci_dev *dev) | |
450 | { | |
451 | struct pci_dev *udev; | |
452 | pci_ers_result_t status; | |
517cae38 | 453 | struct pcie_port_service_driver *driver; |
6c2b374d | 454 | |
89713422 HS |
455 | if (dev->hdr_type & PCI_HEADER_TYPE_BRIDGE) { |
456 | /* Reset this port for all subordinates */ | |
6c2b374d | 457 | udev = dev; |
89713422 HS |
458 | } else { |
459 | /* Reset the upstream component (likely downstream port) */ | |
c9a91883 | 460 | udev = dev->bus->self; |
89713422 | 461 | } |
6c2b374d | 462 | |
517cae38 HS |
463 | /* Use the aer driver of the component firstly */ |
464 | driver = find_aer_service(udev); | |
6c2b374d | 465 | |
89713422 HS |
466 | if (driver && driver->reset_link) { |
467 | status = driver->reset_link(udev); | |
468 | } else if (udev->pcie_type == PCI_EXP_TYPE_DOWNSTREAM) { | |
469 | status = default_downstream_reset_link(udev); | |
470 | } else { | |
471 | dev_printk(KERN_DEBUG, &dev->dev, | |
472 | "no link-reset support at upstream device %s\n", | |
473 | pci_name(udev)); | |
474 | return PCI_ERS_RESULT_DISCONNECT; | |
6c2b374d ZY |
475 | } |
476 | ||
6c2b374d | 477 | if (status != PCI_ERS_RESULT_RECOVERED) { |
4f7ccf6a HS |
478 | dev_printk(KERN_DEBUG, &dev->dev, |
479 | "link reset at upstream device %s failed\n", | |
480 | pci_name(udev)); | |
6c2b374d ZY |
481 | return PCI_ERS_RESULT_DISCONNECT; |
482 | } | |
483 | ||
484 | return status; | |
485 | } | |
486 | ||
487 | /** | |
488 | * do_recovery - handle nonfatal/fatal error recovery process | |
489 | * @aerdev: pointer to a pcie_device data structure of root port | |
490 | * @dev: pointer to a pci_dev data structure of agent detecting an error | |
491 | * @severity: error severity type | |
492 | * | |
493 | * Invoked when an error is nonfatal/fatal. Once being invoked, broadcast | |
494 | * error detected message to all downstream drivers within a hierarchy in | |
495 | * question and return the returned code. | |
d885c6b7 | 496 | */ |
17e21854 | 497 | static void do_recovery(struct pcie_device *aerdev, struct pci_dev *dev, |
6c2b374d ZY |
498 | int severity) |
499 | { | |
500 | pci_ers_result_t status, result = PCI_ERS_RESULT_RECOVERED; | |
501 | enum pci_channel_state state; | |
502 | ||
503 | if (severity == AER_FATAL) | |
504 | state = pci_channel_io_frozen; | |
505 | else | |
506 | state = pci_channel_io_normal; | |
507 | ||
508 | status = broadcast_error_message(dev, | |
509 | state, | |
510 | "error_detected", | |
511 | report_error_detected); | |
512 | ||
513 | if (severity == AER_FATAL) { | |
514 | result = reset_link(aerdev, dev); | |
17e21854 HS |
515 | if (result != PCI_ERS_RESULT_RECOVERED) |
516 | goto failed; | |
6c2b374d ZY |
517 | } |
518 | ||
519 | if (status == PCI_ERS_RESULT_CAN_RECOVER) | |
520 | status = broadcast_error_message(dev, | |
521 | state, | |
522 | "mmio_enabled", | |
523 | report_mmio_enabled); | |
524 | ||
525 | if (status == PCI_ERS_RESULT_NEED_RESET) { | |
526 | /* | |
527 | * TODO: Should call platform-specific | |
528 | * functions to reset slot before calling | |
529 | * drivers' slot_reset callbacks? | |
530 | */ | |
531 | status = broadcast_error_message(dev, | |
532 | state, | |
533 | "slot_reset", | |
534 | report_slot_reset); | |
535 | } | |
536 | ||
17e21854 HS |
537 | if (status != PCI_ERS_RESULT_RECOVERED) |
538 | goto failed; | |
539 | ||
540 | broadcast_error_message(dev, | |
6c2b374d ZY |
541 | state, |
542 | "resume", | |
543 | report_resume); | |
544 | ||
17e21854 HS |
545 | dev_printk(KERN_DEBUG, &dev->dev, |
546 | "AER driver successfully recovered\n"); | |
547 | return; | |
548 | ||
549 | failed: | |
550 | /* TODO: Should kernel panic here? */ | |
551 | dev_printk(KERN_DEBUG, &dev->dev, | |
552 | "AER driver didn't recover\n"); | |
6c2b374d ZY |
553 | } |
554 | ||
555 | /** | |
556 | * handle_error_source - handle logging error into an event log | |
557 | * @aerdev: pointer to pcie_device data structure of the root port | |
558 | * @dev: pointer to pci_dev data structure of error source device | |
559 | * @info: comprehensive error information | |
560 | * | |
561 | * Invoked when an error being detected by Root Port. | |
d885c6b7 | 562 | */ |
c9a91883 | 563 | static void handle_error_source(struct pcie_device *aerdev, |
6c2b374d | 564 | struct pci_dev *dev, |
28eb27cf | 565 | struct aer_err_info *info) |
6c2b374d | 566 | { |
6c2b374d ZY |
567 | int pos; |
568 | ||
28eb27cf | 569 | if (info->severity == AER_CORRECTABLE) { |
6c2b374d ZY |
570 | /* |
571 | * Correctable error does not need software intevention. | |
572 | * No need to go through error recovery process. | |
573 | */ | |
0927678f | 574 | pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ERR); |
6c2b374d ZY |
575 | if (pos) |
576 | pci_write_config_dword(dev, pos + PCI_ERR_COR_STATUS, | |
28eb27cf | 577 | info->status); |
17e21854 HS |
578 | } else |
579 | do_recovery(aerdev, dev, info->severity); | |
6c2b374d ZY |
580 | } |
581 | ||
b1c089b7 HS |
582 | /** |
583 | * get_device_error_info - read error status from dev and store it to info | |
584 | * @dev: pointer to the device expected to have a error record | |
585 | * @info: pointer to structure to store the error record | |
586 | * | |
587 | * Return 1 on success, 0 on error. | |
7c4ec94f HS |
588 | * |
589 | * Note that @info is reused among all error devices. Clear fields properly. | |
b1c089b7 | 590 | */ |
6c2b374d ZY |
591 | static int get_device_error_info(struct pci_dev *dev, struct aer_err_info *info) |
592 | { | |
e7a0d92b | 593 | int pos, temp; |
6c2b374d | 594 | |
7c4ec94f | 595 | /* Must reset in this function */ |
1b4ffcf8 | 596 | info->status = 0; |
273024de | 597 | info->tlp_header_valid = 0; |
1b4ffcf8 | 598 | |
0927678f | 599 | pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ERR); |
6c2b374d ZY |
600 | |
601 | /* The device might not support AER */ | |
602 | if (!pos) | |
b1c089b7 | 603 | return 1; |
6c2b374d ZY |
604 | |
605 | if (info->severity == AER_CORRECTABLE) { | |
606 | pci_read_config_dword(dev, pos + PCI_ERR_COR_STATUS, | |
607 | &info->status); | |
0d90c3ac HS |
608 | pci_read_config_dword(dev, pos + PCI_ERR_COR_MASK, |
609 | &info->mask); | |
610 | if (!(info->status & ~info->mask)) | |
b1c089b7 | 611 | return 0; |
6c2b374d ZY |
612 | } else if (dev->hdr_type & PCI_HEADER_TYPE_BRIDGE || |
613 | info->severity == AER_NONFATAL) { | |
614 | ||
615 | /* Link is still healthy for IO reads */ | |
616 | pci_read_config_dword(dev, pos + PCI_ERR_UNCOR_STATUS, | |
617 | &info->status); | |
0d90c3ac HS |
618 | pci_read_config_dword(dev, pos + PCI_ERR_UNCOR_MASK, |
619 | &info->mask); | |
620 | if (!(info->status & ~info->mask)) | |
b1c089b7 | 621 | return 0; |
6c2b374d | 622 | |
e7a0d92b HS |
623 | /* Get First Error Pointer */ |
624 | pci_read_config_dword(dev, pos + PCI_ERR_CAP, &temp); | |
273024de | 625 | info->first_error = PCI_ERR_CAP_FEP(temp); |
e7a0d92b | 626 | |
6c2b374d | 627 | if (info->status & AER_LOG_TLP_MASKS) { |
273024de | 628 | info->tlp_header_valid = 1; |
6c2b374d ZY |
629 | pci_read_config_dword(dev, |
630 | pos + PCI_ERR_HEADER_LOG, &info->tlp.dw0); | |
631 | pci_read_config_dword(dev, | |
632 | pos + PCI_ERR_HEADER_LOG + 4, &info->tlp.dw1); | |
633 | pci_read_config_dword(dev, | |
634 | pos + PCI_ERR_HEADER_LOG + 8, &info->tlp.dw2); | |
635 | pci_read_config_dword(dev, | |
636 | pos + PCI_ERR_HEADER_LOG + 12, &info->tlp.dw3); | |
637 | } | |
638 | } | |
639 | ||
b1c089b7 | 640 | return 1; |
6c2b374d ZY |
641 | } |
642 | ||
3d5505c5 ZY |
643 | static inline void aer_process_err_devices(struct pcie_device *p_device, |
644 | struct aer_err_info *e_info) | |
645 | { | |
646 | int i; | |
647 | ||
b1c089b7 | 648 | /* Report all before handle them, not to lost records by reset etc. */ |
3d5505c5 | 649 | for (i = 0; i < e_info->error_dev_num && e_info->dev[i]; i++) { |
b1c089b7 | 650 | if (get_device_error_info(e_info->dev[i], e_info)) |
3d5505c5 | 651 | aer_print_error(e_info->dev[i], e_info); |
b1c089b7 HS |
652 | } |
653 | for (i = 0; i < e_info->error_dev_num && e_info->dev[i]; i++) { | |
654 | if (get_device_error_info(e_info->dev[i], e_info)) | |
655 | handle_error_source(p_device, e_info->dev[i], e_info); | |
3d5505c5 ZY |
656 | } |
657 | } | |
658 | ||
6c2b374d ZY |
659 | /** |
660 | * aer_isr_one_error - consume an error detected by root port | |
661 | * @p_device: pointer to error root port service device | |
662 | * @e_src: pointer to an error source | |
d885c6b7 | 663 | */ |
6c2b374d ZY |
664 | static void aer_isr_one_error(struct pcie_device *p_device, |
665 | struct aer_err_source *e_src) | |
666 | { | |
28eb27cf | 667 | struct aer_err_info *e_info; |
28eb27cf ZY |
668 | |
669 | /* struct aer_err_info might be big, so we allocate it with slab */ | |
670 | e_info = kmalloc(sizeof(struct aer_err_info), GFP_KERNEL); | |
7c4ec94f | 671 | if (!e_info) { |
28eb27cf ZY |
672 | dev_printk(KERN_DEBUG, &p_device->port->dev, |
673 | "Can't allocate mem when processing AER errors\n"); | |
674 | return; | |
675 | } | |
6c2b374d ZY |
676 | |
677 | /* | |
678 | * There is a possibility that both correctable error and | |
679 | * uncorrectable error being logged. Report correctable error first. | |
680 | */ | |
7c4ec94f HS |
681 | if (e_src->status & PCI_ERR_ROOT_COR_RCV) { |
682 | e_info->id = ERR_COR_ID(e_src->id); | |
683 | e_info->severity = AER_CORRECTABLE; | |
684 | ||
685 | if (e_src->status & PCI_ERR_ROOT_MULTI_COR_RCV) | |
686 | e_info->multi_error_valid = 1; | |
687 | else | |
688 | e_info->multi_error_valid = 0; | |
689 | ||
690 | aer_print_port_info(p_device->port, e_info); | |
691 | ||
692 | if (find_source_device(p_device->port, e_info)) | |
693 | aer_process_err_devices(p_device, e_info); | |
694 | } | |
695 | ||
696 | if (e_src->status & PCI_ERR_ROOT_UNCOR_RCV) { | |
697 | e_info->id = ERR_UNCOR_ID(e_src->id); | |
698 | ||
699 | if (e_src->status & PCI_ERR_ROOT_FATAL_RCV) | |
700 | e_info->severity = AER_FATAL; | |
701 | else | |
702 | e_info->severity = AER_NONFATAL; | |
703 | ||
704 | if (e_src->status & PCI_ERR_ROOT_MULTI_UNCOR_RCV) | |
273024de | 705 | e_info->multi_error_valid = 1; |
7c4ec94f HS |
706 | else |
707 | e_info->multi_error_valid = 0; | |
28eb27cf | 708 | |
79e4b89b HS |
709 | aer_print_port_info(p_device->port, e_info); |
710 | ||
98ca3964 HS |
711 | if (find_source_device(p_device->port, e_info)) |
712 | aer_process_err_devices(p_device, e_info); | |
6c2b374d | 713 | } |
28eb27cf ZY |
714 | |
715 | kfree(e_info); | |
6c2b374d ZY |
716 | } |
717 | ||
88da13bf HS |
718 | /** |
719 | * get_e_source - retrieve an error source | |
720 | * @rpc: pointer to the root port which holds an error | |
721 | * @e_src: pointer to store retrieved error source | |
722 | * | |
723 | * Return 1 if an error source is retrieved, otherwise 0. | |
724 | * | |
725 | * Invoked by DPC handler to consume an error. | |
726 | */ | |
727 | static int get_e_source(struct aer_rpc *rpc, struct aer_err_source *e_src) | |
728 | { | |
729 | unsigned long flags; | |
88da13bf HS |
730 | |
731 | /* Lock access to Root error producer/consumer index */ | |
732 | spin_lock_irqsave(&rpc->e_lock, flags); | |
f6735590 LT |
733 | if (rpc->prod_idx == rpc->cons_idx) { |
734 | spin_unlock_irqrestore(&rpc->e_lock, flags); | |
735 | return 0; | |
88da13bf | 736 | } |
f6735590 LT |
737 | |
738 | *e_src = rpc->e_sources[rpc->cons_idx]; | |
739 | rpc->cons_idx++; | |
740 | if (rpc->cons_idx == AER_ERROR_SOURCES_MAX) | |
741 | rpc->cons_idx = 0; | |
88da13bf HS |
742 | spin_unlock_irqrestore(&rpc->e_lock, flags); |
743 | ||
f6735590 | 744 | return 1; |
88da13bf HS |
745 | } |
746 | ||
6c2b374d ZY |
747 | /** |
748 | * aer_isr - consume errors detected by root port | |
65f27f38 | 749 | * @work: definition of this work item |
6c2b374d ZY |
750 | * |
751 | * Invoked, as DPC, when root port records new detected error | |
d885c6b7 | 752 | */ |
65f27f38 | 753 | void aer_isr(struct work_struct *work) |
6c2b374d | 754 | { |
65f27f38 DH |
755 | struct aer_rpc *rpc = container_of(work, struct aer_rpc, dpc_handler); |
756 | struct pcie_device *p_device = rpc->rpd; | |
88da13bf | 757 | struct aer_err_source e_src; |
6c2b374d ZY |
758 | |
759 | mutex_lock(&rpc->rpc_mutex); | |
88da13bf HS |
760 | while (get_e_source(rpc, &e_src)) |
761 | aer_isr_one_error(p_device, &e_src); | |
6c2b374d ZY |
762 | mutex_unlock(&rpc->rpc_mutex); |
763 | ||
764 | wake_up(&rpc->wait_release); | |
765 | } | |
766 | ||
6c2b374d ZY |
767 | /** |
768 | * aer_init - provide AER initialization | |
769 | * @dev: pointer to AER pcie device | |
770 | * | |
771 | * Invoked when AER service driver is loaded. | |
d885c6b7 | 772 | */ |
6c2b374d ZY |
773 | int aer_init(struct pcie_device *dev) |
774 | { | |
05843961 MD |
775 | if (forceload) { |
776 | dev_printk(KERN_DEBUG, &dev->device, | |
777 | "aerdrv forceload requested.\n"); | |
affb72c3 | 778 | pcie_aer_force_firmware_first(dev->port, 0); |
05843961 | 779 | } |
28eb5f27 | 780 | return 0; |
6c2b374d | 781 | } |