Migration Guide: QDMA to MDB5 DMA

arrow_back Software Docs

7. Code Migration Reference

This section provides side-by-side code comparisons between QDMA and MDB5 DMA implementations for key operations.

Note

The code snippets shown below highlight the core functionality to illustrate the similarities and differences between QDMA and MDB5 DMA implementations.

7.1 Application Code

7.1.1 Register Access (dma-ctl)

Both use mmap on /sys/bus/pci/devices/.../resource<BAR>. QDMA falls back to netlink if mmap fails.

QDMA (apps/dma-utils/dmactl_reg.c) MDB5 DMA (client-apps/dma-utils/dma_common_utils.c)
static int32_t reg_read_mmap(
    struct xnl_dev_info *dev_info,
    unsigned char barno,
    struct xcmd_info *xcmd)
{
    uint32_t *bar;
    char fname[256];
    int rv = 0;
    uint32_t attrs[XNL_ATTR_MAX] = {0};

    get_syspath_bar_mmap(fname,
        dev_info->pci_bus,
        dev_info->pci_dev,
        dev_info->dev_func, barno);

    bar = mmap_bar(fname,
        xcmd->req.reg.reg + 4, PROT_READ);
    if (!bar) {
        rv = xnl_common_msg_send(xcmd, attrs);
        return rv;
    }

    xcmd->req.reg.val =
        le32toh(bar[xcmd->req.reg.reg / 4]);
    munmap(bar, xcmd->req.reg.reg + 4);
    return rv;
}
int reg_read_mmap(struct mdb5_dma_reg *reg)
{
    uint32_t offset = reg->offset;
    uint32_t *bar;
    uint8_t bar_num = PCI_BAR0;
    char fname[256];
    char *bdf = reg->pci_bdf;
    int ret = 0;

    get_syspath_bar_mmap(fname,
        PCI_DOMAIN, bdf, bar_num);

    if (!is_file_available(fname)) {
        ret = -ENOENT;
        goto error;
    }

    bar = mmap_bar(fname,
        offset + REGISTER_SIZE, PROT_READ);
    if (!bar) {
        ret = -1;
        goto error;
    }

    reg->data = bar[offset / REGISTER_SIZE];
    munmap(bar, offset + REGISTER_SIZE);
error:
    return ret;
}

7.1.2 Queue/Channel Operations (dma-ctl)

QDMA uses netlink for queue management (add, start, stop, delete queues). In contrast, MDB5 DMA channels are always available and do not require explicit creation or destruction. The dma-ctl utility in MDB5 DMA provides various channel management capabilities including: configuring transfer modes (Simple or Scatter-Gather), setting aperture size for SG mode, displaying channel statistics, and reading/writing registers for debugging purposes.

QDMA (apps/dma-utils/dmactl.c) MDB5 DMA (client-apps/dma-ctl/dma_ctl.c)
int qdma_q_add(struct xcmd_info *cmd)
{
    uint32_t attrs[XNL_ATTR_MAX] = {0};
    return xnl_common_msg_send(cmd, attrs);
}

int qdma_q_start(struct xcmd_info *cmd)
{
    uint32_t attrs[XNL_ATTR_MAX] = {0};
    return xnl_common_msg_send(cmd, attrs);
}

int qdma_q_stop(struct xcmd_info *cmd)
{
    uint32_t attrs[XNL_ATTR_MAX] = {0};
    return xnl_common_msg_send(cmd, attrs);
}

int qdma_q_del(struct xcmd_info *cmd)
{
    uint32_t attrs[XNL_ATTR_MAX] = {0};
    return xnl_common_msg_send(cmd, attrs);
}
static int32_t (*process_fn[CMD_MAX + 1])(void *arg) = {
    mdb5_dma_reg_read,           /* CMD_REG_READ */
    mdb5_dma_reg_write,          /* CMD_REG_WRITE */
    mdb5_dma_stats_read,         /* CMD_STATS_READ */
    mdb5_dma_transfer_mode,      /* CMD_SET_TRANSFER_MODE */
    mdb5_dma_transfer_mode,      /* CMD_GET_TRANSFER_MODE */
    mdb5_dma_aperture_sz,        /* CMD_SET_APERTURE_MODE */
    mdb5_dma_aperture_sz,        /* CMD_GET_APERTURE_MODE */
    NULL
};

