跳到主要内容

RGA how to use DMA buffer

DMA buffer

1. 为什么需要DMA buffer?

1.1 硬件直接内存访问(DMA)需求

  • RGA是一个硬件加速器,不是纯软件库.
  • 硬件模块需要直接访问物理内存,而不经过CPU
  • DMA内存提供了硬件可识别的物理连续内存块 避免内存拷贝
// 传统方式:需要CPU参与拷贝
CPU: 应用内存 → 临时缓冲区 → 输出内存
// DMA方式:硬件直接处理
RGA硬件: DMA内存 → 直接处理 → 输出结果

1.2 性能优化

零拷贝架构

  • 消除CPU在内存间的数据搬运
  • 减少内存带宽占用
  • 降低CPU负载,提高并行性

缓存一致性

// 使用非缓存DMA内存(UNCACHED)
DMA_HEAP_DMA32_UNCACHED_PATH
  • 避免CPU缓存与硬件访问之间的同步问题
  • 确保RGA硬件看到的是最新数据

1.3 内存特性要求

物理连续性

  • RGA硬件需要物理上连续的内存块
  • 普通malloc分配的内存可能是虚拟连续但物理分散的
  • DMA分配器保证物理内存的连续性

4G地址空间限制

// 分配在4G地址空间内
DMA_HEAP_DMA32_UNCACHED_PATH
  • 某些硬件只能访问32位地址空间
  • 确保RGA硬件能够直接寻址

1.4 系统架构优势

内存管理单元(MMU)绕过

  • DMA允许硬件直接访问物理地址
  • 避免经过MMU的地址转换开销

进程间共享

  • DMA缓冲区通过文件描述符(dma_fd)共享
  • 多个进程或驱动可以访问同一块物理内存

2. 如何分配DMA内存?

2.1 DMA_HEAP

先确保系统里有DMA_HEAP节点, 如下:

linaro@linaro-alip:~$ ls /dev/dma_heap/system-uncached-dma32
/dev/dma_heap/system-uncached-dma32

2.2 dma_buf_alloc

调用Rockchip提供的API dma_buf_alloc, 如下代码申请DMA内存:

/*
* Allocate dma_buf within 4G from dma32_heap,
* return dma_fd and virtual address.
*/
ret = dma_buf_alloc(DMA_HEAP_DMA32_UNCACHED_PATH, dst_buf_size, &dst_dma_fd, (void **)&dst_buf);
if (ret < 0) {
printf("alloc dma32_heap buffer failed!\n");
return -1;
}

实际上 dma_buf_alloc实现如下:

int dma_buf_alloc(const char *path, size_t size, int *fd, void **va) {
int ret;
int prot;
void *mmap_va;
int dma_heap_fd = -1;
struct dma_heap_allocation_data buf_data;

/* open dma_heap fd */
dma_heap_fd = open(path, O_RDWR);
if (dma_heap_fd < 0) {
printf("open %s fail!\n", path);
return dma_heap_fd;
}

/* alloc buffer */
memset(&buf_data, 0x0, sizeof(struct dma_heap_allocation_data));

buf_data.len = size;
buf_data.fd_flags = O_CLOEXEC | O_RDWR;
ret = ioctl(dma_heap_fd, DMA_HEAP_IOCTL_ALLOC, &buf_data);
if (ret < 0) {
printf("RK_DMA_HEAP_ALLOC_BUFFER failed\n");
return ret;
}

/* mmap va */
if (fcntl(buf_data.fd, F_GETFL) & O_RDWR)
prot = PROT_READ | PROT_WRITE;
else
prot = PROT_READ;

/* mmap contiguors buffer to user */
mmap_va = (void *)mmap(NULL, buf_data.len, prot, MAP_SHARED, buf_data.fd, 0);
if (mmap_va == MAP_FAILED) {
printf("mmap failed: %s\n", strerror(errno));
return -errno;
}

*va = mmap_va;
*fd = buf_data.fd;

close(dma_heap_fd);

return 0;
}

