blob: 8c30a9544d61f07176fbf69a59b84c2afabf9e8a [file] [log] [blame]
Huang Yingc465def2009-06-15 10:42:57 +08001/*
Stefan Assmann45e829e2009-12-03 06:49:24 -05002 * PCIe AER software error injection support.
Huang Yingc465def2009-06-15 10:42:57 +08003 *
Stefan Assmann45e829e2009-12-03 06:49:24 -05004 * Debuging PCIe AER code is quite difficult because it is hard to
Huang Yingc465def2009-06-15 10:42:57 +08005 * trigger various real hardware errors. Software based error
6 * injection can fake almost all kinds of errors with the help of a
7 * user space helper tool aer-inject, which can be gotten from:
8 * http://www.kernel.org/pub/linux/utils/pci/aer-inject/
9 *
10 * Copyright 2009 Intel Corporation.
11 * Huang Ying <ying.huang@intel.com>
12 *
13 * This program is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU General Public License
15 * as published by the Free Software Foundation; version 2
16 * of the License.
17 *
18 */
19
20#include <linux/module.h>
21#include <linux/init.h>
22#include <linux/miscdevice.h>
23#include <linux/pci.h>
24#include <linux/fs.h>
Hidetoshi Setoc9a91882009-09-07 17:07:29 +090025#include <linux/uaccess.h>
Andrew Pattersoncc5d1532009-10-12 13:14:05 -060026#include <linux/stddef.h>
Huang Yingc465def2009-06-15 10:42:57 +080027#include "aerdrv.h"
28
Hidetoshi Setoc9a91882009-09-07 17:07:29 +090029struct aer_error_inj {
Huang Yingc465def2009-06-15 10:42:57 +080030 u8 bus;
31 u8 dev;
32 u8 fn;
33 u32 uncor_status;
34 u32 cor_status;
35 u32 header_log0;
36 u32 header_log1;
37 u32 header_log2;
38 u32 header_log3;
Andrew Pattersoncc5d1532009-10-12 13:14:05 -060039 u16 domain;
Huang Yingc465def2009-06-15 10:42:57 +080040};
41
Hidetoshi Setoc9a91882009-09-07 17:07:29 +090042struct aer_error {
Huang Yingc465def2009-06-15 10:42:57 +080043 struct list_head list;
Andrew Pattersoncc5d1532009-10-12 13:14:05 -060044 u16 domain;
Huang Yingc465def2009-06-15 10:42:57 +080045 unsigned int bus;
46 unsigned int devfn;
47 int pos_cap_err;
48
49 u32 uncor_status;
50 u32 cor_status;
51 u32 header_log0;
52 u32 header_log1;
53 u32 header_log2;
54 u32 header_log3;
55 u32 root_status;
56 u32 source_id;
57};
58
Hidetoshi Setoc9a91882009-09-07 17:07:29 +090059struct pci_bus_ops {
Huang Yingc465def2009-06-15 10:42:57 +080060 struct list_head list;
61 struct pci_bus *bus;
62 struct pci_ops *ops;
63};
64
65static LIST_HEAD(einjected);
66
67static LIST_HEAD(pci_bus_ops_list);
68
69/* Protect einjected and pci_bus_ops_list */
70static DEFINE_SPINLOCK(inject_lock);
71
Andrew Pattersoncc5d1532009-10-12 13:14:05 -060072static void aer_error_init(struct aer_error *err, u16 domain,
73 unsigned int bus, unsigned int devfn,
74 int pos_cap_err)
Huang Yingc465def2009-06-15 10:42:57 +080075{
76 INIT_LIST_HEAD(&err->list);
Andrew Pattersoncc5d1532009-10-12 13:14:05 -060077 err->domain = domain;
Huang Yingc465def2009-06-15 10:42:57 +080078 err->bus = bus;
79 err->devfn = devfn;
80 err->pos_cap_err = pos_cap_err;
81}
82
83/* inject_lock must be held before calling */
Andrew Pattersoncc5d1532009-10-12 13:14:05 -060084static struct aer_error *__find_aer_error(u16 domain, unsigned int bus,
85 unsigned int devfn)
Huang Yingc465def2009-06-15 10:42:57 +080086{
87 struct aer_error *err;
88
89 list_for_each_entry(err, &einjected, list) {
Andrew Pattersoncc5d1532009-10-12 13:14:05 -060090 if (domain == err->domain &&
91 bus == err->bus &&
92 devfn == err->devfn)
Huang Yingc465def2009-06-15 10:42:57 +080093 return err;
94 }
95 return NULL;
96}
97
98/* inject_lock must be held before calling */
99static struct aer_error *__find_aer_error_by_dev(struct pci_dev *dev)
100{
Andrew Pattersoncc5d1532009-10-12 13:14:05 -0600101 int domain = pci_domain_nr(dev->bus);
102 if (domain < 0)
103 return NULL;
104 return __find_aer_error((u16)domain, dev->bus->number, dev->devfn);
Huang Yingc465def2009-06-15 10:42:57 +0800105}
106
107/* inject_lock must be held before calling */
108static struct pci_ops *__find_pci_bus_ops(struct pci_bus *bus)
109{
110 struct pci_bus_ops *bus_ops;
111
112 list_for_each_entry(bus_ops, &pci_bus_ops_list, list) {
113 if (bus_ops->bus == bus)
114 return bus_ops->ops;
115 }
116 return NULL;
117}
118
119static struct pci_bus_ops *pci_bus_ops_pop(void)
120{
121 unsigned long flags;
122 struct pci_bus_ops *bus_ops = NULL;
123
124 spin_lock_irqsave(&inject_lock, flags);
125 if (list_empty(&pci_bus_ops_list))
126 bus_ops = NULL;
127 else {
128 struct list_head *lh = pci_bus_ops_list.next;
129 list_del(lh);
130 bus_ops = list_entry(lh, struct pci_bus_ops, list);
131 }
132 spin_unlock_irqrestore(&inject_lock, flags);
133 return bus_ops;
134}
135
136static u32 *find_pci_config_dword(struct aer_error *err, int where,
137 int *prw1cs)
138{
139 int rw1cs = 0;
140 u32 *target = NULL;
141
142 if (err->pos_cap_err == -1)
143 return NULL;
144
145 switch (where - err->pos_cap_err) {
146 case PCI_ERR_UNCOR_STATUS:
147 target = &err->uncor_status;
148 rw1cs = 1;
149 break;
150 case PCI_ERR_COR_STATUS:
151 target = &err->cor_status;
152 rw1cs = 1;
153 break;
154 case PCI_ERR_HEADER_LOG:
155 target = &err->header_log0;
156 break;
157 case PCI_ERR_HEADER_LOG+4:
158 target = &err->header_log1;
159 break;
160 case PCI_ERR_HEADER_LOG+8:
Hidetoshi Setoc9a91882009-09-07 17:07:29 +0900161 target = &err->header_log2;
Huang Yingc465def2009-06-15 10:42:57 +0800162 break;
163 case PCI_ERR_HEADER_LOG+12:
164 target = &err->header_log3;
165 break;
166 case PCI_ERR_ROOT_STATUS:
167 target = &err->root_status;
168 rw1cs = 1;
169 break;
170 case PCI_ERR_ROOT_COR_SRC:
171 target = &err->source_id;
172 break;
173 }
174 if (prw1cs)
175 *prw1cs = rw1cs;
176 return target;
177}
178
179static int pci_read_aer(struct pci_bus *bus, unsigned int devfn, int where,
180 int size, u32 *val)
181{
182 u32 *sim;
183 struct aer_error *err;
184 unsigned long flags;
185 struct pci_ops *ops;
Andrew Pattersoncc5d1532009-10-12 13:14:05 -0600186 int domain;
Huang Yingc465def2009-06-15 10:42:57 +0800187
188 spin_lock_irqsave(&inject_lock, flags);
189 if (size != sizeof(u32))
190 goto out;
Andrew Pattersoncc5d1532009-10-12 13:14:05 -0600191 domain = pci_domain_nr(bus);
192 if (domain < 0)
193 goto out;
194 err = __find_aer_error((u16)domain, bus->number, devfn);
Huang Yingc465def2009-06-15 10:42:57 +0800195 if (!err)
196 goto out;
197
198 sim = find_pci_config_dword(err, where, NULL);
199 if (sim) {
200 *val = *sim;
201 spin_unlock_irqrestore(&inject_lock, flags);
202 return 0;
203 }
204out:
205 ops = __find_pci_bus_ops(bus);
206 spin_unlock_irqrestore(&inject_lock, flags);
207 return ops->read(bus, devfn, where, size, val);
208}
209
210int pci_write_aer(struct pci_bus *bus, unsigned int devfn, int where, int size,
211 u32 val)
212{
213 u32 *sim;
214 struct aer_error *err;
215 unsigned long flags;
216 int rw1cs;
217 struct pci_ops *ops;
Andrew Pattersoncc5d1532009-10-12 13:14:05 -0600218 int domain;
Huang Yingc465def2009-06-15 10:42:57 +0800219
220 spin_lock_irqsave(&inject_lock, flags);
221 if (size != sizeof(u32))
222 goto out;
Andrew Pattersoncc5d1532009-10-12 13:14:05 -0600223 domain = pci_domain_nr(bus);
224 if (domain < 0)
225 goto out;
226 err = __find_aer_error((u16)domain, bus->number, devfn);
Huang Yingc465def2009-06-15 10:42:57 +0800227 if (!err)
228 goto out;
229
230 sim = find_pci_config_dword(err, where, &rw1cs);
231 if (sim) {
232 if (rw1cs)
233 *sim ^= val;
234 else
235 *sim = val;
236 spin_unlock_irqrestore(&inject_lock, flags);
237 return 0;
238 }
239out:
240 ops = __find_pci_bus_ops(bus);
241 spin_unlock_irqrestore(&inject_lock, flags);
242 return ops->write(bus, devfn, where, size, val);
243}
244
245static struct pci_ops pci_ops_aer = {
246 .read = pci_read_aer,
247 .write = pci_write_aer,
248};
249
250static void pci_bus_ops_init(struct pci_bus_ops *bus_ops,
251 struct pci_bus *bus,
252 struct pci_ops *ops)
253{
254 INIT_LIST_HEAD(&bus_ops->list);
255 bus_ops->bus = bus;
256 bus_ops->ops = ops;
257}
258
259static int pci_bus_set_aer_ops(struct pci_bus *bus)
260{
261 struct pci_ops *ops;
262 struct pci_bus_ops *bus_ops;
263 unsigned long flags;
264
265 bus_ops = kmalloc(sizeof(*bus_ops), GFP_KERNEL);
266 if (!bus_ops)
267 return -ENOMEM;
268 ops = pci_bus_set_ops(bus, &pci_ops_aer);
269 spin_lock_irqsave(&inject_lock, flags);
270 if (ops == &pci_ops_aer)
271 goto out;
272 pci_bus_ops_init(bus_ops, bus, ops);
273 list_add(&bus_ops->list, &pci_bus_ops_list);
274 bus_ops = NULL;
275out:
276 spin_unlock_irqrestore(&inject_lock, flags);
Hidetoshi Setoc9a91882009-09-07 17:07:29 +0900277 kfree(bus_ops);
Huang Yingc465def2009-06-15 10:42:57 +0800278 return 0;
279}
280
281static struct pci_dev *pcie_find_root_port(struct pci_dev *dev)
282{
283 while (1) {
Kenji Kaneshigeb44d7db2009-11-11 14:37:24 +0900284 if (!pci_is_pcie(dev))
Huang Yingc465def2009-06-15 10:42:57 +0800285 break;
286 if (dev->pcie_type == PCI_EXP_TYPE_ROOT_PORT)
287 return dev;
288 if (!dev->bus->self)
289 break;
290 dev = dev->bus->self;
291 }
292 return NULL;
293}
294
295static int find_aer_device_iter(struct device *device, void *data)
296{
297 struct pcie_device **result = data;
298 struct pcie_device *pcie_dev;
299
300 if (device->bus == &pcie_port_bus_type) {
301 pcie_dev = to_pcie_device(device);
302 if (pcie_dev->service & PCIE_PORT_SERVICE_AER) {
303 *result = pcie_dev;
304 return 1;
305 }
306 }
307 return 0;
308}
309
310static int find_aer_device(struct pci_dev *dev, struct pcie_device **result)
311{
312 return device_for_each_child(&dev->dev, result, find_aer_device_iter);
313}
314
315static int aer_inject(struct aer_error_inj *einj)
316{
317 struct aer_error *err, *rperr;
318 struct aer_error *err_alloc = NULL, *rperr_alloc = NULL;
319 struct pci_dev *dev, *rpdev;
320 struct pcie_device *edev;
321 unsigned long flags;
322 unsigned int devfn = PCI_DEVFN(einj->dev, einj->fn);
323 int pos_cap_err, rp_pos_cap_err;
Youquan,Songb49bfd32009-12-17 08:22:48 -0500324 u32 sever, mask;
Huang Yingc465def2009-06-15 10:42:57 +0800325 int ret = 0;
326
Andrew Pattersoncc5d1532009-10-12 13:14:05 -0600327 dev = pci_get_domain_bus_and_slot((int)einj->domain, einj->bus, devfn);
Huang Yingc465def2009-06-15 10:42:57 +0800328 if (!dev)
Andrew Patterson1d024352009-10-12 13:14:10 -0600329 return -ENODEV;
Huang Yingc465def2009-06-15 10:42:57 +0800330 rpdev = pcie_find_root_port(dev);
331 if (!rpdev) {
Andrew Patterson1d024352009-10-12 13:14:10 -0600332 ret = -ENOTTY;
Huang Yingc465def2009-06-15 10:42:57 +0800333 goto out_put;
334 }
335
336 pos_cap_err = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ERR);
337 if (!pos_cap_err) {
Andrew Patterson1d024352009-10-12 13:14:10 -0600338 ret = -ENOTTY;
Huang Yingc465def2009-06-15 10:42:57 +0800339 goto out_put;
340 }
341 pci_read_config_dword(dev, pos_cap_err + PCI_ERR_UNCOR_SEVER, &sever);
342
343 rp_pos_cap_err = pci_find_ext_capability(rpdev, PCI_EXT_CAP_ID_ERR);
344 if (!rp_pos_cap_err) {
Andrew Patterson1d024352009-10-12 13:14:10 -0600345 ret = -ENOTTY;
Huang Yingc465def2009-06-15 10:42:57 +0800346 goto out_put;
347 }
348
349 err_alloc = kzalloc(sizeof(struct aer_error), GFP_KERNEL);
350 if (!err_alloc) {
351 ret = -ENOMEM;
352 goto out_put;
353 }
354 rperr_alloc = kzalloc(sizeof(struct aer_error), GFP_KERNEL);
355 if (!rperr_alloc) {
356 ret = -ENOMEM;
357 goto out_put;
358 }
359
360 spin_lock_irqsave(&inject_lock, flags);
361
362 err = __find_aer_error_by_dev(dev);
363 if (!err) {
364 err = err_alloc;
365 err_alloc = NULL;
Andrew Pattersoncc5d1532009-10-12 13:14:05 -0600366 aer_error_init(err, einj->domain, einj->bus, devfn,
367 pos_cap_err);
Huang Yingc465def2009-06-15 10:42:57 +0800368 list_add(&err->list, &einjected);
369 }
370 err->uncor_status |= einj->uncor_status;
371 err->cor_status |= einj->cor_status;
372 err->header_log0 = einj->header_log0;
373 err->header_log1 = einj->header_log1;
374 err->header_log2 = einj->header_log2;
375 err->header_log3 = einj->header_log3;
376
Youquan,Songb49bfd32009-12-17 08:22:48 -0500377 pci_read_config_dword(dev, pos_cap_err + PCI_ERR_COR_MASK, &mask);
378 if (einj->cor_status && !(einj->cor_status & ~mask)) {
379 ret = -EINVAL;
380 printk(KERN_WARNING "The correctable error(s) is masked "
381 "by device\n");
382 spin_unlock_irqrestore(&inject_lock, flags);
383 goto out_put;
384 }
385
386 pci_read_config_dword(dev, pos_cap_err + PCI_ERR_UNCOR_MASK, &mask);
387 if (einj->uncor_status && !(einj->uncor_status & ~mask)) {
388 ret = -EINVAL;
389 printk(KERN_WARNING "The uncorrectable error(s) is masked "
390 "by device\n");
391 spin_unlock_irqrestore(&inject_lock, flags);
392 goto out_put;
393 }
394
Huang Yingc465def2009-06-15 10:42:57 +0800395 rperr = __find_aer_error_by_dev(rpdev);
396 if (!rperr) {
397 rperr = rperr_alloc;
398 rperr_alloc = NULL;
Andrew Pattersoncc5d1532009-10-12 13:14:05 -0600399 aer_error_init(rperr, pci_domain_nr(rpdev->bus),
400 rpdev->bus->number, rpdev->devfn,
Huang Yingc465def2009-06-15 10:42:57 +0800401 rp_pos_cap_err);
402 list_add(&rperr->list, &einjected);
403 }
404 if (einj->cor_status) {
405 if (rperr->root_status & PCI_ERR_ROOT_COR_RCV)
406 rperr->root_status |= PCI_ERR_ROOT_MULTI_COR_RCV;
407 else
408 rperr->root_status |= PCI_ERR_ROOT_COR_RCV;
409 rperr->source_id &= 0xffff0000;
410 rperr->source_id |= (einj->bus << 8) | devfn;
411 }
412 if (einj->uncor_status) {
413 if (rperr->root_status & PCI_ERR_ROOT_UNCOR_RCV)
414 rperr->root_status |= PCI_ERR_ROOT_MULTI_UNCOR_RCV;
415 if (sever & einj->uncor_status) {
416 rperr->root_status |= PCI_ERR_ROOT_FATAL_RCV;
417 if (!(rperr->root_status & PCI_ERR_ROOT_UNCOR_RCV))
418 rperr->root_status |= PCI_ERR_ROOT_FIRST_FATAL;
419 } else
420 rperr->root_status |= PCI_ERR_ROOT_NONFATAL_RCV;
421 rperr->root_status |= PCI_ERR_ROOT_UNCOR_RCV;
422 rperr->source_id &= 0x0000ffff;
423 rperr->source_id |= ((einj->bus << 8) | devfn) << 16;
424 }
425 spin_unlock_irqrestore(&inject_lock, flags);
426
427 ret = pci_bus_set_aer_ops(dev->bus);
428 if (ret)
429 goto out_put;
430 ret = pci_bus_set_aer_ops(rpdev->bus);
431 if (ret)
432 goto out_put;
433
Youquan,Song46256f82009-12-11 18:42:35 -0500434 if (find_aer_device(rpdev, &edev)) {
435 if (!get_service_data(edev)) {
436 printk(KERN_WARNING "AER service is not initialized\n");
437 ret = -EINVAL;
438 goto out_put;
439 }
Huang Yingc465def2009-06-15 10:42:57 +0800440 aer_irq(-1, edev);
Youquan,Song46256f82009-12-11 18:42:35 -0500441 }
Huang Yingc465def2009-06-15 10:42:57 +0800442 else
443 ret = -EINVAL;
444out_put:
Hidetoshi Setoc9a91882009-09-07 17:07:29 +0900445 kfree(err_alloc);
446 kfree(rperr_alloc);
Huang Yingc465def2009-06-15 10:42:57 +0800447 pci_dev_put(dev);
448 return ret;
449}
450
451static ssize_t aer_inject_write(struct file *filp, const char __user *ubuf,
452 size_t usize, loff_t *off)
453{
454 struct aer_error_inj einj;
455 int ret;
456
457 if (!capable(CAP_SYS_ADMIN))
458 return -EPERM;
Andrew Pattersoncc5d1532009-10-12 13:14:05 -0600459 if (usize < offsetof(struct aer_error_inj, domain) ||
460 usize > sizeof(einj))
Huang Yingc465def2009-06-15 10:42:57 +0800461 return -EINVAL;
462
Andrew Pattersoncc5d1532009-10-12 13:14:05 -0600463 memset(&einj, 0, sizeof(einj));
Huang Yingc465def2009-06-15 10:42:57 +0800464 if (copy_from_user(&einj, ubuf, usize))
465 return -EFAULT;
466
467 ret = aer_inject(&einj);
468 return ret ? ret : usize;
469}
470
471static const struct file_operations aer_inject_fops = {
472 .write = aer_inject_write,
473 .owner = THIS_MODULE,
474};
475
476static struct miscdevice aer_inject_device = {
477 .minor = MISC_DYNAMIC_MINOR,
478 .name = "aer_inject",
479 .fops = &aer_inject_fops,
480};
481
482static int __init aer_inject_init(void)
483{
484 return misc_register(&aer_inject_device);
485}
486
487static void __exit aer_inject_exit(void)
488{
489 struct aer_error *err, *err_next;
490 unsigned long flags;
491 struct pci_bus_ops *bus_ops;
492
493 misc_deregister(&aer_inject_device);
494
495 while ((bus_ops = pci_bus_ops_pop())) {
496 pci_bus_set_ops(bus_ops->bus, bus_ops->ops);
497 kfree(bus_ops);
498 }
499
500 spin_lock_irqsave(&inject_lock, flags);
Andrew Patterson476f6442009-10-12 13:14:15 -0600501 list_for_each_entry_safe(err, err_next, &einjected, list) {
Huang Yingc465def2009-06-15 10:42:57 +0800502 list_del(&err->list);
503 kfree(err);
504 }
505 spin_unlock_irqrestore(&inject_lock, flags);
506}
507
508module_init(aer_inject_init);
509module_exit(aer_inject_exit);
510
Stefan Assmann7e8af372009-12-03 18:00:10 +0100511MODULE_DESCRIPTION("PCIe AER software error injector");
Huang Yingc465def2009-06-15 10:42:57 +0800512MODULE_LICENSE("GPL");