FPGA / PCIE DMA

XDMA or QDMA? Pick the engine, then get /dev/xdma0 talking.

You typed DMA into the Vivado IP catalog and got two PCIe subsystems with nearly the same name. One gives you a few fixed channels and a driver you can test from the shell; the other is a queue manager built for thousands of queues and virtual functions.

THE SHORT ANSWER

XDMA (AMD PG195) gives you up to four host-to-card and four card-to-host DMA channels and a simple Linux character-device driver, which suits most single-host FPGA boards. QDMA (PG302) manages up to 2,048 queues that software creates at runtime, with SR-IOV and completion rings. Start with XDMA unless you need many queues, virtual functions or a device XDMA does not support.

The aha: XDMA has channels, QDMA has queues

The two names hide one real difference. In XDMA, a channel is a hardware engine you choose when you generate the IP. You get up to four in each direction. Software hands an engine a list of descriptors and tells it to go. Count your engines on your fingers, and that is your concurrency.

In QDMA, a queue is mostly state: a context in on-chip memory that software creates, starts and stops at runtime. Thousands of queues share the same engines, and each can belong to a different physical or virtual function, a different process or a different VM.

That one difference decides how you debug. When an XDMA transfer hangs, you ask which of a handful of engines is stuck and what its status register says. When a QDMA transfer hangs, you ask what one queue’s context says: is it enabled, where are its producer and consumer indexes, is an error bit set? The Spec Lab demo walks through a source-linked PG302 queue-context field map for exactly that question.

Rule of thumb: if your design has one host, a few data streams and big buffers, XDMA’s channels are the shorter path to working DMA. If you are counting flows, tenants or virtual functions, you want QDMA’s queues.

XDMA vs QDMA at a glance

XDMA (PG195)QDMA (PG302)
DMA unitUp to 4 H2C and 4 C2H channels, fixed when you generate the IP (2 and 2 on 7 series Gen2)2,048 queue sets, each with H2C, C2H and completion (CMPT) rings, created at runtime
User interfaceAXI4 memory-mapped or AXI4-Stream, for the whole IPMemory-mapped or stream, chosen per queue
FunctionsOne physical function, no SR-IOVUp to 4 PFs and 252 VFs with SR-IOV
InterruptsUp to 16 user interruptsUp to 2,048 MSI-X vectors, up to 8 per function
Devices7 series Gen2, Virtex-7 XT, UltraScale, UltraScale+UltraScale+; Versal has its own PCIe DMA guides
Linux driverxdma.ko with ready-made /dev/xdma0_* character devicesqdma-pf and qdma-vf, with queues set up through dma-ctl
Good fitOne host, a few data streams, large buffersMany flows, tenants, VMs or small packets per queue

Two practical consequences fall out of the table. If your board is 7 series or UltraScale, the soft QDMA in PG302 is not an option; XDMA is. And if you need virtual functions for VM passthrough, XDMA cannot give them to you, however many channels you configure.

Versal is its own branch. Its PL PCIe guide, PG344, offers QDMA, an AXI bridge and an XDMA-style subsystem with four channels each way, so check the guide for your exact device rather than assuming either name carries over.

H2C and C2H: direction is named from the host

Every XDMA name is written from the host’s point of view. H2C, host-to-card, moves data from host memory to the FPGA: the engine reads host memory over PCIe and writes it to AXI. C2H, card-to-host, does the reverse. PG195 also labels them “H2C/Read” and “C2H/Write”, which describes what the engine does on PCIe, and confuses everyone exactly once.

The Linux driver maps this onto files. You write() to /dev/xdma0_h2c_0 to send and read() from /dev/xdma0_c2h_0 to receive. In memory-mapped mode, the file offset is the AXI address on the card. In stream mode there is no address. A C2H stream read then completes when the requested number of bytes arrives, or at end of packet if you ask for that. That single fact explains most C2H “hangs”.

The XDMA BAR layout, and what lspci should show

XDMA can expose up to three BARs: an AXI4-Lite master for your own registers, the DMA BAR holding the engine registers, and an optional DMA bypass BAR that maps straight onto an AXI4 memory-mapped master. Which BAR number each one gets depends on what you enabled:

Options enabled32-bit BARs64-bit BARs
DMA only (default)BAR0 = DMABAR0 = DMA
AXI4-Lite masterBAR0 = Lite, BAR1 = DMABAR0 = Lite, BAR2 = DMA
AXI4-Lite master and bypassBAR0 = Lite, BAR1 = DMA, BAR2 = bypassBAR0 = Lite, BAR2 = DMA, BAR4 = bypass
Bypass onlyBAR0 = DMA, BAR1 = bypassBAR0 = DMA, BAR2 = bypass

The Xilinx driver does not trust BAR numbers. It reads identifier registers in each candidate BAR and picks the one whose IRQ and config blocks identify themselves as XDMA. The BAR before it becomes /dev/xdma0_user and the BAR after it becomes /dev/xdma0_bypass. The DMA BAR becomes /dev/xdma0_control.