3 RGA 如何使用DMA buffer

RGA通过DMA buffer申请时返回的handle即可, 如下:

/*
* Import the allocated dma_fd into RGA by calling
* importbuffer_fd, and use the returned buffer_handle
* to call RGA to process the image.
*/
dst_handle = importbuffer_fd(dst_dma_fd, dst_buf_size);
if (dst_handle == 0) {
printf("import dma_fd error!\n");
ret = -1;
goto free_buf;
}

dst = wrapbuffer_handle(dst_handle, dst_width, dst_height, dst_format);

dst_rect.x = 0;
dst_rect.y = 0;
dst_rect.width = 300;
dst_rect.height = 200;

ret = imcheck({}, dst, {}, dst_rect, IM_COLOR_FILL);
if (IM_STATUS_NOERROR != ret) {
printf("%d, check error! %s", __LINE__, imStrError((IM_STATUS)ret));
goto release_buffer;
}

ts = get_cur_us();

ret = imfill(dst, dst_rect, 0xff00ff00);
if (ret == IM_STATUS_SUCCESS) {
printf("%s running success! cost %ld us\n", LOG_TAG, get_cur_us() - ts);
} else {
printf("%s running failed, %s\n", LOG_TAG, imStrError((IM_STATUS)ret));
goto release_buffer;
}

5. 完整demo代码

此demo是Rockchip github里的sampleRGA sample, 下面是完整的demo代码:

#define LOG_NDEBUG 0
#undef LOG_TAG
#define LOG_TAG "rga_allocator_dma32_demo"

#include <iostream>
#include <fstream>
#include <sstream>
#include <cstddef>
#include <cmath>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <unistd.h>

#include "im2d.h"
#include "RgaUtils.h"

#include "utils.h"
#include "dma_alloc.h"

#define LOCAL_FILE_PATH "/data"

int main(void) {
int ret = 0;
int64_t ts;
int dst_width, dst_height, dst_format;
int dst_buf_size;
char *dst_buf;
int dst_dma_fd;
rga_buffer_t dst = {};
im_rect dst_rect = {};
rga_buffer_handle_t dst_handle;

dst_width = 1280;
dst_height = 720;
dst_format = RK_FORMAT_RGBA_8888;

dst_buf_size = dst_width * dst_height * get_bpp_from_format(dst_format);

/*
* Allocate dma_buf within 4G from dma32_heap,
* return dma_fd and virtual address.
*/
ret = dma_buf_alloc(DMA_HEAP_DMA32_UNCACHED_PATH, dst_buf_size, &dst_dma_fd, (void **)&dst_buf);
if (ret < 0) {
printf("alloc dma32_heap buffer failed!\n");
return -1;
}

memset(dst_buf, 0x33, dst_buf_size);

/*
* Import the allocated dma_fd into RGA by calling
* importbuffer_fd, and use the returned buffer_handle
* to call RGA to process the image.
*/
dst_handle = importbuffer_fd(dst_dma_fd, dst_buf_size);
if (dst_handle == 0) {
printf("import dma_fd error!\n");
ret = -1;
goto free_buf;
}

dst = wrapbuffer_handle(dst_handle, dst_width, dst_height, dst_format);

dst_rect.x = 0;
dst_rect.y = 0;
dst_rect.width = 300;
dst_rect.height = 200;

ret = imcheck({}, dst, {}, dst_rect, IM_COLOR_FILL);
if (IM_STATUS_NOERROR != ret) {
printf("%d, check error! %s", __LINE__, imStrError((IM_STATUS)ret));
goto release_buffer;
}

ts = get_cur_us();

ret = imfill(dst, dst_rect, 0xff00ff00);
if (ret == IM_STATUS_SUCCESS) {
printf("%s running success! cost %ld us\n", LOG_TAG, get_cur_us() - ts);
} else {
printf("%s running failed, %s\n", LOG_TAG, imStrError((IM_STATUS)ret));
goto release_buffer;
}

printf("output [0x%x, 0x%x, 0x%x, 0x%x]\n", dst_buf[0], dst_buf[1], dst_buf[2], dst_buf[3]);
write_image_to_file(dst_buf, LOCAL_FILE_PATH, dst_width, dst_height, dst_format, 0);

release_buffer:
if (dst_handle > 0)
releasebuffer_handle(dst_handle);

free_buf:
dma_buf_free(dst_buf_size, &dst_dma_fd, dst_buf);

return 0;
}

