Skip to content
Levente Santha edited this page May 13, 2026 · 1 revision

DMA (Direct Memory Access)

Java-level abstraction for the x86 ISA DMA controller, enabling hardware-initiated data transfers between I/O devices and system memory without CPU intervention.

Overview

JNode's DMA subsystem provides a complete Java abstraction over the x86 ISA DMA controller hardware. The ISA DMA controller consists of two cascaded 8237-style controllers: controller 1 manages channels 0–3 (byte transfers), and controller 2 manages channels 5–7 (word transfers), with channel 4 permanently reserved as the cascade link between the two controllers.

The architecture follows a three-layer design: device drivers interact with architecture-neutral interfaces (DMAManager, DMAResource), which are implemented by an x86-specific plugin (DMAPlugin) registered in InitialNaming-Service-Registry, which in turn delegates to a low-level hardware class (DMA) that performs raw I/O port programming. This separation means drivers never touch hardware registers directly — they claim a channel, configure a transfer, enable it, and respond to the completion IRQ.

Currently the floppy disk controller (DefaultFDC) is the only driver using DMA, but the architecture is general-purpose and supports any ISA DMA device. DMA transfers are limited to the first 16 MB of physical memory, and no transfer may cross a 64 KB (channels 0–3) or 128 KB (channels 5–7) physical page boundary.

Key Components

Class / File Role
core/src/core/org/jnode/system/resource/DMAManager.java Architecture-neutral service interface; looked up via InitialNaming
core/src/core/org/jnode/system/resource/DMAResource.java Architecture-neutral channel handle; drivers use this to setup/enable transfers
core/src/core/org/jnode/system/resource/DMAException.java Exception type for DMA-specific errors
core/src/core/org/jnode/system/x86/DMAPlugin.java Plugin implementing DMAManager; owns channel allocation and the DMA hardware instance
core/src/core/org/jnode/system/x86/X86DMAChannel.java Per-channel resource handle implementing DMAResource; delegates to DMAPlugin
core/src/core/org/jnode/system/x86/DMA.java Low-level x86 DMA controller programming via I/O ports (package-private)
core/src/core/org/jnode/system/x86/DMAConstants.java Hardware port addresses and mode constants
core/src/driver/org/jnode/driver/block/floppy/DefaultFDC.java Primary DMA consumer; floppy driver using channel 2

How It Works

Channel Allocation

  1. Service lookup: Driver calls InitialNaming.lookup(DMAManager.NAME) to get the DMAManager service.
  2. Claim a channel: dmamanager.claimDMAChannel(owner, dmanr) — validates channel number (0–7), checks the channels[] array for availability, and creates an X86DMAChannel if free. Channel 4 is permanently reserved (cascade).
  3. Allocate DMA buffer: Driver calls ResourceManager.claimMemoryResource(owner, null, size, ResourceManager.MEMMODE_ALLOC_DMA) — this allocates from the bottom of physical memory (via Unsafe.getMinAddress()), ensuring the buffer is within the 16 MB DMA zone.
  4. On failure: ResourceNotFreeException is thrown if the channel is already claimed; IllegalArgumentException for invalid channel numbers.

Transfer Setup and Execution

The transfer lifecycle for a typical DMA operation (e.g., floppy read):

1. dma.setup(dmaMem, length, DMAResource.MODE_READ)
   → dma.test()             -- validate alignment and page boundaries
   → dma.disable(ch)        -- mask the channel (prevent spurious transfers)
   → dma.clearFF(ch)        -- reset byte pointer flip-flop
   → dma.setMode(ch, mode)  -- program direction (read/write)
   → dma.setAddress(ch, addr) -- program 24-bit physical address
   → dma.setLength(ch, len) -- program transfer count (value = count - 1)

2. dma.enable(ch)           -- unmask the channel; hardware begins transfer

3. [Hardware performs transfer: device ↔ DMA buffer]

4. IRQ fires → driver.handleInterrupt()
   → dmaMem.getBytes(data, offset, length)  -- copy from DMA buffer

5. dma.release()            -- free the channel slot

Physical Address Programming

