6.3.8Interconnects, Buses & SoC

DMA controllers

3,635 words17 min readdifficulty · medium2 backlinks

Overview

Direct Memory Access (DMA) is a hardware mechanism that allows peripheral devices to transfer data directly to/from memory without continuous CPU intervention. The DMA controller is the specialized hardware that orchestrates these transfers.

Why DMA Exists

The Problem DMA Solves:

Without DMA (Programed I/O):

  1. CPU reads from peripheral register (1 instruction)
  2. CPU writes to memory address (1 instruction)
  3. CPU increments pointers (1-2 instructions)
  4. CPU checks if done (1 instruction)
  5. Repeat for every byte

For a 1 MB transfer at 1 byte per iteration, this requires ~5 million instructions. At 1 GHz with 5 cycles per iteration, that's 25 milliseconds of pure CPU time wasted on data shuffling.

With DMA:

  1. CPU programs DMA controller once (~10 instructions)
  2. DMA controller performs entire transfer in hardware
  3. CPU does useful work concurrently
  4. DMA signals completion via interrupt

Why this matters: Modern SSDs can transfer at 3+ GB/s. Without DMA, the CPU would be 100% occupied just copying data, with zero cycles left for applications.

DMA Controller Architecture

Source Address Register (SAR)\text{Source Address Register (SAR)} Destination Address Register (DAR)\text{Destination Address Register (DAR)} Byte Count Register (BCR)\text{Byte Count Register (BCR)} Control/Status Register (CSR)\text{Control/Status Register (CSR)}

Derivation of necessity from first principles:

For the controller to perform a memory transfer autonomously, it must know:

  1. Where to read → SAR stores the starting source address
  2. Where to write → DAR stores the starting destination address
  3. How much to transfer → BCR stores remaining byte count
  4. How to transfer → CSR stores mode bits (direction, width, burst size)

Each register must be hardware-writable by CPU (for setup) and hardware-readable/writable by DMA logic (for auto-increment during transfer).

Register Bit-Level Design

