ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

SOF固件与Topology编译实战:从源码到DSP音频调试

SOF固件与Topology编译实战:从源码到DSP音频调试 1. 内容整体设计与思路拆解1.1 先说清楚SOF到底是什么Sound Open FirmwareSOF是一个开源的音频DSP固件框架最早由Intel主导推动现在已经是Linux音频生态里非常重要的一块拼图。它跑在DSP数字信号处理器上负责音频数据的搬运、处理、混音、回声消除、降噪、杜比音效这些功能。说直白点你的笔记本、某些台式机、嵌入式音频设备上跑的音频处理逻辑有不少就是SOF在底层支撑。大多数情况下你不会感知到它的存在因为发行版自带的固件包直接就被内核驱动加载了。但一旦你要做产品预研、调试诡异的音频问题、适配新平台、裁剪DSP处理链路或者想往DSP里塞自定义算法光靠发行版预编译固件就完全不够用了。这时候从源码编译SOF固件和topology就成了一项绕不开的基本功。1.2 为什么不能一直用发行版自带的固件预编译固件最大的问题是黑盒——你根本不知道里面编译了哪些组件、启用了哪些处理模块、topology文件里定义的是什么样的音频管道。比如系统自带的固件默认把DMIC数字麦克风配置成了四通道但你的主板实际只接了双麦克风结果就是录音只有两路有声音另外两路全是噪声。查硬件查了半天最后发现是固件和topology不匹配。另外一个更现实的需求是调试。遇到音频卡顿、爆音、DSP崩溃光看dmesg常常不够你需要在内核里打开SOF的debugfs日志甚至要让DSP固件本身输出更多调试信息这些能力在release版本的固件里往往是关闭的。自编译就是打开这些窗口的唯一途径。1.3 适合谁来学这套流程音频驱动工程师需要修改固件、调试DSP问题定位IPC通信异常。嵌入式音频产品开发者要针对自己的麦克风阵列、音箱配置生成定制topology。Linux系统集成/FAE帮助客户分析音频链路问题裁剪固件减小体积。对音频底层感兴趣的同学想搞清楚声音在DSP里到底怎么流转的。这篇文章我会从源码结构、工具链准备、编译参数、topology生成到固件替换和问题排查完整走一遍。过程中会穿插我实际踩过的坑很多细节是文档里不会明说的。2. 源码获取与分支选择2.1 拉取SOF主仓库SOF的代码托管在GitHub的thesofproject/sof仓库。常规拉取方式git clone https://github.com/thesofproject/sof.git cd sof git submodule update --init --recursive注意submodule这步一定不要省。因为SOF仓库里引用了好几个子模块包括audio的相关库、west构建工具缺了子模块后面编译大概率直接失败。另一个建议是改默认分支前先看看当前在哪个分支git branch -a默认拉下来是main分支也就是开发分支。开发分支的特点是功能新但稳定性没保证某些平台可能build一半就编译报错。如果是做产品更建议切到稳定的tag上git tag -l git checkout v2.9.0 # 或者你测试过稳定的版本2.2 分支和平台的对应关系现在的SOF代码对多平台多架构做了非常重的抽象编译某一款具体固件之前你必须知道它对应哪一个platform和arch。从架构上说SOF分成两个大的CPU架构方向xtensa架构的DSPIntel、AMD等平台的主流DSPBCA系列cavs18、cavs25等、MTL等都用的是Cadence的Xtensa核。ARM架构的DSP部分新平台转向了ARM比如mtl后续的某些平台和nvidia平台。在编译前请务必确认你的目标平台的arch。一个很实用的办法是到源码里看platform的定义ls src/platform/你会发现里面有apollolake、cannonlake、icelake、tigerlake、mtl这些目录。它们对应的xtensa平台名分别是平台目录Xtensa平台名典型用途apollolakeapl低功耗平台cannonlakecnl主流笔记本icelakeicl十代酷睿轻薄本tigerlaketgl十一代酷睿alderlakeadl十二代酷睿meteorlakemtl后续新平台这一点必须提前搞明白因为后面cmake配置阶段要传入的TARGET参数直接决定生成哪个固件。2.3 获取正确的工具链SOF的DSP固件通常是交叉编译的不是在x86主机上直接编译x86目标。你需要为特定的Xtensa DSP获取对应的工具链。我建议不要自己去下通用Xtensa工具链直接用SOF官方发布的工具链它们在sof-bin仓库里git clone https://github.com/thesofproject/sof-bin.git进去以后找类似xtensa-sof-toolchain-*的目录里面是打包好的交叉编译器。核心文件是一个tar压缩包解压后里面就是完整的xtensa-platform-elf-工具链。解压到某个目录后把bin路径加到PATH里export PATH/opt/xtensa/tools/xtensa-cavs25-elf/bin:$PATH这一步很关键如果你PATH里的交叉编译器不对或者指向了别的平台后面cmake阶段可能过了但编译时生成的是错误的汇编指令出现一堆莫名其妙的汇编错误排查起来特别痛苦。注意不同平台对应不同的工具链前缀。cavs25平台的工具链是xtensa-cavs25-elf-cavs18是xtensa-cavs18-elf。混用会直接报unknown target等错误。2.4 主机端依赖准备编译SOF固件除了交叉工具链主机上还必须装好以下几样东西cmake3.13以上版本。ninja-buildSOF的构建系统主要走Ninja。gcc / g用来编译主机端工具如topology生成工具和signtool。python3及pytestSOF的测试框架和部分生成脚本依赖它。flex、bison某些解析器工具的生成需要。Debian/Ubuntu系统可以这样装sudo apt install cmake ninja-build gcc g python3 python3-pip flex bison装完以后验证一下版本。我第一次踩的坑就是cmake太老SOF的新版本要求最低3.13我当时系统还是3.10结果构建脚本直接在配置阶段就报错。如果你用Ubuntu 18.04这种人老系统建议先升cmake。3. 编译配置与固件生成3.1 理解cmake配置阶段的关键参数进入SOF源码根目录后需要先创建一个build目录在里面跑cmake。不要直接在源码根目录下跑那样会把整个源码目录弄脏后续升级或切换分支会非常麻烦。官方推荐的做法是mkdir build_sof cd build_sof cmake ../sof但这样只能得到默认配置很可能会默认构建你根本不关心的平台。实际你要明确指定平台cmake ../sof \ -DTARGETxtensa-cavs25 \ -DBOARDcherrytrail \ -DROOT_DIR/opt/xtensa/tools/xtensa-cavs25-elf \ -DINIT_CONFIGdefconfig这里的参数含义TARGET固件运行的目标DSP平台名。BOARD某个具体板卡或平台变体。ROOT_DIR指向工具链安装的根目录。INIT_CONFIG用于指定初始的kconfig配置文件一般是defconfig。这个阶段如果工具链不对cmake会直接给出toolchain not found之类的错误。如果工具链正确配置阶段会生成大量中间文件并提示你后续使用ninja构建。3.2 配置过程中的menuconfigSOF的kconfig配置风格和Linux内核很像。在完成基础cmake配置后我们还可以手动调整一些编译宏make menuconfig不过需要注意的是在SOF里menuconfig确实可用但用的地方主要是调整编译选项比如启用调试日志、增加某些算法模块、调整缓冲区大小等。比如我经常干的一件事是打开CONFIG_DEBUG_IPC把IPC报文细节打进日志。打开CONFIG_DEBUG_FW_LOGS把固件侧日志输出到内存缓冲区方便后用sof-logger读取。调整CONFIG_HEAP_SIZE某些拓扑很复杂、管道节点很多时默认堆大小会不够用导致固件启动时分配内存失败。这些配置项实际上定义在src/arch/Kconfig或src/platform/Kconfig里。排查问题时它们极其有用但在产品发布时应该关闭避免日志泄露信息以及性能浪费。3.3 真正开始编译在没有额外配置的情况下直接从build目录执行ninja如果平台较多想指定某一个固件目标可以ninja sof所有平台的固件会被打包成.ri文件存放在build_platform/src/arch/xtensa/...目录下具体路径格式类似build_sof/src/arch/xtensa/xtensa-cavs25/src/sof-platform.ri编译时间取决于机器性能一般从零开始完整构建在十六核以上的机器上大约五到十五分钟。首次编译会下载子模块的依赖和构建一部分工具可能会更久。期间如果看到一两个warning属于正常现象但出现error要重点关注。这里我特别提醒一个容易误解的点.ri文件不是闪存到主板BIOS里的而是由Linux内核在启动音频驱动时装进DSP的。所以后面我们只需要把它放到固件目录并确保驱动能找到它。3.4 签名与安全启动现代SOF固件引入了签名机制确保只有被信任的固件能被加载。编译完的.ri文件内部结构其实包含了签名信息、固件版本、平台ID等头部字段。如果你在编译时设置了对应的签名密钥最终产出的就是签名固件。如果你没有设置默认会把一个公开的开发签名密钥嵌入进去。在普通x86平台上Linux驱动只校验格式不严格校验厂商签名开发阶段无所谓但在量产产品上你需要替换成自己的密钥。要查看一个.ri文件的信息可以使用SOF源码里的scripts/sof_ri_info.py脚本python3 scripts/sof_ri_info.py --inbuild_sof/src/arch/xtensa/xtensa-cavs25/src/sof-tgl.ri输出里会显示版本号、ABI版本、签名类型等。这个工具在排查固件加载失败时特别有用有时加载失败就是因为版本过老不匹配驱动要求。4. topology文件生成与定制4.1 topology是什么为什么它和固件同等重要topology直译是拓扑在SOF体系里它是一份描述音频DSP内管道如何连接、有哪些组件、各个PCM设备如何映射到DMA和DAI的配置数据。你可以把它理解成一张音频数据在DSP内部流动的地图。固件本身提供了各种DSP模块如mixer、volume、eq_fir、eq_iir、dcblock、tdfb、drc等但默认情况下固件不知道你要怎么用它们。topology文件就是用来告诉固件我需要创建一个PCM设备接到一个mixer上再接两个EQ最后输出到I2S或者从DMIC进来经过一个tdfb处理再送到host。所以只有固件没有正确的topology驱动依然无法工作。在很多Linux音频问题里报错就发生在failed to load topology这一步。4.2 topology源码的构成SOF源码里tools/topology目录保存着topology的源文件。不过它并不是单一的文本文件而是一套用M4宏语言写的模板配合ALSA的alsatplg工具编译成最终的二进制.tplg文件。你可以看到一个典型的平台文件路径sof/tools/topology/sof-tgl-rt5682.m4里面定义了DAI配置I2S的slot数、位宽、采样率。PCM设备playback和capture的方向、格式集合、PCM ID。Pipeline哪些widget参与通路。Widget之间的route数据流的上下游关系。Controlkcontrol的定义让用户空间可以用tinymix/alsamixer控制音量或EQ参数。举个例子一个最简单的I2S播放链路在m4文件里看起来会定义好PCM、PGA、mixer、DAI这几个节点并且用route把它们串起来。如果你想增加一个EQ模块就要手动添加一个对应的widget和route。4.3 用alsatplg生成最终tplg文件修改完.m4文件后需要用ALSA的topology编译器生成.tplg文件。在编译SOF源码时通常会连带编译好alsatplg工具路径在tools/build_tools/alsatplg/alsatplg。生成命令示例alsatplg -c sof-tgl-rt5682.m4 -o sof-tgl-rt5682.tplg如果.m4文件里有语法错误或者引用了不存在的宏alsatplg会报错并指出行号。这里最常见的问题是主机上缺少必要的m4宏文件路径。很多情况下你需要把tools/topology/m4目录加入include路径alsatplg -I ../tools/topology -I ../tools/topology/m4 -c xxx.m4 -o xxx.tplg生成之后建议用alsatplg -c反编译或者kernel的snd-soc-topology加载测试一下确认文件格式合法。我见过很多人改完m4之后没有生成tplg直接把旧的tplg拿去用搞了大半天还以为是固件问题。4.4 定制topology的实战场景最常见的定制场景有以下几种。修改PCM设备数量默认的tplg可能只暴露了一个前端PCM但你的产品UI层需要多个声卡节点这时就要手动增加PCM_PLAYBACK和PCM_CAPTURE的定义。增加麦克风通道数如果硬件接的是四麦阵列但默认拓扑只映射了双通道采集会缺声道。需要在DMIC的DAI配置中把num_channels改成4。嵌入自定义算法SOF支持加入自定义的process组件但对应的topology里必须加入该组件的widget和IPC配置算法才可能被固件实例化。改采样率与格式集合某些音频子卡只支持16bit/48kHz但默认拓扑声明了24bit/96kHz这会造成应用打开设备失败或驱动侧错误的格式解析。每次改完topology记得保持固件与topology的版本匹配关系。虽然两者没有严格的ABI强绑定但太新的topology格式可能要求固件端支持对应的IPC版本否则加载会报invalid topology。5. 固件安装、加载与验证5.1 标准安装位置编译好的固件和topology文件要放到Linux系统固件加载器能找到的位置。Intel平台的标准路径是固件文件/lib/firmware/intel/sof/topology文件/lib/firmware/intel/sof-tplg/拷贝示例sudo cp build_sof/src/arch/xtensa/xtensa-cavs25/src/sof-tgl.ri /lib/firmware/intel/sof/sof-tgl.ri sudo cp tools/build_tools/topology/sof-tgl-rt5682.tplg /lib/firmware/intel/sof-tplg/sof-tgl-rt5682.tplg拷贝前建议先备份原来的文件sudo cp /lib/firmware/intel/sof/sof-tgl.ri /lib/firmware/intel/sof/sof-tgl.ri.bak然后重启音频相关模块sudo rmmod snd_sof_intel_hda_common snd_sof_pci_intel_tgl snd_sof_intel_hda sudo modprobe snd_sof_pci_intel_tgl具体模块名跟你的平台有关。Intel平台常见模块是snd_sof_pci_intel_cnl或snd_sof_pci_intel_tgl不确定的话先看一下当前已加载的sof模块lsmod | grep snd_sof5.2 确认固件被正确加载加载完成后立刻查看dmesgdmesg | grep -i sof正常输出应该包含类似这样的行sof-audio-pci 0000:00:1f.3: DSP detected with PCI class/subclass/prog-if info 0x000000 sof-audio-pci 0000:00:1f.3: firmware: downloaded sof-audio-pci 0000:00:1f.3: FW loaded sof-audio-pci 0000:00:1f.3: Firmware version: 2:9:0-xxxxx sof-audio-pci 0000:00:1f.3: Topology: ABI 3:22:0 sof-audio-pci 0000:00:1f.3: ASoC: parent NULL not ready看到FW loaded和Topology ABI基本就代表成功了。进一步可以通过aplay -l查看新的声卡设备是否出现以及用tinymix查看topology里定义的kcontrol。如果只是更新了topology没有改固件有时不需要重编固件只需放到对应目录后重新modprobe驱动即可。5.3 内核侧.debugfs检查在固件正常加载后/sys/kernel/debug/sof目录下会有多个调试入口。例如firmware_version固件版本。ipcIPC报文信息。dma_traceDSP侧日志的读取入口配合sof-logger使用。建议把kernel的ftrace和SOF跟踪功能打开配合日志分析cat /sys/kernel/debug/sof/fw_ready这个文件会输出固件就绪信息包括接口版本和内存buffer大小能有效确认固件和驱动版本是否兼容。6. 常见问题与排查技巧实录6.1 固件下载超时现象dmesg中出现类似error: timeout waiting for firmware download或者fw loader error。原因通常有以下几种固件文件不存在或路径不对。先确认/lib/firmware/intel/sof/下有没有对应的.ri文件以及文件名是否和驱动expected完全一致。文件名大小写或后缀不符。例如驱动期望sof-tgl.ri你放的是sof-tgl.ri.bak或者少了.ri都会导致加载失败。固件与DSP平台不匹配。你编译的是cavs25平台固件却硬要用在cavs18平台上PCI设备探测到的DSP型号对不上自然下载失败。排查技巧先去看dmesg中打印的固件路径再用sof_ri_info.py核对固件头部信息里的平台ID。6.2 topology加载失败现象dmesg输出error: failed to load topology或error: tplg load failed。这时候你先确认几个点tplg文件路径是否正确。tplg文件的ABI版本是否和内核驱动匹配。驱动太旧读不了新ABI或者固件太老不认新topology格式。tplg文件是否真的由你当前源码编译生成。有时改了m4没重新生成或者生成的tplg文件是0字节。一个很隐蔽的坑是当你用alsatplg生成tplg时命令行include路径顺序不对导致某些宏被解析成了默认值生成的拓扑跟你的设计完全不一致。建议生成后立刻用alsatplg导出成文本快速核对里面包含的widget列表alsatplg -c -o out.text in.tplg6.3 编译时报unknown arch或汇编错误如果你配置平台时传错了TARGET或者工具链路径指错编译阶段会出各种奇怪错误。解决办法清空build目录重新来不要在原目录上反复改参数。rm -rf build_sof mkdir build_sof cd build_sof cmake ../sof -DTARGETxtensa-cavs25 -DBOARD...另外XTNESA工具链版本与SOF版本存在兼容性关系。官方会在发布说明里注明推荐的工具链版本不要盲目用最新工具链。有些新工具链默认启用了更激进的优化反而会让固件跑起来崩溃。6.4 DSP运行崩溃现象dmesg报sof-audio-pci ... error: DSP panic或者音频卡死日志里有FW panic字样。处理思路先确认是不是固件配置问题比如heap分配太小尝试在menuconfig里增大HEAP_SIZE重新编译。如果刚改过topology优先怀疑某个widget的input/output buffer配置不匹配比如sink widget没定义。检查是否启用了固件日志使用sof-logger工具拿完整栈信息切换到debugfs日志sof-logger -l /sys/kernel/debug/sof/dma_trace拿到日志后重点关注panic时的PC地址和那几行ERROR级别输出通常能直接定位到是哪个模块的问题。有一个容易被忽略的点部分平台需要bios里开启音频DSP电源管理否则DSP上电异常表现就是加载成功但一启动播放就panic。这时候查硬件电源状态比查代码更有效率。6.5 固件版本与驱动版本不匹配现象驱动能识别固件但提示ABI版本不匹配或加载后声卡无法正常工作。SOF的固件、topology和内核驱动三者之间都有ABI版本约定。固件侧ABI、IPC版本号和topology ABI不一致时驱动会主动拒绝加载或部分功能失效。排查方法查看/sys/kernel/debug/sof/fw_ready里的接口版本。对比/lib/firmware/intel/sof/sof-*.ri中ABI版本。如果跑的是主线内核建议同步把SOF固件也升到对应较新版本避免旧固件新驱动这种组合。6.6 一个实际排查案例我之前调试一台TGL平台设备现象是播放声音时系统无输出。dmesg里没有明显的错误但aplay -l能看到声卡节点tinymix也正常。走了一遍排查后发现固件是自编译的但当时忘了更新topology声卡设备加载时使用了系统自带的老topology老topology将DAI配置成了双声道而我编译的固件侧默认audioreference管道声明了更多通道。处理方案很简单把新topology放进去rmmod再modprobe输出立刻正常。所以说固件和topology就像是一把锁和一把钥匙必须配对使用。单独升级其中一个往往会引发一些看起来特别诡异的问题。7. 从入门到实战的一些个人心得整套流程我走了很多遍以后最想强调的是版本配对这个原则。源码版本、工具链版本、内核驱动版本、topology版本四个东西必须在同一个时间坐标上。SOF这个项目迭代速度非常快跨几个月的版本之间ABI都可能发生不兼容的变化。我见过太多人从GitHub拉最新代码结果编译出来固件在旧内核上根本load不了那不是代码问题是版本错位。另外一个建议是动手前先花半小时把sof/doc/目录下的平台说明和release note翻一遍。这些文档虽然很多写得比较粗糙但关键信息都在比如支持的平台列表、推荐工具链版本、已知坑点。省下的时间远比读文档的时间多。最后如果你只是想在现有Linux系统上自定义一套音频管道不打算动固件源码逻辑那很多时候只需要在tools/topology目录里改m4然后重新生成tplg就够了固件不用重编。先把topology这层吃透再考虑往固件里加新模块这条路走起来会顺畅很多。
返回列表