Skip to content
Haiku OSDeep Dive Published Updated 6 min readViews unavailable

Haiku DMA Buffers: Physical Addressability, Contiguity, and Alignment

Plan Haiku DMA buffers around physical address windows, contiguity, alignment, locking, and device-specific constraints instead of virtual pointers alone.

A kernel virtual address is not, by itself, a DMA address. A device consumes addresses and transfer lengths according to its own bus and controller contract; the driver must establish that the backing memory is resident, physically reachable, and aligned in the way that the hardware requires. Haiku exposes low-level area and kernel buffer APIs that help drivers express some of these constraints, but no single allocation choice makes every device’s DMA rules disappear.

This guide focuses on Haiku driver-side decisions: physical address limits, contiguous allocation, inspecting physical mappings, alignment, and transfer ownership. It is not a generic user-space recipe, a guarantee of IOMMU translation, or a replacement for the target device’s programming manual.

Translate the device’s DMA contract into allocation constraints

Before allocating, write down the device-specific address mask or upper address bound, alignment, transfer boundary rules, maximum length, and whether the device can consume a scatter-gather list. These are properties of the controller and bus, not of the C pointer. For example, Haiku’s OS.h defines B_LOMEM as contiguous memory below 16 MiB and B_32_BIT_CONTIGUOUS as contiguous memory with a physical address below 4 GiB. Those flags express Haiku allocation constraints. They do not promise a device-specific alignment, length boundary, or support for addresses below some narrower mask.

If the hardware accepts discontiguous segments, the driver may build descriptors from a verified physical map. If it requires one segment, use an allocation mode that actually promises contiguity and still check the base alignment and total length. A memory range that is merely locked against paging can remain physically fragmented; locking and contiguity solve different problems.

Haiku’s low-level APIs and a real driver example

The kernel driver interface declares lock_memory(), unlock_memory(), get_memory_map_etc(), map_physical_memory(), and the physical_entry structure. In the pinned kernel-source snapshot cited below, lock_memory() wires the requested range and requires a matching unlock_memory() with the exact same parameters. Its implementation documents only B_READ_DEVICE as a defined flag and tests that bit to request writable wiring for a device-to-memory transfer. The Haiku Book API reference describes B_DMA_IO as appropriate when a non-CPU agent modifies the locked range, but that description is not confirmed by this implementation snapshot: its header still declares the constant while lock_memory_etc() does not inspect it. Check the target release’s own implementation before relying on the Book-described B_DMA_IO behavior. Neither flag replaces the device’s DMA addressing rules.

The upstream NVIDIA driver provides a concrete, driver-specific example. It creates a B_32_BIT_CONTIGUOUS area for a GPU command buffer, asks get_memory_map() for its physical address, and derives an aligned subrange. It then maps that range with write-combining memory and falls back to a normal mapping if that option fails. The source comments explain the GPU’s access and alignment requirements; this strategy is evidence of one implementation, not a universal recipe for all devices.

For an inbound transfer using a locked buffer, the following kernel-driver sketch shows the important checks. It uses get_memory_map_etc() so the caller receives the actual number of physical entries and a defined B_BUFFER_OVERFLOW result when the array is too small. This sketch rejects an incomplete map; production code may allocate a larger bounded table and retry, but must not program a device from a partial map. The 4-GiB physical-address limit in the source tree belongs to the legacy BeOS-compatible get_memory_map() ABI implementation; do not generalize that limit to Haiku’s current versioned mapping APIs. Prefer the current get_memory_map_etc() interface shown here and check the target headers and ABI for the exact release being built.

/* This pinned implementation uses B_READ_DEVICE for writable inbound DMA. */
uint32 dmaFlags = B_READ_DEVICE;
status_t status = lock_memory(buffer, byteCount, dmaFlags);
if (status != B_OK)
    return status;

physical_entry map[64] = { 0 };
uint32 entryCount = 64;
status_t mapStatus = get_memory_map_etc(B_CURRENT_TEAM, buffer, byteCount,
    map, &entryCount);
if (mapStatus != B_OK) {
    unlock_memory(buffer, byteCount, dmaFlags);
    return mapStatus;
}

for (uint32 i = 0; i < entryCount; ++i) {
    /* Validate every entry against this device's DMA contract.
       Do not program the device with the virtual value of buffer. */
}

The map must be checked entry by entry. Confirm the address range is within the device’s address window, its alignment and length comply with the controller, and either contiguity holds or the hardware and driver support the resulting scatter-gather list. For a device-to-memory transfer, keep the buffer locked until the hardware has completed and software has consumed or synchronized the results. Then call unlock_memory() with the same address, byte count, and flags. For memory-to-device traffic, the direction differs: do not set B_READ_DEVICE, which specifically means that the device fills the memory range.

Alignment and cache policy are separate from reachability

Physical contiguity does not imply that the first byte has the alignment or boundary placement a controller requires. A device might forbid a descriptor or transfer from crossing a hardware-defined boundary, accept only a subset of physical address bits, or have a maximum segment length. Validate those constraints against the device datasheet and the map you actually obtained. Never infer alignment from a void * address or from the requested allocation size alone.

Cacheability is also a distinct concern. Haiku’s map_physical_memory() accepts memory-type flags such as B_WRITE_COMBINING_MEMORY; the NVIDIA driver uses that type for a particular command-buffer mapping and retries without it if mapping fails. Do not copy write-combining policy to ordinary DMA payloads or device registers without checking the hardware’s cache-coherency and ordering requirements. A mapping attribute does not prove that the device can address the range.

Review a DMA failure with a constraint table

When a transfer fails, compare the actual allocation and every physical segment with the controller’s requirements:

Question Evidence to capture
Is the whole range resident for the DMA interval? Successful lock and a matching later unlock; track cleanup on every error path.
Can the controller address each byte? Physical entries from get_memory_map_etc() and the controller’s address-width limit.
Is the memory contiguous when required? Adjacency of physical segments across the entire transfer, not just the first page.
Are alignment and boundary rules met? Physical base, transfer size, descriptor layout, and controller-specific boundary checks.
Is the direction right? Whether the device reads host memory or writes data into it; use B_READ_DEVICE only for the latter.
Is the CPU mapping’s memory type appropriate? Device/bus documentation and the exact Haiku mapping flags used.

Keep the device manual, Haiku API version, buffer ownership transitions, physical-map validation, completion interrupt, and cleanup path together in the review. A correct DMA setup is a contract among hardware, kernel mapping, memory lifetime, and driver state; changing only the pointer allocation can leave the actual defect untouched.

Related:

Sources:

Comments