Skip to content

Day 109 — DMA Client Driver Architecture and One-Shot DMA Integration

Today's Goal

Understand how a peripheral driver integrates DMA Engine APIs into a complete client-side transfer lifecycle, from long-lived DMA channel setup to per-request mapping, descriptor submission, peripheral start, completion, synchronization, and cleanup.

The main focus is no longer an individual DMA Engine API. Instead, the goal is to connect the DMA concepts learned in the previous days into a complete driver architecture.

The main topics are:

  • DMA client driver responsibilities
  • Driver lifetime vs transfer lifetime
  • Request-based DMA vs streaming DMA
  • DMA channel setup during driver initialization
  • Per-request DMA mapping and descriptor preparation
  • Ordering between dma_async_issue_pending() and peripheral start
  • ARMED vs ACTIVE DMA state
  • DMA completion vs peripheral completion
  • DMA mapping lifetime vs transaction lifetime
  • RX ownership and CPU synchronization
  • Error and timeout cleanup ordering
  • dmaengine_terminate_sync()
  • One-shot DMA client simulation

The implementation goal is to build a small one-shot RX DMA client simulator that combines the previously developed kernel-memory and DMA-mapping simulators with a simplified DMA transaction state machine.


1. DMA Client Driver Perspective

Previous DMA topics examined individual parts of the DMA Engine framework:

DMA Channel
DMA Descriptor
DMA Submission
DMA Completion
DMA Termination
DMA Cyclic
DMA Slave Configuration
DMA Slave Transfer
DMA Scatter-Gather

A real peripheral driver must combine these pieces into one coherent lifecycle.

Conceptually:

Peripheral Driver
        │
        ├── acquire DMA channel
        ├── configure DMA slave parameters
        ├── manage transfer buffers
        ├── map memory for DMA
        ├── prepare descriptors
        ├── submit transactions
        ├── start peripheral hardware
        ├── handle completion
        └── terminate and clean up errors
                │
                ▼
           DMA Engine
                │
                ▼
        DMA Controller Driver
                │
                ▼
        DMA Controller Hardware

The DMA Engine framework manages DMA transactions, but the peripheral client driver remains responsible for coordinating the DMA transaction with the peripheral hardware.


2. Driver Lifetime vs Transfer Lifetime

DMA resources do not necessarily have the same lifetime.

A DMA channel may remain allocated for the entire lifetime of the driver:

probe()
   │
   ├── dma_request_chan()
   │
   ├── dmaengine_slave_config()
   │
   ▼
Driver Ready
   │
   │ many transfers
   │
   ▼
remove()
   │
   └── dma_release_channel()

Individual DMA mappings and descriptors normally have a shorter transfer-specific lifetime:

Request
   │
   ▼
Obtain Buffer
   │
   ▼
DMA Map
   │
   ▼
Prepare Descriptor
   │
   ▼
Submit / Issue
   │
   ▼
Transfer
   │
   ▼
Completion
   │
   ▼
Unmap
   │
   ▼
Request Done

This distinction is important because:

DMA channel lifetime
        ≠
DMA mapping lifetime
        ≠
DMA transaction lifetime
        ≠
buffer lifetime

A client driver must manage each lifetime independently.


3. DMA Client Implementation Sequence

The complete request-based DMA sequence can be organized into four phases:

  1. Driver initialization
  2. Request setup
  3. DMA transfer
  4. Completion or error cleanup

DMA client implementation sequence

The central implementation sequence is:

probe()
   │
   ▼
Request DMA Channel
   │
   ▼
Configure Slave DMA
   │
   ▼
READY
   │
   │ request arrives
   ▼
Choose PIO / DMA
   │
   ▼
Obtain Buffer
   │
   ▼
DMA Map
   │
   ▼
Prepare Descriptor
   │
   ▼
Submit
   │
   ▼
Issue Pending
   │
   ▼
ARMED
   │
   │ start peripheral
   ▼
