Skip to content

DMA Descriptor

Overview

A DMA channel represents an execution resource, but it does not describe an individual DMA transfer.

Linux DMA Engine introduces the DMA Descriptor to represent a single DMA transaction. A descriptor encapsulates all information required to execute, manage, and complete one DMA transfer.

Unlike a DMA channel, which usually exists for a relatively long lifetime, descriptors are created for individual transactions and are consumed as transfers progress through the DMA Engine framework.

DMA Descriptor Architecture

This page explains:

  • Why DMA Descriptors exist
  • Relationship between DMA Channels and Descriptors
  • Descriptor ownership and lifecycle
  • Scatter-Gather transactions
  • DMA Cookie
  • How the DMA Engine framework manages descriptors

Why DMA Descriptor Exists

A DMA channel represents a hardware execution resource.

However, a DMA transfer requires much more information than simply selecting a channel.

Each transaction needs information such as:

  • Source address
  • Destination address
  • Transfer length
  • Transfer direction
  • Completion callback
  • Control flags

Linux groups all of this information into a single object called a DMA Descriptor.

Instead of directly programming hardware registers, client drivers first construct a descriptor that completely describes the transaction. The descriptor is then submitted to the DMA Engine framework for execution.

A useful mental model is:

  • DMA Controller → Hardware
  • DMA Channel → Execution resource
  • DMA Descriptor → One DMA transaction

DMA Channel and Descriptor Relationship

DMA Channel and Descriptor Relationship

A DMA controller owns one or more DMA channels.

Each DMA channel executes DMA transactions.

Each transaction is represented by one struct dma_async_tx_descriptor.

A channel may process many descriptors throughout its lifetime.

Therefore:

  • DMA Controller owns channels.
  • Channels execute descriptors.
  • Descriptors represent transactions.

A descriptor also maintains a reference to its associated DMA channel, allowing the DMA Engine framework to identify the execution context of the transaction.


Descriptor as a Transaction Object

A DMA Descriptor is not the DMA transfer itself.

Instead, it is a kernel object describing a transaction.

Conceptually, a descriptor contains three categories of information:

  • Transfer Information

    • Source
    • Destination
    • Length
    • Direction
  • Completion Information

    • Callback
    • Callback parameter
    • DMA Cookie
  • Execution Information

    • Control flags
    • Submit operation

The descriptor acts as the central object managed throughout the entire DMA transaction lifecycle.


Descriptor Ownership

Descriptor Ownership

Descriptor ownership changes as the transaction progresses.

Typical ownership flow:

  1. Client Driver prepares the descriptor.
  2. DMA Engine accepts the descriptor after submission.
  3. Controller Driver programs the hardware.
  4. DMA Hardware executes the transfer.
  5. DMA Engine receives completion.
  6. Client Driver receives the completion callback.

Once a descriptor has been submitted, client drivers should treat it as immutable and should no longer modify its contents.


Descriptor Lifecycle

Descriptor Lifecycle

A descriptor progresses through several states:

  • Created
  • Prepared
  • Submitted
  • Pending
  • Running
  • Completed
  • Reclaimed

Each state corresponds to a different stage of transaction processing inside the DMA Engine framework.

Separating lifecycle from ownership makes the framework easier to understand, since ownership and execution state evolve independently.


Preparing a Descriptor

DMA Engine provides several preparation APIs.

Rather than directly starting DMA, these APIs create descriptors describing different transaction types.

Common examples include:

  • dmaengine_prep_slave_sg()
  • dmaengine_prep_dma_memcpy()
  • dmaengine_prep_dma_cyclic()
  • dmaengine_prep_interleaved_dma()

Although each API prepares a different type of transaction, they all return the same descriptor abstraction:

struct dma_async_tx_descriptor *

The descriptor is later submitted to the DMA Engine for execution.


Scatter-Gather Descriptor

A descriptor does not necessarily represent a single contiguous memory buffer.

Many DMA controllers support Scatter-Gather transactions, allowing one descriptor to reference multiple memory segments.

Scatter-Gather enables efficient DMA transfers without requiring software to copy fragmented buffers into one contiguous allocation.

The DMA Engine framework represents these fragmented buffers using scatterlists while still exposing a single transaction descriptor to client drivers.


Each submitted descriptor receives a DMA Cookie.

A cookie uniquely identifies a submitted DMA transaction.

Unlike a descriptor pointer, the cookie is simply a transaction identifier used by the DMA Engine framework.

Typical usage includes:

  • Tracking submitted transactions
  • Querying completion status
  • Associating callbacks with completed transfers

Cookies are assigned during descriptor submission rather than descriptor creation.


