Skip to content

Day103 — DMA Completion, Callback, and Transaction Status

Today's Goal

Understand how Linux DMA Engine tracks transaction completion and exposes completion state to DMA client drivers.

The main goals were:

  • Distinguish hardware completion from DMA Engine transaction completion.
  • Understand dmaengine_tx_status().
  • Understand device_tx_status().
  • Understand dma_cookie_status() and dma_async_is_complete().
  • Understand struct dma_tx_state.
  • Understand DMA residue reporting.
  • Understand DMA cookie wrap-around.
  • Understand completion callbacks and result-aware callbacks.
  • Trace the virt-dma completion path.
  • Compare callback-based completion with transaction-status queries.
  • Understand descriptor lifetime after hardware completion.

What I Learned

DMA Completion Is Not a Single Event

DMA completion should not be treated as one synchronous event.

A more accurate model is:

Hardware Completion
Cookie Completion
Transaction Status Becomes Observable
Deferred Completion Processing
Callback Execution
Descriptor Finalization

These stages may occur at different times.

In particular:

Cookie complete
Callback executed

A DMA transaction may already report DMA_COMPLETE while its callback is still waiting for deferred execution.


Hardware completion is detected by the DMA controller and its controller driver.

The DMA Engine Framework does not directly know that hardware has completed a transfer.

The controller driver translates hardware completion into DMA Engine transaction state.

For a virt-dma based implementation, the completion path commonly reaches:

vchan_cookie_complete(vd);

which includes:

dma_cookie_complete(&vd->tx);

dma_cookie_complete() updates generic DMA cookie bookkeeping.

Therefore:

Hardware completion
Controller Driver
dma_cookie_complete()
DMA Engine transaction completion state

dmaengine_tx_status()

A DMA client can query a previously submitted transaction using:

dmaengine_tx_status(chan, cookie, &state);

The API does not directly inspect DMA hardware.

Conceptually:

Client Driver
dmaengine_tx_status()
device_tx_status()
DMA Controller Driver

dmaengine_tx_status() is therefore an observation interface rather than a DMA control operation.

Calling it does not start, stop, or pause the DMA transaction.


device_tx_status()

device_tx_status() is a DMA controller operation provided through struct dma_device.

A controller driver commonly combines:

Generic cookie status
        +
Controller-specific progress

Conceptually:

device_tx_status()
dma_cookie_status()
generic completion state
        +
controller-specific hardware state
status + residue

Generic DMA cookie tracking determines whether a transaction has crossed the completion boundary.

Detailed progress reporting normally requires controller-specific information.


dma_cookie_status() determines generic transaction state from the channel's cookie bookkeeping.

Important values include:

chan->completed_cookie
chan->cookie

The queried cookie is compared against these boundaries.

The generic cookie mechanism can determine whether a transaction is:

DMA_COMPLETE

or:

DMA_IN_PROGRESS

but it does not know detailed DMA hardware progress.


DMA cookies eventually wrap because dma_cookie_t has a finite range.

DMA Engine does not maintain an additional ring-generation counter.

Instead, dma_async_is_complete() handles two cases.

When:

last_complete <= last_used

the outstanding transaction interval does not cross the wrap boundary.

When:

last_complete > last_used

the outstanding transaction interval crosses the wrap boundary.

The requested cookie is then checked against the appropriate interval.

Therefore the algorithm is essentially:

Determine whether the outstanding range wraps
Determine whether the queried cookie lies
inside the outstanding range

No separate wrap counter is required.


Valid submitted DMA transaction cookies begin at:

DMA_MIN_COOKIE

which is a positive value.

A useful mental model is:

cookie < 0
    → submission error

cookie = 0
    → no valid submitted transaction cookie

cookie > 0
    → valid transaction cookie

dma_submit_error() checks submission errors rather than performing complete cookie validation.

Status helpers assume that the caller provides a cookie obtained from a valid submitted transaction.


struct dma_tx_state

struct dma_tx_state represents transaction state observed through the status-query path.

Important information includes:

last
used
residue

The structure represents a snapshot of transaction state at query time.