ACTIVE
   │
   ├──────────── normal completion
   │
   └──────────── error / abort
                    │
                    ▼
                 DMA SAFE
                    │
                    ▼
             Sync for CPU
              if required
                    │
                    ▼
                  Unmap
                    │
                    ▼
            Consume / Deliver
                    │
                    ▼
             Release Buffer

The important rule is that cleanup must follow ownership and synchronization boundaries rather than simply following source-code scope.


4. issue_pending() Does Not Mean the Peripheral Is Running

A useful client-side mental model is:

PREPARED
    │
    ▼
SUBMITTED
    │
    ▼
ARMED
    │
    │ peripheral starts producing DMA requests
    ▼
ACTIVE

After:

dma_async_issue_pending(chan);

the DMA transaction has been made available to the DMA controller provider.

However, for peripheral slave DMA, actual data movement may still depend on DMA requests generated by the peripheral.

Therefore:

issue_pending()
        ≠
peripheral transfer already active

A common client ordering is:

prepare DMA
    ↓
submit DMA
    ↓
issue pending
    ↓
DMA ready / armed
    ↓
enable peripheral
    ↓
peripheral produces DMA requests
    ↓
DMA transfers data

This ordering reduces the risk of the peripheral producing data before the DMA path is ready to accept it.

The exact hardware ordering remains peripheral- and controller-specific, but the client driver should deliberately coordinate the two sides.


5. DMA Completion vs Peripheral Completion

DMA completion means the DMA controller has completed the programmed memory transfer.

It does not necessarily mean every internal stage of the peripheral has completed.

For a TX peripheral, data may already have left memory and entered a peripheral FIFO or shift register while the peripheral is still transmitting it externally.

Conceptually:

Memory
   │
   │ DMA
   ▼
Peripheral FIFO
   │
   ▼
Shift Register
   │
   ▼
Physical Interface

DMA completion may occur after the memory-to-peripheral DMA transaction finishes while the peripheral still has buffered data to transmit.

Therefore:

DMA completion
        ≠
peripheral protocol completion

The client driver must use the appropriate peripheral status when it needs to know that the physical transaction itself has finished.

This distinction is particularly important for TX teardown, chip-select handling, clock shutdown, or peripheral disable operations.


6. Mapping Lifetime After DMA Completion

DMA completion and DMA mapping lifetime are also separate concepts.

For a one-shot transaction:

DMA ACTIVE
    ↓
DMA COMPLETED
    ↓
mapping still exists
    ↓
sync / unmap
    ↓
mapping released

After the DMA transaction has completed, the DMA controller should no longer access the completed transaction's mapping.

However, the mapping remains valid until the client explicitly synchronizes or unmaps it.

Therefore:

DMA transaction completed
        ≠
DMA mapping automatically destroyed

This allows the client to perform the required ownership transition before releasing the mapping.


7. RX Ownership and CPU Visibility

For a streaming DMA_FROM_DEVICE mapping, device-side completion does not by itself imply that the CPU-side view is immediately valid on a non-coherent system.

The ownership sequence is conceptually:

CPU owns buffer
    ↓
DMA map
    ↓
Device owns DMA-visible buffer
    ↓
DMA writes received data
    ↓
DMA completes
    ↓
sync for CPU
    ↓
CPU owns valid received data

When a mapping remains persistent, the client may use:

dma_sync_single_for_cpu();

before CPU access and:

dma_sync_single_for_device();

before returning ownership to the device.

For a one-shot mapping, the exact synchronization behavior of the DMA API and architecture must be respected when the mapping is released.

The Day109 simulator models this ownership boundary explicitly so that CPU-visible and device-visible data do not become implicitly interchangeable.


8. Request-Based DMA vs Streaming DMA

Not every DMA client follows the same transfer lifetime.

A request-based client normally associates a DMA transaction with one logical operation.

Examples include:

  • SPI message transfer
  • One-shot ADC acquisition
  • Storage or communication request
  • Individual TX or RX command