Inside the DMA BAR, bits 15:12 of the offset select a block: 0 for H2C channels, 1 for C2H channels, 2 for IRQ, 3 for config, 4 to 6 for the scatter-gather blocks and 8 for MSI-X. Bits 11:8 select the channel. Every block starts with an identifier whose top twelve bits read 0x1fc. That is your first smoke test.

How to run the XDMA driver on Linux

The driver lives in AMD’s dma_ip_drivers repository under XDMA/linux-kernel. Its readme lists x86_64 only and names the kernel series it was tested on, so check that list against your distribution first.

  1. Confirm the card enumerated.

    lspci -D -nnk -d 10ee:

    You want your vendor and device ID, such as [10ee:9038]. Nothing listed is an enumeration problem, not a driver problem: start with FPGA not detected in lspci.

  2. Check the BARs and the link before loading anything.

    sudo lspci -s 0000:01:00.0 -vv | grep -E "Region|LnkCap:|LnkSta:"

    Every BAR should have an address and a size, not disabled or unassigned; if one is missing, see PCIe BARs explained. To check BAR sizes and the link from raw bytes, paste sudo lspci -s 0000:01:00.0 -xxx into the PCI config space decoder. If the link is narrower than you built for, read XDMA stuck at x1 before you benchmark anything.

  3. Build and install the driver and tools.

    git clone https://github.com/Xilinx/dma_ip_drivers.git
    cd dma_ip_drivers/XDMA/linux-kernel/xdma
    sudo make install
    cd ../tools
    make

    You need the headers for the running kernel. If you changed the device ID in Vivado, add it to the pci_device_id table in xdma/xdma_mod.c first; the readme documents this. The driver only binds to IDs in that table.

  4. Load it and check which xdma you got.

    modinfo xdma | head -n 5
    sudo modprobe xdma
    ls /dev/xdma0_*
    lspci -s 0000:01:00.0 -k

    Since Linux 6.3, mainline also ships a module named xdma: a dmaengine platform driver used by other kernel drivers, with no /dev nodes. modinfo shows which file modprobe will load. If you get the wrong one, load the Xilinx build directly with sudo insmod xdma.ko from the driver directory, or use tests/load_driver.sh.

  5. Read an identifier register.

    # Still in XDMA/linux-kernel/tools
    sudo ./reg_rw /dev/xdma0_control 0x0000 w   # H2C channel 0
    sudo ./reg_rw /dev/xdma0_control 0x1000 w   # C2H channel 0

    Expect values starting 0x1fc0 and 0x1fc1. In the fourth hex digit, an 8 means that channel is AXI4-Stream and a 0 means memory-mapped. The repository’s own run_test.sh uses the same check. All ones, 0xffffffff, means the device is not answering; see unknown header type 7f.

  6. Run one small loopback transfer.

    dd if=/dev/urandom of=in.bin bs=4096 count=1
    sudo ./dma_to_device   -d /dev/xdma0_h2c_0 -a 0x0 -s 4096 -f in.bin
    sudo ./dma_from_device -d /dev/xdma0_c2h_0 -a 0x0 -s 4096 -f out.bin
    cmp in.bin out.bin && echo match

    This only means something on a memory-mapped design with memory at AXI address 0, such as the IP example design. For stream designs, use tests/run_test.sh with the example design, or a test pattern your own logic understands.

  7. Compare interrupt and poll mode once.

    sudo rmmod xdma
    sudo insmod ../xdma/xdma.ko poll_mode=1

    If transfers that timed out with interrupts succeed in poll mode, the engines work and your problem is interrupt delivery. The interrupt_mode parameter selects auto, MSI, legacy or MSI-X explicitly.

Why XDMA transfers time out

The character-device driver gives each transfer a deadline: the h2c_timeout and c2h_timeout module parameters, 10 seconds each by default. When one expires, the driver logs a “timed out” line and dumps the engine’s registers. The timeout is the messenger. These are the usual senders:

What you seeWhat it usually meansCheck next
A C2H read on a stream design waits about 10 s, then failsYour logic sent fewer bytes than requested, or the transfer was waiting for a length while your logic signalled end of packetRequest the exact length, or use dma_from_device -e so the transfer ends on TLAST; probe TVALID, TREADY and TLAST
A second program cannot open the C2H stream deviceStream C2H devices allow one open at a timeFind the process that still holds it
Every transfer times out, but register reads workCompletion interrupts are not reaching the driverRetry with poll_mode=1; check interrupt_mode and IOMMU faults in dmesg
A memory-mapped transfer stops with DECODE_ERR or SLAVE_ERR in the status dumpThe AXI address has no slave behind it, or the slave returned an errorCompare the -a address with your Vivado address map
Timeouts appear under load or after hours, with AER messagesThe PCIe link itself is retraining or erroringAER counters and link width, from the link-training guide
Register reads return 0xffffffffThe device stopped answering on PCIeLink-loss evidence, not driver settings

