DMA Engine development
 help / color / mirror / Atom feed
* [PATCH 0/4] dmatest: shared channel fixes and fatal errors option
@ 2026-07-22 15:47 Nathan Lynch via B4 Relay
  2026-07-22 15:47 ` [PATCH 1/4] dmaengine: dmatest: Don't forcibly terminate channel in polled mode Nathan Lynch via B4 Relay
                   ` (3 more replies)
  0 siblings, 4 replies; 9+ messages in thread
From: Nathan Lynch via B4 Relay @ 2026-07-22 15:47 UTC (permalink / raw)
  To: Vinod Koul, Frank Li
  Cc: Tycho Andersen, Wei Huang, dmaengine, linux-kernel, Nathan Lynch

Sending several fixes for issues in dmatest that arise when running
multiple threads per channel. I have encountered these issues while
testing the sdxi driver.

Patch 1 "Don't forcibly terminate channel in polled mode" is a resend of a
fix I sent a while back that didn't get picked up. [1]

Also included is a feature introducing a "fatal errors" parameter that
provides the option of panicking the kernel after a given number of errors
have occurred in a run.

Claude Code w/Opus 4.8 was used to investigate the problems fixed by
patches 2 and 3, and it assisted in writing the changes in patch 2-4.

[1] https://lore.kernel.org/dmaengine/20250408-dmaengine-dmatest-poll-mode-fix-v1-1-754a446a5363@amd.com/

Signed-off-by: Nathan Lynch <nathan.lynch@amd.com>
---
Nathan Lynch (4):
      dmaengine: dmatest: Don't forcibly terminate channel in polled mode
      dmaengine: dmatest: Suppress bogus failures when stopping a run
      dmaengine: dmatest: Terminate channel before freeing threads
      dmaengine: dmatest: Optionally make test errors fatal

 drivers/dma/dmatest.c | 67 ++++++++++++++++++++++++++++++++++++++++++---------
 1 file changed, 56 insertions(+), 11 deletions(-)
---
base-commit: dc59e4fea9d83f03bad6bddf3fa2e52491777482
change-id: 20260722-dmatest-fixes-and-fatal-errors-3b3653d5ac9b

Best regards,
--  
Nathan Lynch <nathan.lynch@amd.com>



^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH 1/4] dmaengine: dmatest: Don't forcibly terminate channel in polled mode
  2026-07-22 15:47 [PATCH 0/4] dmatest: shared channel fixes and fatal errors option Nathan Lynch via B4 Relay
@ 2026-07-22 15:47 ` Nathan Lynch via B4 Relay
  2026-07-22 15:57   ` sashiko-bot
  2026-07-22 15:47 ` [PATCH 2/4] dmaengine: dmatest: Suppress bogus failures when stopping a run Nathan Lynch via B4 Relay
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 9+ messages in thread
From: Nathan Lynch via B4 Relay @ 2026-07-22 15:47 UTC (permalink / raw)
  To: Vinod Koul, Frank Li
  Cc: Tycho Andersen, Wei Huang, dmaengine, linux-kernel, Nathan Lynch

From: Nathan Lynch <nathan.lynch@amd.com>

Invoking dmaengine_terminate_sync() against the channel after each
submitted descriptor is strange. It's benign when the test is
single-threaded, but disruptive when multiple threads are submitting
descriptors to the channel concurrently:

  # cd /sys/module/dmatest/parameters/
  # echo 1 > polled
  # echo 2 > threads_per_chan
  # echo dma0chan0 > channel
  # echo 1 > run
...
  dmatest: Added 2 threads using dma0chan0
  dmatest: Started 2 threads using dma0chan0
  xilinx-zynqmp-dma ffa80000.dma: dma_sync_wait: timeout!
  xilinx-zynqmp-dma ffa80000.dma: dma_sync_wait: timeout!
  dmatest: dma0chan0-copy0: result #3: 'test timed out' with
    src_off=0x487 dst_off=0xf8 len=0x171b (0)
  dmatest: dma0chan0-copy1: result #6: 'test timed out' with
    src_off=0x227d dst_off=0xf99 len=0xf7a (0)

Remove the call to dmaengine_terminate_sync() from the main thread
loop.

Fixes: fb9816f9d05f ("dmaengine: dmatest: Add support for completion polling")
Signed-off-by: Nathan Lynch <nathan.lynch@amd.com>
---
 drivers/dma/dmatest.c | 1 -
 1 file changed, 1 deletion(-)

diff --git a/drivers/dma/dmatest.c b/drivers/dma/dmatest.c
index 2ae3469397f3..60bc448f42ee 100644
--- a/drivers/dma/dmatest.c
+++ b/drivers/dma/dmatest.c
@@ -837,7 +837,6 @@ static int dmatest_func(void *data)
 
 		if (params->polled) {
 			status = dma_sync_wait(chan, cookie);
-			dmaengine_terminate_sync(chan);
 			if (status == DMA_COMPLETE)
 				done->done = true;
 		} else {

-- 
2.54.0



^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH 2/4] dmaengine: dmatest: Suppress bogus failures when stopping a run
  2026-07-22 15:47 [PATCH 0/4] dmatest: shared channel fixes and fatal errors option Nathan Lynch via B4 Relay
  2026-07-22 15:47 ` [PATCH 1/4] dmaengine: dmatest: Don't forcibly terminate channel in polled mode Nathan Lynch via B4 Relay