6. 编译及运行

在Neardi LKD3588设备上面clone RGA sample代码, 更改如下编译脚本:

diff --git a/toolchains/toolchain_linux.cmake b/toolchains/toolchain_linux.cmake
index f446557..b65e8b2 100644
--- a/toolchains/toolchain_linux.cmake
+++ b/toolchains/toolchain_linux.cmake
@@ -1,15 +1,15 @@
-SET(TOOLCHAIN_HOME "/home/yqw/workspace/linux/common/prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu")
-SET(TOOLCHAIN_NAME "aarch64-rockchip1031-linux-gnu")
+#SET(TOOLCHAIN_HOME "/home/yqw/workspace/linux/common/prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu")
+SET(TOOLCHAIN_NAME "aarch64-linux-gnu")

# this is required
#SET(CMAKE_SYSTEM_NAME Linux)

# specify the cross compiler
-SET(CMAKE_C_COMPILER ${TOOLCHAIN_HOME}/bin/${TOOLCHAIN_NAME}-gcc)
-SET(CMAKE_CXX_COMPILER ${TOOLCHAIN_HOME}/bin/${TOOLCHAIN_NAME}-g++)
+SET(CMAKE_C_COMPILER /usr/bin/${TOOLCHAIN_NAME}-gcc)
+SET(CMAKE_CXX_COMPILER /usr/bin/${TOOLCHAIN_NAME}-g++)

# where is the target environment
-SET(CMAKE_FIND_ROOT_PATH ${TOOLCHAIN_HOME})
+#SET(CMAKE_FIND_ROOT_PATH ${TOOLCHAIN_HOME})

# search for programs in the build host directories (not necessary)
SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)

之后执行如下命令编译:

linaro@linaro-alip:~/librga/samples/allocator_demo$ ./cmake-linux.sh
~/librga/samples/allocator_demo/build/build_linux ~/librga/samples/allocator_demo
load /home/linaro/librga/samples/allocator_demo/../../toolchains/toolchain_linux.cmake
-- The C compiler identification is GNU 12.2.0
-- The CXX compiler identification is GNU 12.2.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/aarch64-linux-gnu-gcc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/aarch64-linux-gnu-g++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/linaro/librga/samples/allocator_demo/build/build_linux
[ 13%] Building CXX object CMakeFiles/utils_obj.dir/home/linaro/librga/samples/utils/allocator/dma_alloc.cpp.o
...

最后, 运行demo, 如下:

linaro@linaro-alip:~/librga/samples/allocator_demo/build/build_linux/install/bin$ ./rga_allocator_dma32_demo
rga_api version 1.10.1_[10]
rga_allocator_dma32_demo running success! cost 323 us
output [0x0, 0xff, 0x0, 0xff]
open /data/out0w1280-h720-rgba8888.bin and write ok

也可以查看每一块DMA内存分配的大小, 如下�路径:

linaro@linaro-alip:/sys/kernel/dmabuf/buffers$ ls
1 16 17 18 19 2 20 21 22 23 24 25 26 27 28 3 35 4 5 6 7 8 9
linaro@linaro-alip:/sys/kernel/dmabuf/buffers$ cd 35
linaro@linaro-alip:/sys/kernel/dmabuf/buffers/35$ ls
exporter_name size
linaro@linaro-alip:/sys/kernel/dmabuf/buffers/35$ cat size
3686400
linaro@linaro-alip:/sys/kernel/dmabuf/buffers/35$ cat exporter_name
system-uncached-dma32