Day 110 — Continuous DMA Streaming and Ring Buffer Ownership¶
Today's Goal¶
Extend the one-shot DMA client model from Day109 into a continuous RX streaming architecture.
The main goal is to understand how a persistent DMA buffer can be divided into reusable slots and shared safely between a DMA producer and a CPU consumer without copying completed data into a second software ring buffer.
The main topics are:
- Continuous DMA streaming architecture
- Cyclic DMA vs request-based one-shot DMA
- Persistent DMA mapping
- DMA buffer slot organization
- Producer and consumer positions
head,tail,count, anddma_idx- Per-slot ownership
- DMA completion and slot publication
- CPU acquire and release
- Partial DMA synchronization
- Consumer lag
- DMA overrun detection
- Stream termination with outstanding CPU data
- Continuous ADC vs FIFO-triggered sensor acquisition
- Continuous DMA stream simulation
The implementation goal is to build a simplified continuous RX DMA simulator with a persistent DMA mapping, cyclic buffer slots, explicit ownership transitions, and overrun detection.
1. From One-Shot DMA to Continuous Streaming¶
Day109 modeled a request-based one-shot DMA lifecycle:
Request
↓
Map Buffer
↓
Prepare DMA
↓
Submit
↓
Issue Pending
↓
Start Peripheral
↓
DMA Transfer
↓
Completion
↓
Sync / Unmap
↓
Consume
This model works well when one logical operation corresponds to one DMA transaction.
Continuous acquisition has a different lifetime.
Instead of repeatedly mapping and unmapping a buffer for every block:
a streaming design may keep one DMA buffer mapped across the entire stream:
Map DMA Buffer
↓
Start Stream
↓
DMA fills Slot 0
↓
DMA fills Slot 1
↓
DMA fills Slot 2
↓
DMA fills Slot 3
↓
wrap
↓
DMA reuses Slot 0
↓
...
↓
Stop Stream
↓
Unmap DMA Buffer
This changes the lifetime relationship:
One-Shot DMA
mapping lifetime
≈
transaction lifetime
Continuous Streaming
mapping lifetime
>
individual slot completion lifetime
The mapping becomes a stream-level resource rather than a transaction-level resource.
2. Avoiding an Extra Software Ring Copy¶
A continuous DMA design does not necessarily need:
For high-throughput streaming, this extra copy may add:
- CPU utilization
- memory bandwidth consumption
- cache traffic
- latency
A more direct architecture can divide the DMA buffer itself into reusable slots:
Persistent DMA Buffer
┌────────┬────────┬────────┬────────┐
│ Slot 0 │ Slot 1 │ Slot 2 │ Slot 3 │
└────────┴────────┴────────┴────────┘
│
└── completed slots become directly available
to the CPU consumer
The DMA buffer therefore also acts as the storage behind the producer-consumer ring.
The ring metadata tracks which slots are available rather than storing another copy of the payload.
This does not mean an additional software buffer is always wrong. Subsystem architecture, userspace interfaces, lifetime requirements, data transformation, and hardware constraints may still require another buffering layer.
The important optimization principle is:
Avoid an additional payload copy when the DMA buffer itself
can safely provide the required producer-consumer storage.
3. DMA Buffer Slots¶
The Day110 simulator divides one persistent DMA mapping into fixed-size slots.
Conceptually:
DMA Buffer
offset 0
│
▼
┌──────────────┐
│ Slot 0 │
├──────────────┤
│ Slot 1 │
├──────────────┤
│ Slot 2 │
├──────────────┤
│ Slot 3 │
└──────────────┘
Each slot contains metadata describing its position and current ownership.
The simplified slot descriptor contains information such as:
The slot does not contain another payload array.
Instead:
and:
This keeps the payload inside the persistent DMA mapping.
4. Ring Descriptor¶
The simulator uses a ring descriptor to track completed slots waiting for the CPU consumer.
The important runtime values are:
| Field | Meaning |
|---|---|
head |
Position where the next completed DMA slot is published |
tail |
Position of the next READY slot to be acquired by the CPU |
count |
Number of completed slots waiting in the ready ring |
capacity |
Number of slots available in the ring |
slots[] |
Metadata describing each DMA buffer slot |
The distinction between head, tail, and count is important.
For example:
by itself is ambiguous.
It may mean:
or:
Tracking count removes this ambiguity.
Therefore:
5. dma_idx vs Ring Positions¶
The DMA producer position is tracked separately from the ready-ring positions.
The simulator uses:
to identify the slot currently owned by DMA.
The roles are therefore:
dma_idx
= current DMA producer slot
ring.head
= next completion publication position
ring.tail
= next CPU acquisition position
ring.count
= number of READY slots waiting for the CPU
During normal streaming, dma_idx and head may often advance together.
However, they describe different concepts.
dma_idx belongs to the DMA producer state.
head and tail belong to the completed-data queue.
Keeping them separate makes the ownership model explicit and allows invalid state transitions to be detected.
6. Slot Ownership Lifecycle¶
Each DMA buffer slot moves through an explicit ownership lifecycle.
The normal sequence is:
The meanings are:
| State | Meaning |
|---|---|
FREE |
The slot is available for future DMA reuse |
DMA_OWNED |
DMA may currently write the slot |
READY |
DMA completed the slot and published it to the consumer |
CPU_OWNED |
The CPU consumer acquired the slot and may access it |
This ownership model separates DMA activity from ready-ring bookkeeping.
A slot becomes READY when DMA completion publishes it:
The CPU then acquires it:
After processing:
Only a FREE slot may safely become the next DMA_OWNED slot.
7. Publishing DMA Completion¶
When the simulated DMA completes the current slot, the client performs several operations.
Conceptually:
DMA completes current slot
↓
write simulated device data
↓
assign sequence number
↓
publish current slot
↓
DMA_OWNED → READY
↓
advance ring.head
↓
increment ring.count
↓
find next cyclic DMA slot
The simulator also verifies:
because cyclic completion is expected to occur in ring order in this simplified model.
The sequence number is independent of the physical slot index.
For example:
The slot index identifies storage location.
The sequence number identifies which generation of streamed data occupies that slot.
8. CPU Acquire and Release¶
The ready ring contains only completed slots that have not yet been acquired by the CPU.
When the CPU acquires a slot:
Advancing tail during acquire means the slot is removed from the ready queue immediately.
The slot itself remains protected by:
until the CPU explicitly releases it.
Therefore the ready-ring state and slot ownership state have different responsibilities:
When the consumer finishes:
The release operation does not modify:
headtailcountdma_idx
It only returns the slot to the pool of DMA-reusable storage.
9. Persistent DMA Mapping¶
Unlike the Day109 one-shot model, Day110 keeps the complete streaming buffer mapped while the stream is active.
Conceptually:
CPU Buffer
↓
dma_map_single()
↓
Persistent Streaming Mapping
┌────────┬────────┬────────┬────────┐
│ Slot 0 │ Slot 1 │ Slot 2 │ Slot 3 │
└────────┴────────┴────────┴────────┘
mapping remains valid
│
├── Slot 0 completion
├── Slot 1 completion
├── Slot 2 completion
├── Slot 3 completion
└── cyclic reuse
The buffer is unmapped only after the stream has stopped and all outstanding CPU-visible slots have been released.
This gives the mapping a longer lifetime than any individual slot transaction.
10. Partial DMA Synchronization¶
A persistent mapping creates a new synchronization requirement.
The complete DMA buffer may remain mapped while only one completed slot is transferred to CPU ownership.
Therefore synchronizing the entire mapping for every slot would not accurately model per-slot ownership.
The Day110 simulator extends the existing DMA-mapping simulator with range-based synchronization helpers:
These are simulator APIs, not Linux kernel API names.
The range is described relative to the base DMA mapping:
DMA mapping base
│
▼
┌────────┬────────┬────────┬────────┐
│ Slot 0 │ Slot 1 │ Slot 2 │ Slot 3 │
└────────┴────────┴────────┴────────┘
▲
│
offset + size
For CPU access:
Before the slot becomes reusable by DMA:
The original whole-mapping simulator APIs remain unchanged.
This preserves the previous simulator behavior while adding a separate capability for persistent streaming mappings.
11. Consumer Lag¶
The DMA producer and CPU consumer do not need to operate at exactly the same instant.
For example:
During this period:
The consumer is behind the producer, but this is not yet an overrun.
The ring exists specifically to tolerate a bounded amount of producer-consumer timing difference.
The important condition is whether the DMA producer reaches a slot that has not yet been released.
12. DMA Overrun¶
An overrun occurs when cyclic DMA needs to reuse the next slot but that slot is not FREE.
For example:
DMA completes Slot 3:
The completed slot is valid and remains published.
The DMA producer then wraps:
but:
The consumer has not released the old data.
Therefore:
The simulator returns failure for this completion operation after preserving the successfully completed current slot.
The resulting full-ring state is:
and every slot is READY.
The Day110 simulator treats this overrun as a fatal streaming condition for the current run rather than silently overwriting unread data.
13. Callback-Side Ring Updates¶
A DMA completion callback should not perform operations that may sleep.
Small ring metadata updates can be performed in the completion path when protected by synchronization appropriate to the execution context.
Possible mechanisms include:
- spinlocks
- atomic operations
- lockless single-producer/single-consumer designs when their assumptions are valid
A mutex is not appropriate in a callback context that cannot sleep.
The simulator itself is single-threaded and therefore does not implement real callback locking, but the ownership model is designed so that synchronization requirements remain visible.
In a real driver, the exact locking strategy depends on:
- callback execution context
- number of producers
- number of consumers
- process-context access
- interrupt-context access
- subsystem buffering architecture
14. Cyclic DMA and Peripheral Trigger Models¶
Cyclic DMA does not mean that the DMA controller independently decides when peripheral data should be transferred.
For slave DMA, actual data movement normally depends on DMA requests generated by the peripheral.
Conceptually:
DMA descriptor ready
↓
DMA controller armed
↓
peripheral generates DMA request
↓
DMA controller transfers data
The peripheral data-production model therefore affects whether cyclic DMA is a natural fit.
15. Continuous ADC Sampling¶
A continuously clocked ADC may produce samples at a regular rate.
Conceptually:
Sample Clock
│
├── sample
├── sample
├── sample
├── sample
▼
Peripheral DMA Requests
│
▼
Cyclic DMA Buffer
For example, a continuously operating ADC may generate samples at 1 kHz while DMA continuously transfers those samples into a cyclic buffer.
The stream has no natural request boundary for each individual block.
Period completion therefore means:
rather than:
This is a natural use case for persistent cyclic DMA.
16. FIFO-Threshold Sensor Acquisition¶
A sensor such as an accelerometer may instead accumulate samples in an internal FIFO and assert a GPIO interrupt when a threshold is reached.
For an external SPI sensor:
Accelerometer
│
│ samples internally
▼
Sensor FIFO
│
│ threshold reached
▼
GPIO IRQ
│
▼
Driver
│
▼
Start SPI FIFO Read
│
▼
One-Shot DMA Transfer
│
▼
Block Complete
In this architecture, the GPIO interrupt creates a natural request boundary.
Each interrupt may correspond to one bounded SPI FIFO read.
Therefore one-shot DMA may be simpler than a permanently running cyclic DMA stream.
The distinction is not simply:
Instead, the important question is:
Does the peripheral expose a continuous DMA request stream,
or does software initiate bounded transfers in response to events?
Hardware architecture and peripheral interface behavior determine the appropriate DMA model.
17. Continuous DMA Stream Simulator¶
The Day110 simulator extends the previous DMA-mapping simulator into a persistent streaming model.
The architecture is:
Simulated Peripheral Data
│
▼
DMA Stream Producer
│
▼
Persistent DMA Mapping
│
▼
┌────────┬────────┬────────┬────────┐
│ Slot 0 │ Slot 1 │ Slot 2 │ Slot 3 │
└────────┴────────┴────────┴────────┘
│
▼
Ready Ring Metadata
│
▼
CPU Consumer
The stream state lifecycle is:
IDLE
↓
map
↓
IDLE + mapped
↓
prepare
↓
PREPARED
↓
start
↓
RUNNING
↓
terminate_sync()
↓
TERMINATED
↓
drain / release outstanding slots
↓
unmap
↓
IDLE
The first slot becomes DMA_OWNED when the stream starts.
Each completion publishes the current slot and attempts to acquire the next cyclic slot for DMA.
18. Case 1 — Normal Producer / Consumer¶
The first test immediately consumes each completed DMA slot.
The sequence is:
DMA completes Slot 0
↓
Slot 0 READY
↓
CPU acquire
↓
Slot 0 CPU_OWNED
↓
sync range for CPU
↓
verify payload
↓
release
↓
Slot 0 FREE
The same sequence repeats for all four slots.
The observed result was:
[TEST] consumed slot=0 sequence=0
[TEST] consumed slot=1 sequence=1
[TEST] consumed slot=2 sequence=2
[TEST] consumed slot=3 sequence=3
[TEST] CASE 1 PASS
No overrun occurs because each slot is released before DMA wraps around to it.
19. Case 2 — Consumer Lag Without Overrun¶
The second test allows DMA to complete three slots before the CPU begins consuming them.
The intermediate state is:
The consumer then acquires and releases the three completed slots in order.
The observed result was:
[TEST] queued slots=3
[TEST] consumed slot=0 sequence=0
[TEST] consumed slot=1 sequence=1
[TEST] consumed slot=2 sequence=2
[TEST] CASE 2 PASS
This verifies that consumer lag alone is not an error.
The error occurs only when the producer exhausts the available reusable slots.
20. Case 3 — Cyclic DMA Overrun¶
The third test deliberately does not consume any completed slot.
After all four slots complete:
Slot 0 = READY
Slot 1 = READY
Slot 2 = READY
Slot 3 = READY
head = 0
tail = 0
count = 4
capacity = 4
DMA then needs to reuse Slot 0, but Slot 0 is still READY.
The simulator detects:
and increments the overrun counter.
The observed state was:
state : RUNNING
mapped : yes
peripheral_enabled : yes
dma_idx : 3
ring head : 0
ring tail : 0
ring count : 4
ring capacity : 4
next_sequence : 4
overrun_count : 1
All four completed slots remain available to the consumer.
The test then terminates the stream, drains the remaining completed slots, releases them, and safely removes the persistent DMA mapping.
21. Stream Termination¶
Synchronous stream termination stops DMA-side activity but does not automatically revoke CPU ownership of completed data.
The simulator uses the following policy:
The important distinction is:
After termination, completed READY slots may still be acquired and consumed.
A CPU_OWNED slot remains owned by the CPU until explicitly released.
This prevents stream teardown from silently invalidating memory still in use by the consumer.
22. Safe Unmapping¶
The persistent mapping may be removed directly from:
when streaming never started.
After a running stream has been terminated, unmapping requires every slot to be FREE.
Therefore:
TERMINATED
↓
check all slots
│
├── READY → reject
├── CPU_OWNED → reject
├── DMA_OWNED → invalid after synchronized termination
└── all FREE → safe
↓
dma_unmap_single()
↓
IDLE + unmapped
This ensures that neither DMA nor the CPU consumer still depends on the persistent mapping.
23. Important Observations¶
Persistent Mapping Changes the Ownership Granularity¶
A one-shot mapping can often be considered as one ownership unit.
A streaming mapping may contain several regions with different runtime states.
Therefore:
Ring Metadata Does Not Replace Slot Ownership¶
head, tail, and count describe the ready queue.
They do not completely describe who currently owns every slot.
CPU_OWNED slots have already left the ready queue but still cannot be reused by DMA.
Consumer Lag Is Expected¶
A ring buffer exists to absorb bounded producer-consumer timing differences.
Therefore:
Overrun occurs only when the producer needs storage that has not yet been released.
Completion and Next-Slot Acquisition Are Separate Events¶
The current slot may complete successfully even when DMA cannot safely continue to the next slot.
Therefore an overrun can occur after the current slot has already transitioned to READY.
DMA Synchronization May Need Subrange Granularity¶
With a persistent multi-slot mapping, CPU and device ownership may transition one slot at a time.
The simulator therefore requires range-based synchronization instead of synchronizing the entire mapping for every completion.
Peripheral Architecture Determines the DMA Model¶
A continuously clocked peripheral naturally fits a persistent streaming model.
A FIFO-based SPI sensor triggered by a GPIO threshold interrupt may instead expose a sequence of bounded read requests.
The DMA architecture should follow the peripheral's actual data-production and request model.
Summary¶
Day110 extended the Day109 one-shot DMA client architecture into a continuous streaming model.
The central architecture is:
Persistent DMA Mapping
↓
DMA Buffer Slots
↓
DMA_OWNED
↓
Completion
↓
READY
↓
CPU Acquire
↓
CPU_OWNED
↓
Partial DMA Sync
↓
CPU Consume
↓
Release
↓
FREE
↓
DMA Reuse
The ready ring is tracked using:
while the DMA producer position is tracked separately using:
The most important ownership rule is:
If cyclic DMA reaches a slot that is still READY or CPU_OWNED, the consumer has not released the storage quickly enough and the stream has overrun.
The simulator verified three cases:
- Normal producer-consumer operation
- Temporary consumer lag without overrun
- Cyclic DMA overrun when all slots remain unread
The resulting model connects DMA Engine streaming behavior with the producer-consumer and buffer-ownership problems that appear in real continuous acquisition drivers.
Next Plan¶
Continue from the continuous DMA streaming model toward integration with a real kernel buffering architecture.
The next topic should build on:
Continuous DMA Acquisition
↓
Completed DMA Blocks
↓
Kernel Buffering Framework
↓
Producer / Consumer Synchronization
↓
Userspace Data Access
This provides the foundation for studying how subsystem frameworks such as IIO manage continuous buffered acquisition between a hardware producer and userspace consumer.