From c6a21d0b8d667f403bc6186ef215a504a26ab682 Mon Sep 17 00:00:00 2001 From: Neil Horman Date: Mon, 8 Aug 2011 15:13:54 -0400 Subject: dma-debug: hash_bucket_find needs to allow for offsets within an entry Summary: Users of the pci_dma_sync_single_* api allow users to sync address ranges within the range of a mapped entry (i.e. you can dma map address X to dma_addr_t A and then pci_dma_sync_single on dma_addr_t A+1. The dma-debug library however assume dma syncs will always occur using the base address of a mapped region, and uses that assumption to find entries in its hash table. Since thats often (but not always the case), the dma debug library can give us false errors about missing entries, which are reported as syncing of memory not allocated by the driver. This was noted in the cxgb3 driver as this error: WARNING: at lib/dma-debug.c:902 check_sync+0xdd/0x48c() Hardware name: To be filled by O.E.M. cxgb3 0000:01:00.0: DMA-API: device driver tries to sync DMA memory it has not allocated [device address=0x00000000fff97800] [size=1984 bytes] Modules linked in: autofs4 sunrpc cpufreq_ondemand acpi_cpufreq freq_table mperf ip6t_REJECT nf_conntrack_ipv6 ip6table_filter ip6_tables ipv6 uinput snd_hda_codec_intelhdmi snd_hda_codec_realtek snd_hda_intel snd_hda_codec snd_hwdep snd_seq snd_seq_device snd_pcm snd_timer e1000e snd soundcore r8169 cxgb3 iTCO_wdt snd_page_alloc mii shpchp i2c_i801 iTCO_vendor_support mdio microcode firewire_ohci firewire_core crc_itu_t ata_generic pata_acpi i915 drm_kms_helper drm i2c_algo_bit i2c_core video output [last unloaded: scsi_wait_scan] Pid: 1818, comm: ifconfig Not tainted 2.6.35-0.23.rc3.git6.fc14.x86_64 #1 Call Trace: [] warn_slowpath_common+0x85/0x9d [] warn_slowpath_fmt+0x46/0x48 [] ? check_sync+0x39/0x48c [] ? trace_hardirqs_on+0xd/0xf [] check_sync+0xdd/0x48c [] debug_dma_sync_single_for_device+0x3f/0x41 [] ? pci_map_page+0x84/0x97 [cxgb3] [] pci_dma_sync_single_for_device.clone.0+0x65/0x6e [cxgb3] [] refill_fl+0x305/0x30a [cxgb3] [] t3_sge_alloc_qset+0x6a7/0x821 [cxgb3] [] cxgb_up+0x4d0/0xe62 [cxgb3] [] ? __module_text_address+0x12/0x58 [] cxgb_open+0x3f/0x309 [cxgb3] [] __dev_open+0x8e/0xbc [] __dev_change_flags+0xbe/0x142 [] dev_change_flags+0x21/0x57 [] devinet_ioctl+0x29a/0x54b [] ? inode_has_perm+0xaa/0xce [] inet_ioctl+0x8f/0xa7 [] sock_do_ioctl+0x29/0x48 [] sock_ioctl+0x213/0x222 [] vfs_ioctl+0x32/0xa6 [] do_vfs_ioctl+0x47a/0x4b3 [] sys_ioctl+0x56/0x79 [] system_call_fastpath+0x16/0x1b ---[ end trace 69a4d4cc77b58004 ]--- (some edits by Joerg Roedel) Signed-off-by: Neil Horman Reported-by: Jay Fenalson CC: Divy LeRay CC: Stanislaw Gruszka CC: Joerg Roedel CC: Arnd Bergmann Signed-off-by: Joerg Roedel --- lib/dma-debug.c | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 61 insertions(+), 7 deletions(-) (limited to 'lib') diff --git a/lib/dma-debug.c b/lib/dma-debug.c index db07bfd9298..79700fa2dfc 100644 --- a/lib/dma-debug.c +++ b/lib/dma-debug.c @@ -62,6 +62,8 @@ struct dma_debug_entry { #endif }; +typedef bool (*match_fn)(struct dma_debug_entry *, struct dma_debug_entry *); + struct hash_bucket { struct list_head list; spinlock_t lock; @@ -240,18 +242,37 @@ static void put_hash_bucket(struct hash_bucket *bucket, spin_unlock_irqrestore(&bucket->lock, __flags); } +static bool exact_match(struct dma_debug_entry *a, struct dma_debug_entry *b) +{ + return ((a->dev_addr == a->dev_addr) && + (a->dev == b->dev)) ? true : false; +} + +static bool containing_match(struct dma_debug_entry *a, + struct dma_debug_entry *b) +{ + if (a->dev != b->dev) + return false; + + if ((b->dev_addr <= a->dev_addr) && + ((b->dev_addr + b->size) >= (a->dev_addr + a->size))) + return true; + + return false; +} + /* * Search a given entry in the hash bucket list */ -static struct dma_debug_entry *hash_bucket_find(struct hash_bucket *bucket, - struct dma_debug_entry *ref) +static struct dma_debug_entry *__hash_bucket_find(struct hash_bucket *bucket, + struct dma_debug_entry *ref, + match_fn match) { struct dma_debug_entry *entry, *ret = NULL; int matches = 0, match_lvl, last_lvl = 0; list_for_each_entry(entry, &bucket->list, list) { - if ((entry->dev_addr != ref->dev_addr) || - (entry->dev != ref->dev)) + if (!match(ref, entry)) continue; /* @@ -293,6 +314,39 @@ static struct dma_debug_entry *hash_bucket_find(struct hash_bucket *bucket, return ret; } +static struct dma_debug_entry *bucket_find_exact(struct hash_bucket *bucket, + struct dma_debug_entry *ref) +{ + return __hash_bucket_find(bucket, ref, exact_match); +} + +static struct dma_debug_entry *bucket_find_contain(struct hash_bucket **bucket, + struct dma_debug_entry *ref, + unsigned long *flags) +{ + + unsigned int max_range = dma_get_max_seg_size(ref->dev); + struct dma_debug_entry *entry, index = *ref; + unsigned int range = 0; + + while (range <= max_range) { + entry = __hash_bucket_find(*bucket, &index, containing_match); + + if (entry) + return entry; + + /* + * Nothing found, go back a hash bucket + */ + put_hash_bucket(*bucket, flags); + range += (1 << HASH_FN_SHIFT); + index.dev_addr -= (1 << HASH_FN_SHIFT); + *bucket = get_hash_bucket(&index, flags); + } + + return NULL; +} + /* * Add an entry to a hash bucket */ @@ -802,7 +856,7 @@ static void check_unmap(struct dma_debug_entry *ref) } bucket = get_hash_bucket(ref, &flags); - entry = hash_bucket_find(bucket, ref); + entry = bucket_find_exact(bucket, ref); if (!entry) { err_printk(ref->dev, NULL, "DMA-API: device driver tries " @@ -902,7 +956,7 @@ static void check_sync(struct device *dev, bucket = get_hash_bucket(ref, &flags); - entry = hash_bucket_find(bucket, ref); + entry = bucket_find_contain(&bucket, ref, &flags); if (!entry) { err_printk(dev, NULL, "DMA-API: device driver tries " @@ -1060,7 +1114,7 @@ static int get_nr_mapped_entries(struct device *dev, int mapped_ents; bucket = get_hash_bucket(ref, &flags); - entry = hash_bucket_find(bucket, ref); + entry = bucket_find_exact(bucket, ref); mapped_ents = 0; if (entry) -- cgit v1.2.3-18-g5258