The x86 DMA controller uses 24-bit addressing: a 16-bit address register plus an 8-bit page register. For channels 5–7 (word mode), the address is right-shifted by 1 and the page register ignores bit 0, resulting in 128 KB page granularity. The DMA.setAddress() method handles this split automatically:

  • Channels 0–3: page = address >>> 16, addr16 = address & 0xFFFF
  • Channels 5–7: page = (address >>> 16) & 0xFE, addr16 = (address >>> 1) & 0xFFFF

DMA Memory Allocation Strategy

MemoryResourceImpl uses a dual-allocation strategy controlled by the mode parameter:

Mode Allocation Start Use Case
MEMMODE_ALLOC_DMA (0x01) Unsafe.getMinAddress() (bottom of RAM) DMA buffers — must be in low 16 MB
MEMMODE_NORMAL (0x00) Unsafe.getMemoryEnd() (top of RAM) General-purpose memory

Using MEMMODE_NORMAL for a DMA buffer would allocate memory above the 16 MB boundary on systems with more RAM, causing silent transfer failures.

x86 DMA Controller Register Map

Register Controller 1 (ch 0–3) Controller 2 (ch 5–7)
Command 0x08 0xD0
Mask 0x0A 0xD4
Mode 0x0B 0xD6
Clear Flip-Flop 0x0C 0xD8
Address (ch 0/5) 0x00 0xC0
Count (ch 0/5) 0x01 0xC2
Page (ch 0) 0x87
Page (ch 1/5) 0x83 0x8B

Controller 2 offsets are all even (2× the controller 1 offset for the same register), which the DMA class handles internally.

Integration with the Plugin System

DMAPlugin extends Plugin and implements DMAManager. Its lifecycle:

  • startPlugin(): Creates the DMA hardware instance (which claims I/O port ranges from ResourceManager), then binds itself in InitialNaming as DMAManager.NAME.
  • stopPlugin(): Unbinds from InitialNaming, calls DMA.release() to free I/O port claims.

The plugin descriptor is in core/descriptors/org.jnode.runtime_x86.xml.

Gotchas & Non-Obvious Behavior

  • Channel 4 is permanently reserved: Pre-allocated in the DMAPlugin constructor as a cascade channel (SimpleResourceOwner("cascade")). Calling claimDMAChannel(owner, 4) will always throw ResourceNotFreeException. This is correct hardware behavior but is not documented in the DMAManager interface.

  • Likely bug in DMA.getLength() and DMA.setAddress(): These methods read/write via dma1IO for ALL channels, including channels 5–7 which should use dma2IO. Compare with setLength() and setMode() which correctly use dma2IO for channels >= 4. This appears to be a copy-paste bug that would cause incorrect behavior on channels 5–7.

  • Transfer count register = actual count − 1: DMA.setLength() decrements the length before writing to the hardware register. This is correct per the x86 8237 spec, but can confuse developers reading the code.

  • DMA buffers must use MEMMODE_ALLOC_DMA: Allocating with MEMMODE_NORMAL places memory at the top of physical RAM, which on systems with >16 MB is outside the DMA-able zone. The hardware would silently fail or corrupt data.

  • No auto-initialization mode support: DMAConstants defines DMA_AUTOINIT = 0x10, but X86DMAChannel.setup() only accepts MODE_READ (1) or MODE_WRITE (2). Any other value throws IllegalArgumentException. Auto-init DMA (continuous transfers) is not exposed.

  • X86DMAChannel.getParent() always returns null: DMA channels have no resource parent-child hierarchy; their lifecycle is managed entirely by DMAPlugin.

  • DMA class is package-private: Cannot be accessed outside org.jnode.system.x86. All driver access must go through DMAManager/DMAResource interfaces.

  • @MagicPermission required: Both DMA.java and DMAPlugin.java are annotated with @MagicPermission to bypass normal Java security checks for raw I/O port access.

  • Resource release order matters: DefaultFDC documents "PRESERVE THIS CLAIMING ORDER!" and releases in reverse: dmaMem → io2 → io1 → dma → irq. Violating this order can leave channels orphaned.

  • Only the floppy driver uses DMA currently: Despite the general-purpose architecture, no other ISA DMA devices (sound cards, parallel port, etc.) are implemented in JNode.

  • DMA limited to lower 16 MB of physical memory: This is an x86 hardware constraint. DMA.test() validates this by checking address and length, but does not enforce it at runtime if a caller bypasses setup().

Related Pages

Clone this wiki locally