It should not be confused with:

struct dmaengine_result

which represents completion-result information supplied through a result-aware callback.

The distinction is:

struct dma_tx_state
    → query-time state

struct dmaengine_result
    → completion-time result

DMA Residue

DMA residue represents the remaining transfer amount according to the progress information available to the controller driver.

However:

Residue is expressed as a remaining byte count, but its accuracy depends on controller capability.

DMA Engine defines several residue granularities:

DMA_RESIDUE_GRANULARITY_DESCRIPTOR
DMA_RESIDUE_GRANULARITY_SEGMENT
DMA_RESIDUE_GRANULARITY_BURST

Descriptor granularity provides only coarse transaction-level progress.

Segment granularity can distinguish transfer-segment boundaries.

Burst granularity can provide finer progress inside a segment.

A controller may therefore continue transferring data while the reported status reflects only the most recently observable progress boundary.

Status reporting does not control DMA execution.


Callback vs callback_result

DMA descriptors support two callback forms:

dma_async_tx_callback

dma_async_tx_callback_result

A normal callback provides asynchronous notification.

A result-aware callback additionally receives:

struct dmaengine_result

which can contain:

transaction result
residue

The two callback interfaces therefore represent:

callback
    → completion notification

callback_result
    → completion notification
      + completion outcome
      + completion residue

Transaction Status vs Completion Result

enum dma_status and enum dmaengine_tx_result represent different concepts.

enum dma_status answers:

What state is this transaction currently in?

Examples include:

DMA_IN_PROGRESS
DMA_COMPLETE
DMA_PAUSED
DMA_ERROR

enum dmaengine_tx_result answers:

How did the completion event finish?

Examples include:

DMA_TRANS_NOERROR
DMA_TRANS_READ_FAILED
DMA_TRANS_WRITE_FAILED
DMA_TRANS_ABORTED

Therefore:

DMA_COMPLETE
DMA_TRANS_NOERROR

The first is transaction status.

The second is completion outcome.


DMA Engine Internal Callback Helpers

Normal DMA client drivers include:

#include <linux/dmaengine.h>

Internal DMA Engine implementation code also uses:

drivers/dma/dmaengine.h

This internal header contains helpers such as:

struct dmaengine_desc_callback
dmaengine_desc_get_callback()
dmaengine_desc_callback_invoke()

These are DMA Engine implementation details rather than normal DMA client-driver APIs.

This clarified the boundary between:

include/linux/dmaengine.h
    → kernel-facing DMA Engine API

drivers/dma/dmaengine.h
    → DMA Engine internal implementation

virt-dma Deferred Completion

For normal non-cyclic DMA descriptors, the virt-dma completion path is conceptually:

Hardware completion
vchan_cookie_complete()
dma_cookie_complete()
desc_completed
tasklet_schedule()
vchan_complete()
Client callback
Descriptor finalization

The descriptor is not destroyed when it is removed from desc_completed.

Removing an object from a Linux linked list only changes its list membership.

It does not free the object.

Therefore:

list removal
object destruction

The descriptor remains valid until the later finalization step.


Local Completion List

vchan_complete() first moves completed descriptors from the shared channel list to a local list while holding vc->lock.

Conceptually:

lock vc->lock
move desc_completed to local list
unlock vc->lock
process local descriptors

This separates shared queue manipulation from callback processing.

New hardware completions can continue entering vc->desc_completed while the tasklet processes the detached local list.

This keeps the critical section short and allows asynchronous DMA completion to continue.


Callback Locking

Client callbacks should not execute while vc->lock is held.

The general pattern is:

lock
detach shared completion state
unlock
invoke client callback

This avoids running arbitrary client code while holding an internal DMA Engine spinlock.

Client callbacks may interact with other kernel mechanisms or initiate additional DMA transactions.


Descriptor Finalization and Reuse

After callback processing, virt-dma finalizes the descriptor.

A descriptor may either:

be released

or:

be retained for reuse

If a reusable descriptor later represents another transaction, it receives a new transaction cookie when submitted again.

Therefore:

Descriptor object identity
Transaction identity
Cookie identity