Descriptor Submission

Preparing a DMA descriptor does not start the DMA transfer.

After the client driver finishes configuring the descriptor, it submits the transaction with dmaengine_submit().

DMA descriptor submission flow

The submission path crosses the DMA Engine framework boundary:

Client Driver
    |
    | dmaengine_submit(desc)
    v
DMA Engine
    |
    | desc->tx_submit(desc)
    v
Controller Driver

dmaengine_submit() is a thin wrapper around the descriptor-specific tx_submit() callback:

dmaengine_submit(desc)
        |
        v
desc->tx_submit(desc)

The actual tx_submit() implementation is provided when the descriptor is prepared.

For controller drivers built on virt-dma, this callback is commonly implemented by vchan_tx_submit().

A simplified submission path is:

dmaengine_submit(desc)
        |
        v
desc->tx_submit(desc)
        |
        v
vchan_tx_submit()
        |
        +--> dma_cookie_assign()
        |
        +--> desc_submitted
        |
        v
return dma_cookie_t

During submission, dma_cookie_assign() assigns the transaction a cookie associated with the DMA channel.

Conceptually:

Channel

cookie = N
    |
    v
Descriptor A

cookie = N + 1
    |
    v
Descriptor B

The cookie allows the DMA Engine to track transaction completion later.

The cookie returned by dmaengine_submit() identifies the submitted transaction, but it does not indicate that the DMA hardware has started executing it.

Submitted State

For virt-dma, vchan_tx_submit() places the descriptor into the channel's submitted descriptor list.

Conceptually:

desc_allocated
      |
      | vchan_tx_submit()
      v
desc_submitted

At this point:

  • The descriptor has been accepted by the DMA subsystem.
  • A DMA cookie has been assigned.
  • The descriptor is pending on the channel.
  • The descriptor is not necessarily eligible for hardware execution yet.
  • The DMA hardware has not necessarily started the transfer.

Submission and execution are therefore separate operations.


Issuing Pending Descriptors

After descriptors have been submitted, the client driver calls:

dma_async_issue_pending(chan);

This tells the DMA Engine that pending transactions on the channel may be made available for execution.

The framework eventually invokes the controller driver's device_issue_pending() operation.

For a controller using virt-dma, the controller driver commonly uses:

vchan_issue_pending(&vc);

to move submitted descriptors into the issued state.

Conceptually:

desc_submitted
      |
      | dma_async_issue_pending()
      | device_issue_pending()
      | vchan_issue_pending()
      v
desc_issued

vchan_issue_pending() moves the descriptors from the submitted list to the issued list.

The important distinction is:

State Meaning
desc_submitted The transaction has been submitted and is waiting to be issued.
desc_issued The transaction is available for controller scheduling.
Controller-private active state The controller has selected the descriptor for execution.
Hardware running The DMA controller is executing the transfer.

Therefore:

Issued does not mean running.

Calling dma_async_issue_pending() makes pending descriptors available to the controller, but a descriptor may still wait while another transaction is using the DMA channel.

This separation allows multiple descriptors to be queued before execution begins:

submit A
submit B
submit C
    |
    v
desc_submitted
    |
    | dma_async_issue_pending()
    v
desc_issued
    |
    +--> A
    +--> B
    +--> C

The controller driver determines when an issued descriptor becomes the active hardware transaction.


Descriptor Queue and Ownership Lifecycle

The descriptor state changes as ownership moves from virt-dma queue management to the controller driver and eventually back to the completion path.

DMA descriptor queue lifecycle

For a controller using virt-dma, the simplified lifecycle is:

desc_allocated
      |
      | vchan_tx_submit()
      v
desc_submitted
      |
      | vchan_issue_pending()
      v
desc_issued
      |
      | controller selects descriptor
      v
Controller-private active state
      |
      | program DMA hardware
      v
Hardware running
      |
      | completion IRQ
      v
desc_completed

Looking at the Next Issued Descriptor

A controller driver can inspect the next descriptor waiting in desc_issued with:

vchan_next_desc(vc)

Conceptually, this returns the descriptor at the head of the issued list.

An important detail is that vchan_next_desc() only looks at the descriptor.

It does not remove the descriptor from desc_issued.

Conceptually:

desc_issued

head
 |
 v
+---------+    +---------+    +---------+
| Desc A  | -> | Desc B  | -> | Desc C  |
+---------+    +---------+    +---------+
     ^
     |
vchan_next_desc()

After this operation, Descriptor A is still linked into desc_issued.

This allows the controller driver to inspect the next runnable transaction before deciding how to consume it.

Removing the Descriptor from the Issued List

The descriptor leaves desc_issued when the controller driver actually consumes it for execution.

