跳转到内容

Mali DDK 在 Android 里管什么:一个用户态库、一个内核驱动、一份固件

结论:Mali DDK 由三样东西组成:用户态的 libGLES_mali.so、内核驱动 kbase、GPU 固件。它只负责渲染,不负责显示。 buffer 由 gralloc 分配,上屏由 HWC 和 KMS 完成。DDK 和它们只在两个地方衔接:

  • 从 gralloc 拿到的 dma-buf,由 DDK 导入 GPU 的地址空间;
  • 渲染完成的信号,由 DDK 变成 sync_file 交出去。

这也是 DDK 能整体换成 Linux 版的原因:只要 kbase 不变,换掉用户态库,衔接方式依旧是 dma-buf 和 fence。

逻辑视图:用户态库负责“翻译”,kbase 负责“调度和内存”,固件在 GPU 里排任务

Section titled “逻辑视图:用户态库负责“翻译”,kbase 负责“调度和内存”,固件在 GPU 里排任务”

逻辑视图:Mali DDK 的三部分和它们与 gralloc、dma-buf、sync_file 的衔接

用户态 libGLES_mali.so:Arm 只给闭源二进制,四件事都在这个库里:

  • API 前端:EGL、GLES、Vulkan、OpenCL 的实现。Vulkan 驱动通常就是指向这个库的链接。
  • 着色器编译器:把 GLSL 或 SPIR-V 编译成 Mali 的指令。编译在应用进程里运行。
  • 命令生成:把绘制调用写进 CSF 命令队列。这些队列在共享内存里,用户态直接写,不用每次都陷入内核。
  • Android 窗口系统:对接 ANativeWindow。eglSwapBuffers 时把 buffer 连同 acquire fence 交回 BLASTBufferQueue。

内核驱动 kbase(mali_kbase.ko):Arm 给源码,厂商对着自己的内核编译。它管四件事:

  • 内存管理:维护每个进程的 GPU 页表;把 gralloc 分配的 dma-buf 导入 GPU 地址空间。
  • 调度:把用户态建好的命令队列组交给 GPU 固件,由固件在硬件上排队执行。
  • 同步:GPU 任务可以先等一个输入 fence 再开工,完成时触发输出 fence,导出成 sync_file。
  • 电源与调频:开关 GPU 电源域,按负载调频率。

GPU 固件(常见文件名 mali_csffw.bin):kbase 启动时把它加载到 GPU 内部的 MCU 上运行。CSF 架构的 GPU 由固件调度命令队列,内核只在队列组切换、出错时介入。

kbase 不是 DRM 驱动。 它的设备节点是 /dev/mali0,接口是 Arm 自定义的 ioctl。所以 Android 上 GPU 和显示控制器之间,只靠 dma-buf 和 sync_file 这两种内核通用机制衔接。

开发视图:DDK 分散在三个分区,加载器按系统属性找到它

Section titled “开发视图:DDK 分散在三个分区,加载器按系统属性找到它”

Android 按分区区分维护方:system 分区归 Google,vendor 和 vendor_dlkm 分区归厂商,boot 分区放 GKI 通用内核。DDK 的文件分别放在下面几处:

开发视图:DDK 和相关部件在 Android 各分区中的位置

文件位置怎么被找到
libGLES_mali.so/vendor/lib64/egl/system 分区的 libEGL 按 ro.hardware.egl(例如 mali)拼出文件名并 dlopen
vulkan.<平台>.so/vendor/lib64/hw/libvulkan 按 ro.hardware.vulkan 拼出文件名;通常是指向 libGLES_mali.so 的链接
GPU 固件/vendor/firmware/kbase 加载时通过内核的固件加载接口读取
mali_kbase.kovendor_dlkm 分区开机时加载;只能使用 GKI 承诺稳定的内核导出符号(KMI)
gralloc 服务vendor 分区通过 AIDL 服务注册,BLASTBufferQueue 申请 buffer 时调用

版本必须对齐:libGLES_mali.so 和 kbase 来自同一个 DDK 版本(rXpY),二者之间的 ioctl 接口不保证跨版本兼容。升级 DDK 时两边要一起换。

DDK 自己不分配要显示的 buffer,也不管上屏。和其他部件的配合集中在三处,都是集成时容易出问题的地方。

buffer 句柄:DDK 要读懂 gralloc 给的句柄

Section titled “buffer 句柄:DDK 要读懂 gralloc 给的句柄”

gralloc 分配的 buffer 以句柄的形式在进程间传递。句柄里有 dma-buf 文件描述符,还有格式、步长、modifier 等元数据。DDK 要从句柄里读出这些信息,才能正确导入和写入。

  • Arm 随 DDK 提供 gralloc 参考实现,句柄格式和 DDK 天然匹配。厂商通常在它的基础上修改,加入自己显示控制器的约束。
  • 如果自己从头写 gralloc,要确保 DDK 能通过标准的 mapper 元数据接口拿到它需要的信息,并和 Arm 确认这一版 DDK 依赖哪些字段。

fence:渲染完成信号变成 sync_file 交出去

Section titled “fence:渲染完成信号变成 sync_file 交出去”

DDK 实现了 EGL_ANDROID_native_fence_sync 扩展。eglSwapBuffers 返回时,GPU 往往还没画完。DDK 把“这次渲染完成”的 fence 变成 sync_file,作为 acquire fence 跟着 buffer 交出去。之后 SurfaceFlinger、HWC、KMS 都等这个 fence。

反方向同理:应用取到一块 buffer 时附带 release fence,DDK 把它作为 GPU 任务的输入 fence,GPU 等它触发后才开始写。

压缩格式:GPU 和显示控制器要对 AFBC 理解一致

Section titled “压缩格式:GPU 和显示控制器要对 AFBC 理解一致”

Mali 支持 AFBC 帧缓冲压缩,能大幅减少 DDR 带宽。前提是读这块 buffer 的显示控制器也能解压 AFBC:

  • gralloc 根据 buffer 的用途决定是否用 AFBC,以及用哪种 AFBC 变体;
  • KMS 驱动在 plane 的 IN_FORMATS 属性里如实上报支持哪些 AFBC modifier;
  • HWC 只把显示控制器读得了的 buffer 分给 plane,读不了的退回 GPU 合成。

三方信息不一致时,常见的结果是显示花屏,或者被迫全程使用不压缩的格式。

三种 Mali 驱动的区别:用户态不同,衔接方式相同

Section titled “三种 Mali 驱动的区别:用户态不同,衔接方式相同”

同一颗 Mali GPU,在三种环境下的驱动组合:

Android 版 DDKLinux 版 DDK上游开源驱动
用户态libGLES_mali.so(链接 bionic)libmali(链接 glibc),带 GBM 和 WaylandMesa 的 panfrost(GLES)和 panvk(Vulkan)
内核kbase(/dev/mali0)同一个 kbasepanthor(/dev/dri/renderD128)
buffer 从哪来grallocGBM(DDK 自带)GBM(Mesa)
交给合成器的方式BLASTBufferQueueWayland linux-dmabufWayland linux-dmabuf
对 Mali G2-Ultra NX 的支持有有,需向 Arm 要交付件截至 2026-11 还没合入主线

前两列共用 kbase,所以 Android 迁到 Omarchy 时,内核里的 GPU 驱动可以原样保留。详细做法见 dev-notes 的《把手机上的 Android 换成 Omarchy》。