编辑
2025-03-03
记录知识
0
请注意,本文编写于 116 天前,最后修改于 59 天前,其中某些信息可能已经过时。

目录

一:问题现象
二:分析
2.1 根据日志简单解析
2.2 跟踪ffplay代码
2.3 基本推断
2.4 跟踪代码
2.5 运行测试
2.6 rga的dest buffer
2.7 权限
三:自测结果

RK平台有一个2D加速器,名字叫做RGA,其主要用来做图片处理的加速,在适配RK3588平台的过程中,有发现一些主板上播放适配卡顿或者不播放。从而定位到是RGA的MMU无法访问4G以上的内存地址空间导致。经排查,通过在ffmpeg中申请4G以内的dma buffer来绕过RGA的硬件缺陷,本文章分享此问题的解题过程

一:问题现象

在使用ffplay播放视频的时候,会出现如下问题

[ 719.046159] rga2 fdb80000.rga: swiotlb buffer is full (sz: 278528 bytes), total 32768 (slots), used 591 (slots) [ 719.047045] rga_dma_buf: Failed to map src attachment [ 719.047491] rga_mm: rga_mm_map_dma_buffer core[4] map dma buffer error! [ 719.048090] rga_mm: rga_mm_map_buffer map dma_buf error! [ 719.048556] rga_mm: job buffer map failed! [ 719.048914] rga_mm: src channel map job buffer failed! [ 719.048915] rga_job: rga_job_run: failed to map buffer [ 719.049996] rga_job: some error on rga_job_run before hw start, rga_job_next(343) [ 719.050652] rga_job: some error on job, rga_job_commit(668) [ 719.051140] rga_job: failed to commit job! [ 719.051509] rga: rga_request_commit failed

将内核升级后,得到如下错误

Nov 24 14:14:35 Kylin kernel: [85438.014740] rga_mm: RGA_MMU unsupported Memory larger than 4G! Nov 24 14:14:35 Kylin kernel: [85438.014772] rga_mm: scheduler core[4] unsupported mm_flag[0x0]! Nov 24 14:14:35 Kylin kernel: [85438.014848] rga_mm: rga_mm_map_buffer iommu_map virtual address error! Nov 24 14:14:35 Kylin kernel: [85438.014859] rga_mm: job buffer map failed! Nov 24 14:14:35 Kylin kernel: [85438.014869] rga_mm: dst channel map job buffer failed! Nov 24 14:14:35 Kylin kernel: [85438.014878] rga_mm: failed to map buffer Nov 24 14:14:35 Kylin kernel: [85438.014894] rga_job: rga_job_commit: failed to map job info Nov 24 14:14:35 Kylin kernel: [85438.014969] rga_job: request[1] task[0] job_commit failed. Nov 24 14:14:35 Kylin kernel: [85438.014981] rga_job: rga request commit failed! Nov 24 14:14:35 Kylin kernel: [85438.014992] rga: request[1] submit failed!

从而导致3588上播放视频性能低下

二:分析

2.1 根据日志简单解析

从日志 Failed to map src attachment 可以看定位代码地址

drivers/video/rockchip/rga3/rga_dma_buf.c
sgt = dma_buf_map_attachment(attach, dir); if (IS_ERR(sgt)) { pr_err("Failed to map src attachment\n"); ret = -EINVAL; goto err_get_sgt; }

并且通过日志 RGA_MMU unsupported Memory larger than 4G 可以发现RGA MMU确实存在此硬件问题

2.2 跟踪ffplay代码

ffplay为ffmpeg的播放测试程序,视频经过ffplay播放,会通过libavcodec做真正的硬解码动作,其代码位置为 libavcodec/rkmppdec.c

经过定位

if (avctx->pix_fmt != AV_PIX_FMT_DRM_PRIME) { ret = rkmpp_convert_frame(avctx, frame, mppframe, buffer); goto out; }

这里判断了pix fmt类型,如果不是DRM_PRIME类型,则主动通过mpp进行帧的转换

avctx->pix_fmt = ff_get_format(avctx, avctx->codec->pix_fmts);

帧格式通过ffmpeg的api获取

这里默认会拿到YCbCr_420_SP的视频格式,但是AV_PIX_FMT_DRM_PRIME默认格式为RK_FORMAT_YCbCr_420_P。所以视频过来的每一帧都需要进行 420sp-→420p的格式转换

这个转换通过两种方式,如果rga不支持,则通过软件转换,如下

for (i = 0; i < frame->height / 2; i++) { for (j = 0; j < frame->width; j++) { dst_u[j] = src[2 * j + 0]; dst_v[j] = src[2 * j + 1]; } dst_u += u_pitch; dst_v += v_pitch; src += hstride; }

如果rga支持,则通过rga的api 加速转换

c_RkRgaBlit(&src_info, &dst_info, NULL)

2.3 基本推断

从上面的信息可以判断,rgaBlit的失败,会导致内核出错,并且每一帧的视频输出格式都通过cpu来计算,从而导致播放卡顿

根据c_RkRgaBlit 的两个参数,结合/usr/include/rga/drmrga.h 的struct rga_info 定义,可以看到 void *virAddr 是rga内部使用的内存地址

根据分析rga源码,从而得到如下为rga内部使用的内存地址

src_info.fd = mpp_buffer_get_fd(buffer); dst_info.virAddr = dst_y;

2.4 跟踪代码

跟踪mpp的代码可以知道,mpp的buffer通过如下申请

ret = mpp_buffer_group_get_internal(&decoder->frame_group, MPP_BUFFER_TYPE_DRM);

获取mpp源码 mpp_1.5.0-1.tar.gz 分析代码,可以发现