\text{bit 0} & \text{Enable (EN): 1 = start transfer} \\ \text{bit 1} & \text{Interrupt Enable (IE): 1 = IRQ on completion} \\ \text{bits 2-3} & \text{Transfer Width: 00=byte, 01=halfword, 10=word} \\ \text{bits 4-5} & \text{Direction: 00=M2M, 01=M2P, 10=P2M} \\ \text{bit 6} & \text{Increment SAR (0=fixed, 1=increment)} \\ \text{bit 7} & \text{Increment DAR} \\ \text{bits 8-15} & \text{Burst Length (0-255 transfers per burst)} \\ \text{bit 31} & \text{Done (D): 1 = transfer complete (read-only)} \end{cases}$$ **Why these specific bits?** - **EN bit**: Hardware AND gate enables transfer state machine - **Transfer width**: Controls address increment (SAR += 1/2/4 bytes) and bus transaction size - **Burst length**: Trades off bus monopolization vs. setup overhead (see Burst Transfers section) ## How DMA Works: Step-by-Step ### Setup Phase (CPU-initiated) ``` 1. CPU writes source address to SAR Why? DMA needs to know where data lives 2. CPU writes destination address to DAR Why? DMA needs to know where data goes 3. CPU writes transfer size to BCR Why? DMA needs termination condition 4. CPU writes control bits to CSR (width, direction, burst) Why? Configures transfer mode 5. CPU sets EN bit in CSR → Transfer starts Why? Hardware signal to activate DMA FSM ``` ### Transfer Phase (Hardware-controlled) The DMA controller operates as **finite state machine**: > [!formula] DMA Transfer State Machine $$\text{State 0 (IDLE)}: \text{EN=0, wait for enable}$$ $$\downarrow \text{EN=1}$$ $$\text{State 1 (REQUEST)}: \text{Assert bus request signal, wait for grant}$$ $$\downarrow \text{Bus granted}$$ $$\text{State 2 (TRANSFER)}: \begin{cases} \text{Place SAR on address bus} \\ \text{Assert READ signal} \\ \text{Capture data} \\ \text{Place DAR on address bus} \\ \text{Place data on bus} \\ \text{Assert WRITE signal} \\ \text{SAR += width, DAR += width, BCR -= width} \end{cases}$$ $$\downarrow \text{If BCR} \neq 0$$ $$\text{Repeat State 2 (or go to State 1if cycle-stealing)}$$ $$\downarrow \text{If BCR} = 0$$ $$\text{State 3 (DONE)}: \text{Set Done bit, trigger IRQ if IE=1, return to IDLE}$$ **Why this design?** - **Bus request/grant**: DMA is a bus master, must arbitrate with CPU - **Atomic read-modify-write of registers**: Prevents race conditions in multi-step transfers - **BCR countdown**: Hardware comparator checks BCR=0 to terminate ### Completion Phase > [!example] DMA Completion Sequence > Scenario: Transfer 1 KB from SD buffer to RAM ``` 1. BCR reaches 0 after 1024 bytes transferred Why? Hardware decrements BCR each cycle 2. DMA controller sets CSR.Done = 1 Why? Status bit visible to CPU polling or interrupt handler 3. If CSR.IE = 1, DMA asserts interrupt line Why? Asynchronous notification to CPU 4. CPU interrupt handler reads CSR.Done, confirms completion Why? Verify transfer succeeded before using data 5. Handler clears CSR.EN (acknowledge) Why? Reset for next transfer ``` ## Transfer Modes ### 1. Burst Mode (Block Transfer) > [!formula] Burst Mode Timing > DMA acquires bus and holds it until entire transfer completes: $$T_{\text{burst}} = T_{\text{setup}} + N \times T_{\text{cycle}}$$ Where: - $T_{\text{setup}}$ = bus arbitration + first address setup (typically 2-5 cycles) - $N$ = number of transfers (BCR / width) - $T_{\text{cycle}}$ = time per transfer (typically 1-2 bus cycles for back-to-back) **Why use burst mode?** - **Minimum overhead**: Setup cost amortized over many transfers - **Maximum throughput**: No arbitration between transfers **Why NOT always use burst mode?** - **CPU starvation**: If DMA transfer takes 1000 cycles, CPU waits 1000 cycles - **Unfair to other masters**: Other devices can't access bus > [!example] Burst Mode Calculation > Transfer 4 KB at 32-bit width, 100 MHz bus: > - $N = 4096 / 4 = 1024$ transfers > - $T_{\text{setup}} = 4$ cycles (typical) > - $T_{\text{cycle}} = 1$ cycle (zero-wait SDRAM) > - $T_{\text{burst}} = 4 + 1024 = 1028$ cycles = **10.28 μs** > - CPU blocked for entire10.28 μs ### 2. Cycle Stealing Mode > [!formula] Cycle Stealing Timing > DMA requests bus for **one transfer at a time**, releases, repeats: $$T_{\text{cycle-steal}} = N \times (T_{\text{arb}} + T_{\text{transfer}})$$ Where: - $T_{\text{arb}}$ = arbitration overhead per transfer (2-3 cycles) - $T_{\text{transfer}}$ = actual data movement (1-2 cycles) **Why this step?** Each transfer requires requesting and releasing the bus. Total time: $1024 \times (3 + 1) = 4096$ cycles = **40.96 μs** **Tradeoff analysis:** - **Throughput**: 4× slower than burst (due to arbitration overhead) - **Latency**: CPU can access bus between DMA cycles → better responsiveness - **Fairness**: CPU gets ~50% of bus cycles (if DMA and CPU alternate) ### 3. Transparent Mode DMA monitors bus and only transfers when CPU is **not using the bus** (e.g., during internal ALU operations). **Why this works:** Modern CPUs don't access external bus every cycle: - Cache hits don't use external bus - Internal register operations don't use bus - During these cycles, DMA can "sneak in" a transfer **Limitation:** Transfer rate depends on CPU bus utilization. If CPU is 90% bus-active, DMA gets only 10% → slow and unpredictable. ## Bus Arbitration > [!formula] DMA Priority Schemes **Fixed Priority:** $$\text{Priority order: CPU} > \text{DMA}_1 > \text{DMA}_2 > \ldots$$ Simple hardware: daisy-chain grant signal. But lower-priority devices can starve. **Rotating Priority (Round-Robin):** $$\text{After device } i \text{ uses bus, priority becomes: } (i+1) \bmod N > (i+2) \bmod N > \ldots$$ Fair, but complex (requires priority tracking register). **Why this matters:** A disk DMA controller and network DMA controller both need high throughput. Fixed priority means one starves; rotating priority ensures both get service. ## Addressing Modes ### Increment Mode ``` SAR = 0x1000, width = 4bytes Transfer 1: Read from0x1000, SAR → 0x1004 Transfer 2: Read from 0x1004, SAR → 0x1008 ... ``` **Why?** Contiguous memory regions (arrays, buffers). ### Fixed Mode ``` SAR = 0x1000 (peripheral FIFO register) Transfer 1: Read from 0x1000, SAR stays 0x1000 Transfer 2: Read from 0x1000, SAR stays 0x1000 ... ``` **Why?** Peripheral has single data register that always holds next byte (UART RX FIFO, ADC result register). ## Scatter-Gather DMA > [!definition] Scatter-Gather > Advanced DMA controllers can transfer data to/from **non-contiguous memory regions** using a ==descriptor chain==. **Problem:** Network packet has header in one buffer, payload in another. Simple DMA requires two separate transfers with CPU intervention between. **Solution:** Descriptor list in memory: ```c struct dma_descriptor { uint32_t source_addr; uint32_t dest_addr; uint16_t byte_count; uint16_t flags; // Last descriptor bit, interrupt bit uint32_t next_desc; // Pointer to next descriptor (linked list) }; ``` > [!formula] Scatter-Gather Operation > 1. CPU writes address of first descriptor to DMA controller > 2. DMA fetches descriptor from memory (additional bus read) > 3. DMA performs transfer described by descriptor > 4. If not last descriptor, DMA fetches next descriptor from `next_desc` > 5. Repeat until last descriptor flag set **Overhead analysis:** - Each descriptor adds ~4 cycles (memory read + parse) - Tradeoff: Flexibility vs. throughput - Optimal for network packets (many small, scattered bufers) > [!example] Scatter-Gather Example > Transmit network packet: 14-byte Ethernet header at 0x2000, 1500-byte payload at 0x5000 Descriptor chain: ``` Desc1: SAR=0x2000, DAR=NIC_TX, BCR=14, next=0x3000 Desc2: SAR=0x5000, DAR=NIC_TX, BCR=1500, next=NULL last=1 ``` DMA controller performs both transfers **without CPU intervention**, NIC sees contiguous 1514-byte stream. ## DMA and Caches > [!mistake] Cache Coherency Problem **The mistake:** After DMA writes data to memory, CPU reads old cached data instead of fresh DMA data. **Why this feels right:** "Memory has the data, CPU reads memory, should work." **The reality:** CPU caches data. When DMA writes to memory address 0x4000, CPU's cache still has old value at 0x4000. CPU reads cache (fast) instead of memory (slow) → stale data. > [!formula] Cache Coherency Solutions **1. Software Cache Management:** ``` Before DMA write to memory: CPU invalidates cache lines covering destination region Why? Ensures CPU won't read stale cached data After DMA read from memory: CPU flushes (writeback) cache lines covering source region Why? Ensures DMA sees latest CPU-written data ``` **2. Hardware Cache Coherency (Cache-coherent DMA):** - DMA controller connects to cache coherency interconnect (.g., ARM ACE, Intel QPI) - DMA write → snoops CPU caches, invalidates matching lines automatically - Transparent to software but adds hardware cost **3. Non-cacheable Memory Regions:** - Mark DMA bufers as non-cacheable in page tables - All accesses bypass cache → always coherent - Performance penalty: CPU pays memory latency on every access ## Performance Analysis > [!formula] DMA Efficiency Metric $$\text{Efficiency} = \frac{\text{Useful data transferred}}{\text{Total bus cycles consumed}}$$ > [!example] Efficiency Comparison > Transfer 16 KB between memory regions: **CPU copy (memcpy):** - Load word: 1 cycle (if cache hit, else +10 for memory) - Store word: 1 cycle - Per word: ~5 cycles average (including loop overhead) - Total: $\frac{16384}{4} \times 5 = 20{,}480$ cycles - CPU 100% busy - Efficiency: $\frac{4096 \text{ words}}{20{,}480} = 0.20$ (20%) **DMA burst:** - Setup: 10 cycles - Transfer: 4096 words × 1 cycle = 4096 cycles - Total: 4106 cycles - CPU free after cycle 10 - Efficiency: $\frac{4096}{4106} \approx 0.998$ (99.8%) **Speedup:** $\frac{20{,}480}{4106} \approx 5\times$ faster, **and** CPU free for other work. ## Multi-Channel DMA Controllers Modern DMA controllers have **multiple independent channels** (4-32 typical). > [!formula] Channel Scheduling > Each channel has separate registers (SAR, DAR, BCR, CSR). Hardware arbitrates between active channels: $$\text{If multiple channels active} \rightarrow \text{Round-robin or priority-based scheduling}$$ **Why multiple channels?** - Simultaneous transfers: SD → Memory while Memory → Network - Avoid head-of-line blocking: Short transfer doesn't wait for long transfer - Separate interrupt handlers per channel > [!example] Dual-Channel Scenario > - Channel 0: Disk read, 1 MB transfer (takes 1000 μs in cycle-steal mode) > - Channel 1: UART receive, 10 bytes (takes 10 μs) With single channel: UART waits 1000 μs → buffer overun With dual channel: UART uses Channel 1, completes in 10 μs in parallel ## Common Mistakes > [!mistake] Forgetting to Flush/Invalidate Caches **The mistake:** Setup DMA transfer, start it, CPU immediately reads destination buffer → sees garbage. **Why it feels right:** "I programed DMA, data should be there." **Steel-man:** You're correct that DMA **will** write data to memory. The issue is **timing** and **cache**: 1. DMA takes time (microseconds to milliseconds) 2. CPU cache might have old data at destination address 3. CPU reads cache before DMA finishes writing memory **The fix:** ```c // Before DMA dma_setup(channel, src dst, size); cache_invalidate(dst, size); // Remove stale dst data from cache dma_start(channel); // Wait for completion while (!dma_done(channel)); // Or use interrupt // Now read dst - cache miss forces read from memory with fresh DMA data process_buffer(dst, size); ``` > [!mistake] Not Checking Alignment **The mistake:** DMA transfer with SAR=0x1001, width=32-bit → system crash or data corruption. **Why it feels right:** "Memory is just bytes, any address works." **Steel-man:** You're right that memory is byte-addressable. But DMA **hardware** often requires **word-aligned** addresses for word transfers: - 32-bit transfer → address must be multiple of 4 (bottom 2 bits = 00) - 16-bit transfer → address must be multiple of 2 (bottom bit = 0) **Why?** Hardware simplification: 32-bit datapath connects to address bits [31:2], not [31:0]. Unaligned access requires extra logic (shift, merge) → most DMA controllers just forbid it. **The fix:** ```c // Wrong uint8_t buffer[1024]; dma_setup(src, buffer+1, 1000, WIDTH_32BIT); // buffer+1 unaligned! // Right uint8_t buffer[1024] __attribute__((aligned(4))); // Force alignment dma_setup(src, buffer, 1000, WIDTH_32BIT); // Aligned ``` > [!mistake] Buffer Overun with Fixed-Mode DAR **The mistake:** Peripheral reads from fixed SAR address, but BCR exceds FIFO depth → DMA reads same stale data repeatedly. **Why it feels right:** "FIFO register address is fixed, so use fixed mode." **Steel-man:** Correct that peripheral FIFO has single address. But you assumed FIFO always has data. Reality: - FIFO has finite depth (e.g., 16 entries) - If DMA reads faster than peripheral writes → underun - DMA reads same old value16 times, then garbage **The fix:** Use **flow control** (aka **DMA handshaking**): - Peripheral asserts DREQ (DMA request) signal when FIFO has data - DMA only transfers when DREQ=1 - Peripheral de-asserts DREQ when FIFO empty - DMA pauses automatically ``` BCR = 1000, FIFO depth = 16 DMA reads 16 bytes → FIFO empty → DREQ=0 → DMA pauses Peripheral refills FIFO → DREQ=1 → DMA resumes Repeats until BCR = 0 ``` ## Advanced: DMA and Virtual Memory > [!formula] DMA with MMU **Problem:** CPU uses **virtual addresses**, but DMA hardware uses **physical addresses**. **Example:** CPU buffer at virtual 0xC000_0000 maps to physical 0x010_0000 (MMU translation). **Solution 1: Physical addressing (most common):** 1. CPU allocates buffer, gets virtual address 2. CPU queries OS for physical address (page table lookup) 3. CPU programs DMA with **physical** address 4. DMA accesses physical memory directly (bypasses MMU) **Solution 2: IOMMU (advanced systems):** - ==IOMMU== (Input/Output Memory Management Unit): MMU for DMA devices - DMA uses device virtual addresses → IOMMU translates to physical - Enables DMA to scattered physical pages without descriptor chains - Security: Prevents DMA from accessing unauthorized memory ## Summary > [!recall]- Explain to a 12-year-old > Imagine your mom asks you to move100 boxes from the garage to the atic. You could carry each box yourself (that's like the CPU moving data byte-by-byte), but it would take all day and you couldn't do your homework. Instead, your mom hires a moving crew (that's the DMA controller). You tell them once: "Move 100 boxes from garage to atic." Then you go do your homework while they work. When they finish, they ring a bell (interrupt) to let you know. The crew is not as smart as you—they can only move boxes in a straight line, and you have to set up the exact plan. But they're really fast at the one job they do, and the best part is you can do other things while they work. That's why computers use DMA: the CPU is too valuable to waste on simple data copying. > [!mnemonic] DMA Register Mnemonic > **S**ally **D**rives **B**ig **C**ars > - **S**AR: Source Address Register > - **D**AR: Destination Address Register > - **B**CR: Byte Count Register > - **C**SR: Control/Status Register ## Connections - [[Bus Architecture]] - DMA acts as bus master, requires arbitration - [[Interrupts]] - DMA completion notification mechanism - [[Cache Coherency]] - Critical interaction with CPU caches - [[Memory-Mapped IO]] - DMA reads/writes peripheral registers - [[PCIe]] - Modern DMA-capable interconnect - [[UART]] - Example peripheral using DMA - [[SDRAM Controllers]] - DMA destination/source for memory - [[ARM AMBA AXI]] - Bus protocol supporting advanced DMA features --- #flashcards/hardware What is the primary purpose of DMA? :: To transfer data between memory and peripherals (or memory-to-memory) without continuous CPU intervention, freing the CPU for other tasks. What are the four essential registers in a DMA controller? ::: Source Address Register (SAR), Destination Address Register (DAR), Byte Count Register (BCR), and Control/Status Register (CSR). How does burst mode differ from cycle-stealing mode? ::: Burst mode acquires the bus and holds it for the entire transfer (high throughput, CPU starvation), while cycle-stealing releases the bus after each transfer (lower throughput, better CPU responsiveness). What is scatter-gather DMA? ::: DMA technique using a descriptor chain to transfer data to/from non-contiguous memory regions in a single operation without CPU intervention. Why must DMA buffers be cache-coherent? ::: Because DMA writes directly to memory while CPU may read from cache; without coherency, CPU reads stale cached data instead of fresh DMA data. What is the cache coherency solution for DMA write? ::: Before DMA write, CPU invalidates cache lines covering the destination region to ensure subsequent CPU reads fetch fresh data from memory. Why do DMA controllers require aligned addresses? ::: Hardware simplification: word-width transfers connect to aligned address bits; unaligned access requires extra shift/merge logic most DMA hardware omits. What is IOMMU? ::: Input/Output Memory Management Unit; provides address translation for DMA devices, enabling them to use virtual addresses and improving security by restricting memory access. What problem does DMA flow control (DREQ) solve? ::: Prevents buffer overrun/underrun by having peripheral assert a request signal only when data is available, pausing DMA when peripheral isn't ready. Calculate efficiency: DMA transfers 8 KB in burst mode with 5-cycle setup and 1 cycle per word (32-bit). :: Transfers = 8192/4 = 2048, Total cycles = 5+ 2048 = 2053, Efficiency = 2048/2053 ≈ 99.76% ## 🖼️ Concept Map ```mermaid flowchart TD PIO[Programmed I/O] -->|wastes CPU cycles| PROB[CPU Bottleneck Problem] PROB -->|motivates| DMA[Direct Memory Access] DMA -->|orchestrated by| DMAC[DMA Controller] DMAC -->|acts as| BM[Bus Master] DMAC -->|frees CPU for| WORK[Concurrent Useful Work] DMAC -->|signals done via| IRQ[Completion Interrupt] DMAC -->|contains registers| REGS[Channel Registers] REGS -->|read source| SAR[Source Address Reg] REGS -->|write dest| DAR[Destination Address Reg] REGS -->|remaining bytes| BCR[Byte Count Reg] REGS -->|mode bits| CSR[Control/Status Reg] CSR -->|controls| MODE[Direction, Width, Increment] ``` ## 🔊 Hinglish (regional understanding) > [!intuition]- Hinglish mein samjho > ![[audio/6.3.08-DMA-controllers.mp3]] ## 🔊 Hinglish (regional understanding) > [!intuition]- Hinglish mein samjho > DMA controller basically ek smart assistant hai jo CPU ka boring kaam sambhalta hai. Socho tumhe 1 MB data ek peripheral se memory mein copy karna hai. Agar CPU khud ye kaam kare (Programmed I/O), toh har single byte ke liye use padhna, likhna, pointer badhana aur check karna padta hai — matlab lakhon instructions aur milliseconds ka time sirf data idhar-udhar shift karne mein waste. Yaha CPU ek manager ki tarah hai jo apna precious time files uthaane mein laga raha hai, bade decisions ke bajaye. DMA ka core idea yahi hai: CPU ek baar controller ko bata deta hai ki "itna data yaha se waha bhej do", aur phir apna asli useful kaam karne lag jaata hai jabki DMA parallel mein transfer handle karta hai. > > Ab ye kaam kaise hota hai, uska mechanism simple hai. DMA controller ke andar kuch registers hote hain — SAR (source address, kaha se padhna hai), DAR (destination address, kaha likhna hai), BCR (byte count, kitna bhejna hai), aur CSR (control/status, kaise bhejna hai jaise direction, width, burst size). CPU setup phase mein in registers ko fill karta hai aur phir CSR ka EN bit set kar deta hai — bas, transfer start ho jaata hai. DMA controller khud automatically address increment karta jaata hai aur count kam karta jaata hai jab tak kaam khatam na ho, aur end mein ek interrupt bhej ke CPU ko bata deta hai ki "ho gaya". > > Ye concept kyun important hai? Aaj ke modern SSDs 3+ GB/s speed se data transfer karte hain. Agar DMA na hota, toh CPU 100% time sirf data copy karne mein busy rahta aur tumhare actual applications ke liye ek bhi cycle nahi bachta. Isliye DMA parallelism deta hai — CPU aur DMA dono ek saath alag-alag kaam karte hain, jisse system fast aur efficient banta hai. Jab bhi tum high-speed I/O, networking, ya disk operations dekhoge, samajh lena ki peeche DMA hi silent hero ki tarah kaam kar raha hai.

Go deeper — visual, from zero

Test yourself — Interconnects, Buses & SoC

Connections