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>
This commit is contained in:
		
							parent
							
								
									788dcfa6f1
								
							
						
					
					
						commit
						2d62ece14f
					
				
							
								
								
									
										188
									
								
								lib/dma-debug.c
									
									
									
									
									
								
							
							
						
						
									
										188
									
								
								lib/dma-debug.c
									
									
									
									
									
								
							| @ -17,10 +17,13 @@ | ||||
|  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA | ||||
|  */ | ||||
| 
 | ||||
| #include <linux/dma-mapping.h> | ||||
| #include <linux/dma-debug.h> | ||||
| #include <linux/spinlock.h> | ||||
| #include <linux/debugfs.h> | ||||
| #include <linux/device.h> | ||||
| #include <linux/types.h> | ||||
| #include <linux/sched.h> | ||||
| #include <linux/list.h> | ||||
| #include <linux/slab.h> | ||||
| 
 | ||||
| @ -50,7 +53,7 @@ struct dma_debug_entry { | ||||
| struct hash_bucket { | ||||
| 	struct list_head list; | ||||
| 	spinlock_t lock; | ||||
| } __cacheline_aligned_in_smp; | ||||
| } ____cacheline_aligned_in_smp; | ||||
| 
 | ||||
| /* Hash list to save the allocated dma addresses */ | ||||
| static struct hash_bucket dma_entry_hash[HASH_SIZE]; | ||||
| @ -85,6 +88,36 @@ static struct dentry *show_num_errors_dent  __read_mostly; | ||||
| static struct dentry *num_free_entries_dent __read_mostly; | ||||
| static struct dentry *min_free_entries_dent __read_mostly; | ||||
| 
 | ||||
| static const char *type2name[4] = { "single", "page", | ||||
| 				    "scather-gather", "coherent" }; | ||||
| 
 | ||||
| static const char *dir2name[4] = { "DMA_BIDIRECTIONAL", "DMA_TO_DEVICE", | ||||
| 				   "DMA_FROM_DEVICE", "DMA_NONE" }; | ||||
| 
 | ||||
| /*
 | ||||
|  * The access to some variables in this macro is racy. We can't use atomic_t | ||||
|  * here because all these variables are exported to debugfs. Some of them even | ||||
|  * writeable. This is also the reason why a lock won't help much. But anyway, | ||||
|  * the races are no big deal. Here is why: | ||||
|  * | ||||
|  *   error_count: the addition is racy, but the worst thing that can happen is | ||||
|  *                that we don't count some errors | ||||
|  *   show_num_errors: the subtraction is racy. Also no big deal because in | ||||
|  *                    worst case this will result in one warning more in the | ||||
|  *                    system log than the user configured. This variable is | ||||
|  *                    writeable via debugfs. | ||||
|  */ | ||||
| #define err_printk(dev, format, arg...) do {			\ | ||||
| 		error_count += 1;				\ | ||||
| 		if (show_all_errors || show_num_errors > 0) {	\ | ||||
| 			WARN(1, "%s %s: " format,		\ | ||||
| 			     dev_driver_string(dev),		\ | ||||
| 			     dev_name(dev) , ## arg);		\ | ||||
| 		}						\ | ||||
| 		if (!show_all_errors && show_num_errors > 0)	\ | ||||
| 			show_num_errors -= 1;			\ | ||||
| 	} while (0); | ||||
| 
 | ||||
| /*
 | ||||
|  * Hash related functions | ||||
|  * | ||||
| @ -380,3 +413,156 @@ static __init int dma_debug_entries_cmdline(char *str) | ||||
| __setup("dma_debug=", dma_debug_cmdline); | ||||
| __setup("dma_debug_entries=", dma_debug_entries_cmdline); | ||||
| 
 | ||||
| static void check_unmap(struct dma_debug_entry *ref) | ||||
| { | ||||
| 	struct dma_debug_entry *entry; | ||||
| 	struct hash_bucket *bucket; | ||||
| 	unsigned long flags; | ||||
| 
 | ||||
| 	if (dma_mapping_error(ref->dev, ref->dev_addr)) | ||||
| 		return; | ||||
| 
 | ||||
| 	bucket = get_hash_bucket(ref, &flags); | ||||
| 	entry = hash_bucket_find(bucket, ref); | ||||
| 
 | ||||
| 	if (!entry) { | ||||
| 		err_printk(ref->dev, "DMA-API: device driver tries " | ||||
| 			   "to free DMA memory it has not allocated " | ||||
| 			   "[device address=0x%016llx] [size=%llu bytes]\n", | ||||
| 			   ref->dev_addr, ref->size); | ||||
| 		goto out; | ||||
| 	} | ||||
| 
 | ||||
| 	if (ref->size != entry->size) { | ||||
| 		err_printk(ref->dev, "DMA-API: device driver frees " | ||||
| 			   "DMA memory with different size " | ||||
| 			   "[device address=0x%016llx] [map size=%llu bytes] " | ||||
| 			   "[unmap size=%llu bytes]\n", | ||||
| 			   ref->dev_addr, entry->size, ref->size); | ||||
| 	} | ||||
| 
 | ||||
| 	if (ref->type != entry->type) { | ||||
| 		err_printk(ref->dev, "DMA-API: device driver frees " | ||||
| 			   "DMA memory with wrong function " | ||||
| 			   "[device address=0x%016llx] [size=%llu bytes] " | ||||
| 			   "[mapped as %s] [unmapped as %s]\n", | ||||
| 			   ref->dev_addr, ref->size, | ||||
| 			   type2name[entry->type], type2name[ref->type]); | ||||
| 	} else if ((entry->type == dma_debug_coherent) && | ||||
| 		   (ref->paddr != entry->paddr)) { | ||||
| 		err_printk(ref->dev, "DMA-API: device driver frees " | ||||
| 			   "DMA memory with different CPU address " | ||||
| 			   "[device address=0x%016llx] [size=%llu bytes] " | ||||
| 			   "[cpu alloc address=%p] [cpu free address=%p]", | ||||
| 			   ref->dev_addr, ref->size, | ||||
| 			   (void *)entry->paddr, (void *)ref->paddr); | ||||
| 	} | ||||
| 
 | ||||
| 	if (ref->sg_call_ents && ref->type == dma_debug_sg && | ||||
| 	    ref->sg_call_ents != entry->sg_call_ents) { | ||||
| 		err_printk(ref->dev, "DMA-API: device driver frees " | ||||
| 			   "DMA sg list with different entry count " | ||||
| 			   "[map count=%d] [unmap count=%d]\n", | ||||
| 			   entry->sg_call_ents, ref->sg_call_ents); | ||||
| 	} | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * This may be no bug in reality - but most implementations of the | ||||
| 	 * DMA API don't handle this properly, so check for it here | ||||
| 	 */ | ||||
| 	if (ref->direction != entry->direction) { | ||||
| 		err_printk(ref->dev, "DMA-API: device driver frees " | ||||
| 			   "DMA memory with different direction " | ||||
| 			   "[device address=0x%016llx] [size=%llu bytes] " | ||||
| 			   "[mapped with %s] [unmapped with %s]\n", | ||||
| 			   ref->dev_addr, ref->size, | ||||
| 			   dir2name[entry->direction], | ||||
| 			   dir2name[ref->direction]); | ||||
| 	} | ||||
| 
 | ||||
| 	hash_bucket_del(entry); | ||||
| 	dma_entry_free(entry); | ||||
| 
 | ||||
| out: | ||||
| 	put_hash_bucket(bucket, &flags); | ||||
| } | ||||
| 
 | ||||
| static void check_for_stack(struct device *dev, void *addr) | ||||
| { | ||||
| 	if (object_is_on_stack(addr)) | ||||
| 		err_printk(dev, "DMA-API: device driver maps memory from stack" | ||||
| 				" [addr=%p]\n", addr); | ||||
| } | ||||
| 
 | ||||
| static void check_sync(struct device *dev, dma_addr_t addr, | ||||
| 		       u64 size, u64 offset, int direction, bool to_cpu) | ||||
| { | ||||
| 	struct dma_debug_entry ref = { | ||||
| 		.dev            = dev, | ||||
| 		.dev_addr       = addr, | ||||
| 		.size           = size, | ||||
| 		.direction      = direction, | ||||
| 	}; | ||||
| 	struct dma_debug_entry *entry; | ||||
| 	struct hash_bucket *bucket; | ||||
| 	unsigned long flags; | ||||
| 
 | ||||
| 	bucket = get_hash_bucket(&ref, &flags); | ||||
| 
 | ||||
| 	entry = hash_bucket_find(bucket, &ref); | ||||
| 
 | ||||
| 	if (!entry) { | ||||
| 		err_printk(dev, "DMA-API: device driver tries " | ||||
| 				"to sync DMA memory it has not allocated " | ||||
| 				"[device address=0x%016llx] [size=%llu bytes]\n", | ||||
| 				addr, size); | ||||
| 		goto out; | ||||
| 	} | ||||
| 
 | ||||
| 	if ((offset + size) > entry->size) { | ||||
| 		err_printk(dev, "DMA-API: device driver syncs" | ||||
| 				" DMA memory outside allocated range " | ||||
| 				"[device address=0x%016llx] " | ||||
| 				"[allocation size=%llu bytes] [sync offset=%llu] " | ||||
| 				"[sync size=%llu]\n", entry->dev_addr, entry->size, | ||||
| 				offset, size); | ||||
| 	} | ||||
| 
 | ||||
| 	if (direction != entry->direction) { | ||||
| 		err_printk(dev, "DMA-API: device driver syncs " | ||||
| 				"DMA memory with different direction " | ||||
| 				"[device address=0x%016llx] [size=%llu bytes] " | ||||
| 				"[mapped with %s] [synced with %s]\n", | ||||
| 				addr, entry->size, | ||||
| 				dir2name[entry->direction], | ||||
| 				dir2name[direction]); | ||||
| 	} | ||||
| 
 | ||||
| 	if (entry->direction == DMA_BIDIRECTIONAL) | ||||
| 		goto out; | ||||
| 
 | ||||
| 	if (to_cpu && !(entry->direction == DMA_FROM_DEVICE) && | ||||
| 		      !(direction == DMA_TO_DEVICE)) | ||||
| 		err_printk(dev, "DMA-API: device driver syncs " | ||||
| 				"device read-only DMA memory for cpu " | ||||
| 				"[device address=0x%016llx] [size=%llu bytes] " | ||||
| 				"[mapped with %s] [synced with %s]\n", | ||||
| 				addr, entry->size, | ||||
| 				dir2name[entry->direction], | ||||
| 				dir2name[direction]); | ||||
| 
 | ||||
| 	if (!to_cpu && !(entry->direction == DMA_TO_DEVICE) && | ||||
| 		       !(direction == DMA_FROM_DEVICE)) | ||||
| 		err_printk(dev, "DMA-API: device driver syncs " | ||||
| 				"device write-only DMA memory to device " | ||||
| 				"[device address=0x%016llx] [size=%llu bytes] " | ||||
| 				"[mapped with %s] [synced with %s]\n", | ||||
| 				addr, entry->size, | ||||
| 				dir2name[entry->direction], | ||||
| 				dir2name[direction]); | ||||
| 
 | ||||
| out: | ||||
| 	put_hash_bucket(bucket, &flags); | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user