dma-mapping: don't trace the DMA address when the allocation fails
PATCH 1/1 dma-mapping: don't trace the DMA address when the allocation fails
dma_alloc_attrs() passes *dma_handle to trace_dma_alloc() and debug_dma_alloc_coherent() without checking whether the allocation succeeded. No backend writes it on failure: dma_direct_alloc(), iommu_dma_alloc() and the dma_map_ops instances assign it only on the path that returns a buffer. Callers usually pass an uninitialized automatic variable, so with the tracepoint enabled a failed allocation records whatever the stack held, next to the virt_addr=(null) that marks the record as an error.
The device coherent pool path is the same: a non-zero return from dma_alloc_from_dev_coherent() means the request was handled, not that it succeeded, so cpu_addr is NULL and dma_handle untouched once the pool runs out.
Split both sites on cpu_addr, as dma_alloc_pages() and dma_alloc_noncontiguous() do further down the file, and pass 0 for the failure case like the two error paths already in this function.
---
Compile-tested only, with CONFIG_DMA_API_DEBUG=y and CONFIG_TRACEPOINTS=y so that both changed calls are built. The claim that no backend writes *dma_handle on failure was checked against dma_direct_alloc() and its helpers, iommu_dma_alloc(), iommu_dma_alloc_remap() and every in-tree dma_map_ops .alloc implementation.
kernel/dma/mapping.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-)
kernel/dma/mapping.c +15 -5
@@ -656,8 +656,12 @@ void *dma_alloc_attrs(struct device *dev, size_t size, dma_addr_t *dma_handle, attrs |= __DMA_ATTR_ALLOC_CC_SHARED;if (dma_alloc_from_dev_coherent (dev, size, dma_handle, &cpu_addr)) { -trace_dma_alloc (dev, cpu_addr, *dma_handle, size, -DMA_BIDIRECTIONAL , flag, attrs); +if (cpu_addr) +trace_dma_alloc (dev, cpu_addr, *dma_handle, size, +DMA_BIDIRECTIONAL , flag, attrs); +else +trace_dma_alloc (dev,NULL ,0 , size,DMA_BIDIRECTIONAL , + flag, attrs);return cpu_addr; }
@@ -676,9 +680,15 @@ void *dma_alloc_attrs(struct device *dev, size_t size, dma_addr_t *dma_handle,return NULL ; } -trace_dma_alloc (dev, cpu_addr, *dma_handle, size,DMA_BIDIRECTIONAL , - flag, attrs); -debug_dma_alloc_coherent (dev, size, *dma_handle, cpu_addr, attrs); +if (cpu_addr) { +trace_dma_alloc (dev, cpu_addr, *dma_handle, size, +DMA_BIDIRECTIONAL , flag, attrs); +debug_dma_alloc_coherent (dev, size, *dma_handle, cpu_addr, + attrs); + }else { +trace_dma_alloc (dev,NULL ,0 , size,DMA_BIDIRECTIONAL , flag, + attrs); + }return cpu_addr; }EXPORT_SYMBOL (dma_alloc_attrs);
Signature · 1 line
2.53.0
Quoted 3 lines · Donggeun Yoo
dma_alloc_attrs()passes*dma_handletotrace_dma_alloc()anddebug_dma_alloc_coherent()without checking whether the allocation succeeded.
I don't understand what you mean. We are literally in an if-statement that checks whether the allocation succeeded.
--Sean
Quoted 67 lines
No backend writes it on failure:
dma_direct_alloc(),iommu_dma_alloc()and thedma_map_opsinstances assign it only on the path that returns a buffer. Callers usually pass an uninitialized automatic variable, so with the tracepoint enabled a failed allocation records whatever the stack held, next to the virt_addr=(null) that marks the record as an error.The device coherent pool path is the same: a non-zero return from
dma_alloc_from_dev_coherent()means the request was handled, not that it succeeded, socpu_addris NULL anddma_handleuntouched once the pool runs out.Split both sites on
cpu_addr, asdma_alloc_pages()anddma_alloc_noncontiguous()do further down the file, and pass 0 for the failure case like the two error paths already in this function.Fixes 038eb433dc14 ("dma-mapping: add tracing for dma-mapping API calls")Fixes 68b6dbf1f441 ("dma-mapping: trace more error paths")Signed-off-by Donggeun Yoo <[email protected]>---
Compile-tested only, withCONFIG_DMA_API_DEBUG=yandCONFIG_TRACEPOINTS=yso that both changed calls are built. The claim that no backend writes*dma_handleon failure was checked againstdma_direct_alloc()and its helpers,iommu_dma_alloc(),iommu_dma_alloc_remap()and every in-treedma_map_ops.alloc implementation.kernel/dma/mapping.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-)kernel/dma/mapping.c +15
-5@@ -656,8 +656,12 @@ void *dma_alloc_attrs(struct device *dev, size_t size, dma_addr_t *dma_handle, attrs |= __DMA_ATTR_ALLOC_CC_SHARED;if (dma_alloc_from_dev_coherent (dev, size, dma_handle, &cpu_addr)) { -trace_dma_alloc (dev, cpu_addr, *dma_handle, size, -DMA_BIDIRECTIONAL , flag, attrs); +if (cpu_addr) +trace_dma_alloc (dev, cpu_addr, *dma_handle, size, +DMA_BIDIRECTIONAL , flag, attrs); +else +trace_dma_alloc (dev,NULL ,0 , size,DMA_BIDIRECTIONAL , + flag, attrs);return cpu_addr; }@@ -676,9 +680,15 @@ void *dma_alloc_attrs(struct device *dev, size_t size, dma_addr_t *dma_handle,return NULL ; } -trace_dma_alloc (dev, cpu_addr, *dma_handle, size,DMA_BIDIRECTIONAL , - flag, attrs); -debug_dma_alloc_coherent (dev, size, *dma_handle, cpu_addr, attrs); +if (cpu_addr) { +trace_dma_alloc (dev, cpu_addr, *dma_handle, size, +DMA_BIDIRECTIONAL , flag, attrs); +debug_dma_alloc_coherent (dev, size, *dma_handle, cpu_addr, + attrs); + }else { +trace_dma_alloc (dev,NULL ,0 , size,DMA_BIDIRECTIONAL , flag, + attrs); + }return cpu_addr; }EXPORT_SYMBOL (dma_alloc_attrs);
Quoted 2 lines · Sean Anderson
I don't understand what you mean. We are literally in an if-statement that checks whether the allocation succeeded.
That if checks whether the device has a coherent pool, not whether the allocation out of it worked. dma_alloc_from_dev_coherent():
* Returns 0 if dma_alloc_coherent should continue with allocating from
* generic memory areas, or !0 if dma_alloc_coherent should return @ret.
It returns 1 as soon as it finds a pool; __dma_alloc_from_coherent() stores NULL in @ret and leaves *dma_handle alone when the request is larger than the pool or the pool is full. The third paragraph of the commit message says this.
The second hunk is under no condition at all: dma_direct_alloc(), iommu_dma_alloc() and ops->alloc() return NULL on failure, and the trace below the if/else chain runs either way.
Quoted 10 lines · Donggeun Yoo
Quoted 2 lines · Sean Anderson
I don't understand what you mean. We are literally in an if-statement that checks whether the allocation succeeded.
That if checks whether the device has a coherent pool, not whether the allocation out of it worked.
dma_alloc_from_dev_coherent():* Returns 0 if
dma_alloc_coherentshould continue with allocating from
* generic memory areas, or !0 ifdma_alloc_coherentshould return @ret.
Quoted 8 lines
It returns 1 as soon as it finds a pool;
__dma_alloc_from_coherent()stores NULL in @ret and leaves*dma_handlealone when the request is larger than the pool or the pool is full. The third paragraph of the commit message says this.The second hunk is under no condition at all:
dma_direct_alloc(),iommu_dma_alloc()andops->alloc()return NULL on failure, and the trace below the if/else chain runs either way.
Ah, I see what you mean. TBH I would just do something like
cpu_handle ? *dma_handle : NULL
to make it clear what we're doing
--Sean
Quoted 5 lines · Sean Anderson
Ah, I see what you mean. TBH I would just do something like
cpu_handle?*dma_handle: NULLto make it clear what we're doing
It would have to be 0, dma_handle being a dma_addr_t, and debug_dma_alloc_coherent() reads *dma_handle too -- it drops the value when virt is NULL, but the read is still there -- so the conditional ends up written three times.
68b6dbf1f441 gave dma_alloc_pages() and dma_alloc_noncontiguous() the if/else split rather than a guarded argument, and I kept dma_alloc_attrs() in that shape so that the three read the same way.
I don't feel strongly about it. Say the word and I'll respin with the conditional inline.
Marek Szyprowski
maintainerQuoted 37 lines · Donggeun Yoo
dma_alloc_attrs()passes*dma_handletotrace_dma_alloc()anddebug_dma_alloc_coherent()without checking whether the allocation succeeded. No backend writes it on failure:dma_direct_alloc(),iommu_dma_alloc()and thedma_map_opsinstances assign it only on the path that returns a buffer. Callers usually pass an uninitialized automatic variable, so with the tracepoint enabled a failed allocation records whatever the stack held, next to the virt_addr=(null) that marks the record as an error.The device coherent pool path is the same: a non-zero return from
dma_alloc_from_dev_coherent()means the request was handled, not that it succeeded, socpu_addris NULL anddma_handleuntouched once the pool runs out.Split both sites on
cpu_addr, asdma_alloc_pages()anddma_alloc_noncontiguous()do further down the file, and pass 0 for the failure case like the two error paths already in this function.Fixes 038eb433dc14 ("dma-mapping: add tracing for dma-mapping API calls")Fixes 68b6dbf1f441 ("dma-mapping: trace more error paths")Signed-off-by Donggeun Yoo <[email protected]>---
Compile-tested only, withCONFIG_DMA_API_DEBUG=yandCONFIG_TRACEPOINTS=yso that both changed calls are built. The claim that no backend writes*dma_handleon failure was checked againstdma_direct_alloc()and its helpers,iommu_dma_alloc(),iommu_dma_alloc_remap()and every in-treedma_map_ops.alloc implementation.kernel/dma/mapping.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-)kernel/dma/mapping.c +0
-0@@ -656,8 +656,12 @@ void *dma_alloc_attrs(struct device *dev, size_t size, dma_addr_t *dma_handle, attrs |= __DMA_ATTR_ALLOC_CC_SHARED;
kernel/dma/mapping.c void *dma_alloc_attrs(struct device *dev, size_t size, dma_addr_t *dma_handle, · line 664
Quoted 10 lines
kernel/dma/mapping.c +6
-2if (dma_alloc_from_dev_coherent (dev, size, dma_handle, &cpu_addr)) { -trace_dma_alloc (dev, cpu_addr, *dma_handle, size, -DMA_BIDIRECTIONAL , flag, attrs); +if (cpu_addr) +trace_dma_alloc (dev, cpu_addr, *dma_handle, size, +DMA_BIDIRECTIONAL , flag, attrs); +else +trace_dma_alloc (dev,NULL ,0 , size,DMA_BIDIRECTIONAL , + flag, attrs);
Frankly speaking I would prefer to fix this in include/trace/events/dma.h with __entry->dma_addr = virt_addr ? dma_addr : 0;
Quoted 9 lines
kernel/dma/mapping.c +0
-2return cpu_addr; }@@ -676,9 +680,15 @@ void *dma_alloc_attrs(struct device *dev, size_t size, dma_addr_t *dma_handle,return NULL ; } -trace_dma_alloc (dev, cpu_addr, *dma_handle, size,DMA_BIDIRECTIONAL , - flag, attrs);
kernel/dma/mapping.c void *dma_alloc_attrs(struct device *dev, size_t size, dma_addr_t *dma_handle, · line 691
Quoted 10 lines
kernel/dma/mapping.c +9
-1-debug_dma_alloc_coherent (dev, size, *dma_handle, cpu_addr, attrs); +if (cpu_addr) { +trace_dma_alloc (dev, cpu_addr, *dma_handle, size, +DMA_BIDIRECTIONAL , flag, attrs); +debug_dma_alloc_coherent (dev, size, *dma_handle, cpu_addr, + attrs); + }else { +trace_dma_alloc (dev,NULL ,0 , size,DMA_BIDIRECTIONAL , flag, + attrs); + }
Here You remove the debug_dma_alloc call for the failed case. Right, it is a noop now for the allocation failure, but it might be extended with some kind of debugging in the future, so I prefer to keep the current code here.
kernel/dma/mapping.c void *dma_alloc_attrs(struct device *dev, size_t size, dma_addr_t *dma_handle, · line 694
Quoted 3 lines
kernel/dma/mapping.c +0
-0return cpu_addr; }EXPORT_SYMBOL (dma_alloc_attrs);
Best regards
Signature · 2 lines
Marek Szyprowski, PhD Samsung R&D Institute Poland
Quoted 2 lines · Marek Szyprowski
Frankly speaking I would prefer to fix this in
include/trace/events/dma.hwith__entry->dma_addr=virt_addr?dma_addr: 0;
Agreed, that is better. It also covers dma_alloc_pages() and dma_alloc_sgt_err(), which share dma_alloc_class, and any caller added later.
Quoted 4 lines
Here You remove the
debug_dma_alloccall for the failed case. Right, it is a noop now for the allocation failure, but it might be extended with some kind of debugging in the future, so I prefer to keep the current code here.
Fair enough. kernel/dma/mapping.c is untouched in v2.
include/trace/events/dma.h is not covered by the DMA MAINTAINERS entry, only by TRACING, so v2 has Steven and Masami on To as well.
v2, tested this time rather than compile-tested only: