dma-debug: New interfaces to debug dma mapping errors

Add dma-debug interface debug_dma_mapping_error() to debug
drivers that fail to check dma mapping errors on addresses
returned by dma_map_single() and dma_map_page() interfaces.
This interface clears a flag set by debug_dma_map_page() to
indicate that dma_mapping_error() has been called by the
driver. When driver does unmap, debug_dma_unmap() checks the
flag and if this flag is still set, prints warning message
that includes call trace that leads up to the unmap. This
interface can be called from dma_mapping_error() routines to
enable dma mapping error check debugging.

Tested: Intel iommu and swiotlb (iommu=soft) on x86-64 with
        CONFIG_DMA_API_DEBUG enabled and disabled.

Signed-off-by: Shuah Khan <shuah.khan@hp.com>
Reviewed-by: Konrad Rzeszutek Wilk <konrad.wilk@oracle.com>
Signed-off-by: Joerg Roedel <joerg.roedel@amd.com>
This commit is contained in:
Shuah Khan 2012-10-08 11:08:06 -06:00 committed by Joerg Roedel
parent 6f0c0580b7
commit 6c9c6d6301
4 changed files with 87 additions and 4 deletions

View File

@ -678,3 +678,15 @@ out of dma_debug_entries. These entries are preallocated at boot. The number
of preallocated entries is defined per architecture. If it is too low for you of preallocated entries is defined per architecture. If it is too low for you
boot with 'dma_debug_entries=<your_desired_number>' to overwrite the boot with 'dma_debug_entries=<your_desired_number>' to overwrite the
architectural default. architectural default.
void debug_dmap_mapping_error(struct device *dev, dma_addr_t dma_addr);
dma-debug interface debug_dma_mapping_error() to debug drivers that fail
to check dma mapping errors on addresses returned by dma_map_single() and
dma_map_page() interfaces. This interface clears a flag set by
debug_dma_map_page() to indicate that dma_mapping_error() has been called by
the driver. When driver does unmap, debug_dma_unmap() checks the flag and if
this flag is still set, prints warning message that includes call trace that
leads up to the unmap. This interface can be called from dma_mapping_error()
routines to enable dma mapping error check debugging.

View File

