14696b828SBjorn Helgaas // SPDX-License-Identifier: GPL-2.0
24696b828SBjorn Helgaas /*
34696b828SBjorn Helgaas * PCIe AER software error injection support.
44696b828SBjorn Helgaas *
5f6b6aefeSBjorn Helgaas * Debugging PCIe AER code is quite difficult because it is hard to
64696b828SBjorn Helgaas * trigger various real hardware errors. Software based error
74696b828SBjorn Helgaas * injection can fake almost all kinds of errors with the help of a
84696b828SBjorn Helgaas * user space helper tool aer-inject, which can be gotten from:
9*602a4edaSYicong Yang * https://git.kernel.org/cgit/linux/kernel/git/gong.chen/aer-inject.git/
104696b828SBjorn Helgaas *
114696b828SBjorn Helgaas * Copyright 2009 Intel Corporation.
124696b828SBjorn Helgaas * Huang Ying <ying.huang@intel.com>
134696b828SBjorn Helgaas */
144696b828SBjorn Helgaas
159cc6f75bSFrederick Lawler #define dev_fmt(fmt) "aer_inject: " fmt
169cc6f75bSFrederick Lawler
174696b828SBjorn Helgaas #include <linux/module.h>
184696b828SBjorn Helgaas #include <linux/init.h>
199ae05225SThomas Gleixner #include <linux/interrupt.h>
204696b828SBjorn Helgaas #include <linux/miscdevice.h>
214696b828SBjorn Helgaas #include <linux/pci.h>
224696b828SBjorn Helgaas #include <linux/slab.h>
234696b828SBjorn Helgaas #include <linux/fs.h>
244696b828SBjorn Helgaas #include <linux/uaccess.h>
254696b828SBjorn Helgaas #include <linux/stddef.h>
264696b828SBjorn Helgaas #include <linux/device.h>
274696b828SBjorn Helgaas
284696b828SBjorn Helgaas #include "portdrv.h"
294696b828SBjorn Helgaas
304696b828SBjorn Helgaas /* Override the existing corrected and uncorrected error masks */
314696b828SBjorn Helgaas static bool aer_mask_override;
324696b828SBjorn Helgaas module_param(aer_mask_override, bool, 0);
334696b828SBjorn Helgaas
344696b828SBjorn Helgaas struct aer_error_inj {
354696b828SBjorn Helgaas u8 bus;
364696b828SBjorn Helgaas u8 dev;
374696b828SBjorn Helgaas u8 fn;
384696b828SBjorn Helgaas u32 uncor_status;
394696b828SBjorn Helgaas u32 cor_status;
404696b828SBjorn Helgaas u32 header_log0;
414696b828SBjorn Helgaas u32 header_log1;
424696b828SBjorn Helgaas u32 header_log2;
434696b828SBjorn Helgaas u32 header_log3;
444696b828SBjorn Helgaas u32 domain;
454696b828SBjorn Helgaas };
464696b828SBjorn Helgaas
474696b828SBjorn Helgaas struct aer_error {
484696b828SBjorn Helgaas struct list_head list;
494696b828SBjorn Helgaas u32 domain;
504696b828SBjorn Helgaas unsigned int bus;
514696b828SBjorn Helgaas unsigned int devfn;
524696b828SBjorn Helgaas int pos_cap_err;
534696b828SBjorn Helgaas
544696b828SBjorn Helgaas u32 uncor_status;
554696b828SBjorn Helgaas u32 cor_status;
564696b828SBjorn Helgaas u32 header_log0;
574696b828SBjorn Helgaas u32 header_log1;
584696b828SBjorn Helgaas u32 header_log2;
594696b828SBjorn Helgaas u32 header_log3;
604696b828SBjorn Helgaas u32 root_status;
614696b828SBjorn Helgaas u32 source_id;
624696b828SBjorn Helgaas };
634696b828SBjorn Helgaas
644696b828SBjorn Helgaas struct pci_bus_ops {
654696b828SBjorn Helgaas struct list_head list;
664696b828SBjorn Helgaas struct pci_bus *bus;
674696b828SBjorn Helgaas struct pci_ops *ops;
684696b828SBjorn Helgaas };
694696b828SBjorn Helgaas
704696b828SBjorn Helgaas static LIST_HEAD(einjected);
714696b828SBjorn Helgaas
724696b828SBjorn Helgaas static LIST_HEAD(pci_bus_ops_list);
734696b828SBjorn Helgaas
744696b828SBjorn Helgaas /* Protect einjected and pci_bus_ops_list */
754696b828SBjorn Helgaas static DEFINE_SPINLOCK(inject_lock);
764696b828SBjorn Helgaas
aer_error_init(struct aer_error * err,u32 domain,unsigned int bus,unsigned int devfn,int pos_cap_err)774696b828SBjorn Helgaas static void aer_error_init(struct aer_error *err, u32 domain,
784696b828SBjorn Helgaas unsigned int bus, unsigned int devfn,
794696b828SBjorn Helgaas int pos_cap_err)
804696b828SBjorn Helgaas {
814696b828SBjorn Helgaas INIT_LIST_HEAD(&err->list);
824696b828SBjorn Helgaas err->domain = domain;
834696b828SBjorn Helgaas err->bus = bus;
844696b828SBjorn Helgaas err->devfn = devfn;
854696b828SBjorn Helgaas err->pos_cap_err = pos_cap_err;
864696b828SBjorn Helgaas }
874696b828SBjorn Helgaas
884696b828SBjorn Helgaas /* inject_lock must be held before calling */
__find_aer_error(u32 domain,unsigned int bus,unsigned int devfn)894696b828SBjorn Helgaas static struct aer_error *__find_aer_error(u32 domain, unsigned int bus,
904696b828SBjorn Helgaas unsigned int devfn)
914696b828SBjorn Helgaas {
924696b828SBjorn Helgaas struct aer_error *err;
934696b828SBjorn Helgaas
944696b828SBjorn Helgaas list_for_each_entry(err, &einjected, list) {
954696b828SBjorn Helgaas if (domain == err->domain &&
964696b828SBjorn Helgaas bus == err->bus &&
974696b828SBjorn Helgaas devfn == err->devfn)
984696b828SBjorn Helgaas return err;
994696b828SBjorn Helgaas }
1004696b828SBjorn Helgaas return NULL;
1014696b828SBjorn Helgaas }
1024696b828SBjorn Helgaas
1034696b828SBjorn Helgaas /* inject_lock must be held before calling */
__find_aer_error_by_dev(struct pci_dev * dev)1044696b828SBjorn Helgaas static struct aer_error *__find_aer_error_by_dev(struct pci_dev *dev)
1054696b828SBjorn Helgaas {
1064696b828SBjorn Helgaas int domain = pci_domain_nr(dev->bus);
1074696b828SBjorn Helgaas if (domain < 0)
1084696b828SBjorn Helgaas return NULL;
1094696b828SBjorn Helgaas return __find_aer_error(domain, dev->bus->number, dev->devfn);
1104696b828SBjorn Helgaas }
1114696b828SBjorn Helgaas
1124696b828SBjorn Helgaas /* inject_lock must be held before calling */
__find_pci_bus_ops(struct pci_bus * bus)1134696b828SBjorn Helgaas static struct pci_ops *__find_pci_bus_ops(struct pci_bus *bus)
1144696b828SBjorn Helgaas {
1154696b828SBjorn Helgaas struct pci_bus_ops *bus_ops;
1164696b828SBjorn Helgaas
1174696b828SBjorn Helgaas list_for_each_entry(bus_ops, &pci_bus_ops_list, list) {
1184696b828SBjorn Helgaas if (bus_ops->bus == bus)
1194696b828SBjorn Helgaas return bus_ops->ops;
1204696b828SBjorn Helgaas }
1214696b828SBjorn Helgaas return NULL;
1224696b828SBjorn Helgaas }
1234696b828SBjorn Helgaas
pci_bus_ops_pop(void)1244696b828SBjorn Helgaas static struct pci_bus_ops *pci_bus_ops_pop(void)
1254696b828SBjorn Helgaas {
1264696b828SBjorn Helgaas unsigned long flags;
1274696b828SBjorn Helgaas struct pci_bus_ops *bus_ops;
1284696b828SBjorn Helgaas
1294696b828SBjorn Helgaas spin_lock_irqsave(&inject_lock, flags);
1304696b828SBjorn Helgaas bus_ops = list_first_entry_or_null(&pci_bus_ops_list,
1314696b828SBjorn Helgaas struct pci_bus_ops, list);
1324696b828SBjorn Helgaas if (bus_ops)
1334696b828SBjorn Helgaas list_del(&bus_ops->list);
1344696b828SBjorn Helgaas spin_unlock_irqrestore(&inject_lock, flags);
1354696b828SBjorn Helgaas return bus_ops;
1364696b828SBjorn Helgaas }
1374696b828SBjorn Helgaas
find_pci_config_dword(struct aer_error * err,int where,int * prw1cs)1384696b828SBjorn Helgaas static u32 *find_pci_config_dword(struct aer_error *err, int where,
1394696b828SBjorn Helgaas int *prw1cs)
1404696b828SBjorn Helgaas {
1414696b828SBjorn Helgaas int rw1cs = 0;
1424696b828SBjorn Helgaas u32 *target = NULL;
1434696b828SBjorn Helgaas
1444696b828SBjorn Helgaas if (err->pos_cap_err == -1)
1454696b828SBjorn Helgaas return NULL;
1464696b828SBjorn Helgaas
1474696b828SBjorn Helgaas switch (where - err->pos_cap_err) {
1484696b828SBjorn Helgaas case PCI_ERR_UNCOR_STATUS:
1494696b828SBjorn Helgaas target = &err->uncor_status;
1504696b828SBjorn Helgaas rw1cs = 1;
1514696b828SBjorn Helgaas break;
1524696b828SBjorn Helgaas case PCI_ERR_COR_STATUS:
1534696b828SBjorn Helgaas target = &err->cor_status;
1544696b828SBjorn Helgaas rw1cs = 1;
1554696b828SBjorn Helgaas break;
1564696b828SBjorn Helgaas case PCI_ERR_HEADER_LOG:
1574696b828SBjorn Helgaas target = &err->header_log0;
1584696b828SBjorn Helgaas break;
1594696b828SBjorn Helgaas case PCI_ERR_HEADER_LOG+4:
1604696b828SBjorn Helgaas target = &err->header_log1;
1614696b828SBjorn Helgaas break;
1624696b828SBjorn Helgaas case PCI_ERR_HEADER_LOG+8:
1634696b828SBjorn Helgaas target = &err->header_log2;
1644696b828SBjorn Helgaas break;
1654696b828SBjorn Helgaas case PCI_ERR_HEADER_LOG+12:
1664696b828SBjorn Helgaas target = &err->header_log3;
1674696b828SBjorn Helgaas break;
1684696b828SBjorn Helgaas case PCI_ERR_ROOT_STATUS:
1694696b828SBjorn Helgaas target = &err->root_status;
1704696b828SBjorn Helgaas rw1cs = 1;
1714696b828SBjorn Helgaas break;
1724696b828SBjorn Helgaas case PCI_ERR_ROOT_ERR_SRC:
1734696b828SBjorn Helgaas target = &err->source_id;
1744696b828SBjorn Helgaas break;
1754696b828SBjorn Helgaas }
1764696b828SBjorn Helgaas if (prw1cs)
1774696b828SBjorn Helgaas *prw1cs = rw1cs;
1784696b828SBjorn Helgaas return target;
1794696b828SBjorn Helgaas }
1804696b828SBjorn Helgaas
aer_inj_read(struct pci_bus * bus,unsigned int devfn,int where,int size,u32 * val)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
aer_inj_write(struct pci_bus * bus,unsigned int devfn,int where,int size,u32 val)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
aer_inj_read_config(struct pci_bus * bus,unsigned int devfn,int where,int size,u32 * val)2174696b828SBjorn Helgaas static int aer_inj_read_config(struct pci_bus *bus, unsigned int devfn,
2184696b828SBjorn Helgaas int where, int size, u32 *val)
2194696b828SBjorn Helgaas {
2204696b828SBjorn Helgaas u32 *sim;
2214696b828SBjorn Helgaas struct aer_error *err;
2224696b828SBjorn Helgaas unsigned long flags;
2234696b828SBjorn Helgaas int domain;
2244696b828SBjorn Helgaas int rv;
2254696b828SBjorn Helgaas
2264696b828SBjorn Helgaas spin_lock_irqsave(&inject_lock, flags);
2274696b828SBjorn Helgaas if (size != sizeof(u32))
2284696b828SBjorn Helgaas goto out;
2294696b828SBjorn Helgaas domain = pci_domain_nr(bus);
2304696b828SBjorn Helgaas if (domain < 0)
2314696b828SBjorn Helgaas goto out;
2324696b828SBjorn Helgaas err = __find_aer_error(domain, bus->number, devfn);
2334696b828SBjorn Helgaas if (!err)
2344696b828SBjorn Helgaas goto out;
2354696b828SBjorn Helgaas
2364696b828SBjorn Helgaas sim = find_pci_config_dword(err, where, NULL);
2374696b828SBjorn Helgaas if (sim) {
2384696b828SBjorn Helgaas *val = *sim;
2394696b828SBjorn Helgaas spin_unlock_irqrestore(&inject_lock, flags);
2404696b828SBjorn Helgaas return 0;
2414696b828SBjorn Helgaas }
2424696b828SBjorn Helgaas out:
243e51cd9ceSKeith Busch rv = aer_inj_read(bus, devfn, where, size, val);
2444696b828SBjorn Helgaas spin_unlock_irqrestore(&inject_lock, flags);
2454696b828SBjorn Helgaas return rv;
2464696b828SBjorn Helgaas }
2474696b828SBjorn Helgaas
aer_inj_write_config(struct pci_bus * bus,unsigned int devfn,int where,int size,u32 val)2484696b828SBjorn Helgaas static int aer_inj_write_config(struct pci_bus *bus, unsigned int devfn,
2494696b828SBjorn Helgaas int where, int size, u32 val)
2504696b828SBjorn Helgaas {
2514696b828SBjorn Helgaas u32 *sim;
2524696b828SBjorn Helgaas struct aer_error *err;
2534696b828SBjorn Helgaas unsigned long flags;
2544696b828SBjorn Helgaas int rw1cs;
2554696b828SBjorn Helgaas int domain;
2564696b828SBjorn Helgaas int rv;
2574696b828SBjorn Helgaas
2584696b828SBjorn Helgaas spin_lock_irqsave(&inject_lock, flags);
2594696b828SBjorn Helgaas if (size != sizeof(u32))
2604696b828SBjorn Helgaas goto out;
2614696b828SBjorn Helgaas domain = pci_domain_nr(bus);
2624696b828SBjorn Helgaas if (domain < 0)
2634696b828SBjorn Helgaas goto out;
2644696b828SBjorn Helgaas err = __find_aer_error(domain, bus->number, devfn);
2654696b828SBjorn Helgaas if (!err)
2664696b828SBjorn Helgaas goto out;
2674696b828SBjorn Helgaas
2684696b828SBjorn Helgaas sim = find_pci_config_dword(err, where, &rw1cs);
2694696b828SBjorn Helgaas if (sim) {
2704696b828SBjorn Helgaas if (rw1cs)
2714696b828SBjorn Helgaas *sim ^= val;
2724696b828SBjorn Helgaas else
2734696b828SBjorn Helgaas *sim = val;
2744696b828SBjorn Helgaas spin_unlock_irqrestore(&inject_lock, flags);
2754696b828SBjorn Helgaas return 0;
2764696b828SBjorn Helgaas }
2774696b828SBjorn Helgaas out:
278e51cd9ceSKeith Busch rv = aer_inj_write(bus, devfn, where, size, val);
2794696b828SBjorn Helgaas spin_unlock_irqrestore(&inject_lock, flags);
2804696b828SBjorn Helgaas return rv;
2814696b828SBjorn Helgaas }
2824696b828SBjorn Helgaas
2834696b828SBjorn Helgaas static struct pci_ops aer_inj_pci_ops = {
2844696b828SBjorn Helgaas .read = aer_inj_read_config,
2854696b828SBjorn Helgaas .write = aer_inj_write_config,
2864696b828SBjorn Helgaas };
2874696b828SBjorn Helgaas
pci_bus_ops_init(struct pci_bus_ops * bus_ops,struct pci_bus * bus,struct pci_ops * ops)2884696b828SBjorn Helgaas static void pci_bus_ops_init(struct pci_bus_ops *bus_ops,
2894696b828SBjorn Helgaas struct pci_bus *bus,
2904696b828SBjorn Helgaas struct pci_ops *ops)
2914696b828SBjorn Helgaas {
2924696b828SBjorn Helgaas INIT_LIST_HEAD(&bus_ops->list);
2934696b828SBjorn Helgaas bus_ops->bus = bus;
2944696b828SBjorn Helgaas bus_ops->ops = ops;
2954696b828SBjorn Helgaas }
2964696b828SBjorn Helgaas
pci_bus_set_aer_ops(struct pci_bus * bus)2974696b828SBjorn Helgaas static int pci_bus_set_aer_ops(struct pci_bus *bus)
2984696b828SBjorn Helgaas {
2994696b828SBjorn Helgaas struct pci_ops *ops;
3004696b828SBjorn Helgaas struct pci_bus_ops *bus_ops;
3014696b828SBjorn Helgaas unsigned long flags;
3024696b828SBjorn Helgaas
3034696b828SBjorn Helgaas bus_ops = kmalloc(sizeof(*bus_ops), GFP_KERNEL);
3044696b828SBjorn Helgaas if (!bus_ops)
3054696b828SBjorn Helgaas return -ENOMEM;
3064696b828SBjorn Helgaas ops = pci_bus_set_ops(bus, &aer_inj_pci_ops);
3074696b828SBjorn Helgaas spin_lock_irqsave(&inject_lock, flags);
3084696b828SBjorn Helgaas if (ops == &aer_inj_pci_ops)
3094696b828SBjorn Helgaas goto out;
3104696b828SBjorn Helgaas pci_bus_ops_init(bus_ops, bus, ops);
3114696b828SBjorn Helgaas list_add(&bus_ops->list, &pci_bus_ops_list);
3124696b828SBjorn Helgaas bus_ops = NULL;
3134696b828SBjorn Helgaas out:
3144696b828SBjorn Helgaas spin_unlock_irqrestore(&inject_lock, flags);
3154696b828SBjorn Helgaas kfree(bus_ops);
3164696b828SBjorn Helgaas return 0;
3174696b828SBjorn Helgaas }
3184696b828SBjorn Helgaas
aer_inject(struct aer_error_inj * einj)3194696b828SBjorn Helgaas static int aer_inject(struct aer_error_inj *einj)
3204696b828SBjorn Helgaas {
3214696b828SBjorn Helgaas struct aer_error *err, *rperr;
3224696b828SBjorn Helgaas struct aer_error *err_alloc = NULL, *rperr_alloc = NULL;
3234696b828SBjorn Helgaas struct pci_dev *dev, *rpdev;
3244696b828SBjorn Helgaas struct pcie_device *edev;
3250e98db25SKeith Busch struct device *device;
3264696b828SBjorn Helgaas unsigned long flags;
3274696b828SBjorn Helgaas unsigned int devfn = PCI_DEVFN(einj->dev, einj->fn);
3284696b828SBjorn Helgaas int pos_cap_err, rp_pos_cap_err;
3294696b828SBjorn Helgaas u32 sever, cor_mask, uncor_mask, cor_mask_orig = 0, uncor_mask_orig = 0;
3304696b828SBjorn Helgaas int ret = 0;
3314696b828SBjorn Helgaas
3324696b828SBjorn Helgaas dev = pci_get_domain_bus_and_slot(einj->domain, einj->bus, devfn);
3334696b828SBjorn Helgaas if (!dev)
3344696b828SBjorn Helgaas return -ENODEV;
3354696b828SBjorn Helgaas rpdev = pcie_find_root_port(dev);
336d292dd0eSQiuxu Zhuo /* If Root Port not found, try to find an RCEC */
337d292dd0eSQiuxu Zhuo if (!rpdev)
338d292dd0eSQiuxu Zhuo rpdev = dev->rcec;
3394696b828SBjorn Helgaas if (!rpdev) {
340d292dd0eSQiuxu Zhuo pci_err(dev, "Neither Root Port nor RCEC found\n");
3414696b828SBjorn Helgaas ret = -ENODEV;
3424696b828SBjorn Helgaas goto out_put;
3434696b828SBjorn Helgaas }
3444696b828SBjorn Helgaas
3454696b828SBjorn Helgaas pos_cap_err = dev->aer_cap;
3464696b828SBjorn Helgaas if (!pos_cap_err) {
3479cc6f75bSFrederick Lawler pci_err(dev, "Device doesn't support AER\n");
3484696b828SBjorn Helgaas ret = -EPROTONOSUPPORT;
3494696b828SBjorn Helgaas goto out_put;
3504696b828SBjorn Helgaas }
3514696b828SBjorn Helgaas pci_read_config_dword(dev, pos_cap_err + PCI_ERR_UNCOR_SEVER, &sever);
3524696b828SBjorn Helgaas pci_read_config_dword(dev, pos_cap_err + PCI_ERR_COR_MASK, &cor_mask);
3534696b828SBjorn Helgaas pci_read_config_dword(dev, pos_cap_err + PCI_ERR_UNCOR_MASK,
3544696b828SBjorn Helgaas &uncor_mask);
3554696b828SBjorn Helgaas
3564696b828SBjorn Helgaas rp_pos_cap_err = rpdev->aer_cap;
3574696b828SBjorn Helgaas if (!rp_pos_cap_err) {
3589cc6f75bSFrederick Lawler pci_err(rpdev, "Root port doesn't support AER\n");
3594696b828SBjorn Helgaas ret = -EPROTONOSUPPORT;
3604696b828SBjorn Helgaas goto out_put;
3614696b828SBjorn Helgaas }
3624696b828SBjorn Helgaas
3634696b828SBjorn Helgaas err_alloc = kzalloc(sizeof(struct aer_error), GFP_KERNEL);
3644696b828SBjorn Helgaas if (!err_alloc) {
3654696b828SBjorn Helgaas ret = -ENOMEM;
3664696b828SBjorn Helgaas goto out_put;
3674696b828SBjorn Helgaas }
3684696b828SBjorn Helgaas rperr_alloc = kzalloc(sizeof(struct aer_error), GFP_KERNEL);
3694696b828SBjorn Helgaas if (!rperr_alloc) {
3704696b828SBjorn Helgaas ret = -ENOMEM;
3714696b828SBjorn Helgaas goto out_put;
3724696b828SBjorn Helgaas }
3734696b828SBjorn Helgaas
3744696b828SBjorn Helgaas if (aer_mask_override) {
3754696b828SBjorn Helgaas cor_mask_orig = cor_mask;
3764696b828SBjorn Helgaas cor_mask &= !(einj->cor_status);
3774696b828SBjorn Helgaas pci_write_config_dword(dev, pos_cap_err + PCI_ERR_COR_MASK,
3784696b828SBjorn Helgaas cor_mask);
3794696b828SBjorn Helgaas
3804696b828SBjorn Helgaas uncor_mask_orig = uncor_mask;
3814696b828SBjorn Helgaas uncor_mask &= !(einj->uncor_status);
3824696b828SBjorn Helgaas pci_write_config_dword(dev, pos_cap_err + PCI_ERR_UNCOR_MASK,
3834696b828SBjorn Helgaas uncor_mask);
3844696b828SBjorn Helgaas }
3854696b828SBjorn Helgaas
3864696b828SBjorn Helgaas spin_lock_irqsave(&inject_lock, flags);
3874696b828SBjorn Helgaas
3884696b828SBjorn Helgaas err = __find_aer_error_by_dev(dev);
3894696b828SBjorn Helgaas if (!err) {
3904696b828SBjorn Helgaas err = err_alloc;
3914696b828SBjorn Helgaas err_alloc = NULL;
3924696b828SBjorn Helgaas aer_error_init(err, einj->domain, einj->bus, devfn,
3934696b828SBjorn Helgaas pos_cap_err);
3944696b828SBjorn Helgaas list_add(&err->list, &einjected);
3954696b828SBjorn Helgaas }
3964696b828SBjorn Helgaas err->uncor_status |= einj->uncor_status;
3974696b828SBjorn Helgaas err->cor_status |= einj->cor_status;
3984696b828SBjorn Helgaas err->header_log0 = einj->header_log0;
3994696b828SBjorn Helgaas err->header_log1 = einj->header_log1;
4004696b828SBjorn Helgaas err->header_log2 = einj->header_log2;
4014696b828SBjorn Helgaas err->header_log3 = einj->header_log3;
4024696b828SBjorn Helgaas
4034696b828SBjorn Helgaas if (!aer_mask_override && einj->cor_status &&
4044696b828SBjorn Helgaas !(einj->cor_status & ~cor_mask)) {
4054696b828SBjorn Helgaas ret = -EINVAL;
4069cc6f75bSFrederick Lawler pci_warn(dev, "The correctable error(s) is masked by device\n");
4074696b828SBjorn Helgaas spin_unlock_irqrestore(&inject_lock, flags);
4084696b828SBjorn Helgaas goto out_put;
4094696b828SBjorn Helgaas }
4104696b828SBjorn Helgaas if (!aer_mask_override && einj->uncor_status &&
4114696b828SBjorn Helgaas !(einj->uncor_status & ~uncor_mask)) {
4124696b828SBjorn Helgaas ret = -EINVAL;
4139cc6f75bSFrederick Lawler pci_warn(dev, "The uncorrectable error(s) is masked by device\n");
4144696b828SBjorn Helgaas spin_unlock_irqrestore(&inject_lock, flags);
4154696b828SBjorn Helgaas goto out_put;
4164696b828SBjorn Helgaas }
4174696b828SBjorn Helgaas
4184696b828SBjorn Helgaas rperr = __find_aer_error_by_dev(rpdev);
4194696b828SBjorn Helgaas if (!rperr) {
4204696b828SBjorn Helgaas rperr = rperr_alloc;
4214696b828SBjorn Helgaas rperr_alloc = NULL;
4224696b828SBjorn Helgaas aer_error_init(rperr, pci_domain_nr(rpdev->bus),
4234696b828SBjorn Helgaas rpdev->bus->number, rpdev->devfn,
4244696b828SBjorn Helgaas rp_pos_cap_err);
4254696b828SBjorn Helgaas list_add(&rperr->list, &einjected);
4264696b828SBjorn Helgaas }
4274696b828SBjorn Helgaas if (einj->cor_status) {
4284696b828SBjorn Helgaas if (rperr->root_status & PCI_ERR_ROOT_COR_RCV)
4294696b828SBjorn Helgaas rperr->root_status |= PCI_ERR_ROOT_MULTI_COR_RCV;
4304696b828SBjorn Helgaas else
4314696b828SBjorn Helgaas rperr->root_status |= PCI_ERR_ROOT_COR_RCV;
4324696b828SBjorn Helgaas rperr->source_id &= 0xffff0000;
4334696b828SBjorn Helgaas rperr->source_id |= (einj->bus << 8) | devfn;
4344696b828SBjorn Helgaas }
4354696b828SBjorn Helgaas if (einj->uncor_status) {
4364696b828SBjorn Helgaas if (rperr->root_status & PCI_ERR_ROOT_UNCOR_RCV)
4374696b828SBjorn Helgaas rperr->root_status |= PCI_ERR_ROOT_MULTI_UNCOR_RCV;
4384696b828SBjorn Helgaas if (sever & einj->uncor_status) {
4394696b828SBjorn Helgaas rperr->root_status |= PCI_ERR_ROOT_FATAL_RCV;
4404696b828SBjorn Helgaas if (!(rperr->root_status & PCI_ERR_ROOT_UNCOR_RCV))
4414696b828SBjorn Helgaas rperr->root_status |= PCI_ERR_ROOT_FIRST_FATAL;
4424696b828SBjorn Helgaas } else
4434696b828SBjorn Helgaas rperr->root_status |= PCI_ERR_ROOT_NONFATAL_RCV;
4444696b828SBjorn Helgaas rperr->root_status |= PCI_ERR_ROOT_UNCOR_RCV;
4454696b828SBjorn Helgaas rperr->source_id &= 0x0000ffff;
4464696b828SBjorn Helgaas rperr->source_id |= ((einj->bus << 8) | devfn) << 16;
4474696b828SBjorn Helgaas }
4484696b828SBjorn Helgaas spin_unlock_irqrestore(&inject_lock, flags);
4494696b828SBjorn Helgaas
4504696b828SBjorn Helgaas if (aer_mask_override) {
4514696b828SBjorn Helgaas pci_write_config_dword(dev, pos_cap_err + PCI_ERR_COR_MASK,
4524696b828SBjorn Helgaas cor_mask_orig);
4534696b828SBjorn Helgaas pci_write_config_dword(dev, pos_cap_err + PCI_ERR_UNCOR_MASK,
4544696b828SBjorn Helgaas uncor_mask_orig);
4554696b828SBjorn Helgaas }
4564696b828SBjorn Helgaas
4574696b828SBjorn Helgaas ret = pci_bus_set_aer_ops(dev->bus);
4584696b828SBjorn Helgaas if (ret)
4594696b828SBjorn Helgaas goto out_put;
4604696b828SBjorn Helgaas ret = pci_bus_set_aer_ops(rpdev->bus);
4614696b828SBjorn Helgaas if (ret)
4624696b828SBjorn Helgaas goto out_put;
4634696b828SBjorn Helgaas
4640e98db25SKeith Busch device = pcie_port_find_device(rpdev, PCIE_PORT_SERVICE_AER);
4650e98db25SKeith Busch if (device) {
4660e98db25SKeith Busch edev = to_pcie_device(device);
4674696b828SBjorn Helgaas if (!get_service_data(edev)) {
4689cc6f75bSFrederick Lawler pci_warn(edev->port, "AER service is not initialized\n");
4694696b828SBjorn Helgaas ret = -EPROTONOSUPPORT;
4704696b828SBjorn Helgaas goto out_put;
4714696b828SBjorn Helgaas }
4729cc6f75bSFrederick Lawler pci_info(edev->port, "Injecting errors %08x/%08x into device %s\n",
4734696b828SBjorn Helgaas einj->cor_status, einj->uncor_status, pci_name(dev));
4749ae05225SThomas Gleixner ret = irq_inject_interrupt(edev->irq);
4754696b828SBjorn Helgaas } else {
4769cc6f75bSFrederick Lawler pci_err(rpdev, "AER device not found\n");
4774696b828SBjorn Helgaas ret = -ENODEV;
4784696b828SBjorn Helgaas }
4794696b828SBjorn Helgaas out_put:
4804696b828SBjorn Helgaas kfree(err_alloc);
4814696b828SBjorn Helgaas kfree(rperr_alloc);
4824696b828SBjorn Helgaas pci_dev_put(dev);
4834696b828SBjorn Helgaas return ret;
4844696b828SBjorn Helgaas }
4854696b828SBjorn Helgaas
aer_inject_write(struct file * filp,const char __user * ubuf,size_t usize,loff_t * off)4864696b828SBjorn Helgaas static ssize_t aer_inject_write(struct file *filp, const char __user *ubuf,
4874696b828SBjorn Helgaas size_t usize, loff_t *off)
4884696b828SBjorn Helgaas {
4894696b828SBjorn Helgaas struct aer_error_inj einj;
4904696b828SBjorn Helgaas int ret;
4914696b828SBjorn Helgaas
4924696b828SBjorn Helgaas if (!capable(CAP_SYS_ADMIN))
4934696b828SBjorn Helgaas return -EPERM;
4944696b828SBjorn Helgaas if (usize < offsetof(struct aer_error_inj, domain) ||
4954696b828SBjorn Helgaas usize > sizeof(einj))
4964696b828SBjorn Helgaas return -EINVAL;
4974696b828SBjorn Helgaas
4984696b828SBjorn Helgaas memset(&einj, 0, sizeof(einj));
4994696b828SBjorn Helgaas if (copy_from_user(&einj, ubuf, usize))
5004696b828SBjorn Helgaas return -EFAULT;
5014696b828SBjorn Helgaas
5024696b828SBjorn Helgaas ret = aer_inject(&einj);
5034696b828SBjorn Helgaas return ret ? ret : usize;
5044696b828SBjorn Helgaas }
5054696b828SBjorn Helgaas
5064696b828SBjorn Helgaas static const struct file_operations aer_inject_fops = {
5074696b828SBjorn Helgaas .write = aer_inject_write,
5084696b828SBjorn Helgaas .owner = THIS_MODULE,
5094696b828SBjorn Helgaas .llseek = noop_llseek,
5104696b828SBjorn Helgaas };
5114696b828SBjorn Helgaas
5124696b828SBjorn Helgaas static struct miscdevice aer_inject_device = {
5134696b828SBjorn Helgaas .minor = MISC_DYNAMIC_MINOR,
5144696b828SBjorn Helgaas .name = "aer_inject",
5154696b828SBjorn Helgaas .fops = &aer_inject_fops,
5164696b828SBjorn Helgaas };
5174696b828SBjorn Helgaas
aer_inject_init(void)5184696b828SBjorn Helgaas static int __init aer_inject_init(void)
5194696b828SBjorn Helgaas {
5204696b828SBjorn Helgaas return misc_register(&aer_inject_device);
5214696b828SBjorn Helgaas }
5224696b828SBjorn Helgaas
aer_inject_exit(void)5234696b828SBjorn Helgaas static void __exit aer_inject_exit(void)
5244696b828SBjorn Helgaas {
5254696b828SBjorn Helgaas struct aer_error *err, *err_next;
5264696b828SBjorn Helgaas unsigned long flags;
5274696b828SBjorn Helgaas struct pci_bus_ops *bus_ops;
5284696b828SBjorn Helgaas
5294696b828SBjorn Helgaas misc_deregister(&aer_inject_device);
5304696b828SBjorn Helgaas
5314696b828SBjorn Helgaas while ((bus_ops = pci_bus_ops_pop())) {
5324696b828SBjorn Helgaas pci_bus_set_ops(bus_ops->bus, bus_ops->ops);
5334696b828SBjorn Helgaas kfree(bus_ops);
5344696b828SBjorn Helgaas }
5354696b828SBjorn Helgaas
5364696b828SBjorn Helgaas spin_lock_irqsave(&inject_lock, flags);
5374696b828SBjorn Helgaas list_for_each_entry_safe(err, err_next, &einjected, list) {
5384696b828SBjorn Helgaas list_del(&err->list);
5394696b828SBjorn Helgaas kfree(err);
5404696b828SBjorn Helgaas }
5414696b828SBjorn Helgaas spin_unlock_irqrestore(&inject_lock, flags);
5424696b828SBjorn Helgaas }
5434696b828SBjorn Helgaas
5444696b828SBjorn Helgaas module_init(aer_inject_init);
5454696b828SBjorn Helgaas module_exit(aer_inject_exit);
5464696b828SBjorn Helgaas
5474696b828SBjorn Helgaas MODULE_DESCRIPTION("PCIe AER software error injector");
5484696b828SBjorn Helgaas MODULE_LICENSE("GPL");
549