13. DMA 操作指南

本文档说明在 CV184x 平台上通过 /dev/dma_memcpy 字符设备进行 DMA 内存到内存拷贝的配置、编译、加载及用户态测试方法。

13.1. dev-to-mem操作指南

使用 DMA 内存到内存拷贝字符设备前需满足以下条件:

13.1.1. 内核要求

  • 使用官方发布的 SDK 编译后的内核镜像,并按下文 mem-to-mem操作指南 中步骤完成内核配置与编译,将 DMA 内存拷贝字符设备编为内核模块(dma-memcpy-dev.ko )。

13.1.2. 驱动说明

  • 驱动为 misc 字符设备,设备节点为 /dev/dma_memcpy (加载模块后由 misc 子系统自动创建)。

  • 驱动会分配 两倍 dma_memcpy_buf_size 的 DMA 连续内存;嵌入式板卡内存有限,请勿dma_memcpy_buf_size 设为 3MB 以免 OOM。默认 1MB 即可;若需测试约 2MB 传输,可尝试模块参数 dma_memcpy_buf_size=2097152

13.2. mem-to-mem操作指南

13.2.2. 编译内核(含模块)

# 同上 source + defconfig 后
build_kernel

模块会随内核一起编出。

模块位于 linux_5.10/<out_dir>/drivers/misc/dma-memcpy-dev.ko ,或从 build 的 modules 安装目录拷贝。

13.2.3. 加载与设备节点

  • 加载insmod dma-memcpy-dev.ko建议不传参数,使用默认 1MB 缓冲区)。

  • 设备节点/dev/dma_memcpy

  • 注意:勿使用 dma_memcpy_buf_size=3145728 (3MB),易 OOM;若需测 2M,可尝试 dma_memcpy_buf_size=2097152 ,失败则用默认 1MB。

13.3. 用户态测试

13.3.1. 完整测试代码

示例程序 dma_memcpy_test.c 完整源码如下:

  1/*
  2 * DMA mem-to-mem test: 数据耗时 + 有效性测试
  3 * 1) 数据耗时: 1M / 2M 拷贝并打印耗时
  4 * 2) 有效性测试: 源缓冲前 4 字节写 0x12345678,拷贝后检查目的前 4 字节
  5 *
  6 * 嵌入式板子内存紧张,请用默认 1MB 加载模块,勿设 3MB 以免 OOM:
  7 *   insmod dma-memcpy-dev.ko
  8 * 测 2M 可尝试: insmod dma-memcpy-dev.ko dma_memcpy_buf_size=2097152 (仍可能 OOM)
  9 */
 10
 11#include <stdio.h>
 12#include <stdlib.h>
 13#include <stdint.h>
 14#include <string.h>
 15#include <fcntl.h>
 16#include <unistd.h>
 17#include <sys/ioctl.h>
 18#include <sys/time.h>
 19#include <errno.h>
 20
 21#define DMA_MEMCPY_DEV_PATH  "/dev/dma_memcpy"
 22#define DMA_MEMCPY_IOC_MAGIC 'D'
 23#define DMA_MEMCPY_IOC_TRANSFER      _IOWR(DMA_MEMCPY_IOC_MAGIC, 1, struct dma_memcpy_transfer)
 24
 25/* 与内核驱动一致 */
 26struct dma_memcpy_transfer {
 27     unsigned long long src;
 28     unsigned long long dst;
 29     unsigned long long len;
 30     int status;
 31};
 32
 33#define LEN_1M       (1024 * 1024)
 34#define LEN_2M       (2 * 1024 * 1024)
 35#define LEN_3M       (3 * 1024 * 1024)
 36#define VALIDITY_MAGIC       0x12345678
 37
 38static int do_one_copy(int fd, void *src, void *dst, size_t len)
 39{
 40     struct dma_memcpy_transfer xfer;
 41     struct timeval tv0, tv1;
 42     long us;
 43
 44     printf("Setup DMA memcpy: src=%p, dst=%p, len=%zu\n", src, dst, len);
 45
 46     xfer.src = (unsigned long long)(uintptr_t)src;
 47     xfer.dst = (unsigned long long)(uintptr_t)dst;
 48     xfer.len = (unsigned long long)len;
 49     xfer.status = 0;
 50
 51     gettimeofday(&tv0, NULL);
 52     if (ioctl(fd, DMA_MEMCPY_IOC_TRANSFER, &xfer) < 0) {
 53             perror("ioctl");
 54             return -1;
 55     }
 56     gettimeofday(&tv1, NULL);
 57     us = (tv1.tv_sec - tv0.tv_sec) * 1000000 + (tv1.tv_usec - tv0.tv_usec);
 58
 59     if (xfer.status != 0) {
 60             fprintf(stderr, "Transfer failed: status=%d (len > driver buf_size?)\n", xfer.status);
 61             return -1;
 62     }
 63     printf("dma_memcpy cost time = %ld us\n", us);
 64     return 0;
 65}
 66
 67int main(int argc, char **argv)
 68{
 69     int fd;
 70     void *src = NULL;
 71     void *dst = NULL;
 72     size_t max_len = LEN_3M; /* 尽量 3M,便于和参考测试一致 */
 73     int ret = 0;
 74
 75     (void)argc;
 76     (void)argv;
 77
 78     fd = open(DMA_MEMCPY_DEV_PATH, O_RDWR);
 79     if (fd < 0) {
 80             perror("open " DMA_MEMCPY_DEV_PATH);
 81             fprintf(stderr, "请先加载模块: insmod dma_memcpy_dev.ko\n");
 82             return 1;
 83     }
 84
 85     src = malloc(max_len);
 86     dst = malloc(max_len);
 87     if (!src || !dst) {
 88             perror("malloc");
 89             ret = 1;
 90             goto out;
 91     }
 92
 93     /* ---- 1、数据耗时 ---- */
 94     printf("1、数据耗时:\n");
 95
 96     memset(src, 0x5a, max_len);
 97     memset(dst, 0, max_len);
 98
 99     if (do_one_copy(fd, src, dst, LEN_1M) < 0) {
100             ret = 1;
101             goto out;
102     }
103     if (do_one_copy(fd, src, dst, LEN_2M) < 0) {
104             fprintf(stderr, "(若 2M 失败,可尝试: insmod dma_memcpy_dev.ko dma_memcpy_buf_size=2097152)\n");
105             /* 不直接退出,继续有效性测试 */
106     }
107
108     /* ---- 2、有效性测试 ---- */
109     printf("2、有效性测试:\n");
110
111     memset(src, 0xff, max_len);
112     memset(dst, 0xff, max_len);
113     *(uint32_t *)src = (uint32_t)VALIDITY_MAGIC;
114
115     /* 优先 3M,否则 1M */
116     if (do_one_copy(fd, src, dst, LEN_3M) < 0) {
117             if (do_one_copy(fd, src, dst, LEN_1M) < 0) {
118                     ret = 1;
119                     goto out;
120             }
121     }
122
123     if (*(uint32_t *)dst == (uint32_t)VALIDITY_MAGIC)
124             printf("Validity OK: dst[0..3] = 0x%08x (expected 0x%08x)\n",
125                    *(uint32_t *)dst, VALIDITY_MAGIC);
126     else {
127             printf("Validity FAIL: dst[0..3] = 0x%08x (expected 0x%08x)\n",
128                    *(uint32_t *)dst, VALIDITY_MAGIC);
129             ret = 1;
130     }
131
132out:
133     if (src) free(src);
134     if (dst) free(dst);
135     close(fd);
136     return ret;
137}