osal/linux/os_allocator.cMPP_RET os_allocator_get(os_allocator *api, MppBufferType type) 作为所有的mpp的buffer的申请器

其支持的申请方式如下:

MPP_BUFFER_TYPE_NORMAL malloc申请 MPP_BUFFER_TYPE_ION android的ion申请 MPP_BUFFER_TYPE_DRM 通过drm设备"/dev/dri/card0"申请

这里可以发现,当前版本的mpp内存申请方式,都没有对内存的实际地址进行判断,也就是有可能在rga的mmu访问的时候,实际内存可能大于4G内存

而在内核内,有申请dma32的堆的驱动drivers/dma-buf/heaps/

打开 DMABUF_HEAPS_SYSTEM 重编译内核,替换内核后重启,可以看到dma的申请设备

/dev/dma_heap/system /dev/dma_heap/system-uncached /dev/dma_heap/system-dma32 /dev/dma_heap/system-uncached-dma32

对应的,要使用dma32,可以借鉴rk的使用例子。代码在linux-rga源码

samples/im2d_slt/sources/dma_alloc.cpp

通过合入patch的方式,可以为mpp增加一个dma heap的内存申请方式。可以参考commit 如下

https://gitlab2.kylin.com/shanghai-team/mpp/-/commit/3ed4d85d6ba174575e7bd0fe0cc6b29f4d54c9ee

2.5 运行测试

将支持dma heap申请的mpp编译后,安装在系统上,进入调试状态,

export mpp_rt_debug=1 && ffplay /data/bbb_sunflower_2160p_60fps_normal.mp4

在另一个窗口监听syslog 如下

tail -f /var/log/syslog | grep mpp_rt

通过日志可以发现,在播放视频时,mpp这边的buffer已经正常的通过heap的方式申请

Nov 24 16:52:38 Kylin mpp[999038]: mpp_rt: use dma heap allocator

通过sysfs也可以看到dma buffer已经申请成功

cat /sys/kernel/debug/dma_buf/bufinfo 04177920 00000002 00080007 00000002 system-uncached-dma32 03377282 1002674-ffplay Attached Devices: fdc38100.rkvdec-core Total 1 devices attached

但是MMU 4G的问题还是存在

2.6 rga的dest buffer

至此mpp buffer这边已经正常的通过dma去申请了。而rga这边的buffer还是有问题的,继续翻阅ffmpeg的代码,可以找到

rkmppdec.c if (avctx->pix_fmt != AV_PIX_FMT_DRM_PRIME) { ret = ff_get_buffer(avctx, frame, 0); if (ret < 0) goto out; }

可以发现,rga的dest buffer通过ffmpeg的标准接口申请,跟踪代码可知,内存的申请通过av_malloc

故,为了解决rga这边的内存问题,需要也通过dma去申请

申请如下:

ret = ioctl(buf->fd, DMA_HEAP_IOCTL_ALLOC, buf->alloc_info); if (ret < 0) { av_log(avctx, AV_LOG_ERROR, "Failed to alloc rga dst dma buffer\n"); goto out; } if (fcntl(buf->alloc_info->fd, F_GETFL) & O_RDWR) prot |= PROT_WRITE; buf->buf_ptr = mmap(NULL, MAX_FRAMESIZE, prot, MAP_SHARED, buf->alloc_info->fd, 0); if (buf->buf_ptr == MAP_FAILED) { av_log(avctx, AV_LOG_ERROR, "Failed to mmap dma buffer.size=%d: %s\n", MAX_FRAMESIZE, strerror(errno)); goto out; }

由此,可以得到一个buf→buf_ptr 的 MAX_FRAMESIZE (819281924) 的内存地址

然后将此地址提供给每个帧buffer上,如下

frame->data[0] = decoder->dma_buf->buf_ptr; frame->data[1] = frame->data[0] + frame->linesize[0]*mpp_frame_get_ver_stride(mppframe); frame->data[2] = frame->data[1] + frame->linesize[1]*mpp_frame_get_ver_stride(mppframe)/2;

此计算是为了满足如下公式

u_pitch == y_pitch / 2 v_pitch == y_pitch / 2 dst_u == dst_y + y_pitch * dst_height dst_v == dst_u + u_pitch * dst_height / 2

frame->data[0]dst_info.virAddr = dst_y;

由此,rga这边的buffer也是dma申请的。

root@kylin:/sys/kernel/debug/dma_buf# cat bufinfo | grep 268435456 -A 3 268435456 00000002 00080007 00000002 system-uncached-dma32 03444815 1029285-ffplay Attached Devices: Total 0 devices attached

这里可以看到 268435456 = MAX_FRAMESIZE 已经正常申请

patch地址为

https://gitlab2.kylin.com/shanghai-team/FFmpeg/-/commit/9fbfadb6f185e73b2140b5ff063ef2445897b301

2.7 权限

ffplay因为要通过dma去申请,这里需要满足权限

需要复制给dma heap 一个777 的最大权限,确保应用正常使用堆

chmod -R 777 /dev/dma_heap/

三:自测结果

通过播放视频,查看rga的使用率,确定rga正常使用,视频播放不卡顿。如下

watch -n 1 cat /sys/kernel/debug/rkrga/load Every 1.0s: cat /sys/kernel/debug/rkrga/load kylin: Thu Nov 24 17:20:31 2022 num of scheduler = 3 ================= load ================== scheduler[0]: rga3_core0 load = 0% ----------------------------------- scheduler[1]: rga3_core1 load = 0% ----------------------------------- scheduler[2]: rga2 load = 22% ----------------------------------- process 1: pid = 1033010, name: ffplay /data/bbb_sunflower_2160p_60fps_normal.mp4

这里可以确定,当前视频通过ffplay播放,走的rga2加速。4K的视频源,rga使用率在20-30%。