Day110 Lab — Continuous DMA Streaming and Ring Buffer Ownership¶
Goal¶
Build a simplified continuous RX DMA streaming simulator and use it to verify the interaction between:
- persistent DMA mapping
- cyclic DMA buffer slots
- DMA producer position
- ready-ring producer and consumer positions
- per-slot ownership
- DMA completion
- CPU synchronization
- CPU acquire and release
- consumer lag
- DMA overrun
- synchronous termination
- safe DMA unmapping
The lab extends the one-shot DMA client lifecycle from Day109 into a persistent streaming model.
The central safety rule is:
DMA may reuse a buffer slot only after the CPU consumer
has released that slot back to the DMA producer.
The simulator intentionally models only the client-side streaming lifecycle. It does not reproduce a complete DMA controller driver or DMA Engine provider.
1. Simulator Architecture¶
The lab combines the existing simulation layers with a new DMA streaming layer:
Test Application
│
▼
DMA Stream Simulator
│
├── stream state
├── cyclic producer position
├── ready-ring metadata
├── slot ownership
├── sequence tracking
└── overrun detection
│
▼
DMA Mapping Simulator
│
├── persistent streaming mapping
├── CPU-visible view
├── device-visible view
└── range synchronization
│
▼
Kernel Memory Simulator
│
└── kzalloc() / kfree()
Each layer has a separate responsibility.
The kernel-memory simulator owns allocation lifetime.
The DMA-mapping simulator owns mapping lifetime and simulated CPU/device visibility.
The DMA-stream simulator owns the continuous producer-consumer lifecycle.
2. Stream State Machine¶
The DMA stream uses the following simplified states:
IDLE
│
│ map buffer
▼
IDLE + mapped
│
│ prepare
▼
PREPARED
│
│ start
▼
RUNNING
│
│ terminate_sync()
▼
TERMINATED
│
│ drain / release slots
│ unmap
▼
IDLE
State Meaning¶
| State | Meaning |
|---|---|
SIM_DMA_STREAM_IDLE |
Stream is inactive; the buffer may be mapped or unmapped |
SIM_DMA_STREAM_PREPARED |
Ring state is initialized and ready to start |
SIM_DMA_STREAM_RUNNING |
Peripheral and cyclic DMA producer are active |
SIM_DMA_STREAM_TERMINATED |
DMA-side activity has been synchronously stopped |
The mapping lifetime is independent of the stream state.
For example:
is valid after mapping but before preparation.
3. Persistent DMA Buffer¶
The simulator maps one complete RX buffer and keeps that mapping valid for the streaming lifetime.
The buffer is divided into fixed-size slots:
Persistent DMA Buffer
┌────────┬────────┬────────┬────────┐
│ Slot 0 │ Slot 1 │ Slot 2 │ Slot 3 │
└────────┴────────┴────────┴────────┘
Each slot refers to a subrange of the same mapping.
For slot n:
The slot payload is not copied into a second software ring.
Instead, the ring tracks metadata describing which DMA-buffer regions are ready for the CPU.
4. DMA Buffer Slot Descriptor¶
Each slot contains metadata similar to:
The fields have different purposes.
| Field | Meaning |
|---|---|
index |
Physical slot position inside the cyclic DMA buffer |
offset |
Byte offset from the persistent buffer base |
length |
Number of bytes in the slot |
sequence |
Generation number assigned when DMA completes the slot |
state |
Current ownership state |
The slot index is reused cyclically.
The sequence number continues increasing across reuse.
For example:
This separates physical storage position from stream ordering.
5. Slot Ownership¶
The DMA ring combines cyclic producer movement with explicit per-slot ownership. The ready-ring metadata and slot ownership state describe different parts of the producer-consumer lifecycle.
Each slot follows this lifecycle:
FREE¶
The slot is available for DMA reuse.
DMA_OWNED¶
DMA may write the slot.
The CPU consumer must not treat the payload as completed data.
READY¶
DMA completed the slot and published it to the ready ring.
The CPU has not acquired it yet.
CPU_OWNED¶
The CPU consumer acquired the completed slot.
The slot cannot be reused by DMA until the consumer releases it.
The central reuse rule is:
Anything else indicates that the storage is still in use.
6. Ready Ring Descriptor¶
The simulator uses a ring descriptor containing:
The runtime positions mean:
| Field | Meaning |
|---|---|
head |
Position where the next DMA completion is published |
tail |
Position where the CPU acquires the next READY slot |
count |
Number of READY slots waiting for the CPU |
capacity |
Total number of slots |
The ready ring is empty when:
It is full when:
Tracking count avoids ambiguity when:
because that condition may represent either an empty or full ring.
7. DMA Producer Position¶
The DMA producer uses a separate index:
Its meaning is:
This is separate from:
Although dma_idx and head often advance together during normal streaming, they represent different runtime responsibilities.
8. Stream Initialization¶
The caller allocates the complete streaming buffer:
The stream initialization API stores:
- CPU buffer
- buffer length
- initial mapping state
- initial stream state
- DMA producer position
- sequence counter
- overrun counter
- peripheral state
The ring descriptor is also initialized.
The initial state is:
stream state = IDLE
mapped = false
peripheral_enabled = false
dma_idx = 0
head = 0
tail = 0
count = 0
all slots = FREE
9. Persistent RX Mapping¶
The complete stream buffer is mapped once:
The mapping remains active across multiple slot completions.
Conceptually:
map complete buffer
↓
Slot 0 DMA
↓
Slot 1 DMA
↓
Slot 2 DMA
↓
Slot 3 DMA
↓
wrap and reuse
↓
...
↓
terminate
↓
unmap complete buffer
This differs from the one-shot model where mapping lifetime closely follows one transaction.
10. Stream Preparation¶
Preparation requires:
Preparation resets runtime streaming state:
- ring descriptor
dma_idx- sequence counter
- overrun counter
- peripheral state
The transition is:
Reinitializing the ring during preparation ensures that a new streaming session does not inherit stale slot ownership from an earlier run.
11. Starting the Stream¶
Starting requires:
The first DMA slot is selected by:
That slot must be:
The start operation then performs:
and:
The resulting initial runtime state is:
DMA
▼
┌───────────┬──────┬──────┬──────┐
│ DMA_OWNED │ FREE │ FREE │ FREE │
│ Slot 0 │ S1 │ S2 │ S3 │
└───────────┴──────┴──────┴──────┘
12. Simulating DMA Completion¶
sim_dma_complete_slot() models completion of the current DMA-owned slot.
The operation validates:
- stream exists
- persistent mapping exists
- peripheral is enabled
- stream is RUNNING
- input length matches the slot size
dma_idxis valid- current slot is
DMA_OWNED
The simulated device then writes into the current slot range.
Conceptually:
After the write succeeds, the slot receives the next sequence number.
The current slot is then published.
13. Publishing a Completed Slot¶
Publishing performs:
and updates the ready ring:
The simulator also verifies:
before publication.
This check verifies the simplified assumption that cyclic DMA completions occur in ring order.
The ready slot now contains completed data but has not yet been acquired by the CPU.
14. Selecting the Next DMA Slot¶
After publishing the current completion:
The simulator checks the state of that next slot.
If:
DMA may safely acquire it:
and:
The stream then continues.
If the next slot is not FREE, the producer has caught the consumer.
This is an overrun.
15. CPU Acquire¶
The CPU acquires the next completed slot from:
The ring must not be empty, and the selected slot must be:
Acquisition performs:
and removes the slot from the ready queue:
The slot remains unavailable to DMA because its ownership state is still:
The ready-ring metadata therefore tracks queued completions, while slot state tracks actual buffer ownership.
16. Range Synchronization for CPU Access¶
The persistent DMA mapping contains multiple slots.
Only the completed slot needs to become visible to the CPU consumer.
The DMA-mapping simulator therefore adds:
This is a simulator extension, not the name of a Linux kernel DMA API.
The API identifies:
- persistent mapping base
- offset inside the mapping
- synchronization size
- DMA direction
For one slot:
Conceptually:
┌────────┬────────┬────────┬────────┐
│ Slot 0 │ Slot 1 │ Slot 2 │ Slot 3 │
└────────┴────────┴────────┴────────┘
▲
│
synchronize only
this completed range
This allows the simulator to model per-slot visibility while keeping the complete buffer mapped.
17. CPU Release¶
After the consumer finishes processing a slot, the slot is synchronized back for device access using:
This is also a simulator extension.
The ownership transition is:
The release operation does not modify:
headtailcountdma_idx
The slot simply becomes eligible for future cyclic DMA reuse.
18. Normal Producer / Consumer Test¶
The first test consumes each slot immediately after completion.
The lifecycle is:
DMA completes Slot 0
↓
Slot 0 READY
↓
CPU acquire
↓
Slot 0 CPU_OWNED
↓
sync for CPU
↓
verify payload
↓
sync for device
↓
release
↓
Slot 0 FREE
The same sequence is repeated for all four slots.
Observed output:
[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.
19. Consumer Lag Test¶
The second test allows DMA to complete three slots before the CPU starts consuming them.
The intermediate state is:
┌───────┬───────┬───────┬───────────┐
│ READY │ READY │ READY │ DMA_OWNED │
│ S0 │ S1 │ S2 │ S3 │
└───────┴───────┴───────┴───────────┘
head = 3
tail = 0
count = 3
The consumer is behind the producer, but the producer still has one available DMA slot.
The consumer then drains:
Observed output:
[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:
A ring exists to tolerate bounded producer-consumer timing differences.
20. Overrun Test¶
The third test deliberately leaves every completed slot unread.
Before the final completion:
┌───────┬───────┬───────┬───────────┐
│ READY │ READY │ READY │ DMA_OWNED │
│ S0 │ S1 │ S2 │ S3 │
└───────┴───────┴───────┴───────────┘
Slot 3 completes successfully:
The ring becomes full:
┌───────┬───────┬───────┬───────┐
│ READY │ READY │ READY │ READY │
│ S0 │ S1 │ S2 │ S3 │
└───────┴───────┴───────┴───────┘
head = 0
tail = 0
count = 4
capacity = 4
The DMA producer then wraps to Slot 0.
However:
Therefore the simulator detects an overrun:
The completed Slot 3 is not rolled back.
Its DMA completion already occurred and the data remains valid.
Observed output:
[DMA] next_slot state mismatched, expected=FREE actual=READY
[DMA] overrun count=1
[TEST] overrun detected correctly
The final stream state is:
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
This verifies that a full ring is distinguishable from an empty ring even though:
because:
21. Synchronous Stream Termination¶
sim_dma_stream_terminate_sync() stops DMA-side activity.
For a running stream:
The slot policy is:
| Slot State | Termination Result |
|---|---|
DMA_OWNED |
Change to FREE after DMA-side activity is stopped |
READY |
Preserve completed data |
CPU_OWNED |
Preserve CPU ownership |
FREE |
Remain FREE |
This models an important lifetime distinction:
A synchronous DMA termination does not automatically release data still owned by the consumer.
22. Draining After Termination¶
The simulator allows the CPU to acquire completed slots while the stream is:
or:
This allows teardown to preserve completed data.
For example:
Only after all outstanding slots are released is the persistent mapping safe to remove.
23. Safe Unmapping¶
Direct unmapping is allowed from:
because streaming has not started.
Unmapping is rejected from:
because the stream lifecycle has not been safely terminated.
For a terminated stream, every slot must be:
before unmapping.
Therefore:
TERMINATED
↓
check all slots
│
├── READY → reject
├── CPU_OWNED → reject
└── all FREE → continue
↓
dma_unmap_single()
↓
mapped = false
state = IDLE
This prevents the mapping from being destroyed while completed or CPU-owned data still depends on it.
24. Test Result¶
The final test result was:
Day110 - Continuous DMA Streaming Simulator
========================================
CASE 1 - Normal Producer / Consumer
========================================
[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
========================================
CASE 2 - Consumer Lag Without Overrun
========================================
[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
========================================
CASE 3 - Cyclic DMA Overrun
========================================
[DMA] next_slot state mismatched, expected=FREE actual=READY
[DMA] overrun count=1
[TEST] overrun detected correctly
[DMA STREAM]
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
slot[0] : READY seq=0 offset=0 len=16
slot[1] : READY seq=1 offset=16 len=16
slot[2] : READY seq=2 offset=32 len=16
slot[3] : READY seq=3 offset=48 len=16
[TEST] CASE 3 PASS
========================================
Test Summary
========================================
CASE 1 - Normal Producer / Consumer : PASS
CASE 2 - Consumer Lag : PASS
CASE 3 - Cyclic DMA Overrun : PASS
[TEST] All Day110 tests passed
All three scenarios passed.
25. Key Takeaways¶
A Persistent Mapping Can Contain Multiple Ownership Regions¶
The complete DMA buffer remains mapped while individual slots independently transition between DMA and CPU use.
Therefore:
Ready-Ring State and Slot Ownership Are Different¶
The ready ring tracks completed slots waiting for acquisition.
A CPU_OWNED slot has already left the ready ring but is still unavailable to DMA.
Therefore both mechanisms are required.
count Resolves Ring Ambiguity¶
When:
the ring may be empty or full.
count makes the state explicit.
Consumer Lag Is Normal Until Storage Is Exhausted¶
Several READY slots do not automatically indicate an error.
Overrun occurs only when DMA needs to reuse a slot that is not FREE.
Current Completion Can Succeed Before Overrun Is Detected¶
The current slot may become READY successfully.
The failure occurs when the producer tries to acquire the next cyclic slot.
Therefore the completed slot must not be rolled back.
DMA Termination Does Not End CPU Ownership¶
Stopping DMA prevents future device access.
It does not mean that the CPU has finished processing previously completed data.
Unmapping Is the Final Lifetime Boundary¶
The persistent mapping may be removed only after:
This keeps DMA mapping lifetime, DMA execution lifetime, and CPU consumer lifetime explicitly separated.
Summary¶
This lab extends the one-shot DMA client model into a continuous streaming architecture.
The complete lifecycle is:
Allocate Buffer
↓
Persistent DMA Map
↓
Prepare Stream
↓
Start
↓
DMA_OWNED
↓
DMA Completion
↓
READY
↓
CPU Acquire
↓
CPU_OWNED
↓
Range Sync for CPU
↓
Process Data
↓
Range Sync for Device
↓
Release
↓
FREE
↓
Cyclic DMA Reuse
If the DMA producer reaches a slot that has not returned to FREE:
The resulting ownership model provides a foundation for understanding how real continuous acquisition drivers coordinate DMA buffers, completion callbacks, kernel consumers, and subsystem buffering frameworks.