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