dma_cookie_complete() completes the current transaction represented by the descriptor.

It does not necessarily end the lifetime of the descriptor object itself.


Cyclic DMA Observation

While tracing vchan_complete(), the source also showed a separate cyclic callback path.

A cyclic DMA callback may represent:

period completion

rather than:

entire transaction completion

Conceptually:

Period 0 complete
callback

Period 1 complete
callback

Period 2 complete
callback

The cyclic descriptor remains active.

Cyclic DMA was not studied in detail today and should be treated as a separate topic.


Callback Execution Context

For the virt-dma completion implementation examined today:

DMA IRQ
tasklet_schedule()
tasklet / softirq context
Client callback

The callback therefore does not execute in Process Context.

Code in this callback path must respect atomic-context restrictions and must not perform operations that may sleep.

If sleepable processing is required, the callback can defer that work to a mechanism such as a workqueue.

This tasklet behavior is an implementation characteristic of the virt-dma path studied here and should not automatically be assumed for every possible DMA controller implementation.


Callback-Based Completion vs Status Query

DMA Engine supports two independent ways to observe a transaction.

Callback-based completion is event-driven:

DMA completes
completion processing
callback

Status queries are client-driven:

dmaengine_tx_status()
current transaction state

They may be used together.

For example:

DMA running
dmaengine_tx_status()
DMA_IN_PROGRESS

DMA completes
dma_cookie_complete()
dmaengine_tx_status()
DMA_COMPLETE

callback executes later

This demonstrates that transaction completion state and callback execution are independent.


Important Source-Code Discoveries

Important source locations examined today include:

include/linux/dmaengine.h
drivers/dma/dmaengine.h
drivers/dma/virt-dma.h
drivers/dma/virt-dma.c

Important symbols include:

dmaengine_tx_status()
device_tx_status()
dma_cookie_status()
dma_async_is_complete()
dma_cookie_complete()

struct dma_tx_state
struct dmaengine_result
struct dmaengine_desc_callback

vchan_cookie_complete()
vchan_complete()
vchan_vdesc_fini()

The most important implementation lesson was to keep public DMA Engine APIs separate from internal DMA Engine and virt-dma helpers.


Diagram

A reusable completion timeline was created:

docs/assets/diagrams/kernel-driver/dma/
    dma-completion-status-timeline.drawio
    dma-completion-status-timeline.svg

The diagram shows the ordering between:

Descriptor state
Hardware execution
Cookie state
Transaction-status queries
Deferred callback processing

Its main observation is:

Hardware Complete
Cookie Complete
DMA_COMPLETE may already be observable
Callback executes later
Descriptor finalization

Summary

Today completed the transaction-completion side of the DMA Engine mental model.

The most important conclusions are:

  • Hardware completion, cookie completion, status reporting, callback execution, and descriptor finalization are separate events.
  • dmaengine_tx_status() queries transaction state through the controller's device_tx_status() operation.
  • dma_cookie_status() provides generic completion tracking based on DMA cookies.
  • dma_async_is_complete() handles cookie wrap-around through outstanding-range comparison rather than a separate generation counter.
  • struct dma_tx_state represents query-time state.
  • struct dmaengine_result represents completion-time result information.
  • Residue accuracy depends on DMA controller capabilities.
  • Status queries observe DMA execution without controlling it.
  • virt-dma defers normal completion callbacks through tasklet processing.
  • Removing a descriptor from a list does not destroy the descriptor object.
  • Descriptor object lifetime and transaction cookie lifetime are independent.
  • Cyclic period callbacks are different from normal transaction-completion callbacks.

The complete mental model is:

DMA Hardware
Hardware Completion
Controller Driver
Cookie Completion
Transaction Status Becomes Observable
Deferred Completion Processing
Client Callback
Descriptor Finalization

Next Plan

Continue the DMA Engine learning path by studying the next transaction-management topic after completion and status tracking.

Potential follow-up areas include:

  • DMA termination and synchronization
  • Cyclic DMA
  • DMA Engine integration in a real client driver
  • Controller-specific DMA execution and error handling