Panfrost开源驱动深度拆解:架构、Job Chain与调试实战

发布时间:2026/9/16 1:59:03
Panfrost开源驱动深度拆解:架构、Job Chain与调试实战 常年跟ARM开发板打交道的朋友对Mali-GPU应该是又爱又恨。爱的是它几乎无处不在恨的是在Panfrost成熟之前Linux上想用Mali老老实实还得到处找闭源blob调试全靠黑盒。这几年Mesa社区把Panfrost这个开源驱动一步步做起来从Midgard到Bifrost再到Valhall相关的探索总算让我们这些普通开发者能像研究其它开源项目一样把Mali驱动的架构、实现细节甚至着色器编译流程都翻个底朝天。这篇文章我就基于实际使用经验把Panfrost从用户态到内核态的整体架构、Job Chain组织方式、内存管理和着色器编译这几块拆开来讲也顺便把我在RK3399、RK3566这类板子上折腾时遇到的各种坑拿出来复盘一遍。如果你最近正准备给手头的Mali设备换一个可调试、可追溯、不依赖厂商抬头的驱动方案或者单纯想看开源GPU驱动到底是怎么跟硬件打交道的这篇内容应该能帮你少走不少弯路。1. Panfrost是什么为什么值得专门拆一拆Panfrost不是一个简单的“Mali临时替代品”它现在已经是Mesa项目里相当成熟的驱动分支同时包含用户态驱动和对应的内核态DRM驱动。1.1 它解决了什么核心问题过去在ARM Linux平台上Mali GPU的用户态驱动基本只有ARM官方给的二进制blob比如mali r34p0、r43p0这种release版本。这些blob闭源、不透明内核接口也经常随版本变化。一旦出问题普通开发者的排查手段非常有限要么对着/var/log猜要么只能干瞪眼。Panfrost要解决的就是这个问题。它把Mali GPU的用户态逻辑全部用开源代码实现放进Mesa里内核侧也有对应的DRM驱动。这样从着色器编译、命令流拼装、内存分配到Job提交、Fence同步整条链路都是能读源码、能加日志、能改行为的。对于跑嵌入式Linux、做产品定制、或者单纯想学习GPU驱动原理的人来说这是巨大的优势。因为Panfrost走的是标准DRM / render节点接口应用层完全不用感知驱动是开源还是闭源。EGL照常初始化OpenGL ES照常调用Vulkan也有panvk在推进。这一点让它在兼容性上很讨喜你不用为了切驱动把整个图形栈换掉。1.2 硬件支持范围和边界首先得搞清楚Panfrost并不是所有Mali GPU都能用。它主要在两条硬件家族上比较可靠一条是Midgard架构代表性芯片有Mali-T760、T820、T860另一条是Bifrost架构比如Mali-G31、G52、G76。像RK3399里的Mali-T860、RK3566/RK3568里的Mali-G52都是Panfrost的重点支持对象。再往前的Utgard架构比如Mali-400、Mali-450就别指望Panfrost了那个时期的Mali基本只有老旧的lima? 不lima主要支持Utgard的另一个分支总之不是Panfrost的覆盖范围。再往后的Valhall架构比如RK3588上的Mali-G610内核侧已经有更新的Panthor驱动在负责用户态编译器仍然和Panfrost共享不少代码但本文重点还是讲Panfrost本身。这里有一个很关键的判断经验拿到一块板子先别急着编译先确认GPU的架构代号和Panfrost的硬件数据库是否匹配。我见过不少人拿着Mali-T720的板子折腾Panfrost最后走了很多弯路原因就是T720在支持列表里属于边缘产品各种功能不完整。具体的支持情况可以在Mesa源码的src/panfrost目录里查看硬件相关的判定逻辑或者参考kernel的panfrost_drv.c里的compatible名单。2. 宏观架构用户态、内核态与硬件三方如何分工Panfrost的整体架构可以分成三层应用层的Mesa用户态驱动、内核里的DRM驱动、以及最底层的Mali硬件。2.1 用户态部分Mesa里的Panfrost用户态的Panfrost驱动位于Mesa源码树的src/gallium/drivers/panfrost目录它实现了Gallium框架要求的pipe驱动接口。应用通过GL或GLES调用经过Mesa的GL state tracker后会变成Gallium层的命令Panfrost负责把这些命令翻译成Mali硬件能理解的数据结构。一个很重要的细节是Panfrost用户态并没有直接给Mali写一串寄存器而是把渲染命令组织成一堆内存里的描述符比如Shader Program Descriptor、Tiler Descriptor、Fragment Descriptor再把这些描述符串联成硬件能逐条执行的Job Chain。这跟桌面GPU习惯用command buffer的模型有一点像但Mali的描述符格式又自成一体。用户态还要负责着色器的编译。GLSL或SPIR-V进入Mesa后先被转换成NIR中间表示然后由Panfrost的编译器模块继续做lowering、寄存器分配和指令调度最终生成Midgard或Bifrost指令二进制。这些二进制会被填进Shader Program Descriptor交给GPU执行。2.2 内核态部分drm/panfrost与ioctl内核侧的Panfrost驱动在Linux源码的drivers/gpu/drm/panfrost目录。它承担了几件核心工作管理GPU的MMU页表、创建和销毁BOBuffer Object、把用户态提交的Job放进执行队列、通过Fence或Syncobj完成job之间的同步。用户态和内核态之间的接口是一组DRM ioctl比较常见的有ioctl作用DRM_IOCTL_PANFROST_GET_PARAM获取GPU ID、特性参数等信息DRM_IOCTL_PANFROST_CREATE_BO创建GPU缓冲区对象DRM_IOCTL_PANFROST_MMAP_BO把BO映射到用户态CPU地址DRM_IOCTL_PANFROST_SUBMIT提交一个Job ChainDRM_IOCTL_PANFROST_WAIT_BO等待某个BO的作业完成在这套框架里用户态负责“拼装内容”内核态负责“执行和调度”硬件本身则负责“实际渲染”。这样分工的逻辑很清楚用户态可以很灵活地决定command list怎么做优化而内核态保持精简把精力集中在资源生命周期和并发安全上不容易把系统搞崩。2.3 一次Draw请求在Panfrost里的完整旅程为了更直观地看架构可以跟着一次简单的Draw Call走一遍。应用调用glDrawArraysMesa里的GL state tracker会更新当前管线状态最终调用Panfrost的pipe_draw_vbo入口。这时候Panfrost先判断当前的Shader Program是否需要重编译判断顶点缓冲、纹理、FrameBuffer是否有更新。接着Panfrost会把相关信息填充到panfrost_batch结构体OpenGL的draw会被记录成batch中的一个“job”并且被link到该batch的job chain。如果是一帧里有多个draw驱动会根据硬件特性做合并或排序。最终在flush的时候用户态把batch对应的所有BO列表、同步对象、job chain信息封装成一次SUBMIT ioctl送到内核。内核收到SUBMIT后首先把相关的BO都绑定到GPU地址空间让它能访问然后构造panfrost_job结构体并入DRM scheduler队列最终提交给Mali硬件。GPU完成作业后触发中断驱动在中断上下文中更新fence再由Fence唤醒等待CPU的应用线程。至此你屏幕上看到的一帧才算真正画完。3. 核心实现细节拆解这个部分我想把几个最容易让人看得云里雾里的点拿出来详细说尤其是Job Chain、内存地址空间和着色器编译器。3.1 Job Chain与命令流的组织方式Mali GPU没有像PC GPU那种复杂的GPU虚拟内存命令队列硬件更多时候靠一段内存里的“任务链表”来工作。Panfrost用户态在内存中创建好一个或多个Job Descriptor然后用指针把它们串成链表头指针传给内核。硬件在执行时会一个接一个地往下走链表的Job。常见的Job类型有Vertex Job、Tiler Job、Fragment Job还有Fused Job这类将顶点和图元处理合并在一起的形式。对于TBDRTile-Based Deferred Rendering架构来说Vertex和Tiler常常是不可分的所以Panfrost会把它们打包成Fused Job期望减少GPU内部的状态切换和往返延迟。为什么Panfrost要在用户态把这些描述符做得这么细因为硬件就是这样设计的驱动根本绕不开。闭源驱动也一样要拼这些描述符只是你看不到而已。开源之后这些结构体在头文件里写得清清楚楚比如panfrost_job_descriptor、panfrost_tiler_descriptor、panfrost_fragment_descriptor。想理解Mali硬件的人光靠读驱动代码就能学到很多东西。有一个经验值得分享如果你在调试时怀疑是命令流拼错了别用肉眼死盯十六进制Mesa里有一组叫pandecode的工具可以直接把用户态写入的GPU命令流解码成人能读懂的描述。这个工具在排查“某个draw为什么画出来是花的”这类问题上能省非常多时间。3.2 内存对象、MMU和地址空间Mali GPU有自己独立的MMUCPU地址和GPU地址是两个不同的空间。Panfrost的BO在内核里创建后用户态通过MMAP拿到CPU侧映射而GPU侧访问则依赖一个drm_panfrost_gem_mapping对象。这个mapping的本质就是把某个BO映射到GPU的地址空间并做好页表。对于写驱动的新手这里最容易混的就是“明明CPU这边数据是对的GPU却读不到”。原因大概率是mapping没建立或者BO在提交前没有做正确的内存屏障。Panfrost在SUBMIT时会把bo_list里的每个BO都准备好确保GPU能访问但如果用户态漏了某个BOGPU访问到空页表项直接就是Page Fault。我知道有些同学会问为什么不直接用CPU的物理地址因为Mali本身支持IOMMU通过GPU MMU可以做按需换页、权限控制、多进程地址空间隔离这些在一个跑着Linux的板子上特别重要。闭源驱动也采用类似方案这不是Panfrost的发明但Panfrost把这个机制实现得相对清爽读源码收获很大。3.3 着色器编译与指令调度Panfrost的着色器编译器在src/panfrost/compiler里。它的输入是NIR输出是Midgard或Bifrost架构的二进制指令。大体流程是NIR先做通用的优化与lowering把GLSL/Vulkan里那些高级概念降成贴近硬件的操作比如把数学函数展开成基础指令、处理字节序和特殊纹理操作。然后进入寄存器和指令调度阶段Panfrost自己实现了一个针对Mali特性的寄存器分配器和一般编译器教材里的线性扫描做法不太一样它更了解Mali指令格式的限制比如哪些寄存器不能随便跨写。到了指令编码这一步Midgard和Bifrost是两条完全不同的路子。Midgard的指令宽度相对固定编码也比较朴素Bifrost则采用更宽的指令槽包含更多并行信息。前者让我想起多年前的维密超模? 不扯远了简单记忆就是Bifrost是现代Mali的主旋律指令编码更复杂编译器要做的调度工作更多。如果你正在做图形开发并且发现某个shader在闭源驱动下正常、在Panfrost下效果不对先用PAN_MESA_DEBUG或Mesa自身的debug工具把中间NIR和汇编导出来看。大部分问题其实不是硬件错误而是编译器lowering阶段对某个指令的语义理解有偏差。这种问题放到开源社区修起来也很快因为你能把寄存器变体和指令编码一行行对上。3.4 Tiler与Fragment的硬件配合Mali是Tile-Based Deferred Rendering渲染过程不是把所有图元一次性画到全屏framebuffer而是先做几何处理把图元切分到一个个小的tile上再逐tile执行Fragment Shader。这样能极大减少带宽消耗因为在tile内部利用on-chip cache做颜色和深度累加不用频繁访问显存。Panfrost需要为硬件分配“Polygon List”缓冲区这是tiler输出给fragment阶段用的中间数据。tiler计算完图元归属后会往每个tile对应的polygon list里追加引用fragment阶段再按tile读取这些列表逐个像素执行。这里面隐藏着一个经典问题如果你给tiler分配的缓冲区太小很容易触发Overmemory之类的异常。Panfrost的处理方式是通过预算进行动态扩展但如果你在嵌入式板子上内存紧张或者用了一些不常用分辨率这个问题依然可能出现。我在遇到tiler相关Page Fault时第一反应就是检查“多边形列表预算够不够”往往比去追着色器更快。4. 从零搭建Panfrost调试环境光看代码和文档不够一定要亲手把Panfrost跑起来才算真正理解。这一节我把从内核到用户态的搭建流程完整走一遍。4.1 内核配置和编译Panfrost从Linux 5.1开始进入内核主线所以第一件事是确认你的内核版本。如果还在用厂商BSP的4.4或4.9内核基本无缘Panfrost需要切到比较新的mainline或LTS。开启Panfrost的Kconfig一般这样操作cd linux make menuconfig然后在菜单里定位到Device Drivers - Graphics support - * Panfrost (DRM support for ARM Mali Midgard/Bifrost GPUs)对应的配置项就是CONFIG_DRM_PANFROSTy或m。确认勾选后重新编译内核和模块。如果你是交叉编译记得把模块也安装到rootfs里否则运行时会提示找不到驱动。在比较新的内核版本里Panfrost通常会自动被ARM相关配置依赖选中但保险起见还是手动检查一遍config文件比较稳妥grep PANFROST /boot/config-$(uname -r)如果没有这个文件就用zcat /proc/config.gz | grep PANFROST。4.2 用户态Mesa编译内核搞定了接下来是用户态。Mesa的构建现在都走Meson编译Panfrost相关的部分可以这样做git clone --depth 1 https://gitlab.freedesktop.org/mesa/mesa.git cd mesa meson setup build/ -Dgallium-driverspanfrost -Dvulkan-driverspanfrost -Dglxauto ninja -C build/构建之前请确保系统里装了meson、ninja、bison、flex、python3、libdrm开发头这些基础依赖。不同发行版包名略有差异但缺什么装什么就好。有人会问直接装发行版仓库里的Mesa行不行多数现代发行版的Mesa已经默认带Panfrost如果你只是跑应用直接用发行版Mesa更省事。自己编译Mesa主要是为了调试、dump中间IR、改代码加日志这些场景。有个小坑想提一下自己编译Mesa时如果系统里同时有多个Mesa版本容易出现运行时libGL加载混乱。建议用meson configure把安装路径指定到独立目录或者用环境变量让应用正确加载你编译出来的驱动。4.3 确认Panfrost已经工作驱动装好后不一定马上能看到效果建议先跑一个最简单的验证。第一步看设备节点是否存在ls -l /dev/dri/如果有renderD128之类的节点并且没有报错说明驱动注册成功了。再配合EGL/GL工具确认渲染后端比如运行glmark2-es2看输出信息里是否出现panfrost对应的EGL平台描述。如果屏幕上能正常跑出动画属于大成功。如果黑屏或卡住马上查看内核日志dmesg | grep panfrost dmesg | tail -n 50不要小看这一步很多问题在日志里其实已经写得很明白了只是很多人习惯性跳过。5. 实战踩坑和性能调优用Panfrost这几年我踩过的坑说多不多说少也不少。这一节挑几个最有代表性的给后来人提个醒。5.1 内核与Mesa版本不匹配Panfrost是典型的内核态和用户态需要配合的项目。太老的内核加太新的Mesa或者反过来都可能出现ioctl参数不一致、新BO flag不被识别之类的问题。我遇到过最典型的场景是内核是某个LTS的早期版本Mesa却拉到了最新master结果应用初始化到一半直接崩溃。最后排查到是因为Mesa新代码用了一个内核还没支持的提交参数。经验是先用发行版自带的内核和Mesa正常跑通一套再去尝试“新内核新Mesa”或者“老内核对应老Mesa”。不要同时把两边都换成最新版否则遇到问题都不知道该查谁。5.2 GPU Page Fault排查记录Page Fault是Panfrost调试时遇到最多的硬件异常之一。内核日志常见这样一行panfrost 13000000.gpu: Unhandled Page fault in AS0 at VA 0x00000000看到这行先别慌它只说明GPU访问了一个没有映射的地址。接下来从几个方向排查可能原因排查思路job descriptor地址错误查看用户态是否有未初始化内存被提交给GPUtiler buffer预算不足检查分辨率是否超出tiler采样预算shader二进制损坏用pandecode反汇编程序检查指令编码BO未正确绑定确认SUBMIT的bo_list覆盖了所有被访问的BO我自己遇到最多的其实是“某个临时buffer没有及时创建mapping”属于用户态驱动里的生命周期问题。后来在代码路径里打了几个断点很快就定位到了这就是开源驱动的优势。5.3 Fence超时和作业卡死如果应用一直卡住不渲染多半是Fence等不到。内核日志里可能见到类似“job timeout”的提示。这通常意味着GPU根本没执行到该job或者执行时卡在某个死循环里。最简单的排查是先把GPU调频策略改成performance排除频率切换导致的问题ls /sys/class/devfreq/ echo performance /sys/class/devfreq/13000000.gpu/governor注意路径里的13000000.gpu是设备树里GPU节点对应的地址不同板子不一样先看ls结果。如果改成performance后场景正常说明是调频策略的响应速度不够快或者电压配置偏低重点去查电源管理部分。5.4 性能、调频和功耗的取舍最后聊一下性能调优。Panfrost在性能上确实比早期版本好了太多但在某些极端负载下仍可能比闭源驱动表现激进一些的浮点压榨技术有差距。实际产品里性能优化不一定要去改编译器或驱动源码。先看GPU频率是否跑在合理档位再看Buffer的提交方式是否频繁触发CPU和GPU同步。Mali是tile-based架构带宽瓶颈往往比算力瓶颈更容易先出现所以优化带宽比优化ALUs更现实。如果想监控实时的GPU频率和占用可以读devfreq下的统计文件cat /sys/class/devfreq/13000000.gpu/cur_freq cat /sys/class/devfreq/13000000.gpu/trans_stat6. 写在最后我的一点个人体会我自己在RK3399和RK3566上把Panfrost从内核编译到用户态Mesa完整跑通之后最大的感受是开源驱动最大的价值不是“免费”而是“可解释”。过去遇到Mali上的图形异常闭源环境里我只能靠猜测现在我可以打开源码一层层往下追看到底是哪里把状态算错了哪个buffer没绑定哪条指令调度得不合理这种掌控感是用多少性能对比都换不来的。如果你也正在折腾Mali平台我的建议是先别急着定论“开源驱动性能一定差”把环境搭好跑一遍glmark2、跑一遍自己的真实场景再回去看代码你会理解得特别快。