diff options
Diffstat (limited to 'arch/powerpc/platforms/powernv/eeh-powernv.c')
| -rw-r--r-- | arch/powerpc/platforms/powernv/eeh-powernv.c | 379 |
1 files changed, 379 insertions, 0 deletions
diff --git a/arch/powerpc/platforms/powernv/eeh-powernv.c b/arch/powerpc/platforms/powernv/eeh-powernv.c new file mode 100644 index 000000000000..969cce73055a --- /dev/null +++ b/arch/powerpc/platforms/powernv/eeh-powernv.c | |||
| @@ -0,0 +1,379 @@ | |||
| 1 | /* | ||
| 2 | * The file intends to implement the platform dependent EEH operations on | ||
| 3 | * powernv platform. Actually, the powernv was created in order to fully | ||
| 4 | * hypervisor support. | ||
| 5 | * | ||
| 6 | * Copyright Benjamin Herrenschmidt & Gavin Shan, IBM Corporation 2013. | ||
| 7 | * | ||
| 8 | * This program is free software; you can redistribute it and/or modify | ||
| 9 | * it under the terms of the GNU General Public License as published by | ||
| 10 | * the Free Software Foundation; either version 2 of the License, or | ||
| 11 | * (at your option) any later version. | ||
| 12 | */ | ||
| 13 | |||
| 14 | #include <linux/atomic.h> | ||
| 15 | #include <linux/delay.h> | ||
| 16 | #include <linux/export.h> | ||
| 17 | #include <linux/init.h> | ||
| 18 | #include <linux/list.h> | ||
| 19 | #include <linux/msi.h> | ||
| 20 | #include <linux/of.h> | ||
| 21 | #include <linux/pci.h> | ||
| 22 | #include <linux/proc_fs.h> | ||
| 23 | #include <linux/rbtree.h> | ||
| 24 | #include <linux/sched.h> | ||
| 25 | #include <linux/seq_file.h> | ||
| 26 | #include <linux/spinlock.h> | ||
| 27 | |||
| 28 | #include <asm/eeh.h> | ||
| 29 | #include <asm/eeh_event.h> | ||
| 30 | #include <asm/firmware.h> | ||
| 31 | #include <asm/io.h> | ||
| 32 | #include <asm/iommu.h> | ||
| 33 | #include <asm/machdep.h> | ||
| 34 | #include <asm/msi_bitmap.h> | ||
| 35 | #include <asm/opal.h> | ||
| 36 | #include <asm/ppc-pci.h> | ||
| 37 | |||
| 38 | #include "powernv.h" | ||
| 39 | #include "pci.h" | ||
| 40 | |||
| 41 | /** | ||
| 42 | * powernv_eeh_init - EEH platform dependent initialization | ||
| 43 | * | ||
| 44 | * EEH platform dependent initialization on powernv | ||
| 45 | */ | ||
| 46 | static int powernv_eeh_init(void) | ||
| 47 | { | ||
| 48 | /* We require OPALv3 */ | ||
| 49 | if (!firmware_has_feature(FW_FEATURE_OPALv3)) { | ||
| 50 | pr_warning("%s: OPALv3 is required !\n", __func__); | ||
| 51 | return -EINVAL; | ||
| 52 | } | ||
| 53 | |||
| 54 | /* Set EEH probe mode */ | ||
| 55 | eeh_probe_mode_set(EEH_PROBE_MODE_DEV); | ||
| 56 | |||
| 57 | return 0; | ||
| 58 | } | ||
| 59 | |||
| 60 | /** | ||
| 61 | * powernv_eeh_post_init - EEH platform dependent post initialization | ||
| 62 | * | ||
| 63 | * EEH platform dependent post initialization on powernv. When | ||
| 64 | * the function is called, the EEH PEs and devices should have | ||
| 65 | * been built. If the I/O cache staff has been built, EEH is | ||
| 66 | * ready to supply service. | ||
| 67 | */ | ||
| 68 | static int powernv_eeh_post_init(void) | ||
| 69 | { | ||
| 70 | struct pci_controller *hose; | ||
| 71 | struct pnv_phb *phb; | ||
| 72 | int ret = 0; | ||
| 73 | |||
| 74 | list_for_each_entry(hose, &hose_list, list_node) { | ||
| 75 | phb = hose->private_data; | ||
| 76 | |||
| 77 | if (phb->eeh_ops && phb->eeh_ops->post_init) { | ||
| 78 | ret = phb->eeh_ops->post_init(hose); | ||
| 79 | if (ret) | ||
| 80 | break; | ||
| 81 | } | ||
| 82 | } | ||
| 83 | |||
| 84 | return ret; | ||
| 85 | } | ||
| 86 | |||
| 87 | /** | ||
| 88 | * powernv_eeh_dev_probe - Do probe on PCI device | ||
| 89 | * @dev: PCI device | ||
| 90 | * @flag: unused | ||
| 91 | * | ||
| 92 | * When EEH module is installed during system boot, all PCI devices | ||
| 93 | * are checked one by one to see if it supports EEH. The function | ||
| 94 | * is introduced for the purpose. By default, EEH has been enabled | ||
| 95 | * on all PCI devices. That's to say, we only need do necessary | ||
| 96 | * initialization on the corresponding eeh device and create PE | ||
| 97 | * accordingly. | ||
| 98 | * | ||
| 99 | * It's notable that's unsafe to retrieve the EEH device through | ||
| 100 | * the corresponding PCI device. During the PCI device hotplug, which | ||
| 101 | * was possiblly triggered by EEH core, the binding between EEH device | ||
| 102 | * and the PCI device isn't built yet. | ||
| 103 | */ | ||
| 104 | static int powernv_eeh_dev_probe(struct pci_dev *dev, void *flag) | ||
| 105 | { | ||
| 106 | struct pci_controller *hose = pci_bus_to_host(dev->bus); | ||
| 107 | struct pnv_phb *phb = hose->private_data; | ||
| 108 | struct device_node *dn = pci_device_to_OF_node(dev); | ||
| 109 | struct eeh_dev *edev = of_node_to_eeh_dev(dn); | ||
| 110 | |||
| 111 | /* | ||
| 112 | * When probing the root bridge, which doesn't have any | ||
| 113 | * subordinate PCI devices. We don't have OF node for | ||
| 114 | * the root bridge. So it's not reasonable to continue | ||
| 115 | * the probing. | ||
| 116 | */ | ||
| 117 | if (!dn || !edev) | ||
| 118 | return 0; | ||
| 119 | |||
| 120 | /* Skip for PCI-ISA bridge */ | ||
| 121 | if ((dev->class >> 8) == PCI_CLASS_BRIDGE_ISA) | ||
| 122 | return 0; | ||
| 123 | |||
| 124 | /* Initialize eeh device */ | ||
| 125 | edev->class_code = dev->class; | ||
| 126 | edev->mode = 0; | ||
| 127 | edev->config_addr = ((dev->bus->number << 8) | dev->devfn); | ||
| 128 | edev->pe_config_addr = phb->bdfn_to_pe(phb, dev->bus, dev->devfn & 0xff); | ||
| 129 | |||
| 130 | /* Create PE */ | ||
| 131 | eeh_add_to_parent_pe(edev); | ||
| 132 | |||
| 133 | /* | ||
| 134 | * Enable EEH explicitly so that we will do EEH check | ||
| 135 | * while accessing I/O stuff | ||
| 136 | * | ||
| 137 | * FIXME: Enable that for PHB3 later | ||
| 138 | */ | ||
| 139 | if (phb->type == PNV_PHB_IODA1) | ||
| 140 | eeh_subsystem_enabled = 1; | ||
| 141 | |||
| 142 | /* Save memory bars */ | ||
| 143 | eeh_save_bars(edev); | ||
| 144 | |||
| 145 | return 0; | ||
| 146 | } | ||
| 147 | |||
| 148 | /** | ||
| 149 | * powernv_eeh_set_option - Initialize EEH or MMIO/DMA reenable | ||
| 150 | * @pe: EEH PE | ||
| 151 | * @option: operation to be issued | ||
| 152 | * | ||
| 153 | * The function is used to control the EEH functionality globally. | ||
| 154 | * Currently, following options are support according to PAPR: | ||
| 155 | * Enable EEH, Disable EEH, Enable MMIO and Enable DMA | ||
| 156 | */ | ||
| 157 | static int powernv_eeh_set_option(struct eeh_pe *pe, int option) | ||
| 158 | { | ||
| 159 | struct pci_controller *hose = pe->phb; | ||
| 160 | struct pnv_phb *phb = hose->private_data; | ||
| 161 | int ret = -EEXIST; | ||
| 162 | |||
| 163 | /* | ||
| 164 | * What we need do is pass it down for hardware | ||
| 165 | * implementation to handle it. | ||
| 166 | */ | ||
| 167 | if (phb->eeh_ops && phb->eeh_ops->set_option) | ||
| 168 | ret = phb->eeh_ops->set_option(pe, option); | ||
| 169 | |||
| 170 | return ret; | ||
| 171 | } | ||
| 172 | |||
| 173 | /** | ||
| 174 | * powernv_eeh_get_pe_addr - Retrieve PE address | ||
| 175 | * @pe: EEH PE | ||
| 176 | * | ||
| 177 | * Retrieve the PE address according to the given tranditional | ||
| 178 | * PCI BDF (Bus/Device/Function) address. | ||
| 179 | */ | ||
| 180 | static int powernv_eeh_get_pe_addr(struct eeh_pe *pe) | ||
| 181 | { | ||
| 182 | return pe->addr; | ||
| 183 | } | ||
| 184 | |||
| 185 | /** | ||
| 186 | * powernv_eeh_get_state - Retrieve PE state | ||
| 187 | * @pe: EEH PE | ||
| 188 | * @delay: delay while PE state is temporarily unavailable | ||
| 189 | * | ||
| 190 | * Retrieve the state of the specified PE. For IODA-compitable | ||
| 191 | * platform, it should be retrieved from IODA table. Therefore, | ||
| 192 | * we prefer passing down to hardware implementation to handle | ||
| 193 | * it. | ||
| 194 | */ | ||
| 195 | static int powernv_eeh_get_state(struct eeh_pe *pe, int *delay) | ||
| 196 | { | ||
| 197 | struct pci_controller *hose = pe->phb; | ||
| 198 | struct pnv_phb *phb = hose->private_data; | ||
| 199 | int ret = EEH_STATE_NOT_SUPPORT; | ||
| 200 | |||
| 201 | if (phb->eeh_ops && phb->eeh_ops->get_state) { | ||
| 202 | ret = phb->eeh_ops->get_state(pe); | ||
| 203 | |||
| 204 | /* | ||
| 205 | * If the PE state is temporarily unavailable, | ||
| 206 | * to inform the EEH core delay for default | ||
| 207 | * period (1 second) | ||
| 208 | */ | ||
| 209 | if (delay) { | ||
| 210 | *delay = 0; | ||
| 211 | if (ret & EEH_STATE_UNAVAILABLE) | ||
| 212 | *delay = 1000; | ||
| 213 | } | ||
| 214 | } | ||
| 215 | |||
| 216 | return ret; | ||
| 217 | } | ||
| 218 | |||
| 219 | /** | ||
| 220 | * powernv_eeh_reset - Reset the specified PE | ||
| 221 | * @pe: EEH PE | ||
| 222 | * @option: reset option | ||
| 223 | * | ||
| 224 | * Reset the specified PE | ||
| 225 | */ | ||
| 226 | static int powernv_eeh_reset(struct eeh_pe *pe, int option) | ||
| 227 | { | ||
| 228 | struct pci_controller *hose = pe->phb; | ||
| 229 | struct pnv_phb *phb = hose->private_data; | ||
| 230 | int ret = -EEXIST; | ||
| 231 | |||
| 232 | if (phb->eeh_ops && phb->eeh_ops->reset) | ||
| 233 | ret = phb->eeh_ops->reset(pe, option); | ||
| 234 | |||
| 235 | return ret; | ||
| 236 | } | ||
| 237 | |||
| 238 | /** | ||
| 239 | * powernv_eeh_wait_state - Wait for PE state | ||
| 240 | * @pe: EEH PE | ||
| 241 | * @max_wait: maximal period in microsecond | ||
| 242 | * | ||
| 243 | * Wait for the state of associated PE. It might take some time | ||
| 244 | * to retrieve the PE's state. | ||
| 245 | */ | ||
| 246 | static int powernv_eeh_wait_state(struct eeh_pe *pe, int max_wait) | ||
| 247 | { | ||
| 248 | int ret; | ||
| 249 | int mwait; | ||
| 250 | |||
| 251 | while (1) { | ||
| 252 | ret = powernv_eeh_get_state(pe, &mwait); | ||
| 253 | |||
| 254 | /* | ||
| 255 | * If the PE's state is temporarily unavailable, | ||
| 256 | * we have to wait for the specified time. Otherwise, | ||
| 257 | * the PE's state will be returned immediately. | ||
| 258 | */ | ||
| 259 | if (ret != EEH_STATE_UNAVAILABLE) | ||
| 260 | return ret; | ||
| 261 | |||
| 262 | max_wait -= mwait; | ||
| 263 | if (max_wait <= 0) { | ||
| 264 | pr_warning("%s: Timeout getting PE#%x's state (%d)\n", | ||
| 265 | __func__, pe->addr, max_wait); | ||
| 266 | return EEH_STATE_NOT_SUPPORT; | ||
| 267 | } | ||
| 268 | |||
| 269 | msleep(mwait); | ||
| 270 | } | ||
| 271 | |||
| 272 | return EEH_STATE_NOT_SUPPORT; | ||
| 273 | } | ||
| 274 | |||
| 275 | /** | ||
| 276 | * powernv_eeh_get_log - Retrieve error log | ||
| 277 | * @pe: EEH PE | ||
| 278 | * @severity: temporary or permanent error log | ||
| 279 | * @drv_log: driver log to be combined with retrieved error log | ||
| 280 | * @len: length of driver log | ||
| 281 | * | ||
| 282 | * Retrieve the temporary or permanent error from the PE. | ||
| 283 | */ | ||
| 284 | static int powernv_eeh_get_log(struct eeh_pe *pe, int severity, | ||
| 285 | char *drv_log, unsigned long len) | ||
| 286 | { | ||
| 287 | struct pci_controller *hose = pe->phb; | ||
| 288 | struct pnv_phb *phb = hose->private_data; | ||
| 289 | int ret = -EEXIST; | ||
| 290 | |||
| 291 | if (phb->eeh_ops && phb->eeh_ops->get_log) | ||
| 292 | ret = phb->eeh_ops->get_log(pe, severity, drv_log, len); | ||
| 293 | |||
| 294 | return ret; | ||
| 295 | } | ||
| 296 | |||
| 297 | /** | ||
| 298 | * powernv_eeh_configure_bridge - Configure PCI bridges in the indicated PE | ||
| 299 | * @pe: EEH PE | ||
| 300 | * | ||
| 301 | * The function will be called to reconfigure the bridges included | ||
| 302 | * in the specified PE so that the mulfunctional PE would be recovered | ||
| 303 | * again. | ||
| 304 | */ | ||
| 305 | static int powernv_eeh_configure_bridge(struct eeh_pe *pe) | ||
| 306 | { | ||
| 307 | struct pci_controller *hose = pe->phb; | ||
| 308 | struct pnv_phb *phb = hose->private_data; | ||
| 309 | int ret = 0; | ||
| 310 | |||
| 311 | if (phb->eeh_ops && phb->eeh_ops->configure_bridge) | ||
| 312 | ret = phb->eeh_ops->configure_bridge(pe); | ||
| 313 | |||
| 314 | return ret; | ||
| 315 | } | ||
| 316 | |||
| 317 | /** | ||
| 318 | * powernv_eeh_next_error - Retrieve next EEH error to handle | ||
| 319 | * @pe: Affected PE | ||
| 320 | * | ||
| 321 | * Using OPAL API, to retrieve next EEH error for EEH core to handle | ||
| 322 | */ | ||
| 323 | static int powernv_eeh_next_error(struct eeh_pe **pe) | ||
| 324 | { | ||
| 325 | struct pci_controller *hose; | ||
| 326 | struct pnv_phb *phb = NULL; | ||
| 327 | |||
| 328 | list_for_each_entry(hose, &hose_list, list_node) { | ||
| 329 | phb = hose->private_data; | ||
| 330 | break; | ||
| 331 | } | ||
| 332 | |||
| 333 | if (phb && phb->eeh_ops->next_error) | ||
| 334 | return phb->eeh_ops->next_error(pe); | ||
| 335 | |||
| 336 | return -EEXIST; | ||
| 337 | } | ||
| 338 | |||
| 339 | static struct eeh_ops powernv_eeh_ops = { | ||
| 340 | .name = "powernv", | ||
| 341 | .init = powernv_eeh_init, | ||
| 342 | .post_init = powernv_eeh_post_init, | ||
| 343 | .of_probe = NULL, | ||
| 344 | .dev_probe = powernv_eeh_dev_probe, | ||
| 345 | .set_option = powernv_eeh_set_option, | ||
| 346 | .get_pe_addr = powernv_eeh_get_pe_addr, | ||
| 347 | .get_state = powernv_eeh_get_state, | ||
| 348 | .reset = powernv_eeh_reset, | ||
| 349 | .wait_state = powernv_eeh_wait_state, | ||
| 350 | .get_log = powernv_eeh_get_log, | ||
| 351 | .configure_bridge = powernv_eeh_configure_bridge, | ||
| 352 | .read_config = pnv_pci_cfg_read, | ||
| 353 | .write_config = pnv_pci_cfg_write, | ||
| 354 | .next_error = powernv_eeh_next_error | ||
| 355 | }; | ||
| 356 | |||
| 357 | /** | ||
| 358 | * eeh_powernv_init - Register platform dependent EEH operations | ||
| 359 | * | ||
| 360 | * EEH initialization on powernv platform. This function should be | ||
| 361 | * called before any EEH related functions. | ||
| 362 | */ | ||
| 363 | static int __init eeh_powernv_init(void) | ||
| 364 | { | ||
| 365 | int ret = -EINVAL; | ||
| 366 | |||
| 367 | if (!machine_is(powernv)) | ||
| 368 | return ret; | ||
| 369 | |||
| 370 | ret = eeh_ops_register(&powernv_eeh_ops); | ||
| 371 | if (!ret) | ||
| 372 | pr_info("EEH: PowerNV platform initialized\n"); | ||
| 373 | else | ||
| 374 | pr_info("EEH: Failed to initialize PowerNV platform (%d)\n", ret); | ||
| 375 | |||
| 376 | return ret; | ||
| 377 | } | ||
| 378 | |||
| 379 | early_initcall(eeh_powernv_init); | ||