A common controller-driver pattern is:

vd = vchan_next_desc(vc);

if (vd) {
    list_del(&vd->node);

    /* Controller now owns the descriptor as active work. */
}

The important ownership transition is therefore:

virt-dma desc_issued
        |
        | vchan_next_desc()
        |     peek only
        |
        | list_del()
        v
Controller-private active state

vchan_next_desc() and list_del() have different responsibilities:

Operation Purpose
vchan_next_desc() Obtain the next issued descriptor without removing it.
list_del() Remove the descriptor from the virt-dma issued list when the controller consumes it.

The removal is therefore normally performed by the controller driver, not by vchan_next_desc() itself.

Active State Is Controller-Specific

virt-dma provides generic descriptor lists such as submitted, issued, and completed descriptors.

The active transaction, however, is typically maintained by the controller driver's own state.

Conceptually:

virt-dma

desc_issued
    |
    | controller consumes descriptor
    v

Controller Driver

active descriptor
    |
    | program hardware
    v

DMA Hardware

running transaction

There is no requirement for a generic virt-dma desc_active list.

A controller may instead keep its active descriptor through controller-specific state, for example:

struct my_dma_chan {
    struct virt_dma_chan vc;

    struct my_dma_desc *active;
};

The exact representation depends on the controller driver.

The architectural distinction is more important than the specific field:

  • desc_issued is managed by the virt-dma queue mechanism.
  • Active state belongs to the controller driver's scheduling and hardware-management logic.
  • Hardware running state belongs to the DMA controller itself.

Pending Queue vs Active Work

A descriptor can therefore pass through several waiting states before hardware execution actually begins:

Submitted
   |
   v
Issued
   |
   | may still wait
   v
Selected by Controller
   |
   v
Active
   |
   v
Hardware Running

This explains why calling dma_async_issue_pending() does not guarantee immediate execution.

If another descriptor is already active on the channel:

desc_issued

+---------+    +---------+
| Desc B  | -> | Desc C  |
+---------+    +---------+

Controller-private state

+---------+
| Desc A  |  ACTIVE
+---------+
     |
     v
DMA Hardware

Descriptors B and C are already issued and eligible for scheduling, but they must wait until the controller can start them.

The distinction can be summarized as:

Submitted means accepted, issued means runnable, active means selected, and running means the hardware is executing the transfer.


Descriptor Completion

When the DMA hardware finishes a transaction, the controller typically raises an interrupt.

The controller driver's interrupt handler identifies the completed descriptor and performs the controller-specific hardware cleanup before passing the descriptor into the DMA Engine completion path.

For controllers using virt-dma, this commonly leads to:

vchan_cookie_complete(vd)

The simplified completion flow is:

DMA Hardware
      |
      | transfer complete
      v
Completion IRQ
      |
      v
Controller Driver
      |
      | vchan_cookie_complete()
      v
dma_cookie_complete()
      |
      v
desc_completed
      |
      v
tasklet_schedule()

dma_cookie_complete() updates the channel's cookie state to record that the transaction has completed.

Conceptually:

Descriptor A
cookie = N
    |
    | DMA completes
    v
dma_cookie_complete()
    |
    v
completed_cookie = N

This allows completion-status queries to determine whether a previously submitted transaction has finished.

The cookie state tracks transaction completion independently from callback execution.

Therefore, these are separate events:

DMA transaction completed
        |
        v
Cookie marked complete
        |
        v
Callback scheduled
        |
        v
Callback executes later

A completed cookie does not mean that the client callback has already executed.

Moving the Descriptor to the Completed List

vchan_cookie_complete() also transfers the descriptor into the virt-dma completed-descriptor path.

Conceptually:

Controller-private active descriptor
        |
        | hardware completion
        v
vchan_cookie_complete()
        |
        +--> dma_cookie_complete()
        |
        +--> desc_completed
        |
        +--> tasklet_schedule()

At this point, the DMA transaction itself is complete, but deferred completion processing may still remain.

Scheduling Deferred Completion Processing

vchan_cookie_complete() schedules the virt-dma tasklet:

tasklet_schedule(&vc->task);

tasklet_schedule() does not immediately execute the tasklet at the call site.

Instead, it marks the tasklet for deferred execution in tasklet/softirq context.

Conceptually:

Completion IRQ
     |
     | tasklet_schedule()
     v
Tasklet pending
     |
     | IRQ handling continues / returns
     v
Tasklet executes later

This is different from an RTOS task scheduler.

The tasklet is not a normal schedulable task with an application-defined task priority. tasklet_schedule() requests deferred kernel execution; it does not mean that the tasklet immediately preempts the current interrupt handler.