@ 2026-07-22 15:47 ` Nathan Lynch via B4 Relay
  2026-07-22 16:09   ` sashiko-bot
  2026-07-22 15:47 ` [PATCH 3/4] dmaengine: dmatest: Terminate channel before freeing threads Nathan Lynch via B4 Relay
  2026-07-22 15:47 ` [PATCH 4/4] dmaengine: dmatest: Optionally make test errors fatal Nathan Lynch via B4 Relay
  3 siblings, 1 reply; 9+ messages in thread
From: Nathan Lynch via B4 Relay @ 2026-07-22 15:47 UTC (permalink / raw)
  To: Vinod Koul, Frank Li
  Cc: Tycho Andersen, Wei Huang, dmaengine, linux-kernel, Nathan Lynch

From: Nathan Lynch <nathan.lynch@amd.com>

Writing 0 to the module 'run' attribute stops the test threads with
kthread_stop(), waking threads out of wait_event_freezable_timeout()
before the last transfer's completion callback has run. The stopping
thread samples the transfer status and then checks done->done at two
different instants, so the last transaction gets misreported two ways:

- "test timed out": the wait returns early with done->done still clear
  even though the transfer is simply still finishing — not a real
  timeout.

- "completion busy status": status is read as DMA_IN_PROGRESS a moment
  before the transfer completes and latches done->done, so the stale
  status is reported as a failure against a transfer that actually
  succeeded.

This is not a recent regression: the threads have used an interruptible,
freezable timeout wait since long before the wait queue was moved into
thread context by commit 6f6a23a213be ("dmaengine: dmatest: move callback
wait queue to thread context"), so a stop has always been able to wake
the wait before the final callback runs.

When kthread_should_stop() returns true, use dma_sync_wait() to settle
the pending transaction. Real errors still yield DMA_ERROR.

Assisted-by: Claude:claude-opus-4-8 [Claude-Code]
Signed-off-by: Nathan Lynch <nathan.lynch@amd.com>
---
 drivers/dma/dmatest.c | 17 ++++++++++++-----
 1 file changed, 12 insertions(+), 5 deletions(-)

diff --git a/drivers/dma/dmatest.c b/drivers/dma/dmatest.c
index 60bc448f42ee..3267c7e86f5d 100644
--- a/drivers/dma/dmatest.c
+++ b/drivers/dma/dmatest.c
@@ -702,6 +702,7 @@ static int dmatest_func(void *data)
 		struct dmaengine_unmap_data *um;
 		dma_addr_t *dsts;
 		unsigned int len;
+		bool stopping;
 
 		total_tests++;
 
@@ -839,6 +840,7 @@ static int dmatest_func(void *data)
 			status = dma_sync_wait(chan, cookie);
 			if (status == DMA_COMPLETE)
 				done->done = true;
+			stopping = kthread_should_stop();
 		} else {
 			dma_async_issue_pending(chan);
 
@@ -846,14 +848,19 @@ static int dmatest_func(void *data)
 					done->done,
 					msecs_to_jiffies(params->timeout));
 
