1670 lines · 44.0 KB · C
RawDownload
		active_cacheline_inc_overlap(cln, is_cache_clean);		existing = radix_tree_lookup(&dma_active_cacheline, cln);		/* A lookup failure here after we got -EEXIST is unexpected. */		WARN_ON(!existing);		if (existing)			*overlap_cache_clean =				existing->attrs &				(DMA_ATTR_DEBUGGING_IGNORE_CACHELINES |				 DMA_ATTR_REQUIRE_COHERENT);	}	spin_unlock_irqrestore(&radix_lock, flags); 	return rc;} static void active_cacheline_remove(struct dma_debug_entry *entry){	phys_addr_t cln = to_cacheline_number(entry);	unsigned long flags; 	/* ...mirror the insert case */	if (entry->direction == DMA_TO_DEVICE)		return; 	spin_lock_irqsave(&radix_lock, flags);	/* since we are counting overlaps the final put of the	 * cacheline will occur when the overlap count is 0.	 * active_cacheline_dec_overlap() returns -1 in that case	 */	if (active_cacheline_dec_overlap(cln) < 0)		radix_tree_delete(&dma_active_cacheline, cln);	spin_unlock_irqrestore(&radix_lock, flags);} /* * Dump mappings entries on kernel space for debugging purposes */void debug_dma_dump_mappings(struct device *dev){	int idx;	phys_addr_t cln; 	for (idx = 0; idx < HASH_SIZE; idx++) {		struct hash_bucket *bucket = &dma_entry_hash[idx];		struct dma_debug_entry *entry;		unsigned long flags; 		spin_lock_irqsave(&bucket->lock, flags);		list_for_each_entry(entry, &bucket->list, list) {			if (!dev || dev == entry->dev) {				cln = to_cacheline_number(entry);				dev_info(entry->dev,					 "%s idx %d P=%pa D=%llx L=%llx cln=%pa %s %s attrs=0x%lx\n",					 type2name[entry->type], idx,					 &entry->paddr, entry->dev_addr,					 entry->size, &cln,					 dir2name[entry->direction],					 maperr2str[entry->map_err_type],					 entry->attrs);			}		}		spin_unlock_irqrestore(&bucket->lock, flags); 		cond_resched();	}} /* * Dump mappings entries on user space via debugfs */static int dump_show(struct seq_file *seq, void *v){	int idx;	phys_addr_t cln; 	for (idx = 0; idx < HASH_SIZE; idx++) {		struct hash_bucket *bucket = &dma_entry_hash[idx];		struct dma_debug_entry *entry;		unsigned long flags; 		spin_lock_irqsave(&bucket->lock, flags);		list_for_each_entry(entry, &bucket->list, list) {			cln = to_cacheline_number(entry);			seq_printf(seq,				   "%s %s %s idx %d P=%pa D=%llx L=%llx cln=%pa %s %s attrs=0x%lx\n",				   dev_driver_string(entry->dev),				   dev_name(entry->dev),				   type2name[entry->type], idx,				   &entry->paddr, entry->dev_addr,				   entry->size, &cln,				   dir2name[entry->direction],				   maperr2str[entry->map_err_type],				   entry->attrs);		}		spin_unlock_irqrestore(&bucket->lock, flags);	}	return 0;}DEFINE_SHOW_ATTRIBUTE(dump); /* * Wrapper function for adding an entry to the hash. * This function takes care of locking itself. */static void add_dma_entry(struct dma_debug_entry *entry){	unsigned long attrs = entry->attrs;	bool overlap_cache_clean;	struct hash_bucket *bucket;	unsigned long flags;	int rc; 	bucket = get_hash_bucket(entry, &flags);	hash_bucket_add(bucket, entry);	put_hash_bucket(bucket, flags); 	rc = active_cacheline_insert(entry, &overlap_cache_clean);	if (rc == -ENOMEM) {		pr_err_once("cacheline tracking ENOMEM, dma-debug disabled\n");		global_disable = true;	} else if (rc == -EEXIST && !(attrs & DMA_ATTR_SKIP_CPU_SYNC) &&		   !(attrs & (DMA_ATTR_DEBUGGING_IGNORE_CACHELINES |			      DMA_ATTR_REQUIRE_COHERENT) &&		     overlap_cache_clean) &&		   dma_get_cache_alignment() >= L1_CACHE_BYTES &&		   !(IS_ENABLED(CONFIG_DMA_BOUNCE_UNALIGNED_KMALLOC) &&		     is_swiotlb_active(entry->dev))) {		err_printk(entry->dev, entry,			"cacheline tracking EEXIST, overlapping mappings aren't supported\n");	}} static int dma_debug_create_entries(gfp_t gfp){	struct dma_debug_entry *entry;	int i; 	entry = (void *)get_zeroed_page(gfp);	if (!entry)		return -ENOMEM; 	for (i = 0; i < DMA_DEBUG_DYNAMIC_ENTRIES; i++)		list_add_tail(&entry[i].list, &free_entries); 	num_free_entries += DMA_DEBUG_DYNAMIC_ENTRIES;	nr_total_entries += DMA_DEBUG_DYNAMIC_ENTRIES; 	return 0;} static struct dma_debug_entry *__dma_entry_alloc(void){	struct dma_debug_entry *entry; 	entry = list_entry(free_entries.next, struct dma_debug_entry, list);	list_del(&entry->list);	memset(entry, 0, sizeof(*entry)); 	num_free_entries -= 1;	if (num_free_entries < min_free_entries)		min_free_entries = num_free_entries; 	return entry;} /* * This should be called outside of free_entries_lock scope to avoid potential * deadlocks with serial consoles that use DMA. */static void __dma_entry_alloc_check_leak(u32 nr_entries){	u32 tmp = nr_entries % nr_prealloc_entries; 	/* Shout each time we tick over some multiple of the initial pool */	if (tmp < DMA_DEBUG_DYNAMIC_ENTRIES) {		pr_info("dma_debug_entry pool grown to %u (%u00%%)\n",			nr_entries,			(nr_entries / nr_prealloc_entries));	}} /* struct dma_entry allocator * * The next two functions implement the allocator for * struct dma_debug_entries. */static struct dma_debug_entry *dma_entry_alloc(void){	bool alloc_check_leak = false;	struct dma_debug_entry *entry;	unsigned long flags;	u32 nr_entries; 	spin_lock_irqsave(&free_entries_lock, flags);	if (num_free_entries == 0) {		if (dma_debug_create_entries(GFP_ATOMIC)) {			global_disable = true;			spin_unlock_irqrestore(&free_entries_lock, flags);			pr_err("debugging out of memory - disabling\n");			return NULL;		}		alloc_check_leak = true;		nr_entries = nr_total_entries;	} 	entry = __dma_entry_alloc(); 	spin_unlock_irqrestore(&free_entries_lock, flags); 	if (alloc_check_leak)		__dma_entry_alloc_check_leak(nr_entries); #ifdef CONFIG_STACKTRACE	entry->stack_len = stack_trace_save(entry->stack_entries,					    ARRAY_SIZE(entry->stack_entries),					    1);#endif	return entry;} static void dma_entry_free(struct dma_debug_entry *entry){	unsigned long flags; 	active_cacheline_remove(entry); 	/*	 * add to beginning of the list - this way the entries are	 * more likely cache hot when they are reallocated.	 */	spin_lock_irqsave(&free_entries_lock, flags);	list_add(&entry->list, &free_entries);	num_free_entries += 1;	spin_unlock_irqrestore(&free_entries_lock, flags);} /* * DMA-API debugging init code * * The init code does two things: *   1. Initialize core data structures *   2. Preallocate a given number of dma_debug_entry structs */ static ssize_t filter_read(struct file *file, char __user *user_buf,			   size_t count, loff_t *ppos){	char buf[NAME_MAX_LEN + 1];	unsigned long flags;	int len; 	if (!current_driver_name[0])		return 0; 	/*	 * We can't copy to userspace directly because current_driver_name can	 * only be read under the driver_name_lock with irqs disabled. So	 * create a temporary copy first.	 */	read_lock_irqsave(&driver_name_lock, flags);	len = scnprintf(buf, NAME_MAX_LEN + 1, "%s\n", current_driver_name);	read_unlock_irqrestore(&driver_name_lock, flags); 	return simple_read_from_buffer(user_buf, count, ppos, buf, len);} static ssize_t filter_write(struct file *file, const char __user *userbuf,			    size_t count, loff_t *ppos){	char buf[NAME_MAX_LEN];	unsigned long flags;	size_t len;	int i; 	/*	 * We can't copy from userspace directly. Access to	 * current_driver_name is protected with a write_lock with irqs	 * disabled. Since copy_from_user can fault and may sleep we	 * need to copy to temporary buffer first	 */	len = min(count, (size_t)(NAME_MAX_LEN - 1));	if (copy_from_user(buf, userbuf, len))		return -EFAULT; 	buf[len] = 0; 	write_lock_irqsave(&driver_name_lock, flags); 	/*	 * Now handle the string we got from userspace very carefully.	 * The rules are:	 *         - only use the first token we got	 *         - token delimiter is everything looking like a space	 *           character (' ', '\n', '\t' ...)	 *	 */	if (!isalnum(buf[0])) {		/*		 * If the first character userspace gave us is not		 * alphanumerical then assume the filter should be		 * switched off.		 */		if (current_driver_name[0])			pr_info("switching off dma-debug driver filter\n");		current_driver_name[0] = 0;		current_driver = NULL;		goto out_unlock;	} 	/*	 * Now parse out the first token and use it as the name for the	 * driver to filter for.	 */	for (i = 0; i < NAME_MAX_LEN - 1; ++i) {		current_driver_name[i] = buf[i];		if (isspace(buf[i]) || buf[i] == ' ' || buf[i] == 0)			break;	}	current_driver_name[i] = 0;	current_driver = NULL; 	pr_info("enable driver filter for driver [%s]\n",		current_driver_name); out_unlock:	write_unlock_irqrestore(&driver_name_lock, flags); 	return count;} static const struct file_operations filter_fops = {	.read  = filter_read,	.write = filter_write,	.llseek = default_llseek,}; static int __init dma_debug_fs_init(void){	struct dentry *dentry = debugfs_create_dir("dma-api", NULL); 	debugfs_create_bool("disabled", 0444, dentry, &global_disable);	debugfs_create_u32("error_count", 0444, dentry, &error_count);	debugfs_create_u32("all_errors", 0644, dentry, &show_all_errors);	debugfs_create_u32("num_errors", 0644, dentry, &show_num_errors);	debugfs_create_u32("num_free_entries", 0444, dentry, &num_free_entries);	debugfs_create_u32("min_free_entries", 0444, dentry, &min_free_entries);	debugfs_create_u32("nr_total_entries", 0444, dentry, &nr_total_entries);	debugfs_create_file("driver_filter", 0644, dentry, NULL, &filter_fops);	debugfs_create_file("dump", 0444, dentry, NULL, &dump_fops); 	return 0;}core_initcall_sync(dma_debug_fs_init); static int device_dma_allocations(struct device *dev, struct dma_debug_entry **out_entry){	struct dma_debug_entry *entry;	unsigned long flags;	int count = 0, i; 	for (i = 0; i < HASH_SIZE; ++i) {		spin_lock_irqsave(&dma_entry_hash[i].lock, flags);		list_for_each_entry(entry, &dma_entry_hash[i].list, list) {			if (entry->dev == dev) {				count += 1;				*out_entry = entry;			}		}		spin_unlock_irqrestore(&dma_entry_hash[i].lock, flags);	} 	return count;} static int dma_debug_device_change(struct notifier_block *nb, unsigned long action, void *data){	struct device *dev = data;	struct dma_debug_entry *entry;	int count; 	if (dma_debug_disabled())		return 0; 	switch (action) {	case BUS_NOTIFY_UNBOUND_DRIVER:		count = device_dma_allocations(dev, &entry);		if (count == 0)			break;		err_printk(dev, entry, "device driver has pending "				"DMA allocations while released from device "				"[count=%d]\n"				"One of leaked entries details: "				"[device address=0x%016llx] [size=%llu bytes] "				"[mapped with %s] [mapped as %s]\n",			count, entry->dev_addr, entry->size,			dir2name[entry->direction], type2name[entry->type]);		break;	default:		break;	} 	return 0;} void dma_debug_add_bus(const struct bus_type *bus){	struct notifier_block *nb; 	if (dma_debug_disabled())		return; 	nb = kzalloc_obj(struct notifier_block);	if (nb == NULL) {		pr_err("dma_debug_add_bus: out of memory\n");		return;	} 	nb->notifier_call = dma_debug_device_change; 	bus_register_notifier(bus, nb);} static int dma_debug_init(void){	int i, nr_pages; 	/* Do not use dma_debug_initialized here, since we really want to be	 * called to set dma_debug_initialized	 */	if (global_disable)		return 0; 	for (i = 0; i < HASH_SIZE; ++i) {		INIT_LIST_HEAD(&dma_entry_hash[i].list);		spin_lock_init(&dma_entry_hash[i].lock);	} 	nr_pages = DIV_ROUND_UP(nr_prealloc_entries, DMA_DEBUG_DYNAMIC_ENTRIES);	for (i = 0; i < nr_pages; ++i)		dma_debug_create_entries(GFP_KERNEL);	if (num_free_entries >= nr_prealloc_entries) {		pr_info("preallocated %d debug entries\n", nr_total_entries);	} else if (num_free_entries > 0) {		pr_warn("%d debug entries requested but only %d allocated\n",			nr_prealloc_entries, nr_total_entries);	} else {		pr_err("debugging out of memory error - disabled\n");		global_disable = true; 		return 0;	}	min_free_entries = num_free_entries; 	dma_debug_initialized = true; 	pr_info("debugging enabled by kernel config\n");	return 0;}core_initcall(dma_debug_init); static __init int dma_debug_cmdline(char *str){	if (!str)		return -EINVAL; 	if (strncmp(str, "off", 3) == 0) {		pr_info("debugging disabled on kernel command line\n");		global_disable = true;	} 	return 1;} static __init int dma_debug_entries_cmdline(char *str){	if (!str)		return -EINVAL;	if (!get_option(&str, &nr_prealloc_entries))		nr_prealloc_entries = PREALLOC_DMA_DEBUG_ENTRIES;	return 1;} __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; 	bucket = get_hash_bucket(ref, &flags);	entry = bucket_find_exact(bucket, ref); 	if (!entry) {		/* must drop lock before calling dma_mapping_error */		put_hash_bucket(bucket, flags); 		if (dma_mapping_error(ref->dev, ref->dev_addr)) {			err_printk(ref->dev, NULL,

Lines 501–1000 of 1670. Previous lines

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t