A streaming client instead keeps DMA resources active across many data-production periods.

Examples include:

  • Continuous ADC sampling
  • Audio capture
  • Audio playback
  • Continuous sensor acquisition

Request-based DMA vs streaming DMA

The key distinction is:

Request-Based DMA Streaming DMA
One logical request owns the transfer Stream remains active across many data periods
Completion normally ends the transaction Period completion normally means data became available
Mapping may follow request lifetime Mapping is commonly persistent
One-shot or SG descriptors are common Cyclic or block-based DMA is common
Cleanup occurs after each request Cleanup occurs when the stream stops

The same DMA Engine primitives may participate in both architectures, but the buffer lifetime and completion semantics are different.


9. Streaming DMA and IIO Buffers

A continuous acquisition driver may combine DMA with a subsystem such as IIO.

The roles are different:

Peripheral
    │
    ▼
DMA
    │
    ▼
Kernel acquisition buffer
    │
    ▼
IIO buffering interface
    │
    ▼
Userspace

DMA handles data movement between the peripheral and DMA-accessible memory.

The IIO buffer framework provides the subsystem-level buffering and userspace interface.

These are complementary mechanisms rather than alternatives.

A userspace consumer may be slower than the hardware producer. DMA does not eliminate this producer-consumer problem.

The driver and subsystem therefore require an appropriate buffering strategy:

Producer rate
      vs
Consumer rate

If the consumer cannot keep up indefinitely, buffering only delays eventual overflow. The driver must define an appropriate overflow or data-loss policy.


10. One-Shot DMA Client Simulator

A guided simulator was implemented to connect the DMA client concepts into a runnable lifecycle.

The simulator combines:

Kernel Memory Simulator
        │
        ▼
DMA Mapping Simulator
        │
        ▼
DMA Client Simulator

The simplified transaction states are:

IDLE
  ↓
PREPARED
  ↓
SUBMITTED
  ↓
ARMED
  ↓
ACTIVE
  ├──────────────► COMPLETED
  │
  └──────────────► TERMINATED

The states deliberately distinguish:

SUBMITTED
    transaction accepted by the simulated DMA layer

ARMED
    transaction issued and ready for peripheral DMA requests

ACTIVE
    peripheral enabled and DMA transfer considered active

COMPLETED
    normal DMA completion

TERMINATED
    explicit synchronized cancellation

11. Mapping Ownership Rule

The simulator tracks whether a DMA transaction may still access its mapping.

The central safety rule is:

PREPARED
SUBMITTED
ARMED
ACTIVE
    │
    └── DMA mapping must not be released

After:

COMPLETED
or
TERMINATED

the transaction itself no longer owns the mapping.

This does not mean the mapping has already been destroyed.

Conceptually:

COMPLETED
    │
    ├── DMA transaction no longer accesses mapping
    │
    └── mapping may still exist

This distinction allows the simulator to reject unsafe cleanup ordering.


12. Case 1 — Normal Completion

The first test verifies the normal one-shot RX lifecycle.

Initialize Request
        ↓
Allocate RX Buffer
        ↓
DMA Map
        ↓
Prepare
        ↓
Submit
        ↓
Issue Pending
        ↓
ARMED
        ↓
Start Peripheral
        ↓
ACTIVE
        ↓
Simulated Device Write
        ↓
COMPLETED
        ↓
Completion Callback
        ↓
Sync for CPU
        ↓
Unmap
        ↓
Verify RX Data

Before the peripheral starts, the simulator reports:

DMA state         : ARMED
Mapping state     : true
Peripheral enabled: false

After completion, the callback observes:

state=COMPLETED

The callback therefore receives notification after the transaction state has already transitioned to completion.

The received data is synchronized to the CPU side before verification.


13. Case 2 — Abort While DMA Is Active

The second test verifies the error path.

The transaction first reaches:

DMA state         : ACTIVE
Mapping state     : true
Peripheral enabled: true

