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 主要做两件事:
数据耗时:1M/2M 拷贝并打印耗时。
有效性测试:源缓冲前 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 time 和 DMA 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 usDMA mem-to-mem done: 1048576 bytes (src_dma=0x... dst_dma=0x...)
13.5.2. 看 CPU 占用¶
做一次较大、连续多次的拷贝,同时用 top 看进程 CPU。DMA 拷贝时 CPU 占用应很低;若改成纯软件 memcpy,CPU 会明显升高。