Callback Execution

Client callbacks are executed through the deferred completion path rather than directly from the controller's completion critical section.

The high-level flow is:

Completion IRQ
      |
      v
vchan_cookie_complete()
      |
      +--> dma_cookie_complete()
      |
      +--> desc_completed
      |
      +--> tasklet_schedule()
      |
      v
IRQ path finishes
      |
      v
virt-dma tasklet
      |
      v
Client callback

Complete Transaction Execution Timeline

A complete DMA transaction crosses several execution contexts and ownership boundaries.

The following timeline combines descriptor submission, issuing, controller scheduling, hardware execution, completion handling, and callback execution.

DMA transaction execution timeline

The complete path can be summarized as:

Client Driver
    |
    | prepare descriptor
    | set callback
    |
    | dmaengine_submit()
    v
DMA Engine / virt-dma
    |
    | desc->tx_submit()
    | dma_cookie_assign()
    v
desc_submitted
    |
    | dma_async_issue_pending()
    | device_issue_pending()
    | vchan_issue_pending()
    v
desc_issued
    |
    | controller selects descriptor
    | vchan_next_desc()
    | list_del()
    v
Controller-private active state
    |
    | program hardware
    v
DMA Hardware Running
    |
    | transfer complete
    v
Completion IRQ
    |
    | vchan_cookie_complete()
    | dma_cookie_complete()
    v
desc_completed
    |
    | tasklet_schedule()
    v
Deferred Completion Processing
    |
    v
Client Callback

One Transaction, Multiple Execution Contexts

The complete transaction is not executed as one synchronous call chain.

Different parts run in different contexts:

Stage Typical Context Responsibility
Prepare descriptor Client driver context Describe the DMA transaction
Submit descriptor Client driver context Assign a cookie and queue the transaction
Issue pending Client driver context Make submitted work available for scheduling
Select active descriptor Controller driver Choose the next runnable transaction
Execute transfer DMA hardware Move the data
Handle completion Interrupt context Acknowledge hardware completion and update state
Deferred completion Tasklet / softirq context Process completed descriptors
Callback Deferred completion context Notify the client driver

This separation is one of the key architectural properties of the DMA Engine framework.

The client submits work asynchronously and does not remain blocked while the DMA hardware performs the transfer.

Descriptor A and Descriptor B

Consider two descriptors queued on the same DMA channel:

desc_issued

+--------+    +--------+
|   A    | -> |   B    |
+--------+    +--------+

The controller first consumes Descriptor A:

desc_issued

+--------+
|   B    |
+--------+

Controller-private active state

+--------+
|   A    |
+--------+

When Descriptor A completes, its software completion path can be deferred while the controller continues scheduling DMA work.

A possible ordering is:

Descriptor A running
        |
        v
A hardware completion
        |
        v
Completion IRQ
        |
        +--> dma_cookie_complete(A)
        |
        +--> move A to completion processing
        |
        +--> tasklet_schedule()
        |
        v
Controller selects Descriptor B
        |
        v
Descriptor B starts running
        |
        v
virt-dma tasklet executes
        |
        v
Callback A executes

The important ordering is:

Descriptor B may already be running when Descriptor A's callback executes.

The callback is therefore not part of the hardware scheduling dependency between Descriptor A and Descriptor B.

Hardware Completion vs Callback Completion

It is useful to distinguish four events:

1. Hardware completion
        |
        v
2. Cookie completion
        |
        v
3. Callback scheduling
        |
        v
4. Callback execution

They represent different things:

Event Meaning
Hardware completion The DMA controller finished the data transfer.
Cookie completion DMA Engine records the transaction as completed.
Callback scheduling Deferred completion processing has been requested.
Callback execution Client notification code actually runs.

These events may occur close together, but they should not be treated as the same operation.

Complete Mental Model

The descriptor lifecycle can now be viewed as three major phases:

Submission

Prepared
   |
   v
Submitted
   |
   v

Scheduling

Issued
   |
   v
Controller Active
   |
   v
Hardware Running
   |
   v

Completion

Hardware Complete
   |
   v
Cookie Complete
   |
   v
Deferred Completion
   |
   v
Callback

The DMA Engine framework manages the transaction abstraction and common state tracking.

The controller driver decides how issued descriptors are scheduled onto the actual DMA hardware.

The DMA controller executes the transfer independently.

Completion then moves control back from hardware to the controller driver, through the DMA Engine completion mechanism, and finally to the client callback.

This gives the complete execution path of one asynchronous DMA transaction:

Prepare → Submit → Issue → Schedule → Execute → Complete → Notify

vchan_cookie_complete() expects the virtual channel lock to already be held by its caller.

Conceptually:

Controller IRQ handler
      |
      | spin_lock(&vc->lock)
      v
vchan_cookie_complete()
      |
      | update completion state
      | queue completed descriptor
      | schedule tasklet
      v
return
      |
      | spin_unlock(&vc->lock)
      v
IRQ handler continues

The lock assertion inside vchan_cookie_complete() verifies this calling contract.

It does not acquire or release the lock.

The caller remains responsible for releasing the lock after the protected completion-state updates are finished.

Why the Callback Is Deferred

The client callback should not execute while the controller is still holding the virtual channel lock.

Instead, the completion path records the completed descriptor and schedules deferred processing.

Later, the virt-dma tasklet processes completed descriptors and invokes callbacks outside the controller's original locked completion section.

This separation is important because callback code may perform operations that should not run inside the controller's critical section.

Conceptually:

IRQ / Controller completion path

spin_lock(&vc->lock)
        |
        v
update completion state
        |
        v
schedule tasklet
        |
        v
spin_unlock(&vc->lock)

             later

        tasklet runs
             |
             v
      process completion
             |
             v
       client callback

The important rule is:

Scheduling the callback while holding the lock is different from executing the callback while holding the lock.

tasklet_schedule() can safely request deferred processing while the lock is held because the callback itself is not executed synchronously by that call.

Completion Does Not Block the Next Transaction

Callback execution is also separate from DMA hardware scheduling.

After Descriptor A completes, the controller may be able to start Descriptor B before Descriptor A's callback executes.

Conceptually:

Descriptor A running
        |
        v
A completes
        |
        +--> mark cookie A complete
        |
        +--> schedule callback A
        |
        v
Controller selects Descriptor B
        |
        v
Descriptor B starts running
        |
        v
Deferred completion processing
        |
        v
Callback A executes

Therefore, callback execution should not be treated as the event that releases the DMA hardware for the next transaction.

The hardware transaction has already completed before the callback is dispatched.

This separation allows DMA execution and software completion processing to overlap efficiently.



Kernel Object Relationships

The complete DMA transaction path involves several kernel objects with different responsibilities.

Object Responsibility
struct dma_device Represents one DMA controller and exposes controller operations to the DMA Engine framework.
struct dma_chan Represents one DMA execution channel.
struct dma_async_tx_descriptor Represents one DMA transaction and provides the generic descriptor interface.
Controller-private descriptor Extends the generic descriptor with hardware-specific transaction state.
struct virt_dma_chan Provides common descriptor-list and completion infrastructure for controllers using virt-dma.
struct virt_dma_desc Provides the virt-dma descriptor wrapper used for queue management.

The relationships can be summarized as:

struct dma_device
        |
        +--> struct dma_chan
                  |
                  +--> DMA transactions
                            |
                            v
                  struct dma_async_tx_descriptor
                            ^
                            |
                  embedded in controller-private
                  descriptor representation

For controllers using virt-dma, the controller-private channel and descriptor structures also integrate with the common virtual DMA infrastructure.

The architectural responsibilities remain separated:

  • DMA Engine defines the generic transaction interface.
  • virt-dma provides reusable descriptor queue and completion helpers.
  • The Controller Driver owns hardware-specific scheduling and active state.
  • DMA Hardware performs the actual data transfer.

Key Takeaways

A DMA descriptor represents one asynchronous DMA transaction.

Its execution is not a single synchronous operation. Instead, the descriptor progresses through several distinct stages:

Prepare
   |
   v
Submit
   |
   v
Issue
   |
   v
Controller Scheduling
   |
   v
Hardware Execution
   |
   v
Completion
   |
   v
Callback

The most important distinctions are:

  • Preparing a descriptor does not submit it.
  • Submitting a descriptor does not start the hardware.
  • dmaengine_submit() dispatches through the descriptor's tx_submit() callback.
  • dma_cookie_assign() assigns the transaction cookie during submission.
  • dma_async_issue_pending() makes submitted work available for controller scheduling.
  • An issued descriptor is runnable, but it may still be waiting.
  • vchan_next_desc() only inspects the next issued descriptor; it does not remove it.
  • The Controller Driver removes a descriptor from the issued list when it consumes the descriptor.
  • Active state is typically controller-specific rather than a generic virt-dma active list.
  • Hardware completion, cookie completion, callback scheduling, and callback execution are separate events.
  • Deferred callback processing allows the controller to continue scheduling DMA work without waiting for the previous callback to execute.

A useful final mental model is:

The DMA Engine manages transactions, the Controller Driver schedules transactions, and the DMA Controller executes transactions.