A simulated peripheral error then triggers:

ACTIVE
    ↓
terminate_sync()
    ↓
TERMINATED
    ↓
unmap
    ↓
free buffer

The simplified terminate_sync() model represents a synchronous teardown point.

After it returns successfully:

DMA transaction must no longer access the mapping

The simulator intentionally does not model a separate asynchronous termination state because asynchronous termination and later synchronization were already studied separately.


14. Case 3 — Invalid Cleanup Ordering

The third test deliberately attempts an unsafe operation:

ACTIVE
    ↓
unmap

The simulator rejects the request:

[DMA] dma may still access the mapping: state=ACTIVE

The correct sequence is then executed:

ACTIVE
    ↓
terminate_sync()
    ↓
TERMINATED
    ↓
unmap
    ↓
success

This test demonstrates that cleanup correctness depends on DMA ownership rather than only on whether the client wants to abandon the request.


15. Cleanup Ordering

The normal path is:

DMA completes
    ↓
transaction becomes safe
    ↓
sync for CPU if required
    ↓
unmap
    ↓
consume data
    ↓
free buffer

The error path is:

error / timeout
    ↓
stop peripheral
    ↓
terminate and synchronize DMA
    ↓
transaction becomes safe
    ↓
unmap
    ↓
free buffer

The critical rule is:

Never release memory while DMA may still access it.

This applies whether the memory came from:

  • kmalloc()
  • page allocation
  • an SG list
  • a subsystem-managed buffer
  • another DMA-capable allocation path

16. Important Observations

DMA Engine and Peripheral State Are Separate

DMA Engine state and peripheral state must be coordinated explicitly.

DMA ARMED
        ≠
Peripheral enabled

DMA COMPLETED
        ≠
Peripheral protocol necessarily finished

Completion Does Not Destroy the Mapping

Normal completion only ends the DMA transaction.

The client still owns the mapping lifecycle:

complete
    ↓
sync / unmap

Buffer Lifetime Extends Beyond DMA Transaction Lifetime

For RX:

allocate
    ↓
map
    ↓
DMA transaction
    ↓
sync / unmap
    ↓
CPU consumes data
    ↓
free

The buffer cannot be freed merely because a DMA callback has executed.

Streaming Changes the Lifetime Model

Request-based DMA normally ends one transaction at completion.

Streaming DMA instead repeatedly exposes new data while the stream remains active.

Therefore cyclic DMA, persistent mappings, subsystem buffers, and producer-consumer behavior become increasingly important for continuous acquisition.


Summary

Day109 connected the previously studied DMA Engine mechanisms into a complete DMA client driver architecture.

The main lifecycle is:

Driver Setup
    ↓
Request
    ↓
Buffer
    ↓
DMA Map
    ↓
Prepare
    ↓
Submit
    ↓
Issue
    ↓
ARMED
    ↓
Start Peripheral
    ↓
ACTIVE
    ↓
Complete or Terminate
    ↓
DMA Safe
    ↓
Sync / Unmap
    ↓
Consume
    ↓
Release

The most important mental model is:

DMA channel lifetime
        ≠
DMA transaction lifetime
        ≠
DMA mapping lifetime
        ≠
buffer lifetime
        ≠
peripheral completion lifetime

A correct DMA client driver coordinates all of these lifetimes and does not release a mapping or buffer until the DMA transaction can no longer access it.

The one-shot DMA simulator verified this model through:

  1. Normal RX completion
  2. Abort while DMA is active
  3. Rejection of unsafe unmapping while DMA is active

Next Plan

Continue from the one-shot DMA client architecture toward a more realistic continuous acquisition model.

The next topic should build on:

Persistent DMA Mapping
        ↓
Cyclic / Repeated DMA
        ↓
Period or Block Completion
        ↓
Kernel Buffering
        ↓
Userspace Consumption

This will connect the DMA Engine lifecycle with continuous data acquisition and buffering architectures such as those used by IIO-style drivers.