xref: /openbmc/linux/drivers/pci/pcie/aer_inject.c (revision 4f2c0a4acffbec01079c28f839422e64ddeff004)
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