static int process_cmd(struct mdb5_dma_common *h)
{
    struct mdb5_dma_ioctl *hioc = &h->hioc;
    struct mdb5_dma_reg *reg = &h->reg;
    struct mdb5_dma_channel *chan = h->chan;
    void *arg;

    switch (h->op) {
    case CMD_REG_READ:
    case CMD_REG_WRITE:
        arg = (void *)reg;
        break;
    case CMD_STATS:
        hioc->h = h;
        memcpy(&hioc->cstats.name, &chan->name,
               MDB5_NODE_CHAN_SZ);
        arg = (void *)hioc;
        break;
    case CMD_SET_TRANSFER_MODE:
    case CMD_GET_TRANSFER_MODE:
        hioc->h = h;
        memcpy(&hioc->cmode.name, &chan->name,
               MDB5_NODE_CHAN_SZ);
        arg = (void *)hioc;
        break;
    case CMD_SET_APERTURE_MODE:
    case CMD_GET_APERTURE_MODE:
        hioc->h = h;
        memcpy(&hioc->caperture.name, &chan->name,
               MDB5_NODE_CHAN_SZ);
        arg = (void *)hioc;
        break;
    default:
        usage(stderr);
    }

    if (process_fn[h->op])
        return process_fn[h->op](arg);

    return -EOPNOTSUPP;
}

7.1.3 Channel/Queue Information Flow

Understanding how channel and queue information is passed through the application is crucial for migration:

QDMA Approach:

MDB5 DMA Approach:

QDMA (apps/dma-xfer/dmaxfer.c) MDB5 DMA (client-apps/dma-xfer/dma_xfer.c)
/* QDMA: Explicit queue management */

// 1. Add queue via netlink
qdma_prepare_q_add(&xcmd, dir, qid, pf);
qdma_q_add(&xcmd);

// 2. Start queue
qdma_q_start(&xcmd);

// 3. Open device node
sprintf(dev_path, "/dev/qdma%02x%03x-MM-%d",
        bus, dev_func, qid);
fd = open(dev_path, O_RDWR);

// 4. Perform transfers using fd
// ... (transfer operations)

// 5. Cleanup: stop and delete queue
qdma_q_stop(&xcmd);
qdma_q_del(&xcmd);
close(fd);
/* MDB5: Direct channel access */

// 1. Open pre-created channel device node
sprintf(dev_path, "/dev/mdb5_%s%02d",
        dir == TO_DEV ? "write" : "read",
        channel_id);
fd = open(dev_path, O_RDWR);

// 2. Optionally configure transfer mode
ctrl_fd = open("/dev/mdb5_ctrl", O_RDWR);
cmode.channel = channel_id;
cmode.mode = MDB5_MODE_SG; // or MDB5_MODE_SIMPLE
ioctl(ctrl_fd, IOCTL_MDB5_SET_TRANSFER_MODE, &cmode);
close(ctrl_fd);

// 3. Perform transfers using fd
// ... (transfer operations)

// 4. Cleanup
close(fd);

7.1.4 Data Transfer (dma-to-device / dma-from-device)

The core data transfer functions in both drivers use standard POSIX I/O syscalls. The channel/queue information is implicitly passed through the file descriptor obtained by opening the appropriate device node (e.g., /dev/qdma... or /dev/mdb5_write00). The offset parameter specifies the target address on the device.

QDMA uses lseek() followed by write(), while MDB5 uses the combined pwrite() call.

H2C Transfer (write_from_buffer)
QDMA (apps/dma-utils/dma_xfer_utils.c) MDB5 DMA (client-apps/dma-utils/dma_common_utils.c)
ssize_t write_from_buffer(char *fname,
    int fd, char *buffer,
    uint64_t size, uint64_t base)
{
    ssize_t rc;
    uint64_t count = 0;
    char *buf = buffer;
    off_t offset = base;

    do {
        uint64_t bytes = size - count;
        if (bytes > RW_MAX_SIZE)
            bytes = RW_MAX_SIZE;

        if (offset) {
            rc = lseek(fd, offset, SEEK_SET);
            if (rc != offset)
                return -EIO;
        }

        rc = write(fd, buf, bytes);
        if (rc < 0)
            return -EIO;

        count += bytes;
        buf += bytes;
        offset += bytes;
    } while (count < size);

    return count;
}
ssize_t write_from_buffer(char *fname,
    int fd, char *buffer,
    size_t size, size_t base)
{
    ssize_t rc;
    size_t count = 0;
    char *buf = buffer;
    off_t offset = (off_t) base;
    int ret;

    do {
        size_t bytes = size - count;
        if (bytes > RW_MAX_SIZE)
            bytes = RW_MAX_SIZE;

        rc = pwrite(fd, buf, bytes, offset);
        if (rc < 0) {
            ret = -errno;
            goto error;
        }

        count += bytes;
        buf += bytes;
        offset += bytes;
    } while (count < size);

    return count;
error:
    return ret;
}
C2H Transfer (read_to_buffer)

Similarly, for reading data from device to host, both use POSIX read syscalls:

QDMA (apps/dma-utils/dma_xfer_utils.c) MDB5 DMA (client-apps/dma-utils/dma_common_utils.c)
ssize_t read_to_buffer(char *fname,
    int fd, char *buffer,
    uint64_t size, uint64_t base)
{
    ssize_t rc;
    uint64_t count = 0;
    char *buf = buffer;
    off_t offset = base;

    do {
        uint64_t bytes = size - count;
        if (bytes > RW_MAX_SIZE)
            bytes = RW_MAX_SIZE;

        if (offset) {
            rc = lseek(fd, offset, SEEK_SET);
            if (rc != offset)
                return -EIO;
        }

        rc = read(fd, buf, bytes);
        if (rc < 0)
            return -EIO;

        count += bytes;
        buf += bytes;
        offset += bytes;
    } while (count < size);

    return count;
}
ssize_t read_to_buffer(char *fname,
    int fd, char *buffer,
    size_t size, size_t base)
{
    ssize_t rc;
    size_t count = 0;
    char *buf = buffer;
    off_t offset = (off_t) base;
    int ret;

    do {
        size_t bytes = size - count;
        if (bytes > RW_MAX_SIZE)
            bytes = RW_MAX_SIZE;

        rc = pread(fd, buf, bytes, offset);
        if (rc < 0) {
            ret = -errno;
            goto error;
        }

        count += bytes;
        buf += bytes;
        offset += bytes;
    } while (count < size);

    return count;
error:
    return ret;
}

7.1.5 dma-xfer Tool

Both dma-xfer implementations share the same configuration file format and overall structure: parsing key=value pairs, validating parameters, setting up channels/queues, and executing transfers. The main difference lies in resource lifecycle management.

QDMA Approach:

MDB5 DMA Approach:

Queue/Channel Setup and Transfer Execution
QDMA (apps/dma-xfer/dmaxfer.c) MDB5 DMA (client-apps/dma-xfer/dma_xfer.c)
int main(int argc, char *argv[])
{
    struct queue_info *q_info;
    int q_count;

    /* 1. Parse and validate config */
    parse_config_file(cfg_fname);
    qdma_validate_qrange();

    /* 2. Create queues via netlink */
    q_count = qdma_setup_queues(&q_info);
    // Internally calls:
    //   qdma_prepare_q_add() -> qdma_q_add()
    //   qdma_prepare_q_start() -> qdma_q_start()

    /* 3. Register cleanup handler */
    atexit(qdma_env_cleanup);
    // Will call qdma_q_stop() and qdma_q_del()
    // for each queue on exit

    /* 4. Perform transfers */
    qdmautils_xfer(q_info, q_count, io_type);
    // Opens /dev/qdma<BDF>-MM-<qid>
    // Calls write_from_buffer() or read_to_buffer()

    return 0;
}

static int qdma_setup_queues(
    struct queue_info **pq_info)
{
    struct queue_info *q_info;
    struct xcmd_info xcmd;

    q_info = calloc(q_count,
                    sizeof(struct queue_info));

    for (qid = 0; qid < num_q; qid++) {
        /* Prepare queue via netlink */
        qdma_prepare_q_add(&xcmd, dir, qid, pf);
        qdma_q_add(&xcmd);

        qdma_prepare_q_start(&xcmd, dir, qid, pf);
        qdma_q_start(&xcmd);

        /* Store queue info */
        qdma_q_prep_name(q_info, qid, pf);
        q_info->qid = qid;
        q_info->dir = dir;
    }
    return q_count;
}
int main(int argc, char *argv[])
{
    struct mdb5_dma_common h;
    struct mdb5_dma_channel chan[max_chan];
    int ch_count = 0;

    /* 1. Parse and validate config */
    parse_config_file(cfg_fname, &h);
    validate_input_params(&h, cfg_fname);

    /* 2. Configure channels (no creation needed) */
    ch_count = mdb5_dma_config_channels(&h, chan);
    // Opens existing /dev/mdb5_write<NN> or
    // /dev/mdb5_read<NN> nodes
    // Sets transfer mode via ioctl

    /* 3. Perform transfers */
    mdb5_dmautils_xfer(&h, ch_count);
    // Calls mdb5_dmautils_write() or
    // mdb5_dmautils_read()
    // Using write_from_buffer() or read_to_buffer()

    /* 4. No cleanup needed - channels persist */
    return 0;
}

static int mdb5_dma_config_channels(
    struct mdb5_dma_common *h,
    struct mdb5_dma_channel *chan)
{
    struct mdb5_dma_ioctl *hioc = &h->hioc;

    /* Open control device */
    prepare_node_name(h->ctrl_name, "ctrl", 0);

    /* Configure write channels */
    for (i = 0; i < num_wr_chan; i++) {
        chan[i].ch_id = i;
        chan[i].dir = MDB5_CHAN_DIR_TO_DEV;

        /* Open existing device node */
        prepare_node_name(chan[i].name, "write", i);

        /* Set transfer mode via ioctl */
        memcpy(&hioc->cmode.name, &chan[i].name,
               MDB5_NODE_CHAN_SZ);
        mdb5_dma_transfer_mode((void *)hioc);
    }

    /* Configure read channels similarly */
    for (i = 0; i < num_rd_chan; i++) {
        /* Same pattern for read channels */
    }
    return ch_count;
}