-			status = dma_async_is_tx_complete(chan, cookie, NULL,
-							  NULL);
+			stopping = kthread_should_stop();
+			status = stopping ?
+				dma_sync_wait(chan, cookie) :
+				dma_async_is_tx_complete(chan, cookie, NULL, NULL);
 		}
 
 		if (!done->done) {
-			result("test timed out", total_tests, src->off, dst->off,
-			       len, 0);
-			goto error_unmap_continue;
+			/* stopping: dma_sync_wait() let the transfer finish, not a timeout */
+			if (!stopping) {
+				result("test timed out", total_tests, src->off,
+				       dst->off, len, 0);
+				goto error_unmap_continue;
+			}
 		} else if (status != DMA_COMPLETE &&
 			   !(dma_has_cap(DMA_COMPLETION_NO_ORDER,
 					 dev->cap_mask) &&

-- 
2.54.0



^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH 3/4] dmaengine: dmatest: Terminate channel before freeing threads
  2026-07-22 15:47 [PATCH 0/4] dmatest: shared channel fixes and fatal errors option Nathan Lynch via B4 Relay
  2026-07-22 15:47 ` [PATCH 1/4] dmaengine: dmatest: Don't forcibly terminate channel in polled mode Nathan Lynch via B4 Relay
  2026-07-22 15:47 ` [PATCH 2/4] dmaengine: dmatest: Suppress bogus failures when stopping a run Nathan Lynch via B4 Relay
@ 2026-07-22 15:47 ` Nathan Lynch via B4 Relay
  2026-07-22 16:23   ` sashiko-bot
  2026-07-22 15:47 ` [PATCH 4/4] dmaengine: dmatest: Optionally make test errors fatal Nathan Lynch via B4 Relay
  3 siblings, 1 reply; 9+ messages in thread
From: Nathan Lynch via B4 Relay @ 2026-07-22 15:47 UTC (permalink / raw)
  To: Vinod Koul, Frank Li
  Cc: Tycho Andersen, Wei Huang, dmaengine, linux-kernel, Nathan Lynch

From: Nathan Lynch <nathan.lynch@amd.com>

On a channel shared by multiple threads, the completion callbacks run from
one tasklet, and dmaengine_terminate_sync() flushes it. Freeing a thread
before that flush can leave a pending callback pointing at freed memory
(dmatest_callback() reads thread->done).

Stop all channel threads and then flush the channel before freeing the
threads.

Fixes: 6f6a23a213be ("dmaengine: dmatest: move callback wait queue to thread context")
Assisted-by: Claude:claude-opus-4-8 [Claude-Code]
Signed-off-by: Nathan Lynch <nathan.lynch@amd.com>
---
 drivers/dma/dmatest.c | 13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)

diff --git a/drivers/dma/dmatest.c b/drivers/dma/dmatest.c
index 3267c7e86f5d..0d7752c2451a 100644
--- a/drivers/dma/dmatest.c
+++ b/drivers/dma/dmatest.c
@@ -959,18 +959,23 @@ static void dmatest_cleanup_channel(struct dmatest_chan *dtc)
 	struct dmatest_thread	*_thread;
 	int			ret;
 
-	list_for_each_entry_safe(thread, _thread, &dtc->threads, node) {
+	/* stop all threads first */
+	list_for_each_entry(thread, &dtc->threads, node) {
 		ret = kthread_stop(thread->task);
 		pr_debug("thread %s exited with status %d\n",
 			 thread->task->comm, ret);
+	}
+
+	/* flush the channel (e.g. vchan_synchronize() -> tasklet_kill()) */
+	dmaengine_terminate_sync(dtc->chan);
+
+	/* now it is safe to free the thread memory */
+	list_for_each_entry_safe(thread, _thread, &dtc->threads, node) {
 		list_del(&thread->node);
 		put_task_struct(thread->task);
 		kfree(thread);
 	}
 
-	/* terminate all transfers on specified channels */
-	dmaengine_terminate_sync(dtc->chan);
-
 	kfree(dtc);
 }
 

-- 
2.54.0



^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH 4/4] dmaengine: dmatest: Optionally make test errors fatal
  2026-07-22 15:47 [PATCH 0/4] dmatest: shared channel fixes and fatal errors option Nathan Lynch via B4 Relay
                   ` (2 preceding siblings ...)
  2026-07-22 15:47 ` [PATCH 3/4] dmaengine: dmatest: Terminate channel before freeing threads Nathan Lynch via B4 Relay
@ 2026-07-22 15:47 ` Nathan Lynch via B4 Relay
  2026-07-22 16:33   ` sashiko-bot
  3 siblings, 1 reply; 9+ messages in thread
From: Nathan Lynch via B4 Relay @ 2026-07-22 15:47 UTC (permalink / raw)
  To: Vinod Koul, Frank Li
  Cc: Tycho Andersen, Wei Huang, dmaengine, linux-kernel, Nathan Lynch

From: Nathan Lynch <nathan.lynch@amd.com>

For automated testing it is useful to have dmatest halt the machine when
errors occur rather than logging them and continuing, so that the system
state at the point of failure can be captured instead of allowing the run
to carry on.

Add a 'fatal_errors' module parameter, defaulting to 0. When set to a
non-zero value N, panic the kernel once N test errors have accumulated in
a run; the count is reset at the start of each run. The panic is triggered
after the existing result() line is logged, so the specific error (data
mismatch, mapping/prep/submit error, timeout, or bad completion status) is
recorded before the crash.

Assisted-by: Claude:claude-opus-4-8 [Claude-Code]
Signed-off-by: Nathan Lynch <nathan.lynch@amd.com>
---
 drivers/dma/dmatest.c | 36 +++++++++++++++++++++++++++++++++++-
 1 file changed, 35 insertions(+), 1 deletion(-)

diff --git a/drivers/dma/dmatest.c b/drivers/dma/dmatest.c
index 0d7752c2451a..a070ce6f2fa9 100644
--- a/drivers/dma/dmatest.c
+++ b/drivers/dma/dmatest.c
@@ -7,6 +7,7 @@
  */
 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
 
+#include <linux/atomic.h>
 #include <linux/err.h>
 #include <linux/delay.h>
 #include <linux/dma-mapping.h>
@@ -92,6 +93,11 @@ static bool polled;
 module_param(polled, bool, 0644);
 MODULE_PARM_DESC(polled, "Use polling for completion instead of interrupts");
 
+static unsigned int fatal_errors;
+module_param(fatal_errors, uint, 0644);
+MODULE_PARM_DESC(fatal_errors,
+		"Panic after this many test errors in a run; 0 = never (default: 0)");
+
 /**
  * struct dmatest_params - test parameters.
  * @nobounce:		prevent using swiotlb buffer
@@ -109,6 +115,7 @@ MODULE_PARM_DESC(polled, "Use polling for completion instead of interrupts");
  * @alignment:		custom data address alignment taken as 2^alignment
  * @transfer_size:	custom transfer size in bytes
  * @polled:		use polling for completion instead of interrupts
+ * @fatal_errors:	panic after this many test errors in a run (0 = never)
  */
 struct dmatest_params {
 	bool		nobounce;
@@ -126,6 +133,7 @@ struct dmatest_params {
 	int		alignment;
 	unsigned int	transfer_size;
 	bool		polled;
+	unsigned int	fatal_errors;
 };
 
 /**
@@ -133,6 +141,7 @@ struct dmatest_params {
  * @params:		test parameters
  * @channels:		channels under test
  * @nr_channels:	number of channels under test
+ * @error_count:	test errors in the current run (for fatal_errors)
  * @lock:		access protection to the fields of this structure
  * @did_init:		module has been initialized completely
  * @last_error:		test has faced configuration issues
@@ -144,6 +153,7 @@ struct dmatest_info {
 	/* Internal state */
 	struct list_head	channels;
 	unsigned int		nr_channels;
+	atomic_t		error_count;
 	int			last_error;
 	struct mutex		lock;
 	bool			did_init;
@@ -487,6 +497,24 @@ static void dbg_result(const char *err, unsigned int n, unsigned int src_off,
 		dbg_result(err, n, src_off, dst_off, len, data);\
 })
 
+/*
+ * Record a test error whose details were just logged via result(). When
+ * fatal_errors is set, panic once that many errors have accumulated in the
+ * current run.
+ */
+static void dmatest_note_error(struct dmatest_info *info)
+{
+	unsigned int errors;
+
+	if (!info->params.fatal_errors)
+		return;
+
+	errors = atomic_inc_return(&info->error_count);
+	if (errors >= info->params.fatal_errors)
+		panic("dmatest: fatal_errors=%u and %u error(s) encountered\n",
+		      info->params.fatal_errors, errors);
+}
+
 static unsigned long long dmatest_persec(s64 runtime, unsigned int val)
 {
 	unsigned long long per_sec = 1000000;
@@ -583,7 +611,7 @@ static int dmatest_func(void *data)
 	struct dma_device	*dev;
 	struct device		*dma_dev;
 	unsigned int		error_count;
-	unsigned int		failed_tests = 0;
+	unsigned int		failed_tests = 0;	/* for this thread */
 	unsigned int		total_tests = 0;
 	dma_cookie_t		cookie;
 	enum dma_status		status;
@@ -755,6 +783,7 @@ static int dmatest_func(void *data)
 			failed_tests++;
 			result("unmap data NULL", total_tests,
 			       src->off, dst->off, len, ret);
+			dmatest_note_error(info);
 			continue;
 		}
 
@@ -910,6 +939,7 @@ static int dmatest_func(void *data)
 			result("data error", total_tests, src->off, dst->off,
 			       len, error_count);
 			failed_tests++;
+			dmatest_note_error(info);
 		} else {
 			verbose_result("test passed", total_tests, src->off,
 				       dst->off, len, 0);
@@ -920,6 +950,7 @@ static int dmatest_func(void *data)
 error_unmap_continue:
 		dmaengine_unmap_put(um);
 		failed_tests++;
+		dmatest_note_error(info);
 	}
 	ktime = ktime_sub(ktime_get(), ktime);
 	ktime = ktime_sub(ktime, comparetime);
@@ -1135,6 +1166,7 @@ static void add_threaded_test(struct dmatest_info *info)
 	params->alignment = alignment;
 	params->transfer_size = transfer_size;
 	params->polled = polled;
+	params->fatal_errors = fatal_errors;
 
 	request_channels(info, DMA_MEMCPY);
 	request_channels(info, DMA_MEMSET);
@@ -1147,6 +1179,8 @@ static void run_pending_tests(struct dmatest_info *info)
 	struct dmatest_chan *dtc;
 	unsigned int thread_count = 0;
 
+	atomic_set(&info->error_count, 0);
+
 	list_for_each_entry(dtc, &info->channels, node) {
 		struct dmatest_thread *thread;
 

-- 
2.54.0



^ permalink raw reply related	[flat|nested] 9+ messages in thread

* Re: [PATCH 1/4] dmaengine: dmatest: Don't forcibly terminate channel in polled mode
  2026-07-22 15:47 ` [PATCH 1/4] dmaengine: dmatest: Don't forcibly terminate channel in polled mode Nathan Lynch via B4 Relay
@ 2026-07-22 15:57   ` sashiko-bot
  0 siblings, 0 replies; 9+ messages in thread
From: sashiko-bot @ 2026-07-22 15:57 UTC (permalink / raw)
  To: Nathan Lynch; +Cc: Frank.Li, dmaengine, vkoul

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:

New issues:
- [High] Removing dmaengine_terminate_sync(chan) from the polled mode path causes the test to unmap DMA buffers while the hardware might still be actively processing the queued descriptor.

Pre-existing issues:
- [High] Test data buffers are freed before pending DMA transfers are terminated at the end of the test thread, creating a race window where active DMA hardware can corrupt freed slab memory.
--

--- Patch [1]: [PATCH 1/4] dmaengine: dmatest: Don't forcibly terminate channel in polled mode ---
commit ff3488aa5c4eb22b5aa3631448708f7560e37c84
Author: Nathan Lynch <nathan.lynch@amd.com>

dmaengine: dmatest: Don't forcibly terminate channel in polled mode
    
This commit removes the call to dmaengine_terminate_sync() from the
main thread loop for polled transfers. This prevents disruptive
terminations when multiple threads are concurrently submitting descriptors.

> diff --git a/drivers/dma/dmatest.c b/drivers/dma/dmatest.c
> index 2ae3469397f37..60bc448f42eeb 100644
> --- a/drivers/dma/dmatest.c
> +++ b/drivers/dma/dmatest.c
> @@ -837,7 +837,6 @@ static int dmatest_func(void *data)
>  
>  		if (params->polled) {
>  			status = dma_sync_wait(chan, cookie);
> -			dmaengine_terminate_sync(chan);
>  			if (status == DMA_COMPLETE)

[Severity: High]
By removing the termination here, does this code leave the hardware running
if dma_sync_wait() times out?

If dma_sync_wait() returns DMA_ERROR on a timeout, done->done remains false.
The loop then eventually branches to error_unmap_continue without terminating
the pending transfer.

This error path calls dmaengine_unmap_put(), unmapping the buffers while the
hardware might still be actively processing the queued descriptor. Could this
lead to active DMA memory corruption?

[Severity: High]
This is a pre-existing issue, but does dmatest_func() free test buffers
before terminating pending DMA transfers during thread cleanup?

Looking at the error handling and exit path at the end of the test thread:

dmatest_func() {
    ...
    err_dst:
        dmatest_free_test_data(dst);
    err_src:
        dmatest_free_test_data(src);
    ...
    err_thread_type:
        dmaengine_terminate_sync(chan);
    ...
}

If a transfer had timed out and was left running, could the DMA hardware
execute the pending descriptor in the window between freeing the slab
memory and terminating the channel?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260722-dmatest-fixes-and-fatal-errors-v1-0-8287709987ea@amd.com?part=1

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH 2/4] dmaengine: dmatest: Suppress bogus failures when stopping a run
  2026-07-22 15:47 ` [PATCH 2/4] dmaengine: dmatest: Suppress bogus failures when stopping a run Nathan Lynch via B4 Relay
@ 2026-07-22 16:09   ` sashiko-bot
  0 siblings, 0 replies; 9+ messages in thread
From: sashiko-bot @ 2026-07-22 16:09 UTC (permalink / raw)
  To: Nathan Lynch; +Cc: dmaengine, vkoul, Frank.Li

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Bypassing the error path during thread stop prevents dmaengine_terminate_sync() from being called, leading to a Use-After-Free of the dmatest_thread struct by the completion callback.
- [Medium] Bypassed DMA status check when stopping results in unhandled DMA errors.
--

--- Patch [2]: [PATCH 2/4] dmaengine: dmatest: Suppress bogus failures when stopping a run ---
commit 65f43e5ca342a54064656c1ebe548edaa5c088bd
Author: Nathan Lynch <nathan.lynch@amd.com>

dmaengine: dmatest: Suppress bogus failures when stopping a run

Writing 0 to the module 'run' attribute stops the test threads with
kthread_stop(). This commit introduces checks against kthread_should_stop()
to suppress stale failures against transfers that actually succeeded.

> diff --git a/drivers/dma/dmatest.c b/drivers/dma/dmatest.c
> --- a/drivers/dma/dmatest.c
> +++ b/drivers/dma/dmatest.c
[ ... ]
> @@ -848,14 +848,19 @@ static int dmatest_func(void *data)
>  					done->done,
>  					msecs_to_jiffies(params->timeout));
>  
> -			status = dma_async_is_tx_complete(chan, cookie, NULL,
> -							  NULL);
> +			stopping = kthread_should_stop();
> +			status = stopping ?
> +				dma_sync_wait(chan, cookie) :
> +				dma_async_is_tx_complete(chan, cookie, NULL, NULL);
>  		}
>  
>  		if (!done->done) {
> -			result("test timed out", total_tests, src->off, dst->off,
> -			       len, 0);
> -			goto error_unmap_continue;
> +			/* stopping: dma_sync_wait() let the transfer finish, not a timeout */
> +			if (!stopping) {
> +				result("test timed out", total_tests, src->off,
> +				       dst->off, len, 0);
> +				goto error_unmap_continue;
> +			}

[Severity: Medium]
By skipping this error path when stopping is true, does this bypass evaluating
the status returned from dma_sync_wait()?

If a legitimate hardware error or timeout occurs exactly when the thread is
stopped, it looks like it would either falsely report a successful test (if
noverify is set) or misreport it as a data mismatch.

[Severity: High]
Could skipping the goto error_unmap_continue here lead to a use-after-free
in a delayed callback?

If a delayed callback leaves done->done false when stopping, failed_tests
won't be incremented. Later, when dmatest_func() exits:

    if (ret || failed_tests)
        dmaengine_terminate_sync(chan);

Since failed_tests remained 0, the thread exits without synchronizing the
channel. Then dmatest_cleanup_channel() executes:

    put_task_struct(thread->task);
    kfree(thread);
}

/* terminate all transfers on specified channels */
dmaengine_terminate_sync(dtc->chan);

Is there a window where a pending tasklet executes dmatest_callback()
after kfree(thread) but before dmaengine_terminate_sync() is finally called,
leading to a use-after-free on the thread structure?

>  		} else if (status != DMA_COMPLETE &&
>  			   !(dma_has_cap(DMA_COMPLETION_NO_ORDER,
>  					 dev->cap_mask) &&

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260722-dmatest-fixes-and-fatal-errors-v1-0-8287709987ea@amd.com?part=2

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH 3/4] dmaengine: dmatest: Terminate channel before freeing threads
  2026-07-22 15:47 ` [PATCH 3/4] dmaengine: dmatest: Terminate channel before freeing threads Nathan Lynch via B4 Relay
@ 2026-07-22 16:23   ` sashiko-bot
  0 siblings, 0 replies; 9+ messages in thread
From: sashiko-bot @ 2026-07-22 16:23 UTC (permalink / raw)
  To: Nathan Lynch; +Cc: Frank.Li, dmaengine, vkoul

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:

New issues:
- [High] Spurious WARN_ON due to racing callback execution and thread shutdown.

Pre-existing issues:
- [High] Incomplete fix leaves Use-After-Free for drivers lacking terminate_all support.
--

--- Patch [3]: [PATCH 3/4] dmaengine: dmatest: Terminate channel before freeing threads ---
commit 45380ad31d07f2d946845ad6c99217e3ae5ea8ae
Author: Nathan Lynch <nathan.lynch@amd.com>

dmaengine: dmatest: Terminate channel before freeing threads

This commit modifies dmatest_cleanup_channel() to stop all threads first,
flush the channel, and only then free the thread memory. This prevents
pending callbacks from accessing freed memory.

> diff --git a/drivers/dma/dmatest.c b/drivers/dma/dmatest.c
> index 3267c7e86f5d5..0d7752c2451a9 100644
> --- a/drivers/dma/dmatest.c
> +++ b/drivers/dma/dmatest.c
> @@ -959,18 +959,23 @@ static void dmatest_cleanup_channel(struct dmatest_chan *dtc)
>  	struct dmatest_thread	*_thread;
>  	int			ret;
>  
> -	list_for_each_entry_safe(thread, _thread, &dtc->threads, node) {
> +	/* stop all threads first */
> +	list_for_each_entry(thread, &dtc->threads, node) {
>  		ret = kthread_stop(thread->task);
>  		pr_debug("thread %s exited with status %d\n",
>  			 thread->task->comm, ret);
> +	}
> +
> +	/* flush the channel (e.g. vchan_synchronize() -> tasklet_kill()) */
> +	dmaengine_terminate_sync(dtc->chan);

[Severity: High]
Could this trigger a spurious WARN_ON when flushing pending callbacks?

When a test is stopped, kthread_stop() runs, and dmatest_func() sets
thread->done to true before exiting.

dmatest_cleanup_channel() then calls dmaengine_terminate_sync() which flushes
any pending callbacks.

Since thread->done is already true, will the pending dmatest_callback() read
it and incorrectly fall into the warning path?

drivers/dma/dmatest.c:dmatest_callback() {
    ...
    } else {
        /* ... */
        WARN(1, "dmatest: Kernel memory may be corrupted!!\n");
    }
}

[Severity: High]
This is a pre-existing issue, but does ignoring the return value of
dmaengine_terminate_sync() leave a use-after-free risk for drivers that lack
terminate_all support?

If a driver doesn't support device_terminate_all, dmaengine_terminate_sync()
returns -EINVAL and skips dmaengine_synchronize():

include/linux/dmaengine.h:dmaengine_terminate_sync() {
    ...
    ret = dmaengine_terminate_async(chan);
    if (ret)
        return ret;

    dmaengine_synchronize(chan);
    return 0;
}

If synchronization is skipped, couldn't the hardware transfer still finish
and fire the tasklet after we proceed to kfree(thread) below, causing
dmatest_callback() to access freed memory?

> +
> +	/* now it is safe to free the thread memory */
> +	list_for_each_entry_safe(thread, _thread, &dtc->threads, node) {
>  		list_del(&thread->node);
>  		put_task_struct(thread->task);
>  		kfree(thread);
>  	}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260722-dmatest-fixes-and-fatal-errors-v1-0-8287709987ea@amd.com?part=3

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH 4/4] dmaengine: dmatest: Optionally make test errors fatal
  2026-07-22 15:47 ` [PATCH 4/4] dmaengine: dmatest: Optionally make test errors fatal Nathan Lynch via B4 Relay
@ 2026-07-22 16:33   ` sashiko-bot
  0 siblings, 0 replies; 9+ messages in thread
From: sashiko-bot @ 2026-07-22 16:33 UTC (permalink / raw)
  To: Nathan Lynch; +Cc: vkoul, Frank.Li, dmaengine

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] A Time-of-Check to Time-of-Use (TOCTOU) race condition on `info->params.fatal_errors` can cause an unintended kernel panic when the parameter is concurrently set to 0 ('never panic').
--

--- Patch [4]: [PATCH 4/4] dmaengine: dmatest: Optionally make test errors fatal ---
commit 289cdfd12c204ee46d7f18f6c08608f36bd53956
Author: Nathan Lynch <nathan.lynch@amd.com>

dmaengine: dmatest: Optionally make test errors fatal

This commit adds a module parameter fatal_errors to dmatest. When set to a
non-zero value, the kernel will panic after accumulating that many test errors
during a run.

> diff --git a/drivers/dma/dmatest.c b/drivers/dma/dmatest.c
> index 0d7752c2451a9..a070ce6f2fa94 100644
> --- a/drivers/dma/dmatest.c
> +++ b/drivers/dma/dmatest.c

[ ... ]

> @@ -487,6 +497,24 @@ static void dbg_result(const char *err, unsigned int n, unsigned int src_off,
>  		dbg_result(err, n, src_off, dst_off, len, data);\
>  })
>  
> +/*
> + * Record a test error whose details were just logged via result(). When
> + * fatal_errors is set, panic once that many errors have accumulated in the
> + * current run.
> + */
> +static void dmatest_note_error(struct dmatest_info *info)
> +{
> +	unsigned int errors;
> +
> +	if (!info->params.fatal_errors)
> +		return;
> +
> +	errors = atomic_inc_return(&info->error_count);
> +	if (errors >= info->params.fatal_errors)
> +		panic("dmatest: fatal_errors=%u and %u error(s) encountered\n",
> +		      info->params.fatal_errors, errors);

[Severity: High]
Can a concurrent modification of the fatal_errors parameter cause a spurious
kernel panic here?

If a user modifies the fatal_errors parameter to 0 (meaning 'never panic') via
sysfs while test threads are executing, info->params.fatal_errors could be
concurrently overwritten with 0 via add_threaded_test().

If this happens between the initial check and the second read:

CPU1 in dmatest_note_error()
    if (!info->params.fatal_errors) // reads > 0
        return;

    errors = atomic_inc_return(&info->error_count); // acts as a barrier

CPU2 modifying sysfs
    info->params.fatal_errors = 0;

CPU1 in dmatest_note_error()
    if (errors >= info->params.fatal_errors) // reads 0

Will the condition errors >= 0 evaluate to true and inadvertently trigger the
panic? Could we read info->params.fatal_errors into a local variable once to
prevent this race?

> +}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260722-dmatest-fixes-and-fatal-errors-v1-0-8287709987ea@amd.com?part=4

^ permalink raw reply	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2026-07-22 16:33 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-22 15:47 [PATCH 0/4] dmatest: shared channel fixes and fatal errors option Nathan Lynch via B4 Relay
2026-07-22 15:47 ` [PATCH 1/4] dmaengine: dmatest: Don't forcibly terminate channel in polled mode Nathan Lynch via B4 Relay
2026-07-22 15:57   ` sashiko-bot
2026-07-22 15:47 ` [PATCH 2/4] dmaengine: dmatest: Suppress bogus failures when stopping a run Nathan Lynch via B4 Relay
2026-07-22 16:09   ` sashiko-bot
2026-07-22 15:47 ` [PATCH 3/4] dmaengine: dmatest: Terminate channel before freeing threads Nathan Lynch via B4 Relay
2026-07-22 16:23   ` sashiko-bot
2026-07-22 15:47 ` [PATCH 4/4] dmaengine: dmatest: Optionally make test errors fatal Nathan Lynch via B4 Relay
2026-07-22 16:33   ` sashiko-bot

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox