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.
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¶
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 changes as the transaction progresses.
Typical ownership flow:
- Client Driver prepares the descriptor.
- DMA Engine accepts the descriptor after submission.
- Controller Driver programs the hardware.
- DMA Hardware executes the transfer.
- DMA Engine receives completion.
- 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¶
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:
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.
DMA Cookie¶
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().
The submission path crosses the DMA Engine framework boundary:
dmaengine_submit() is a thin wrapper around the descriptor-specific tx_submit() callback:
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
Cookie Assignment¶
During submission, dma_cookie_assign() assigns the transaction a cookie associated with the DMA channel.
Conceptually:
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:
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:
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:
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.
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:
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:
The exact representation depends on the controller driver.
The architectural distinction is more important than the specific field:
desc_issuedis managed by thevirt-dmaqueue 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:
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:
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()
Completing the DMA Cookie¶
dma_cookie_complete() updates the channel's cookie state to record that the transaction has completed.
Conceptually:
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() 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.
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:
The controller first consumes Descriptor 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
Locking and vchan_cookie_complete()¶
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-dmaprovides 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'stx_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-dmaactive 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.
Related Topics¶
- Linux DMA Engine Framework
- DMA Controller Driver
- DMA Completion and Transaction Status
- DMA Termination and Synchronization