Day 108 — DMA Scatter-Gather Transfer and dmaengine_prep_slave_sg()¶
Today's Goal¶
Understand how Linux represents non-contiguous or segmented memory for DMA using scatter-gather lists, how DMA mapping transforms the original scatterlist into DMA-visible segments, and how dmaengine_prep_slave_sg() passes those mapped segments to a DMA controller provider.
The main focus is the relationship between:
struct scatterliststruct sg_table- Original SG entries
- DMA-mapped SG segments
dma_map_sg()dma_map_sgtable()sg_dma_address()sg_dma_len()dmaengine_prep_slave_sg()- DMA controller hardware descriptors and LLIs
- SG mapping lifetime
- Persistent mapping and
dma_sync_sg_*()
The implementation goal is to extend the Day107 one-shot DMA client workflow from a single DMA address range to a scatter-gather transaction.
1. Scatter-Gather Memory Representation¶
Scatter-gather DMA allows one DMA transaction to describe memory using multiple regions without first copying the data into one contiguous temporary buffer.
Conceptually:
CPU memory regions
│
▼
struct scatterlist
│
▼
DMA mapping
│
▼
DMA-visible segments
│
▼
DMA Engine
│
▼
DMA controller hardware descriptors
A scatterlist should not be understood only as "multiple buffers chained together."
A single logical buffer may also require multiple SG entries because of:
- Page boundaries
- DMA maximum segment size
- Controller DMA length limits
- Memory type
- Subsystem-specific constraints
2. Building an Original Scatterlist¶
A fixed scatterlist can be initialized with:
struct scatterlist sg[3];
unsigned int nents = ARRAY_SIZE(sg);
sg_init_table(sg, nents);
sg_set_buf(&sg[0], buf0, len0);
sg_set_buf(&sg[1], buf1, len1);
sg_set_buf(&sg[2], buf2, len2);
At this point, the scatterlist describes the CPU-side memory representation.
Conceptually:
The original SG entry count describes the memory representation known by the kernel component building the scatterlist.
It does not represent the number of final DMA-visible segments or hardware descriptors.
3. SG Entry Count Is Not Physical Fragmentation¶
A driver or subsystem does not normally guess the required scatterlist size from physical memory fragmentation.
Instead, it builds an SG representation from memory information it already owns.
Possible sources include:
- A known set of kernel buffers
- A known set of
struct pageobjects - A subsystem-provided
struct sg_table - A virtual buffer divided according to page or DMA constraints
The important question is:
not:
DMA address-space fragmentation is handled later by the DMA mapping layer.
4. DMA Mapping of Scatter-Gather Lists¶
For a TX transaction:
mapped_nents = dma_map_sg(dev, sg, nents, DMA_TO_DEVICE);
if (!mapped_nents) {
/* Handle DMA mapping failure. */
}
For RX:
Unlike dma_map_single(), dma_map_sg() returns the number of DMA-visible segments rather than a DMA address.
A return value of zero indicates mapping failure.
For a successful mapping:
5. Original Entries vs DMA-Mapped Segments¶
DMA mapping may merge original SG entries when they can be represented as a larger DMA-visible segment.
For example:
Original SG entries
SG0
SG1
SG2
SG3
nents = 4
│
│ dma_map_sg()
▼
DMA-visible segments
Segment 0
Segment 1
Segment 2
mapped_nents = 3
Therefore:
The mapping layer may reduce the number of segments but does not increase the mapped segment count beyond the original entry count.
6. CPU-Side vs DMA-Side SG Information¶
Before mapping, scatterlist entries primarily describe CPU-side memory information such as:
After mapping, the DMA-visible information must be obtained using:
The responsibility boundary is:
CPU-side memory representation
──────────────────────────────
sg_page()
sg->offset
sg->length
│
│ DMA mapping
▼
DMA-visible representation
──────────────────────────
sg_dma_address()
sg_dma_len()
A DMA controller provider should use the mapped DMA information rather than reconstructing DMA addresses from CPU virtual or physical addresses.
7. Count Semantics¶
The original and mapped entry counts have different responsibilities.
For the raw SG DMA APIs:
| Operation | Count |
|---|---|
dma_map_sg() |
Original nents |
dmaengine_prep_slave_sg() |
mapped_nents |
dma_sync_sg_for_cpu() |
Original nents |
dma_sync_sg_for_device() |
Original nents |
dma_unmap_sg() |
Original nents |
Conceptually:
dma_map_sg(..., N)
│
│ returns M
▼
dmaengine_prep_slave_sg(..., M)
DMA mapping lifecycle:
sync(..., N)
unmap(..., N)
This distinction is one of the most important rules when implementing scatter-gather DMA clients.
8. struct sg_table¶
Linux subsystems commonly use:
which maintains both the original and mapped counts.
The important fields are conceptually:
dma_map_sgtable() uses orig_nents as the mapping input and stores the resulting mapped count in nents.
The source path confirms:
dma_unmap_sgtable() later uses sgt->orig_nents.
9. DMA Mapping Direction vs DMA Engine Direction¶
Scatter-gather DMA uses the same two direction abstractions learned on Day107.
For TX:
| Layer | Direction |
|---|---|
| DMA Mapping API | DMA_TO_DEVICE |
| DMA Engine API | DMA_MEM_TO_DEV |
For RX:
| Layer | Direction |
|---|---|
| DMA Mapping API | DMA_FROM_DEVICE |
| DMA Engine API | DMA_DEV_TO_MEM |
The mapping direction describes device access to memory.
The DMA Engine direction describes the transaction topology between memory and the peripheral endpoint.
10. dmaengine_prep_slave_sg()¶
After the SG has been mapped, a client can prepare the transaction with:
desc = dmaengine_prep_slave_sg(chan,
sg,
mapped_nents,
DMA_MEM_TO_DEV,
DMA_PREP_INTERRUPT |
DMA_CTRL_ACK);
The API receives the mapped segment count.
The conceptual flow is:
dmaengine_prep_slave_sg() does not perform DMA mapping.
The SG must remain mapped while the DMA transaction may access the memory.
11. SG Entry vs Hardware Descriptor¶
A mapped SG segment is not equivalent to one hardware DMA descriptor or LLI.
The provider may need to split one mapped segment because of:
- Maximum hardware block size
- Transfer width
- Alignment
- Burst constraints
- Controller-specific limits
Conceptually:
Original SG entries
N
│
│ DMA mapping
▼
Mapped DMA segments
M
│
│ provider conversion
▼
Hardware LLIs
H
M <= N, but the hardware descriptor count H is controller dependent and may be greater than M.
12. Synopsys AXI DMA Provider Source Trace¶
The Synopsys AXI DMA provider uses:
when processing mapped SG segments.
Its preparation path is conceptually:
dw_axi_dma_chan_prep_slave_sg()
│
├── iterate mapped SG
├── obtain DMA address and length
├── calculate hardware block length
├── count required hardware descriptors
├── split large mapped segments
└── build hardware LLIs
The provider first calculates how many hardware descriptors are required and then allocates descriptor storage.
For each mapped segment it may create one or more hardware descriptors using:
13. Hardware Block Length¶
calculate_block_len() calculates the maximum byte length that can fit in one hardware DMA block under the current width constraints.
Conceptually:
For memory-to-device transfers, the provider derives a safe memory transfer width from:
- Controller data width
- DMA address alignment
- Buffer length alignment
The implementation further limits the memory-side width to 32 bits on this path.
For device-to-memory transfers, block sizing uses the configured peripheral source width.
14. Building the Hardware LLI¶
dw_axi_dma_set_hw_desc() translates the higher-level DMA transaction information into controller-specific hardware descriptor fields.
The function configures information such as:
- SAR
- DAR
- Source and destination transfer width
- Increment / no-increment behavior
- Block transfer count
- Burst length
- LLI control fields
For TX:
For RX:
The helper constructs a hardware programming image in an LLI rather than directly programming every transfer through MMIO registers.
15. BCM2835 SPI Client Source Trace¶
The BCM2835 SPI DMA path consumes SG tables already prepared by the SPI Core.
For TX:
For RX:
The driver then calls:
The BCM2835 SPI driver does not perform the DMA mapping itself on this path.
This reinforces the Day107 rule:
The first question should always be:
16. SPI Core SG Construction¶
The SPI Core prepares SG tables in spi_map_buf_attrs().
For normal kernel buffers, it divides the buffer according to:
- DMA maximum segment size
ctlr->max_dma_len
For vmalloc() or kmap-backed buffers, it creates page-based entries.
The vmalloc() path uses:
Conceptually:
vmalloc buffer
│
▼
walk backing pages
│
▼
vmalloc_to_page()
│
▼
sg_set_page()
│
▼
SG table
│
▼
dma_map_sgtable()
This confirms that:
but an appropriate page-based SG representation and DMA mapping path are required.
17. SPI Core Mapping Ownership¶
The SPI Core maps the completed SG table using:
After successful mapping:
BCM2835 SPI uses sgt->nents when preparing the DMA Engine transaction.
On cleanup, the SPI Core performs:
This demonstrates a subsystem-level ownership model in which the SPI Core owns the SG allocation and DMA mapping lifetime while the controller driver owns DMA Engine transaction preparation and peripheral execution.
18. BCM2835 DMA Execution Ordering¶
The BCM2835 SPI DMA path intentionally overlaps hardware execution and software preparation.
The approximate sequence is:
Prepare / submit TX DMA
↓
Program SPI transfer length
↓
Enable SPI DMA mode
↓
Issue TX DMA
↓
Prepare / submit RX DMA
↓
Issue RX DMA
↓
Asynchronous completion
This shows that real drivers do not always follow a simplified sequence in which every descriptor is prepared before hardware execution starts.
However, mapping lifetime and synchronization rules must still remain valid.
19. Guided Implementation — Blocking SG TX¶
The SG TX exercise used three fixed buffers.
The initial SG representation was:
struct scatterlist sg[3];
unsigned int nents = ARRAY_SIZE(sg);
int mapped_nents;
sg_init_table(sg, nents);
sg_set_buf(&sg[0], buf0, len0);
sg_set_buf(&sg[1], buf1, len1);
sg_set_buf(&sg[2], buf2, len2);
Mapping:
Preparation:
desc = dmaengine_prep_slave_sg(chan,
sg,
mapped_nents,
DMA_MEM_TO_DEV,
DMA_PREP_INTERRUPT |
DMA_CTRL_ACK);
if (!desc) {
dma_unmap_sg(dev, sg, nents, DMA_TO_DEVICE);
return -EIO;
}
The transaction then follows:
Set callback
↓
dmaengine_submit()
↓
dma_submit_error()
↓
dma_async_issue_pending()
↓
wait_for_completion_timeout()
↓
completion
↓
dma_unmap_sg()
20. Pre-Issue and Post-Issue Cleanup¶
If preparation or submission fails before dma_async_issue_pending():
No active DMA transfer needs to be terminated.
After issue:
dma_async_issue_pending()
↓
DMA may access SG buffers
↓
timeout / cancellation
↓
dmaengine_terminate_sync()
↓
dma_unmap_sg()
This preserves the Day107 resource safety rule:
21. Guided Implementation — SG RX¶
For RX, the direction pair becomes:
After successful one-shot DMA completion:
No additional dma_sync_sg_for_cpu() is required when the streaming mapping is immediately unmapped.
22. Persistent SG Mapping¶
If the mapping remains active across repeated DMA operations, CPU/device ownership can be transferred with:
Both synchronization APIs use the original SG entry count, not the mapped segment count.
23. DMA Mapping Synchronization Does Not Pause DMA¶
dma_sync_sg_for_cpu() performs DMA mapping and cache ownership synchronization.
It does not stop or pause the DMA controller.
For a cyclic or persistent DMA buffer:
The driver must separately determine that a region is safe for CPU access based on DMA progress or period lifecycle.
If DMA wraps around before the CPU finishes processing a period, the problem is a producer/consumer overrun rather than something dma_sync_sg_*() can prevent.
Three different synchronization concerns should remain separate:
DMA Engine execution synchronization
↓
completion / terminate_sync()
DMA mapping ownership synchronization
↓
dma_sync_sg_for_cpu()
dma_sync_sg_for_device()
Producer / consumer synchronization
↓
period state / indices / overrun handling
Important Observations¶
struct scatterlistis a kernel memory representation, not a hardware DMA descriptor.- An SG list does not need to correspond one-to-one with physical fragmentation.
- One virtual buffer can generate multiple original SG entries.
dma_map_sg()may merge original SG entries into fewer DMA-visible segments.- A successful mapped segment count is always less than or equal to the original entry count.
sg_dma_address()andsg_dma_len()represent the DMA-side view.- DMA Mapping API lifecycle operations use the original SG count.
dmaengine_prep_slave_sg()uses the mapped DMA segment count.struct sg_tablepreserves bothorig_nentsandnents.- One mapped SG segment may become multiple hardware LLIs.
- The Synopsys AXI DMA provider converts mapped SG information into controller-specific SAR, DAR, width, increment mode, block count, burst, and LLI fields.
- The SPI Core can build page-based SG entries for
vmalloc()buffers. - BCM2835 SPI consumes SG tables mapped by the SPI Core instead of performing mapping itself.
- DMA mapping ownership and DMA hardware execution synchronization are separate concerns.
dma_sync_sg_for_cpu()does not pause DMA hardware.
Summary¶
Day108 extended the one-shot slave DMA model from a single DMA address range to scatter-gather memory.
The key three-layer model is:
CPU Memory Representation
↓
Original Scatterlist
↓
DMA Mapping
↓
DMA-Visible Segments
↓
DMA Engine Provider
↓
Hardware Descriptors / LLIs
The most important count distinction is:
Original SG count
│
│ dma_map_sg()
▼
Mapped DMA segment count
│
│ DMA provider
▼
Hardware descriptor count
These counts belong to different abstraction layers and must not be treated as interchangeable.
The Linux source trace through the SPI Core, BCM2835 SPI driver, DMA mapping API, and Synopsys AXI DMA provider confirmed the entire path from CPU buffer representation through DMA-visible segments to hardware LLIs.
Next Plan¶
Continue the DMA Engine learning path by building on the slave SG transaction lifecycle and examining the next client-side DMA pattern.
The next session should continue emphasizing:
- Real DMA client implementation patterns
- Resource ownership and cleanup
- Existing Linux client-driver source paths
- Provider/client responsibility boundaries
- Practical preparation for future hardware DMA labs