summaryrefslogtreecommitdiff
path: root/kernel/dma/debug.c
diff options
context:
space:
mode:
authorThomas Gleixner <tglx@kernel.org>2026-08-17 10:29:52 +0200
committerThomas Gleixner <tglx@kernel.org>2026-08-17 10:29:52 +0200
commit0eaed89c18aeedf0898baf2dbf5ff027c6795152 (patch)
tree4422cb10597581af05704ba6510018aabf25b268 /kernel/dma/debug.c
parentc66494c79ede1af529dbf67f9ed6fdbf42e05ef3 (diff)
parent8b4127f6db40381229f3564d34ac35f36311c201 (diff)
Merge tag 'timers-v7.3-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/daniel.lezcano/linux into timers/clocksource
- Use designated initializers for sh_mtu2, sh_cmt, and sh_tmu, and drop the unused initializer in the platform_device_id table for sh_mtu2 (Uwe Kleine-König) - Remove redundant dev_err()/dev_err_probe() messages when devm_request_*_irq() fails, as the helper already logs an error message (Pan Chuang) - Fix a boot hang on Allwinner D1 when a forced minimum delta is used with the sun4i timer (Felix Yan) - Fix an IRQ leak in the cpuhp_setup_state() error path by freeing the IRQ on failure in the NXP PIT driver (WenTao Liang) - Fix incorrect unmapping of shared MMIO between the clocksource and clockevent drivers. If one of them fails to initialize, the error path unmaps the shared MMIO region, leaving the other driver with an invalid mapping on clps711x (Guangshuo Li) - Make the samsung_pwm driver compatible with PREEMPT_RT by replacing regular spinlocks with raw_spinlock_t in atomic contexts (Marek Szyprowski) - Use __raw_readl() and __raw_writel() instead of ioread32() and iowrite32() to support SWAP_IO_SPACE in the rtl-otto driver (Rustam Adilov) - Fix a missing clk_disable_unprepare() call in the timer initialization error path of the Armada driver (Yuho Choi) Link: https://lore.kernel.org/lkml/75feea31-683d-45a1-87f4-ab045e0152ae@oss.qualcomm.com
Diffstat (limited to 'kernel/dma/debug.c')
-rw-r--r--kernel/dma/debug.c98
1 files changed, 70 insertions, 28 deletions
diff --git a/kernel/dma/debug.c b/kernel/dma/debug.c
index 1a725edbbbf6..026d50de340e 100644
--- a/kernel/dma/debug.c
+++ b/kernel/dma/debug.c
@@ -63,7 +63,7 @@ enum map_err_types {
* @sg_mapped_ents: 'mapped_ents' from dma_map_sg
* @paddr: physical start address of the mapping
* @map_err_type: track whether dma_mapping_error() was checked
- * @is_cache_clean: driver promises not to write to buffer while mapped
+ * @attrs: dma attributes
* @stack_len: number of backtrace entries in @stack_entries
* @stack_entries: stack of backtrace history
*/
@@ -78,7 +78,7 @@ struct dma_debug_entry {
int sg_mapped_ents;
phys_addr_t paddr;
enum map_err_types map_err_type;
- bool is_cache_clean;
+ unsigned long attrs;
#ifdef CONFIG_STACKTRACE
unsigned int stack_len;
unsigned long stack_entries[DMA_DEBUG_STACKTRACE_ENTRIES];
@@ -478,6 +478,9 @@ static int active_cacheline_insert(struct dma_debug_entry *entry,
bool *overlap_cache_clean)
{
phys_addr_t cln = to_cacheline_number(entry);
+ bool is_cache_clean = entry->attrs &
+ (DMA_ATTR_DEBUGGING_IGNORE_CACHELINES |
+ DMA_ATTR_REQUIRE_COHERENT);
unsigned long flags;
int rc;
@@ -495,12 +498,15 @@ static int active_cacheline_insert(struct dma_debug_entry *entry,
if (rc == -EEXIST) {
struct dma_debug_entry *existing;
- active_cacheline_inc_overlap(cln, entry->is_cache_clean);
+ 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->is_cache_clean;
+ *overlap_cache_clean =
+ existing->attrs &
+ (DMA_ATTR_DEBUGGING_IGNORE_CACHELINES |
+ DMA_ATTR_REQUIRE_COHERENT);
}
spin_unlock_irqrestore(&radix_lock, flags);
@@ -544,12 +550,13 @@ void debug_dma_dump_mappings(struct device *dev)
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\n",
+ "%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]);
+ maperr2str[entry->map_err_type],
+ entry->attrs);
}
}
spin_unlock_irqrestore(&bucket->lock, flags);
@@ -575,14 +582,15 @@ static int dump_show(struct seq_file *seq, void *v)
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\n",
+ "%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]);
+ maperr2str[entry->map_err_type],
+ entry->attrs);
}
spin_unlock_irqrestore(&bucket->lock, flags);
}
@@ -594,16 +602,14 @@ 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)
+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;
- entry->is_cache_clean = attrs & (DMA_ATTR_DEBUGGING_IGNORE_CACHELINES |
- DMA_ATTR_REQUIRE_COHERENT);
-
bucket = get_hash_bucket(entry, &flags);
hash_bucket_add(bucket, entry);
put_hash_bucket(bucket, flags);
@@ -612,9 +618,10 @@ static void add_dma_entry(struct dma_debug_entry *entry, unsigned long attrs)
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) &&
- !(entry->is_cache_clean && overlap_cache_clean) &&
+ } 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))) {
@@ -1067,6 +1074,29 @@ static void check_unmap(struct dma_debug_entry *ref)
type2name[entry->type]);
}
+ /*
+ * This may be no bug in reality - but DMA API still expects
+ * that entry is unmapped with same attributes as it was mapped.
+ *
+ * DMA_ATTR_UNMAP_VALID lists the attributes that must be identical
+ * between map and unmap. Any attribute outside this set (e.g.
+ * DMA_ATTR_NO_WARN, DMA_ATTR_SKIP_CPU_SYNC) is allowed to differ.
+ */
+#define DMA_ATTR_UNMAP_VALID \
+ (DMA_ATTR_NO_KERNEL_MAPPING | DMA_ATTR_FORCE_CONTIGUOUS | \
+ DMA_ATTR_MMIO | DMA_ATTR_REQUIRE_COHERENT | DMA_ATTR_PRIVILEGED | \
+ DMA_ATTR_CC_SHARED)
+ if ((ref->attrs & DMA_ATTR_UNMAP_VALID) !=
+ (entry->attrs & DMA_ATTR_UNMAP_VALID)) {
+ err_printk(ref->dev, entry,
+ "device driver frees "
+ "DMA memory with different attributes "
+ "[device address=0x%016llx] [size=%llu bytes] "
+ "[mapped with 0x%lx] [unmapped with 0x%lx]\n",
+ ref->dev_addr, ref->size, entry->attrs, ref->attrs);
+ }
+#undef DMA_ATTR_UNMAP_VALID
+
hash_bucket_del(entry);
put_hash_bucket(bucket, flags);
@@ -1250,15 +1280,23 @@ void debug_dma_map_phys(struct device *dev, phys_addr_t phys, size_t size,
entry->size = size;
entry->direction = direction;
entry->map_err_type = MAP_ERR_NOT_CHECKED;
+ entry->attrs = attrs;
+
+ if (attrs & DMA_ATTR_MMIO) {
+ unsigned long pfn = PHYS_PFN(phys);
- if (!(attrs & DMA_ATTR_MMIO)) {
+ if (pfn_valid(pfn) && !PageReserved(pfn_to_page(pfn)))
+ err_printk(dev, entry,
+ "dma_map_resource called for RAM address %pa\n",
+ &phys);
+ } else {
check_for_stack(dev, phys);
if (!PhysHighMem(phys))
check_for_illegal_area(dev, phys_to_virt(phys), size);
}
- add_dma_entry(entry, attrs);
+ add_dma_entry(entry);
}
void debug_dma_mapping_error(struct device *dev, dma_addr_t dma_addr)
@@ -1299,8 +1337,8 @@ void debug_dma_mapping_error(struct device *dev, dma_addr_t dma_addr)
}
EXPORT_SYMBOL(debug_dma_mapping_error);
-void debug_dma_unmap_phys(struct device *dev, dma_addr_t dma_addr,
- size_t size, int direction)
+void debug_dma_unmap_phys(struct device *dev, dma_addr_t dma_addr, size_t size,
+ int direction, unsigned long attrs)
{
struct dma_debug_entry ref = {
.type = dma_debug_phy,
@@ -1308,6 +1346,7 @@ void debug_dma_unmap_phys(struct device *dev, dma_addr_t dma_addr,
.dev_addr = dma_addr,
.size = size,
.direction = direction,
+ .attrs = attrs,
};
if (unlikely(dma_debug_disabled()))
@@ -1345,10 +1384,11 @@ void debug_dma_map_sg(struct device *dev, struct scatterlist *sg,
entry->direction = direction;
entry->sg_call_ents = nents;
entry->sg_mapped_ents = mapped_ents;
+ entry->attrs = attrs;
check_sg_segment(dev, s);
- add_dma_entry(entry, attrs);
+ add_dma_entry(entry);
}
}
@@ -1372,7 +1412,7 @@ static int get_nr_mapped_entries(struct device *dev,
}
void debug_dma_unmap_sg(struct device *dev, struct scatterlist *sglist,
- int nelems, int dir)
+ int nelems, int dir, unsigned long attrs)
{
struct scatterlist *s;
int mapped_ents = 0, i;
@@ -1390,6 +1430,7 @@ void debug_dma_unmap_sg(struct device *dev, struct scatterlist *sglist,
.size = sg_dma_len(s),
.direction = dir,
.sg_call_ents = nelems,
+ .attrs = attrs,
};
if (mapped_ents && i >= mapped_ents)
@@ -1440,12 +1481,13 @@ void debug_dma_alloc_coherent(struct device *dev, size_t size,
entry->size = size;
entry->dev_addr = dma_addr;
entry->direction = DMA_BIDIRECTIONAL;
+ entry->attrs = attrs;
- add_dma_entry(entry, attrs);
+ add_dma_entry(entry);
}
-void debug_dma_free_coherent(struct device *dev, size_t size,
- void *virt, dma_addr_t dma_addr)
+void debug_dma_free_coherent(struct device *dev, size_t size, void *virt,
+ dma_addr_t dma_addr, unsigned long attrs)
{
struct dma_debug_entry ref = {
.type = dma_debug_coherent,
@@ -1453,6 +1495,7 @@ void debug_dma_free_coherent(struct device *dev, size_t size,
.dev_addr = dma_addr,
.size = size,
.direction = DMA_BIDIRECTIONAL,
+ .attrs = attrs,
};
/* handle vmalloc and linear addresses */
@@ -1549,7 +1592,7 @@ void debug_dma_sync_sg_for_device(struct device *dev, struct scatterlist *sg,
struct dma_debug_entry ref = {
.type = dma_debug_sg,
.dev = dev,
- .paddr = sg_phys(sg),
+ .paddr = sg_phys(s),
.dev_addr = sg_dma_address(s),
.size = sg_dma_len(s),
.direction = direction,
@@ -1567,8 +1610,7 @@ void debug_dma_sync_sg_for_device(struct device *dev, struct scatterlist *sg,
void debug_dma_alloc_pages(struct device *dev, struct page *page,
size_t size, int direction,
- dma_addr_t dma_addr,
- unsigned long attrs)
+ dma_addr_t dma_addr)
{
struct dma_debug_entry *entry;
@@ -1586,7 +1628,7 @@ void debug_dma_alloc_pages(struct device *dev, struct page *page,
entry->dev_addr = dma_addr;
entry->direction = direction;
- add_dma_entry(entry, attrs);
+ add_dma_entry(entry);
}
void debug_dma_free_pages(struct device *dev, struct page *page,