18cfab3cfSBjorn Helgaas // SPDX-License-Identifier: GPL-2.0 2c465def6SHuang Ying /* 345e829eaSStefan Assmann * PCIe AER software error injection support. 4c465def6SHuang Ying * 5f6b6aefeSBjorn Helgaas * Debugging PCIe AER code is quite difficult because it is hard to 6c465def6SHuang Ying * trigger various real hardware errors. Software based error 7c465def6SHuang Ying * injection can fake almost all kinds of errors with the help of a 8c465def6SHuang Ying * user space helper tool aer-inject, which can be gotten from: 9*7ecd4a81SAlexander A. Klimov * https://www.kernel.org/pub/linux/utils/pci/aer-inject/ 10c465def6SHuang Ying * 11c465def6SHuang Ying * Copyright 2009 Intel Corporation. 12c465def6SHuang Ying * Huang Ying <ying.huang@intel.com> 13c465def6SHuang Ying */ 14c465def6SHuang Ying 159cc6f75bSFrederick Lawler #define dev_fmt(fmt) "aer_inject: " fmt 169cc6f75bSFrederick Lawler 17c465def6SHuang Ying #include <linux/module.h> 18c465def6SHuang Ying #include <linux/init.h> 199ae05225SThomas Gleixner #include <linux/interrupt.h> 20c465def6SHuang Ying #include <linux/miscdevice.h> 21c465def6SHuang Ying #include <linux/pci.h> 225a0e3ad6STejun Heo #include <linux/slab.h> 23c465def6SHuang Ying #include <linux/fs.h> 24c9a91883SHidetoshi Seto #include <linux/uaccess.h> 25cc5d153aSAndrew Patterson #include <linux/stddef.h> 263bc11851SJean Delvare #include <linux/device.h> 27f53e7418SBjorn Helgaas 284696b828SBjorn Helgaas #include "portdrv.h" 29c465def6SHuang Ying 30457d9d08SPrarit Bhargava /* Override the existing corrected and uncorrected error masks */ 3190ab5ee9SRusty Russell static bool aer_mask_override; 32457d9d08SPrarit Bhargava module_param(aer_mask_override, bool, 0); 33457d9d08SPrarit Bhargava 34c9a91883SHidetoshi Seto struct aer_error_inj { 35c465def6SHuang Ying u8 bus; 36c465def6SHuang Ying u8 dev; 37c465def6SHuang Ying u8 fn; 38c465def6SHuang Ying u32 uncor_status; 39c465def6SHuang Ying u32 cor_status; 40c465def6SHuang Ying u32 header_log0; 41c465def6SHuang Ying u32 header_log1; 42c465def6SHuang Ying u32 header_log2; 43c465def6SHuang Ying u32 header_log3; 4428ef241fSKeith Busch u32 domain; 45c465def6SHuang Ying }; 46c465def6SHuang Ying 47c9a91883SHidetoshi Seto struct aer_error { 48c465def6SHuang Ying struct list_head list; 4928ef241fSKeith Busch u32 domain; 50c465def6SHuang Ying unsigned int bus; 51c465def6SHuang Ying unsigned int devfn; 52c465def6SHuang Ying int pos_cap_err; 53c465def6SHuang Ying 54c465def6SHuang Ying u32 uncor_status; 55c465def6SHuang Ying u32 cor_status; 56c465def6SHuang Ying u32 header_log0; 57c465def6SHuang Ying u32 header_log1; 58c465def6SHuang Ying u32 header_log2; 59c465def6SHuang Ying u32 header_log3; 60c465def6SHuang Ying u32 root_status; 61c465def6SHuang Ying u32 source_id; 62c465def6SHuang Ying }; 63c465def6SHuang Ying 64c9a91883SHidetoshi Seto struct pci_bus_ops { 65c465def6SHuang Ying struct list_head list; 66c465def6SHuang Ying struct pci_bus *bus; 67c465def6SHuang Ying struct pci_ops *ops; 68c465def6SHuang Ying }; 69c465def6SHuang Ying 70c465def6SHuang Ying static LIST_HEAD(einjected); 71c465def6SHuang Ying 72c465def6SHuang Ying static LIST_HEAD(pci_bus_ops_list); 73c465def6SHuang Ying 74c465def6SHuang Ying /* Protect einjected and pci_bus_ops_list */ 75c465def6SHuang Ying static DEFINE_SPINLOCK(inject_lock); 76c465def6SHuang Ying 7728ef241fSKeith Busch static void aer_error_init(struct aer_error *err, u32 domain, 78cc5d153aSAndrew Patterson unsigned int bus, unsigned int devfn, 79cc5d153aSAndrew Patterson int pos_cap_err) 80c465def6SHuang Ying { 81c465def6SHuang Ying INIT_LIST_HEAD(&err->list); 82cc5d153aSAndrew Patterson err->domain = domain; 83c465def6SHuang Ying err->bus = bus; 84c465def6SHuang Ying err->devfn = devfn; 85c465def6SHuang Ying err->pos_cap_err = pos_cap_err; 86c465def6SHuang Ying } 87c465def6SHuang Ying 88c465def6SHuang Ying /* inject_lock must be held before calling */ 8928ef241fSKeith Busch static struct aer_error *__find_aer_error(u32 domain, unsigned int bus, 90cc5d153aSAndrew Patterson unsigned int devfn) 91c465def6SHuang Ying { 92c465def6SHuang Ying struct aer_error *err; 93c465def6SHuang Ying 94c465def6SHuang Ying list_for_each_entry(err, &einjected, list) { 95cc5d153aSAndrew Patterson if (domain == err->domain && 96cc5d153aSAndrew Patterson bus == err->bus && 97cc5d153aSAndrew Patterson devfn == err->devfn) 98c465def6SHuang Ying return err; 99c465def6SHuang Ying } 100c465def6SHuang Ying return NULL; 101c465def6SHuang Ying } 102c465def6SHuang Ying 103c465def6SHuang Ying /* inject_lock must be held before calling */ 104c465def6SHuang Ying static struct aer_error *__find_aer_error_by_dev(struct pci_dev *dev) 105c465def6SHuang Ying { 106cc5d153aSAndrew Patterson int domain = pci_domain_nr(dev->bus); 107cc5d153aSAndrew Patterson if (domain < 0) 108cc5d153aSAndrew Patterson return NULL; 10928ef241fSKeith Busch return __find_aer_error(domain, dev->bus->number, dev->devfn); 110c465def6SHuang Ying } 111c465def6SHuang Ying 112c465def6SHuang Ying /* inject_lock must be held before calling */ 113c465def6SHuang Ying static struct pci_ops *__find_pci_bus_ops(struct pci_bus *bus) 114c465def6SHuang Ying { 115c465def6SHuang Ying struct pci_bus_ops *bus_ops; 116c465def6SHuang Ying 117c465def6SHuang Ying list_for_each_entry(bus_ops, &pci_bus_ops_list, list) { 118c465def6SHuang Ying if (bus_ops->bus == bus) 119c465def6SHuang Ying return bus_ops->ops; 120c465def6SHuang Ying } 121c465def6SHuang Ying return NULL; 122c465def6SHuang Ying } 123c465def6SHuang Ying 124c465def6SHuang Ying static struct pci_bus_ops *pci_bus_ops_pop(void) 125c465def6SHuang Ying { 126c465def6SHuang Ying unsigned long flags; 1270e6053dcSGeliang Tang struct pci_bus_ops *bus_ops; 128c465def6SHuang Ying 129c465def6SHuang Ying spin_lock_irqsave(&inject_lock, flags); 1300e6053dcSGeliang Tang bus_ops = list_first_entry_or_null(&pci_bus_ops_list, 1310e6053dcSGeliang Tang struct pci_bus_ops, list); 1320e6053dcSGeliang Tang if (bus_ops) 1330e6053dcSGeliang Tang list_del(&bus_ops->list); 134c465def6SHuang Ying spin_unlock_irqrestore(&inject_lock, flags); 135c465def6SHuang Ying return bus_ops; 136c465def6SHuang Ying } 137c465def6SHuang Ying 138c465def6SHuang Ying static u32 *find_pci_config_dword(struct aer_error *err, int where, 139c465def6SHuang Ying int *prw1cs) 140c465def6SHuang Ying { 141c465def6SHuang Ying int rw1cs = 0; 142c465def6SHuang Ying u32 *target = NULL; 143c465def6SHuang Ying 144c465def6SHuang Ying if (err->pos_cap_err == -1) 145c465def6SHuang Ying return NULL; 146c465def6SHuang Ying 147c465def6SHuang Ying switch (where - err->pos_cap_err) { 148c465def6SHuang Ying case PCI_ERR_UNCOR_STATUS: 149c465def6SHuang Ying target = &err->uncor_status; 150c465def6SHuang Ying rw1cs = 1; 151c465def6SHuang Ying break; 152c465def6SHuang Ying case PCI_ERR_COR_STATUS: 153c465def6SHuang Ying target = &err->cor_status; 154c465def6SHuang Ying rw1cs = 1; 155c465def6SHuang Ying break; 156c465def6SHuang Ying case PCI_ERR_HEADER_LOG: 157c465def6SHuang Ying target = &err->header_log0; 158c465def6SHuang Ying break; 159c465def6SHuang Ying case PCI_ERR_HEADER_LOG+4: 160c465def6SHuang Ying target = &err->header_log1; 161c465def6SHuang Ying break; 162c465def6SHuang Ying case PCI_ERR_HEADER_LOG+8: 163c465def6SHuang Ying target = &err->header_log2; 164c465def6SHuang Ying break; 165c465def6SHuang Ying case PCI_ERR_HEADER_LOG+12: 166c465def6SHuang Ying target = &err->header_log3; 167c465def6SHuang Ying break; 168c465def6SHuang Ying case PCI_ERR_ROOT_STATUS: 169c465def6SHuang Ying target = &err->root_status; 170c465def6SHuang Ying rw1cs = 1; 171c465def6SHuang Ying break; 172f647a44fSHidetoshi Seto case PCI_ERR_ROOT_ERR_SRC: 173c465def6SHuang Ying target = &err->source_id; 174c465def6SHuang Ying break; 175c465def6SHuang Ying } 176c465def6SHuang Ying if (prw1cs) 177c465def6SHuang Ying *prw1cs = rw1cs; 178c465def6SHuang Ying return target; 179c465def6SHuang Ying } 180c465def6SHuang Ying 181e51cd9ceSKeith Busch static int aer_inj_read(struct pci_bus *bus, unsigned int devfn, int where, 182e51cd9ceSKeith Busch int size, u32 *val) 183e51cd9ceSKeith Busch { 184e51cd9ceSKeith Busch struct pci_ops *ops, *my_ops; 185e51cd9ceSKeith Busch int rv; 186e51cd9ceSKeith Busch 187e51cd9ceSKeith Busch ops = __find_pci_bus_ops(bus); 188e51cd9ceSKeith Busch if (!ops) 189e51cd9ceSKeith Busch return -1; 190e51cd9ceSKeith Busch 191e51cd9ceSKeith Busch my_ops = bus->ops; 192e51cd9ceSKeith Busch bus->ops = ops; 193e51cd9ceSKeith Busch rv = ops->read(bus, devfn, where, size, val); 194e51cd9ceSKeith Busch bus->ops = my_ops; 195e51cd9ceSKeith Busch 196e51cd9ceSKeith Busch return rv; 197e51cd9ceSKeith Busch } 198e51cd9ceSKeith Busch 199e51cd9ceSKeith Busch static int aer_inj_write(struct pci_bus *bus, unsigned int devfn, int where, 200e51cd9ceSKeith Busch int size, u32 val) 201e51cd9ceSKeith Busch { 202e51cd9ceSKeith Busch struct pci_ops *ops, *my_ops; 203e51cd9ceSKeith Busch int rv; 204e51cd9ceSKeith Busch 205e51cd9ceSKeith Busch ops = __find_pci_bus_ops(bus); 206e51cd9ceSKeith Busch if (!ops) 207e51cd9ceSKeith Busch return -1; 208e51cd9ceSKeith Busch 209e51cd9ceSKeith Busch my_ops = bus->ops; 210e51cd9ceSKeith Busch bus->ops = ops; 211e51cd9ceSKeith Busch rv = ops->write(bus, devfn, where, size, val); 212e51cd9ceSKeith Busch bus->ops = my_ops; 213e51cd9ceSKeith Busch 214e51cd9ceSKeith Busch return rv; 215e51cd9ceSKeith Busch } 216e51cd9ceSKeith Busch 2173b0a6d1aSBjorn Helgaas static int aer_inj_read_config(struct pci_bus *bus, unsigned int devfn, 2183b0a6d1aSBjorn Helgaas int where, int size, u32 *val) 219c465def6SHuang Ying { 220c465def6SHuang Ying u32 *sim; 221c465def6SHuang Ying struct aer_error *err; 222c465def6SHuang Ying unsigned long flags; 223cc5d153aSAndrew Patterson int domain; 2247e8fbdc6SDavid Daney int rv; 225c465def6SHuang Ying 226c465def6SHuang Ying spin_lock_irqsave(&inject_lock, flags); 227c465def6SHuang Ying if (size != sizeof(u32)) 228c465def6SHuang Ying goto out; 229cc5d153aSAndrew Patterson domain = pci_domain_nr(bus); 230cc5d153aSAndrew Patterson if (domain < 0) 231cc5d153aSAndrew Patterson goto out; 23228ef241fSKeith Busch err = __find_aer_error(domain, bus->number, devfn); 233c465def6SHuang Ying if (!err) 234c465def6SHuang Ying goto out; 235c465def6SHuang Ying 236c465def6SHuang Ying sim = find_pci_config_dword(err, where, NULL); 237c465def6SHuang Ying if (sim) { 238c465def6SHuang Ying *val = *sim; 239c465def6SHuang Ying spin_unlock_irqrestore(&inject_lock, flags); 240c465def6SHuang Ying return 0; 241c465def6SHuang Ying } 242c465def6SHuang Ying out: 243e51cd9ceSKeith Busch rv = aer_inj_read(bus, devfn, where, size, val); 244c465def6SHuang Ying spin_unlock_irqrestore(&inject_lock, flags); 2457e8fbdc6SDavid Daney return rv; 246c465def6SHuang Ying } 247c465def6SHuang Ying 2483b0a6d1aSBjorn Helgaas static int aer_inj_write_config(struct pci_bus *bus, unsigned int devfn, 2493b0a6d1aSBjorn Helgaas int where, int size, u32 val) 250c465def6SHuang Ying { 251c465def6SHuang Ying u32 *sim; 252c465def6SHuang Ying struct aer_error *err; 253c465def6SHuang Ying unsigned long flags; 254c465def6SHuang Ying int rw1cs; 255cc5d153aSAndrew Patterson int domain; 2567e8fbdc6SDavid Daney int rv; 257c465def6SHuang Ying 258c465def6SHuang Ying spin_lock_irqsave(&inject_lock, flags); 259c465def6SHuang Ying if (size != sizeof(u32)) 260c465def6SHuang Ying goto out; 261cc5d153aSAndrew Patterson domain = pci_domain_nr(bus); 262cc5d153aSAndrew Patterson if (domain < 0) 263cc5d153aSAndrew Patterson goto out; 26428ef241fSKeith Busch err = __find_aer_error(domain, bus->number, devfn); 265c465def6SHuang Ying if (!err) 266c465def6SHuang Ying goto out; 267c465def6SHuang Ying 268c465def6SHuang Ying sim = find_pci_config_dword(err, where, &rw1cs); 269c465def6SHuang Ying if (sim) { 270c465def6SHuang Ying if (rw1cs) 271c465def6SHuang Ying *sim ^= val; 272c465def6SHuang Ying else 273c465def6SHuang Ying *sim = val; 274c465def6SHuang Ying spin_unlock_irqrestore(&inject_lock, flags); 275c465def6SHuang Ying return 0; 276c465def6SHuang Ying } 277c465def6SHuang Ying out: 278e51cd9ceSKeith Busch rv = aer_inj_write(bus, devfn, where, size, val); 279c465def6SHuang Ying spin_unlock_irqrestore(&inject_lock, flags); 2807e8fbdc6SDavid Daney return rv; 281c465def6SHuang Ying } 282c465def6SHuang Ying 2833b0a6d1aSBjorn Helgaas static struct pci_ops aer_inj_pci_ops = { 2843b0a6d1aSBjorn Helgaas .read = aer_inj_read_config, 2853b0a6d1aSBjorn Helgaas .write = aer_inj_write_config, 286c465def6SHuang Ying }; 287c465def6SHuang Ying 288c465def6SHuang Ying static void pci_bus_ops_init(struct pci_bus_ops *bus_ops, 289c465def6SHuang Ying struct pci_bus *bus, 290c465def6SHuang Ying struct pci_ops *ops) 291c465def6SHuang Ying { 292c465def6SHuang Ying INIT_LIST_HEAD(&bus_ops->list); 293c465def6SHuang Ying bus_ops->bus = bus; 294c465def6SHuang Ying bus_ops->ops = ops; 295c465def6SHuang Ying } 296c465def6SHuang Ying 297c465def6SHuang Ying static int pci_bus_set_aer_ops(struct pci_bus *bus) 298c465def6SHuang Ying { 299c465def6SHuang Ying struct pci_ops *ops; 300c465def6SHuang Ying struct pci_bus_ops *bus_ops; 301c465def6SHuang Ying unsigned long flags; 302c465def6SHuang Ying 303c465def6SHuang Ying bus_ops = kmalloc(sizeof(*bus_ops), GFP_KERNEL); 304c465def6SHuang Ying if (!bus_ops) 305c465def6SHuang Ying return -ENOMEM; 3063b0a6d1aSBjorn Helgaas ops = pci_bus_set_ops(bus, &aer_inj_pci_ops); 307c465def6SHuang Ying spin_lock_irqsave(&inject_lock, flags); 3083b0a6d1aSBjorn Helgaas if (ops == &aer_inj_pci_ops) 309c465def6SHuang Ying goto out; 310c465def6SHuang Ying pci_bus_ops_init(bus_ops, bus, ops); 311c465def6SHuang Ying list_add(&bus_ops->list, &pci_bus_ops_list); 312c465def6SHuang Ying bus_ops = NULL; 313c465def6SHuang Ying out: 314c465def6SHuang Ying spin_unlock_irqrestore(&inject_lock, flags); 315c465def6SHuang Ying kfree(bus_ops); 316c465def6SHuang Ying return 0; 317c465def6SHuang Ying } 318c465def6SHuang Ying 319c465def6SHuang Ying static int aer_inject(struct aer_error_inj *einj) 320c465def6SHuang Ying { 321c465def6SHuang Ying struct aer_error *err, *rperr; 322c465def6SHuang Ying struct aer_error *err_alloc = NULL, *rperr_alloc = NULL; 323c465def6SHuang Ying struct pci_dev *dev, *rpdev; 324c465def6SHuang Ying struct pcie_device *edev; 3250e98db25SKeith Busch struct device *device; 326c465def6SHuang Ying unsigned long flags; 327c465def6SHuang Ying unsigned int devfn = PCI_DEVFN(einj->dev, einj->fn); 328c465def6SHuang Ying int pos_cap_err, rp_pos_cap_err; 32940294d8fSWanlong Gao u32 sever, cor_mask, uncor_mask, cor_mask_orig = 0, uncor_mask_orig = 0; 330c465def6SHuang Ying int ret = 0; 331c465def6SHuang Ying 33228ef241fSKeith Busch dev = pci_get_domain_bus_and_slot(einj->domain, einj->bus, devfn); 333c465def6SHuang Ying if (!dev) 3341d024355SAndrew Patterson return -ENODEV; 335c465def6SHuang Ying rpdev = pcie_find_root_port(dev); 336c465def6SHuang Ying if (!rpdev) { 3379cc6f75bSFrederick Lawler pci_err(dev, "Root port not found\n"); 338e82b14bdSPrarit Bhargava ret = -ENODEV; 339c465def6SHuang Ying goto out_put; 340c465def6SHuang Ying } 341c465def6SHuang Ying 342f0553ba0SFrederick Lawler pos_cap_err = dev->aer_cap; 343c465def6SHuang Ying if (!pos_cap_err) { 3449cc6f75bSFrederick Lawler pci_err(dev, "Device doesn't support AER\n"); 34520ac75e5SJean Delvare ret = -EPROTONOSUPPORT; 346c465def6SHuang Ying goto out_put; 347c465def6SHuang Ying } 348c465def6SHuang Ying pci_read_config_dword(dev, pos_cap_err + PCI_ERR_UNCOR_SEVER, &sever); 349bd1f46deSAndrew Patterson pci_read_config_dword(dev, pos_cap_err + PCI_ERR_COR_MASK, &cor_mask); 350bd1f46deSAndrew Patterson pci_read_config_dword(dev, pos_cap_err + PCI_ERR_UNCOR_MASK, 351bd1f46deSAndrew Patterson &uncor_mask); 352c465def6SHuang Ying 353f0553ba0SFrederick Lawler rp_pos_cap_err = rpdev->aer_cap; 354c465def6SHuang Ying if (!rp_pos_cap_err) { 3559cc6f75bSFrederick Lawler pci_err(rpdev, "Root port doesn't support AER\n"); 35620ac75e5SJean Delvare ret = -EPROTONOSUPPORT; 357c465def6SHuang Ying goto out_put; 358c465def6SHuang Ying } 359c465def6SHuang Ying 360c465def6SHuang Ying err_alloc = kzalloc(sizeof(struct aer_error), GFP_KERNEL); 361c465def6SHuang Ying if (!err_alloc) { 362c465def6SHuang Ying ret = -ENOMEM; 363c465def6SHuang Ying goto out_put; 364c465def6SHuang Ying } 365c465def6SHuang Ying rperr_alloc = kzalloc(sizeof(struct aer_error), GFP_KERNEL); 366c465def6SHuang Ying if (!rperr_alloc) { 367c465def6SHuang Ying ret = -ENOMEM; 368c465def6SHuang Ying goto out_put; 369c465def6SHuang Ying } 370c465def6SHuang Ying 371457d9d08SPrarit Bhargava if (aer_mask_override) { 372457d9d08SPrarit Bhargava cor_mask_orig = cor_mask; 373457d9d08SPrarit Bhargava cor_mask &= !(einj->cor_status); 374457d9d08SPrarit Bhargava pci_write_config_dword(dev, pos_cap_err + PCI_ERR_COR_MASK, 375457d9d08SPrarit Bhargava cor_mask); 376457d9d08SPrarit Bhargava 377457d9d08SPrarit Bhargava uncor_mask_orig = uncor_mask; 378457d9d08SPrarit Bhargava uncor_mask &= !(einj->uncor_status); 379457d9d08SPrarit Bhargava pci_write_config_dword(dev, pos_cap_err + PCI_ERR_UNCOR_MASK, 380457d9d08SPrarit Bhargava uncor_mask); 381457d9d08SPrarit Bhargava } 382457d9d08SPrarit Bhargava 383c465def6SHuang Ying spin_lock_irqsave(&inject_lock, flags); 384c465def6SHuang Ying 385c465def6SHuang Ying err = __find_aer_error_by_dev(dev); 386c465def6SHuang Ying if (!err) { 387c465def6SHuang Ying err = err_alloc; 388c465def6SHuang Ying err_alloc = NULL; 389cc5d153aSAndrew Patterson aer_error_init(err, einj->domain, einj->bus, devfn, 390cc5d153aSAndrew Patterson pos_cap_err); 391c465def6SHuang Ying list_add(&err->list, &einjected); 392c465def6SHuang Ying } 393c465def6SHuang Ying err->uncor_status |= einj->uncor_status; 394c465def6SHuang Ying err->cor_status |= einj->cor_status; 395c465def6SHuang Ying err->header_log0 = einj->header_log0; 396c465def6SHuang Ying err->header_log1 = einj->header_log1; 397c465def6SHuang Ying err->header_log2 = einj->header_log2; 398c465def6SHuang Ying err->header_log3 = einj->header_log3; 399c465def6SHuang Ying 400457d9d08SPrarit Bhargava if (!aer_mask_override && einj->cor_status && 401457d9d08SPrarit Bhargava !(einj->cor_status & ~cor_mask)) { 402b49bfd32SYouquan,Song ret = -EINVAL; 4039cc6f75bSFrederick Lawler pci_warn(dev, "The correctable error(s) is masked by device\n"); 404b49bfd32SYouquan,Song spin_unlock_irqrestore(&inject_lock, flags); 405b49bfd32SYouquan,Song goto out_put; 406b49bfd32SYouquan,Song } 407457d9d08SPrarit Bhargava if (!aer_mask_override && einj->uncor_status && 408457d9d08SPrarit Bhargava !(einj->uncor_status & ~uncor_mask)) { 409b49bfd32SYouquan,Song ret = -EINVAL; 4109cc6f75bSFrederick Lawler pci_warn(dev, "The uncorrectable error(s) is masked by device\n"); 411b49bfd32SYouquan,Song spin_unlock_irqrestore(&inject_lock, flags); 412b49bfd32SYouquan,Song goto out_put; 413b49bfd32SYouquan,Song } 414b49bfd32SYouquan,Song 415c465def6SHuang Ying rperr = __find_aer_error_by_dev(rpdev); 416c465def6SHuang Ying if (!rperr) { 417c465def6SHuang Ying rperr = rperr_alloc; 418c465def6SHuang Ying rperr_alloc = NULL; 419cc5d153aSAndrew Patterson aer_error_init(rperr, pci_domain_nr(rpdev->bus), 420cc5d153aSAndrew Patterson rpdev->bus->number, rpdev->devfn, 421c465def6SHuang Ying rp_pos_cap_err); 422c465def6SHuang Ying list_add(&rperr->list, &einjected); 423c465def6SHuang Ying } 424c465def6SHuang Ying if (einj->cor_status) { 425c465def6SHuang Ying if (rperr->root_status & PCI_ERR_ROOT_COR_RCV) 426c465def6SHuang Ying rperr->root_status |= PCI_ERR_ROOT_MULTI_COR_RCV; 427c465def6SHuang Ying else 428c465def6SHuang Ying rperr->root_status |= PCI_ERR_ROOT_COR_RCV; 429c465def6SHuang Ying rperr->source_id &= 0xffff0000; 430c465def6SHuang Ying rperr->source_id |= (einj->bus << 8) | devfn; 431c465def6SHuang Ying } 432c465def6SHuang Ying if (einj->uncor_status) { 433c465def6SHuang Ying if (rperr->root_status & PCI_ERR_ROOT_UNCOR_RCV) 434c465def6SHuang Ying rperr->root_status |= PCI_ERR_ROOT_MULTI_UNCOR_RCV; 435c465def6SHuang Ying if (sever & einj->uncor_status) { 436c465def6SHuang Ying rperr->root_status |= PCI_ERR_ROOT_FATAL_RCV; 437c465def6SHuang Ying if (!(rperr->root_status & PCI_ERR_ROOT_UNCOR_RCV)) 438c465def6SHuang Ying rperr->root_status |= PCI_ERR_ROOT_FIRST_FATAL; 439c465def6SHuang Ying } else 440c465def6SHuang Ying rperr->root_status |= PCI_ERR_ROOT_NONFATAL_RCV; 441c465def6SHuang Ying rperr->root_status |= PCI_ERR_ROOT_UNCOR_RCV; 442c465def6SHuang Ying rperr->source_id &= 0x0000ffff; 443c465def6SHuang Ying rperr->source_id |= ((einj->bus << 8) | devfn) << 16; 444c465def6SHuang Ying } 445c465def6SHuang Ying spin_unlock_irqrestore(&inject_lock, flags); 446c465def6SHuang Ying 447457d9d08SPrarit Bhargava if (aer_mask_override) { 448457d9d08SPrarit Bhargava pci_write_config_dword(dev, pos_cap_err + PCI_ERR_COR_MASK, 449457d9d08SPrarit Bhargava cor_mask_orig); 450457d9d08SPrarit Bhargava pci_write_config_dword(dev, pos_cap_err + PCI_ERR_UNCOR_MASK, 451457d9d08SPrarit Bhargava uncor_mask_orig); 452457d9d08SPrarit Bhargava } 453457d9d08SPrarit Bhargava 454c465def6SHuang Ying ret = pci_bus_set_aer_ops(dev->bus); 455c465def6SHuang Ying if (ret) 456c465def6SHuang Ying goto out_put; 457c465def6SHuang Ying ret = pci_bus_set_aer_ops(rpdev->bus); 458c465def6SHuang Ying if (ret) 459c465def6SHuang Ying goto out_put; 460c465def6SHuang Ying 4610e98db25SKeith Busch device = pcie_port_find_device(rpdev, PCIE_PORT_SERVICE_AER); 4620e98db25SKeith Busch if (device) { 4630e98db25SKeith Busch edev = to_pcie_device(device); 46446256f83SYouquan,Song if (!get_service_data(edev)) { 4659cc6f75bSFrederick Lawler pci_warn(edev->port, "AER service is not initialized\n"); 46620ac75e5SJean Delvare ret = -EPROTONOSUPPORT; 46746256f83SYouquan,Song goto out_put; 46846256f83SYouquan,Song } 4699cc6f75bSFrederick Lawler pci_info(edev->port, "Injecting errors %08x/%08x into device %s\n", 4708e47e15eSJean Delvare einj->cor_status, einj->uncor_status, pci_name(dev)); 4719ae05225SThomas Gleixner ret = irq_inject_interrupt(edev->irq); 47296b45ea5SJean Delvare } else { 4739cc6f75bSFrederick Lawler pci_err(rpdev, "AER device not found\n"); 47420ac75e5SJean Delvare ret = -ENODEV; 47596b45ea5SJean Delvare } 476c465def6SHuang Ying out_put: 477c465def6SHuang Ying kfree(err_alloc); 478c465def6SHuang Ying kfree(rperr_alloc); 479c465def6SHuang Ying pci_dev_put(dev); 480c465def6SHuang Ying return ret; 481c465def6SHuang Ying } 482c465def6SHuang Ying 483c465def6SHuang Ying static ssize_t aer_inject_write(struct file *filp, const char __user *ubuf, 484c465def6SHuang Ying size_t usize, loff_t *off) 485c465def6SHuang Ying { 486c465def6SHuang Ying struct aer_error_inj einj; 487c465def6SHuang Ying int ret; 488c465def6SHuang Ying 489c465def6SHuang Ying if (!capable(CAP_SYS_ADMIN)) 490c465def6SHuang Ying return -EPERM; 491cc5d153aSAndrew Patterson if (usize < offsetof(struct aer_error_inj, domain) || 492cc5d153aSAndrew Patterson usize > sizeof(einj)) 493c465def6SHuang Ying return -EINVAL; 494c465def6SHuang Ying 495cc5d153aSAndrew Patterson memset(&einj, 0, sizeof(einj)); 496c465def6SHuang Ying if (copy_from_user(&einj, ubuf, usize)) 497c465def6SHuang Ying return -EFAULT; 498c465def6SHuang Ying 499c465def6SHuang Ying ret = aer_inject(&einj); 500c465def6SHuang Ying return ret ? ret : usize; 501c465def6SHuang Ying } 502c465def6SHuang Ying 503c465def6SHuang Ying static const struct file_operations aer_inject_fops = { 504c465def6SHuang Ying .write = aer_inject_write, 505c465def6SHuang Ying .owner = THIS_MODULE, 5066038f373SArnd Bergmann .llseek = noop_llseek, 507c465def6SHuang Ying }; 508c465def6SHuang Ying 509c465def6SHuang Ying static struct miscdevice aer_inject_device = { 510c465def6SHuang Ying .minor = MISC_DYNAMIC_MINOR, 511c465def6SHuang Ying .name = "aer_inject", 512c465def6SHuang Ying .fops = &aer_inject_fops, 513c465def6SHuang Ying }; 514c465def6SHuang Ying 515c465def6SHuang Ying static int __init aer_inject_init(void) 516c465def6SHuang Ying { 517c465def6SHuang Ying return misc_register(&aer_inject_device); 518c465def6SHuang Ying } 519c465def6SHuang Ying 520c465def6SHuang Ying static void __exit aer_inject_exit(void) 521c465def6SHuang Ying { 522c465def6SHuang Ying struct aer_error *err, *err_next; 523c465def6SHuang Ying unsigned long flags; 524c465def6SHuang Ying struct pci_bus_ops *bus_ops; 525c465def6SHuang Ying 526c465def6SHuang Ying misc_deregister(&aer_inject_device); 527c465def6SHuang Ying 528c465def6SHuang Ying while ((bus_ops = pci_bus_ops_pop())) { 529c465def6SHuang Ying pci_bus_set_ops(bus_ops->bus, bus_ops->ops); 530c465def6SHuang Ying kfree(bus_ops); 531c465def6SHuang Ying } 532c465def6SHuang Ying 533c465def6SHuang Ying spin_lock_irqsave(&inject_lock, flags); 534476f644eSAndrew Patterson list_for_each_entry_safe(err, err_next, &einjected, list) { 535c465def6SHuang Ying list_del(&err->list); 536c465def6SHuang Ying kfree(err); 537c465def6SHuang Ying } 538c465def6SHuang Ying spin_unlock_irqrestore(&inject_lock, flags); 539c465def6SHuang Ying } 540c465def6SHuang Ying 541c465def6SHuang Ying module_init(aer_inject_init); 542c465def6SHuang Ying module_exit(aer_inject_exit); 543c465def6SHuang Ying 5447e8af37aSStefan Assmann MODULE_DESCRIPTION("PCIe AER software error injector"); 545c465def6SHuang Ying MODULE_LICENSE("GPL"); 546