13.3.2. 编译说明

在主机上使用 cvitek 交叉编译链、静态链接生成可在板子上运行的二进制(无需板子上的动态库)。将上文中的源码保存为 dma_memcpy_test.c 后执行:

arm-none-linux-uclibcgnueabihf-gcc -o dma_memcpy_test dma_memcpy_test.c -static

将生成的 dma_memcpy_test 拷到板子(如 /mnt/sd/ )后执行:

./dma_memcpy_test

13.3.3. 测试内容说明

dma_memcpy_test.c 主要做两件事:

  1. 数据耗时:1M/2M 拷贝并打印耗时。

  2. 有效性测试:源缓冲前 4 字节写 0x12345678 ,拷贝后检查目的缓冲前 4 字节是否一致。

13.4. ioctl 接口

  • 命令DMA_MEMCPY_IOC_TRANSFER

  • 参数struct dma_memcpy_transfer { src, dst, len; status; } - src / dst :用户空间源/目的地址 - len :拷贝长度(不超过模块参数 dma_memcpy_buf_size ) - status :返回 0 成功,负数为 errno

数据路径:用户 src → 内核缓冲 → DMA 拷贝 → 内核缓冲 → 用户 dst。

13.5. 如何验证为 DMA mem-to-mem

13.5.1. 看内核日志

驱动在每次 DMA 完成后会打印 dma_memcpy cost timeDMA mem-to-mem done 。在板子上按顺序执行:

dmesg -c
./dma_memcpy_test
dmesg | grep -i dma

若看到类似下面输出,说明走的是硬件 DMA mem-to-mem,且 src_dma/dst_dma 为 DMA 总线地址:

  • dma_memcpy cost time = xxxx us

  • DMA mem-to-mem done: 1048576 bytes (src_dma=0x... dst_dma=0x...)

13.5.2. 看 CPU 占用

做一次较大、连续多次的拷贝,同时用 top 看进程 CPU。DMA 拷贝时 CPU 占用应很低;若改成纯软件 memcpy,CPU 会明显升高。