ISA DMA on DOS PCs: Page Boundaries, Address Units, and Safe Buffers
Apply the original PC/AT DMA controller's 64 KiB and 128 KiB page constraints when allocating, programming, and validating DOS driver buffers.
On an original IBM PC/AT-style ISA system, a driver cannot assume that any memory buffer can be transferred by DMA in one operation. The 8237-compatible DMA controllers use a 16-bit address counter plus page-address state, and the transfer must remain within the page boundary supported by the selected channel. The original IBM PC/AT Technical Reference documents a 64 KiB page for channels 0 through 3 and a 128 KiB page for channels 5 through 7. The second controller’s channel 4 is used to cascade the first controller, not as an ordinary peripheral DMA channel.
These are constraints of the classic PC/AT DMA architecture. They are not a blanket statement about every modern machine, every ISA-compatible bridge, or every virtual device. A DOS driver must follow the adapter, controller, and chipset documentation actually in use and verify the programmed transfer rather than relying on a buffer’s apparent size alone.
Address and count registers describe different units
The first 8237 controller handles byte transfers on channels 0 through 3. Its current address and page register together identify the memory location, while a 16-bit count register represents the number of transfer units minus one. The AT’s second 8237 controller handles word transfers on channels 5 through 7. Its address counter advances in words, so byte alignment and count conversion must be handled consistently. The page register extends the controller’s limited address counter but does not remove the page-crossing restriction.
For a byte channel, the maximum safe length before the next 64 KiB boundary is:
boundary = (physical_address & 0xFFFF) ^ 0xFFFF, plus 1
safe_len = min(requested_len, boundary, controller_byte_limit)
Equivalently, using subtraction:
safe_len = min(requested_len, 0x10000 - (physical_address & 0xFFFF))
For a word channel on the AT, the corresponding 128 KiB byte window is:
safe_len = min(requested_len, 0x20000 - (physical_address & 0x1FFFF), controller_byte_limit)
The word channel also requires a valid even-byte start and a length represented in whole words. controller_byte_limit means the controller’s maximum transfer count converted into bytes for the selected channel; the peripheral may impose a smaller limit. After choosing the byte length, convert it to transfer units (bytes on channels 0–3, words on channels 5–7) and program the 16-bit count register as units minus one. The page-remainder calculation does not replace the controller’s count encoding, direction, mode, or device-specific constraints.
For example, a byte-mode request beginning at physical address 0x12FF00 reaches the next 64 KiB boundary at 0x130000, so only 0x100 bytes fit in that page. A 1 KiB request must be split or copied to another suitable buffer. On a word channel, a request beginning at 0x21FFF0 reaches the next 128 KiB boundary at 0x220000 after 16 bytes; it also must be split, and the transfer size must be even.
Calculate from the final physical start address after segment:offset normalization or host mapping. For byte mode, subtract the low 16-bit offset from 0x10000; for the AT word channels, subtract the low 17 bits from 0x20000. If those low address bits are zero, the start is exactly at a page boundary and the remaining window is a full page, subject to the controller count limit and device maximum. Use a wide intermediate type so an address-plus-length check does not wrap before the boundary test.
When splitting a transfer, advance the data pointer and device-side block/sector offset by exactly the completed byte count, then recompute the next segment from its new physical address. The second segment may begin at a fresh boundary, but its size still must respect the maximum count and peripheral protocol. Treat segment completion independently; if the first succeeds and the second fails, report a partial transfer rather than total success. On reads, do not expose incomplete data as a valid full record.
A DOS pointer is not automatically a DMA address
Real-mode code commonly receives a segment:offset pointer. The physical address for a conventional real-mode pointer is calculated as segment * 16 + offset, but DMA suitability requires more than that arithmetic. The buffer must be within memory the adapter can address, aligned for the selected channel, contiguous for the requested transfer, and not cross its page boundary. A protected-mode DOS extender or DPMI host adds another mapping layer; an application’s linear pointer is not necessarily a bus address.
The original PC DMA controllers have a finite address reach, and some adapters impose a lower limit or alignment rule of their own. A driver should use a memory-allocation or mapping mechanism supported by the target environment, then verify the returned region against the controller and adapter limits. Do not take an arbitrary pointer from a high-level language and program its low 16 bits into the DMA address register.
When a transfer does not fit, common design choices are to split it at the legal boundary or copy through a dedicated bounce buffer that does fit. Splitting requires the device protocol to support continuation and the driver to preserve sector/frame offsets correctly. A bounce buffer costs memory and a copy, but simplifies the hardware transfer contract. In either case, record the actual physical address and size programmed; logging only a segment:offset can hide a page calculation bug.
Program and verify the controller as a stateful device
DMA programming is more than writing an address and count. The driver must select the channel, mask it while changing registers, clear the controller’s byte/word flip-flop where required, program address and page state, set the transfer count, configure direction and mode, then unmask the channel in the order documented for the hardware. Competing software that touches the same controller can invalidate assumptions. The channel may be reserved by a floppy controller, sound card, or another device, so resource assignment must be confirmed before use.
This article does not provide a universal register-write sequence because the exact controller and adapter requirements vary. Use the IBM PC/AT Technical Reference and the peripheral manual for the target. Keep channel selection, I/O-port addresses, and transfer mode tied to that hardware contract. An incorrect transfer direction or a stale flip-flop state can corrupt memory even when the boundary arithmetic is correct.
Before enabling a DMA channel, confirm that the device uses the classic ISA DMA controller and that no other driver owns the same channel. An application’s DMA=1 setting does not prove that the selected adapter is wired to that channel or that a virtual machine models it faithfully. Record IRQ, I/O base, DMA channel, and device mode as one resource tuple. Changing only the IRQ cannot repair a DMA page crossing.
After a transfer, inspect the controller’s terminal-count or residue state using the documented mechanism and verify the data at both ends. Compare a checksum or known pattern, test sizes immediately below and at the boundary, then test an operation split across two DMA segments. Use disposable media and a reproducible pattern. A successful interrupt or a device-ready bit does not prove that all bytes arrived at the intended address.
Common failure patterns
- A transfer works at small sizes but corrupts data near a 64 KiB boundary. Check the channel class and whether the start and length cross the page.
- A 16-bit-channel transfer is shifted or corrupted. Recheck even alignment and whether the driver expressed the count in words rather than bytes.
- The driver reports completion but a checksum differs. Verify mode, direction, terminal count, and adapter-specific timing, not only IRQ delivery.
- Failures occur only with another resident driver loaded. Check channel ownership and whether another component reprograms the DMA controller.
- An emulator works but physical hardware fails. Compare the virtual DMA model with the PC/AT hardware constraints; emulators can mask real-device assumptions.
- A real machine works but a DOS extender fails. Confirm that the buffer is mapped to a bus-addressable physical region and that the host’s DMA services are being used correctly.
The acceptance record should include the machine or emulator, controller and adapter, channel, direction, transfer mode, physical start address, length, boundary calculation, count-register value, and a post-transfer data check. Preserve the exact driver and resource configuration used. DMA is a low-level shared resource whose correctness is established by both a valid programming sequence and verified memory contents.
Include boundary-edge test lengths in a non-production image: one byte less than the computed remainder, exactly the remainder, and one byte more. The last case must be rejected or split before programming the controller. Repeat from several offsets within one page and with a pattern that makes a wrap visible, such as monotonically increasing byte values. Place a known canary before and after the target buffer and verify it after each transfer. These checks catch arithmetic defects that an ordinary aligned sector transfer can hide.
Related:
- Device Drivers on FreeDOS: How .SYS Files Extend the Kernel
- Fixing Sound Blaster Configuration Issues on FreeDOS
Sources: