diff options
author | Joerg Roedel <joerg.roedel@amd.com> | 2009-01-09 08:10:26 -0500 |
---|---|---|
committer | Joerg Roedel <joerg.roedel@amd.com> | 2009-03-05 14:35:17 -0500 |
commit | 2d62ece14fe04168a7d16688ddd2d17ac472268c (patch) | |
tree | 735c3b728bffc75331abab54c23e2939aeeba9f4 /lib/dma-debug.c | |
parent | 788dcfa6f17424695823152890d30da09f62f9c3 (diff) |
dma-debug: add core checking functions
Impact: add functions to check on dma unmap and sync
Signed-off-by: Joerg Roedel <joerg.roedel@amd.com>
Diffstat (limited to 'lib/dma-debug.c')
-rw-r--r-- | lib/dma-debug.c | 188 |
1 files changed, 187 insertions, 1 deletions
diff --git a/lib/dma-debug.c b/lib/dma-debug.c index 20d6cdbcacdc..d0cb47a4211e 100644 --- a/lib/dma-debug.c +++ b/lib/dma-debug.c | |||
@@ -17,10 +17,13 @@ | |||
17 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | 17 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
18 | */ | 18 | */ |
19 | 19 | ||
20 | #include <linux/dma-mapping.h> | ||
20 | #include <linux/dma-debug.h> | 21 | #include <linux/dma-debug.h> |
21 | #include <linux/spinlock.h> | 22 | #include <linux/spinlock.h> |
22 | #include <linux/debugfs.h> | 23 | #include <linux/debugfs.h> |
24 | #include <linux/device.h> | ||
23 | #include <linux/types.h> | 25 | #include <linux/types.h> |
26 | #include <linux/sched.h> | ||
24 | #include <linux/list.h> | 27 | #include <linux/list.h> |
25 | #include <linux/slab.h> | 28 | #include <linux/slab.h> |
26 | 29 | ||
@@ -50,7 +53,7 @@ struct dma_debug_entry { | |||
50 | struct hash_bucket { | 53 | struct hash_bucket { |
51 | struct list_head list; | 54 | struct list_head list; |
52 | spinlock_t lock; | 55 | spinlock_t lock; |
53 | } __cacheline_aligned_in_smp; | 56 | } ____cacheline_aligned_in_smp; |
54 | 57 | ||
55 | /* Hash list to save the allocated dma addresses */ | 58 | /* Hash list to save the allocated dma addresses */ |
56 | static struct hash_bucket dma_entry_hash[HASH_SIZE]; | 59 | static struct hash_bucket dma_entry_hash[HASH_SIZE]; |
@@ -85,6 +88,36 @@ static struct dentry *show_num_errors_dent __read_mostly; | |||
85 | static struct dentry *num_free_entries_dent __read_mostly; | 88 | static struct dentry *num_free_entries_dent __read_mostly; |
86 | static struct dentry *min_free_entries_dent __read_mostly; | 89 | static struct dentry *min_free_entries_dent __read_mostly; |
87 | 90 | ||
91 | static const char *type2name[4] = { "single", "page", | ||
92 | "scather-gather", "coherent" }; | ||
93 | |||
94 | static const char *dir2name[4] = { "DMA_BIDIRECTIONAL", "DMA_TO_DEVICE", | ||
95 | "DMA_FROM_DEVICE", "DMA_NONE" }; | ||
96 | |||
97 | /* | ||
98 | * The access to some variables in this macro is racy. We can't use atomic_t | ||
99 | * here because all these variables are exported to debugfs. Some of them even | ||
100 | * writeable. This is also the reason why a lock won't help much. But anyway, | ||
101 | * the races are no big deal. Here is why: | ||
102 | * | ||
103 | * error_count: the addition is racy, but the worst thing that can happen is | ||
104 | * that we don't count some errors | ||
105 | * show_num_errors: the subtraction is racy. Also no big deal because in | ||
106 | * worst case this will result in one warning more in the | ||
107 | * system log than the user configured. This variable is | ||
108 | * writeable via debugfs. | ||
109 | */ | ||
110 | #define err_printk(dev, format, arg...) do { \ | ||
111 | error_count += 1; \ | ||
112 | if (show_all_errors || show_num_errors > 0) { \ | ||
113 | WARN(1, "%s %s: " format, \ | ||
114 | dev_driver_string(dev), \ | ||
115 | dev_name(dev) , ## arg); \ | ||
116 | } \ | ||
117 | if (!show_all_errors && show_num_errors > 0) \ | ||
118 | show_num_errors -= 1; \ | ||
119 | } while (0); | ||
120 | |||
88 | /* | 121 | /* |
89 | * Hash related functions | 122 | * Hash related functions |
90 | * | 123 | * |
@@ -380,3 +413,156 @@ static __init int dma_debug_entries_cmdline(char *str) | |||
380 | __setup("dma_debug=", dma_debug_cmdline); | 413 | __setup("dma_debug=", dma_debug_cmdline); |
381 | __setup("dma_debug_entries=", dma_debug_entries_cmdline); | 414 | __setup("dma_debug_entries=", dma_debug_entries_cmdline); |
382 | 415 | ||
416 | static void check_unmap(struct dma_debug_entry *ref) | ||
417 | { | ||
418 | struct dma_debug_entry *entry; | ||
419 | struct hash_bucket *bucket; | ||
420 | unsigned long flags; | ||
421 | |||
422 | if (dma_mapping_error(ref->dev, ref->dev_addr)) | ||
423 | return; | ||
424 | |||
425 | bucket = get_hash_bucket(ref, &flags); | ||
426 | entry = hash_bucket_find(bucket, ref); | ||
427 | |||
428 | if (!entry) { | ||
429 | err_printk(ref->dev, "DMA-API: device driver tries " | ||
430 | "to free DMA memory it has not allocated " | ||
431 | "[device address=0x%016llx] [size=%llu bytes]\n", | ||
432 | ref->dev_addr, ref->size); | ||
433 | goto out; | ||
434 | } | ||
435 | |||
436 | if (ref->size != entry->size) { | ||
437 | err_printk(ref->dev, "DMA-API: device driver frees " | ||
438 | "DMA memory with different size " | ||
439 | "[device address=0x%016llx] [map size=%llu bytes] " | ||
440 | "[unmap size=%llu bytes]\n", | ||
441 | ref->dev_addr, entry->size, ref->size); | ||
442 | } | ||
443 | |||
444 | if (ref->type != entry->type) { | ||
445 | err_printk(ref->dev, "DMA-API: device driver frees " | ||
446 | "DMA memory with wrong function " | ||
447 | "[device address=0x%016llx] [size=%llu bytes] " | ||
448 | "[mapped as %s] [unmapped as %s]\n", | ||
449 | ref->dev_addr, ref->size, | ||
450 | type2name[entry->type], type2name[ref->type]); | ||
451 | } else if ((entry->type == dma_debug_coherent) && | ||
452 | (ref->paddr != entry->paddr)) { | ||
453 | err_printk(ref->dev, "DMA-API: device driver frees " | ||
454 | "DMA memory with different CPU address " | ||
455 | "[device address=0x%016llx] [size=%llu bytes] " | ||
456 | "[cpu alloc address=%p] [cpu free address=%p]", | ||
457 | ref->dev_addr, ref->size, | ||
458 | (void *)entry->paddr, (void *)ref->paddr); | ||
459 | } | ||
460 | |||
461 | if (ref->sg_call_ents && ref->type == dma_debug_sg && | ||
462 | ref->sg_call_ents != entry->sg_call_ents) { | ||
463 | err_printk(ref->dev, "DMA-API: device driver frees " | ||
464 | "DMA sg list with different entry count " | ||
465 | "[map count=%d] [unmap count=%d]\n", | ||
466 | entry->sg_call_ents, ref->sg_call_ents); | ||
467 | } | ||
468 | |||
469 | /* | ||
470 | * This may be no bug in reality - but most implementations of the | ||
471 | * DMA API don't handle this properly, so check for it here | ||
472 | */ | ||
473 | if (ref->direction != entry->direction) { | ||
474 | err_printk(ref->dev, "DMA-API: device driver frees " | ||
475 | "DMA memory with different direction " | ||
476 | "[device address=0x%016llx] [size=%llu bytes] " | ||
477 | "[mapped with %s] [unmapped with %s]\n", | ||
478 | ref->dev_addr, ref->size, | ||
479 | dir2name[entry->direction], | ||
480 | dir2name[ref->direction]); | ||
481 | } | ||
482 | |||
483 | hash_bucket_del(entry); | ||
484 | dma_entry_free(entry); | ||
485 | |||
486 | out: | ||
487 | put_hash_bucket(bucket, &flags); | ||
488 | } | ||
489 | |||
490 | static void check_for_stack(struct device *dev, void *addr) | ||
491 | { | ||
492 | if (object_is_on_stack(addr)) | ||
493 | err_printk(dev, "DMA-API: device driver maps memory from stack" | ||
494 | " [addr=%p]\n", addr); | ||
495 | } | ||
496 | |||
497 | static void check_sync(struct device *dev, dma_addr_t addr, | ||
498 | u64 size, u64 offset, int direction, bool to_cpu) | ||
499 | { | ||
500 | struct dma_debug_entry ref = { | ||
501 | .dev = dev, | ||
502 | .dev_addr = addr, | ||
503 | .size = size, | ||
504 | .direction = direction, | ||
505 | }; | ||
506 | struct dma_debug_entry *entry; | ||
507 | struct hash_bucket *bucket; | ||
508 | unsigned long flags; | ||
509 | |||
510 | bucket = get_hash_bucket(&ref, &flags); | ||
511 | |||
512 | entry = hash_bucket_find(bucket, &ref); | ||
513 | |||
514 | if (!entry) { | ||
515 | err_printk(dev, "DMA-API: device driver tries " | ||
516 | "to sync DMA memory it has not allocated " | ||
517 | "[device address=0x%016llx] [size=%llu bytes]\n", | ||
518 | addr, size); | ||
519 | goto out; | ||
520 | } | ||
521 | |||
522 | if ((offset + size) > entry->size) { | ||
523 | err_printk(dev, "DMA-API: device driver syncs" | ||
524 | " DMA memory outside allocated range " | ||
525 | "[device address=0x%016llx] " | ||
526 | "[allocation size=%llu bytes] [sync offset=%llu] " | ||
527 | "[sync size=%llu]\n", entry->dev_addr, entry->size, | ||
528 | offset, size); | ||
529 | } | ||
530 | |||
531 | if (direction != entry->direction) { | ||
532 | err_printk(dev, "DMA-API: device driver syncs " | ||
533 | "DMA memory with different direction " | ||
534 | "[device address=0x%016llx] [size=%llu bytes] " | ||
535 | "[mapped with %s] [synced with %s]\n", | ||
536 | addr, entry->size, | ||
537 | dir2name[entry->direction], | ||
538 | dir2name[direction]); | ||
539 | } | ||
540 | |||
541 | if (entry->direction == DMA_BIDIRECTIONAL) | ||
542 | goto out; | ||
543 | |||
544 | if (to_cpu && !(entry->direction == DMA_FROM_DEVICE) && | ||
545 | !(direction == DMA_TO_DEVICE)) | ||
546 | err_printk(dev, "DMA-API: device driver syncs " | ||
547 | "device read-only DMA memory for cpu " | ||
548 | "[device address=0x%016llx] [size=%llu bytes] " | ||
549 | "[mapped with %s] [synced with %s]\n", | ||
550 | addr, entry->size, | ||
551 | dir2name[entry->direction], | ||
552 | dir2name[direction]); | ||
553 | |||
554 | if (!to_cpu && !(entry->direction == DMA_TO_DEVICE) && | ||
555 | !(direction == DMA_FROM_DEVICE)) | ||
556 | err_printk(dev, "DMA-API: device driver syncs " | ||
557 | "device write-only DMA memory to device " | ||
558 | "[device address=0x%016llx] [size=%llu bytes] " | ||
559 | "[mapped with %s] [synced with %s]\n", | ||
560 | addr, entry->size, | ||
561 | dir2name[entry->direction], | ||
562 | dir2name[direction]); | ||
563 | |||
564 | out: | ||
565 | put_hash_bucket(bucket, &flags); | ||
566 | |||
567 | } | ||
568 | |||