Resist two tempting fixes. Setting a timeout to 0 makes the driver wait forever, which turns a clear error into a hung process. Raising it only helps when you know the transfer is slow but progressing. AMD’s XDMA debug guide, Answer Record 71435, is the vendor’s own checklist for the IP side.

Annotated example: a C2H stream read that times out

$ sudo ./dma_from_device -d /dev/xdma0_c2h_0 -s 8192 -f out.bin
# ...about 10 seconds pass, then the read fails...

$ sudo dmesg | grep -E "timed out|\(id\)|SG engine"
xdma:xdma_xfer_submit: xfer 0x00000000c0ffee00,8192, s 0x1 timed out, ep 0x0.
#   ^ 8192 bytes requested; the engine never finished the transfer
xdma:engine_reg_dump: 0-C2H0-ST: ioread32(0x00000000deadbeef) = 0x1fc18006 (id).
#   ^ C2H (1fc1) channel 0; the 8 in the fourth digit = AXI4-Stream
xdma:engine_status_dump: SG engine 0-C2H0-ST status: 0x00000001: BUSY,
#   ^ still busy, no error flags: it is waiting for data, not failing
Synthetic example. Pointer values, sizes and the version byte are illustrative, and the driver prints more register lines than the filter shows. Message formats come from the driver source and can change between releases. Comments were added for this guide.

Busy with no error flags is the signature of an engine that is waiting. The user logic delivered less than 8 KiB before going quiet, or it marked the end of a shorter packet that the read was not set up to accept. Capture TVALID, TREADY and TLAST on the C2H stream interface with an ILA during the read. Then either make the logic produce the requested length or rerun with -e and size the buffer for your largest packet.

If you chose QDMA: what changes on Linux

The QDMA driver sits in the same repository under QDMA/linux-kernel and builds two modules, qdma-pf and qdma-vf. Nothing moves until you create a queue. The dma-ctl utility does that, and only then does a character device appear:

dma-ctl dev list
dma-ctl qdma01000 q add idx 0 mode mm dir h2c
dma-ctl qdma01000 q start idx 0 dir h2c
# A device such as /dev/qdma01000-MM-0 now accepts transfers
dma-ctl qdma01000 q dump idx 0 dir h2c

The device name encodes the PCI address, so yours will differ. The last command is the QDMA equivalent of reading an XDMA status register: it dumps that queue’s configuration, and with extra arguments its descriptor and completion ring entries. The DMA BAR is larger too; PG302 asks for 256 KB at BAR0 by default, with the AXI-Lite master at BAR2.

To understand what a queue context field means, rather than just its value, open the Spec Lab. It maps selected PG302 software-descriptor-context fields to their source sections and compares a working capture with a stalled one.

Questions

What is the difference between XDMA and QDMA?

XDMA (PG195) has up to four DMA channels in each direction, fixed when you generate the IP, and one PCIe function. QDMA (PG302) has up to 2,048 queues created by software at runtime, completion rings and SR-IOV with up to 252 virtual functions. XDMA is simpler to bring up; QDMA scales to many flows and VMs.

Is XDMA deprecated?

Not according to its product guide. PG195 was revised in July 2026 and contains no deprecation notice. It supports 7 series Gen2, Virtex-7 XT, UltraScale and UltraScale+ devices. Versal devices are covered by separate guides, such as PG344 for PL PCIe, which includes an XDMA-style subsystem.

Can I use QDMA on a 7 series or UltraScale FPGA?

Not the PG302 soft IP: its product guide covers UltraScale+ devices. On 7 series and UltraScale parts, XDMA is the AMD PCIe DMA subsystem to use.

Is there an XDMA driver in the mainline Linux kernel?

Yes, since Linux 6.3, but it is a different driver. The mainline xdma module is a dmaengine platform driver that other kernel drivers instantiate, and it creates no /dev/xdma0_* files. The character-device driver comes from AMD’s dma_ip_drivers repository. Both build a module called xdma, so check with modinfo xdma.

Why does my XDMA C2H read hang or time out?

On a stream design, the read waits until the requested number of bytes arrives, then gives up after c2h_timeout, 10 seconds by default. Make your logic send the exact length, or use dma_from_device -e to end the transfer on TLAST. If every transfer times out, compare with poll_mode=1 to test interrupt delivery.

What are /dev/xdma0_user and /dev/xdma0_control?

/dev/xdma0_user maps the AXI4-Lite master BAR, so reads and writes reach your own registers. /dev/xdma0_control maps the DMA BAR with the engine registers. The h2c and c2h nodes carry DMA data, and the events nodes deliver user interrupts.

Sources and scope

Source-based guide published by Header7F on 19 October 2026. Commands use placeholder addresses. Header7F has not reproduced every scenario on physical hardware, and no outcome is guaranteed.

KEEP READING

Message us