@ -47,6 +47,7 @@ static inline struct dma_map_ops *get_dma_ops(struct device *dev)
static inline int dma_mapping_error(struct device *dev, dma_addr_t dma_addr) static inline int dma_mapping_error(struct device *dev, dma_addr_t dma_addr)
{ {
struct dma_map_ops *ops = get_dma_ops(dev); struct dma_map_ops *ops = get_dma_ops(dev);
debug_dma_mapping_error(dev, dma_addr);
if (ops->mapping_error) if (ops->mapping_error)
return ops->mapping_error(dev, dma_addr); return ops->mapping_error(dev, dma_addr);

View File

@ -39,6 +39,8 @@ extern void debug_dma_map_page(struct device *dev, struct page *page,
int direction, dma_addr_t dma_addr, int direction, dma_addr_t dma_addr,
bool map_single); bool map_single);
extern void debug_dma_mapping_error(struct device *dev, dma_addr_t dma_addr);
extern void debug_dma_unmap_page(struct device *dev, dma_addr_t addr, extern void debug_dma_unmap_page(struct device *dev, dma_addr_t addr,
size_t size, int direction, bool map_single); size_t size, int direction, bool map_single);
@ -105,6 +107,11 @@ static inline void debug_dma_map_page(struct device *dev, struct page *page,
{ {
} }
static inline void debug_dma_mapping_error(struct device *dev,
dma_addr_t dma_addr)
{
}
static inline void debug_dma_unmap_page(struct device *dev, dma_addr_t addr, static inline void debug_dma_unmap_page(struct device *dev, dma_addr_t addr,
size_t size, int direction, size_t size, int direction,
bool map_single) bool map_single)

View File

@ -45,6 +45,12 @@ enum {
dma_debug_coherent, dma_debug_coherent,
}; };
enum map_err_types {
MAP_ERR_CHECK_NOT_APPLICABLE,
MAP_ERR_NOT_CHECKED,
MAP_ERR_CHECKED,
};
#define DMA_DEBUG_STACKTRACE_ENTRIES 5 #define DMA_DEBUG_STACKTRACE_ENTRIES 5
struct dma_debug_entry { struct dma_debug_entry {
@ -57,6 +63,7 @@ struct dma_debug_entry {
int direction; int direction;
int sg_call_ents; int sg_call_ents;
int sg_mapped_ents; int sg_mapped_ents;
enum map_err_types map_err_type;
#ifdef CONFIG_STACKTRACE #ifdef CONFIG_STACKTRACE
struct stack_trace stacktrace; struct stack_trace stacktrace;
unsigned long st_entries[DMA_DEBUG_STACKTRACE_ENTRIES]; unsigned long st_entries[DMA_DEBUG_STACKTRACE_ENTRIES];
@ -114,6 +121,12 @@ static struct device_driver *current_driver __read_mostly;
static DEFINE_RWLOCK(driver_name_lock); static DEFINE_RWLOCK(driver_name_lock);
static const char *const maperr2str[] = {
[MAP_ERR_CHECK_NOT_APPLICABLE] = "dma map error check not applicable",
[MAP_ERR_NOT_CHECKED] = "dma map error not checked",
[MAP_ERR_CHECKED] = "dma map error checked",
};
static const char *type2name[4] = { "single", "page", static const char *type2name[4] = { "single", "page",
"scather-gather", "coherent" }; "scather-gather", "coherent" };
@ -376,11 +389,12 @@ void debug_dma_dump_mappings(struct device *dev)
list_for_each_entry(entry, &bucket->list, list) { list_for_each_entry(entry, &bucket->list, list) {
if (!dev || dev == entry->dev) { if (!dev || dev == entry->dev) {
dev_info(entry->dev, dev_info(entry->dev,
"%s idx %d P=%Lx D=%Lx L=%Lx %s\n", "%s idx %d P=%Lx D=%Lx L=%Lx %s %s\n",
type2name[entry->type], idx, type2name[entry->type], idx,
(unsigned long long)entry->paddr, (unsigned long long)entry->paddr,
entry->dev_addr, entry->size, entry->dev_addr, entry->size,
dir2name[entry->direction]); dir2name[entry->direction],
maperr2str[entry->map_err_type]);
} }
} }
@ -838,13 +852,28 @@ static __init int dma_debug_entries_cmdline(char *str)
__setup("dma_debug=", dma_debug_cmdline); __setup("dma_debug=", dma_debug_cmdline);
__setup("dma_debug_entries=", dma_debug_entries_cmdline); __setup("dma_debug_entries=", dma_debug_entries_cmdline);
/* Calling dma_mapping_error() from dma-debug api will result in calling
debug_dma_mapping_error() - need internal mapping error routine to
avoid debug checks */
#ifndef DMA_ERROR_CODE
#define DMA_ERROR_CODE 0
#endif
static inline int has_mapping_error(struct device *dev, dma_addr_t dma_addr)
{
const struct dma_map_ops *ops = get_dma_ops(dev);
if (ops->mapping_error)
return ops->mapping_error(dev, dma_addr);
return (dma_addr == DMA_ERROR_CODE);
}
static void check_unmap(struct dma_debug_entry *ref) static void check_unmap(struct dma_debug_entry *ref)
{ {
struct dma_debug_entry *entry; struct dma_debug_entry *entry;
struct hash_bucket *bucket; struct hash_bucket *bucket;
unsigned long flags; unsigned long flags;
if (dma_mapping_error(ref->dev, ref->dev_addr)) { if (unlikely(has_mapping_error(ref->dev, ref->dev_addr))) {
err_printk(ref->dev, NULL, "DMA-API: device driver tries " err_printk(ref->dev, NULL, "DMA-API: device driver tries "
"to free an invalid DMA memory address\n"); "to free an invalid DMA memory address\n");
return; return;
@ -910,6 +939,15 @@ static void check_unmap(struct dma_debug_entry *ref)
dir2name[ref->direction]); dir2name[ref->direction]);
} }
if (entry->map_err_type == MAP_ERR_NOT_CHECKED) {
err_printk(ref->dev, entry,
"DMA-API: device driver failed to check map error"
"[device address=0x%016llx] [size=%llu bytes] "
"[mapped as %s]",
ref->dev_addr, ref->size,
type2name[entry->type]);
}
hash_bucket_del(entry); hash_bucket_del(entry);
dma_entry_free(entry); dma_entry_free(entry);
@ -1017,7 +1055,7 @@ void debug_dma_map_page(struct device *dev, struct page *page, size_t offset,
if (unlikely(global_disable)) if (unlikely(global_disable))
return; return;
if (unlikely(dma_mapping_error(dev, dma_addr))) if (unlikely(has_mapping_error(dev, dma_addr)))
return; return;
entry = dma_entry_alloc(); entry = dma_entry_alloc();
@ -1030,6 +1068,7 @@ void debug_dma_map_page(struct device *dev, struct page *page, size_t offset,
entry->dev_addr = dma_addr; entry->dev_addr = dma_addr;
entry->size = size; entry->size = size;
entry->direction = direction; entry->direction = direction;
entry->map_err_type = MAP_ERR_NOT_CHECKED;
if (map_single) if (map_single)
entry->type = dma_debug_single; entry->type = dma_debug_single;
@ -1045,6 +1084,30 @@ void debug_dma_map_page(struct device *dev, struct page *page, size_t offset,
} }
EXPORT_SYMBOL(debug_dma_map_page); EXPORT_SYMBOL(debug_dma_map_page);
void debug_dma_mapping_error(struct device *dev, dma_addr_t dma_addr)
{
struct dma_debug_entry ref;
struct dma_debug_entry *entry;
struct hash_bucket *bucket;
unsigned long flags;
if (unlikely(global_disable))
return;
ref.dev = dev;
ref.dev_addr = dma_addr;
bucket = get_hash_bucket(&ref, &flags);
entry = bucket_find_exact(bucket, &ref);
if (!entry)
goto out;
entry->map_err_type = MAP_ERR_CHECKED;
out:
put_hash_bucket(bucket, &flags);
}
EXPORT_SYMBOL(debug_dma_mapping_error);
void debug_dma_unmap_page(struct device *dev, dma_addr_t addr, void debug_dma_unmap_page(struct device *dev, dma_addr_t addr,
size_t size, int direction, bool map_single) size_t size, int direction, bool map_single)
{ {