首页/目录/全部文章

全部文章

八个专题的源码、算法与协议笔记都在这里。

笔记列表

sound 文档索引

sound 文档索引

Linux 6.1(RK3588)内核 ALSA / ASoC 音频 子系统源码分析与原理说明。

总览文档

文档 说明
sound音频子系统机制与原理详解.md 总览:ALSA 分层、目录结构、RK3588 重点
源码目录与模块索引.md sound/ 目录树、Makefile、Kconfig

功能组专题(原理 + 实现)

编号 文档 源码重点
01 ALSA-Core与PCM设备机制与实现详解.md core/sound.cpcm.ccontrol.c
02 ASoC框架与DAPM机制与实现详解.md soc-core.csoc-dapm.csoc-pcm.c
03 Rockchip-DAI控制器机制与实现详解.md soc/rockchip/ I2S/TDM、PDM、SAI、SPDIF
04 Rockchip机器驱动与HDMI机制与实现详解.md multicodecs、HDMI、VAD、ASRC、DLP
05 Codec与其它音频路径机制与实现详解.md soc/codecs/、USB、HDA
06 RK3588平台音频拓扑与使用场景详解.md DT、编解码产品、与 VPU 关系
07 ALSA-PCM运行状态机与数据路径.md open、hw_params、trigger、period、XRUN
08 ASoC组件绑定与DAI-Link详解.md component/DAI/card/runtime/DPCM
09 DAPM-Control-Jack与Topology.md 控件、电源图、插拔检测、拓扑
10 DMAengine-PCM缓冲与实时性.md cyclic DMA、pointer、cache、延迟
11 I2S-TDM时钟格式与RK3588控制器.md BCLK/LRCK/MCLK、slot、TRCM
12 PDM-SPDIF-VAD-ASRC与数字音频.md 数字麦、S/PDIF、VAD、ASRC、DLP
13 HDMI-DP音频与DRM协同.md hdmi-codec、ELD、hotplug、显示边界
14 ALSA-Control-MIDI-Compress与其它接口.md control、timer、rawmidi、seq、compress
15 RK3588音频设备树调试与性能调优.md DT、时钟、XRUN、延迟与排障

源码路径

1
2
rk3588/kernel-6.1/sound/
arch/arm64/boot/dts/rockchip/ # 板级 sound 节点(机器图)

阅读顺序建议

总览01 ALSA Core02 ASoC03 RK DAI04 RK Machine06 RK3588 拓扑05 Codec/USB07 PCM 状态机08 DAI Link09 DAPM/Control10 DMAengine11 I2S/TDM12 数字音频13 HDMI/DP14 其它 ALSA 接口15 RK3588 调试

速查

需求 入口
/dev/snd/* 复用 core/sound.c snd_open
声卡注册 snd_card_new / snd_soc_register_card
DAI 驱动 snd_soc_component_driver
RK3588 I2S/TDM rockchip_i2s_tdm.c rockchip,rk3588-i2s-tdm
RK3588 PDM 麦克风 rockchip_pdm.c(含 rockchip,rk3588-pdm
HDMI 声卡 rockchip_hdmi.c + DRM HDMI
数字 Codec GRF codecs/rk_codec_digital.c

关联文档

  • DRM/HDMI 显示:drivers/gpu/drm(音频与显示绑定)
  • DMA:sound/soc/soc-generic-dmaengine-pcm.c
  • 编解码业务:用户态 GStreamer/MPP 通常 不经过 本目录 ALSA 播放路径(视频有音轨时可能用 HDMI/I2S)

RK3588 kernel-6.1:sound/core 六文件专项分析

RK3588 kernel-6.1:sound/core 六文件专项分析

源码基准:/home/work2/SeagullYpcEncode/rk3588/kernel-6.1/sound/core/。本文仅覆盖:

文件 编入核心模块
sound.c snd.oCONFIG_SND
init.c snd.o
memory.c snd.o
control.c snd.o
misc.c snd.o
device.c snd.o

更完整的 core 目录说明见:rk3588-kernel-6.1-sound-core分析.md、源码走读见:rk3588-kernel-6.1-sound-core源码详细分析.md


总览:各自解决什么问题

文件 作用(一句话) 主要解决的工程问题
sound.c ALSA 字符设备入口minor 注册表 统一 /dev/snd/* major、按 minor 分发 f_ops、类型校验与 snd_card 引用计数
init.c snd_card 生命周期与 热插拔/关闭同步 声卡创建/devres、slots 绑定、register 前对外阻塞控制接口、断开时替换 f_op 防 UAF
memory.c MMIO ↔ 用户空间的 安全拷贝 非 i386/sparc32 上不能直接 copy_*_userioremapped 窗口,需 memcpy_fromio/toio 分段缓冲
control.c Mixer/控制元素 与用户 ioctl snd_kcontrol 链表、读写、事件 snd_ctl_notify、用户创建控件配额 max_user_ctl_alloc_size
misc.c 调试打印、PCI quirk、fasync 延后唤醒release_resource 封装 kill_fasync 与 tasklist 锁死锁;驱动重复 snd_pci_quirk_lookup 样板
device.c 挂在 snd_card 上的 子设备链表 PCM/control 等 注册顺序disconnect/free 顺序CONTROL 最后释放

1. sound.c

1.1 作用

  • alsa_sound_initregister_chrdev 注册 major(默认 CONFIG_SND_MAJOR = 116),snd_fops 仅实现 open;调用 snd_info_init;调试下创建 debugfs sound
  • snd_register_device:分配 struct snd_minor,按 CONFIG_SND_DYNAMIC_MINORS 选择 静态 minor 编码扫描空闲槽位,设 device->devtdevice_add,填入 snd_minors[minor]
  • snd_open:根据 iminor(inode)snd_minor,必要时 autoload_device(模块化下 request_module("snd-card-%d")snd-seq/snd-timer),replace_fops 后调用真实子驱动的 open

1.2 解决的问题

  • 单一 major、多子设备类型:避免每种声卡各自注册字符设备号。
  • snd_lookup_minor_data(minor, type):打开前校验 设备类型,并对 card_ptr get_device,要求调用方 snd_card_unref,避免 snd_card_disconnect 与并发 open 竞态导致悬空指针。
  • snd_unregister_device:按 struct device * 反查 minor,device_del 并释放 snd_minor

2. init.c

2.1 作用(核心 API)

  • snd_card_new / snd_devm_card_new:分配 snd_card(可选 extra_size 嵌入私有数据),snd_card_init 初始化 ctl_dev、链表、电源与 slots 逻辑等;devm 路径在 snd_card_register 后自动排队 snd_card_free
  • snd_card_registerdevice_add(card_dev)snd_device_register_all → 填入全局 snd_cards[]snd_info_card_register;注释强调:此前控制接口对外不可用
  • snd_card_disconnect:置 shutdown,对 snd_monitor_file 链表把 file->f_op 换成 snd_shutdown_f_ops(仅 release 有效),snd_device_disconnect_alldevice_del,从 snd_cards 摘除。
  • snd_card_free / snd_card_free_when_closedwait_for_completion 等到引用归零;内部 snd_device_free_all
  • snd_card_file_add / snd_card_file_remove:跟踪仍打开的 struct fileget_device/put_deviceremove_sleep 唤醒,支撑 「先 disconnect,等全关闭再释放」
  • 其它:snd_card_lockedslots 模块参数、snd_power_*(PM)、snd_component_add 等。

2.2 解决的问题

  • 热插拔/USB 声卡卸载:用户进程仍持有 /dev/snd/* fd 时,断开阶段不能把内核对象立刻 kfree;通过 dummy f_ops + 文件列表 + snd_card_free_when_closed 协调释放顺序。
  • probe 失败与 devressnd_card_free_on_error 文档警告 devres 顺序导致的 UAF,需在错误路径显式 snd_card_free
  • 多卡共存snd_cards_lock bitmap + slots 把逻辑卡槽与模块名绑定。

3. memory.c

3.1 作用

仅两个导出函数:

  • copy_to_user_fromio:从 volatile void __iomem * 读到用户空间。
  • copy_from_user_toio:从用户空间写到 ioremapped MMIO。

在非 __i386__ / CONFIG_SPARC32 上,使用 栈上 256 字节缓冲区memcpy_fromiocopy_to_user(或反向),避免把 __iomem 指针非法当作普通指针参与 copy_to_user

3.2 解决的问题

  • 架构上与 MMIO 访问模型不一致:legacy ISA/PCI 声卡驱动在 ioctl 或 hwdep 里批量读写寄存器时,需要 正确、可移植 的搬运例程;RK3588 上以 SoC MMIO + regmap 为主,直接用到本文件的场景较少,但 Core 仍保留给通用 ALSA 驱动。

4. control.c

4.1 作用

  • snd_ctl_opensnd_lookup_minor_data(..., SNDRV_DEVICE_TYPE_CONTROL)snd_card_file_add,分配 snd_ctl_file(事件队列、change_sleeppreferred_subdevice),挂入 card->ctl_files
  • snd_ctl_release:从控件上剥离 owner(用户创建的 volatile 控件)、snd_fasync_free、清空事件队列、snd_card_file_remove
  • snd_ctl_notify / snd_ctl_notify_one:向已 subscribe 的 ctl 客户端投递 snd_kctl_eventwake_up + snd_kill_fasync(异步 IO);可在 原子上下文调用。
  • snd_ctl_newsnd_ctl_add(及变体):创建 snd_kcontrolvd[] 多元素、MAX_CONTROL_COUNT),分配 numid,挂 card->controls,向用户空间广播添加事件。
  • ioctl 层SNDRV_CTL_IOCTL_*(枚举控件、读写值、订阅等);snd_ctl_layer_ops 钩子供扩展;snd_register_*_ioctl 允许其它子系统注册额外 ioctl。
  • 模块参数max_user_ctl_alloc_size(默认 8MiB)限制 用户态分配型控件,减轻恶意/故障进程的内存放大。

4.2 解决的问题

  • 统一 mixer 与控制通道:音量、路由、开关等与 PCM 数据路径解耦,并通过 poll/epoll 感知变化(热点切换、HDMI ELD 等常经 snd_ctl_notify)。
  • 并发与安全controls_rwsemctl_files_rwlock;关闭 fd 时清理 snd_ctl_file 归属的 volatile 控件

说明:32 位 ABI 的 ioctl 兼容在 control_compat.c(同一 snd.o),本文不展开。


5. misc.c

5.1 作用

  • CONFIG_SND_DEBUGdebug 模块参数 + __snd_printk(可选 CONFIG_SND_VERBOSE_PRINTK 带文件名行号)。
  • release_and_free_resourcerelease_resource + kfree,避免驱动重复样板。
  • CONFIG_PCIsnd_pci_quirk_lookup(_id):按 subsystem vendor/device 匹配 snd_pci_quirk 表(子设备 mask),供 PCI 声卡 quirks。
  • snd_fasync_helper / snd_kill_fasync / snd_fasync_free:用 schedule_work(snd_fasync_work) 在工作队列里调用 kill_fasync,注释写明动机是 避开 tasklist_lock 等与 kill_fasync 相关的死锁;PCM/control 等 fasync 应走此封装。

5.2 解决的问题

  • 在中断或持锁上下文触发异步通知:推迟 kill_fasync,降低与进程扫描锁的交互风险。
  • PCI DMI/SSID 桌面条目:集中 quirk 查找逻辑。

6. device.c

6.1 作用

  • snd_device_new:按 enum snd_device_type 递增顺序插入 card->devicesdevice_data 指针唯一标识子对象)。
  • snd_device_register / snd_device_register_all:调用 ops->dev_register,状态 BUILD → REGISTERED
  • snd_device_disconnect_all反向遍历,dev_disconnect
  • snd_device_free_all:先释放 SNDRV_DEV_CONTROLSNDRV_DEV_LOWLEVEL 的所有节点,再 第二轮释放剩余,保证 control 最后 teardown。
  • snd_device_get_state:查询 BUILD / REGISTERED / DISCONNECTED

6.2 解决的问题

  • 注册次序语义include/sound/core.h 规定 SNDRV_DEV_CONTROL 必须枚举最后一项,以便 mixer 在 PCM/rawmidi 等设备就绪后再暴露。
  • 卸载次序与引用:防止 control 过早拆除导致其它子设备仍暴露 sysfs/ioctl 路径。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
enum snd_device_type {
SNDRV_DEV_LOWLEVEL,
SNDRV_DEV_INFO,
SNDRV_DEV_BUS,
SNDRV_DEV_CODEC,
SNDRV_DEV_PCM,
SNDRV_DEV_COMPRESS,
SNDRV_DEV_RAWMIDI,
SNDRV_DEV_TIMER,
SNDRV_DEV_SEQUENCER,
SNDRV_DEV_HWDEP,
SNDRV_DEV_JACK,
SNDRV_DEV_CONTROL, /* NOTE: this must be the last one */
};

7. 与 RK3588 / ASoC 的实务关系

  • Rockchip 机器驱动与 Codec 主要在 sound/soc,但最终仍调用 snd_card_new/snd_card_register,并通过 snd_device_new 挂上 PCM、control 等;理解 init.c + device.c 有助于排查 probe 顺序、snd_card_register 时机 问题。
  • misc.csnd_kill_fasyncpcm_libsnd_pcm_period_elapsed 末尾 snd_kill_fasync(runtime->fasync, ...) 配合,影响 用户态 SIGIO/POLL 行为。
  • memory.c纯 ASoC + regmap 路径通常不是热点,保留为 ALSA 通用基础设施。

8. 小结

这六个文件共同构成 ALSA Core 的「设备模型 + 字符入口 + 控制面 + 杂项工具」

  • sound.c:对谁 open、minor 是什么;
  • init.csnd_card 何时对用户可见、如何 disconnect/free
  • device.c:卡上子对象的 顺序化注册与释放
  • control.cmixer/控件事件通知
  • memory.cMMIO ↔ 用户 拷贝;
  • misc.c调试、PCI quirk、fasync 延后

RK3588 kernel-6.1:sound/core 功能与实现要点

RK3588 kernel-6.1:sound/core 功能与实现要点

源码基准:/home/work2/SeagullYpcEncode/rk3588/kernel-6.1/sound/core/

六文件专项(sound.cinit.cmemory.ccontrol.cmisc.cdevice.c)见: rk3588-kernel-6.1-sound-core-sound-init-memory-control-misc-device分析.md

源码级走读(调用链、状态机、snd_pcm_period_elapsed、compress 状态等)见: rk3588-kernel-6.1-sound-core源码详细分析.md

此处实现 ALSA 内核通用中层(Core):在用户可见硬件驱动之上、sound/socsound/usbsound/pci 等之下,统一 snd_card、字符设备 minor、ioctl 语义与 PCM/Control 生命周期。公开数据结构:include/sound/*.h(如 core.hpcm.hcontrol.h)。

内核文档入口:Documentation/sound/kernel-api/writing-an-alsa-driver.rst

整机 sound 分层可与 linuxDoc/sound/rk3588-kernel-6.1-sound声卡架构分析.md 对照阅读:彼处偏重 sound/soc + Rockchip;本文只做 sound/core 的文件级拆解。


0. 代码的作用与解决了什么问题

0.1 作用(要解决的核心痛点)

在没有 ALSA Core 时,各家声卡驱动会直接占用 misc/自建字符设备,ioctl、缓冲区生命周期与电源管理 互不兼容sound/core 解决:

  • 统一「声卡」容器snd_cardinit.c):分配编号、slots 参数绑定模块、snd_card_register/free/disconnect 与用户可见 /proc/asound/dev/snd/* 挂钩;
  • 有序挂载子组件device.csnd_device_newenum snd_device_type(CONTROL、PCM、RAW MIDI…)排序链表,统一 dev_register/dev_disconnect/dev_free 顺序;
  • 控制面抽象control.csnd_kcontrolSNDRV_CTL_IOCTL_*、用户控件与安全 max_user_ctl_alloc_size 上限;
  • 数据面 PCMpcm*.c + memalloc.csnd_pcm / substream/runtime、hw/sw params、mmapioctl 与用户 pcm_native 路径;
  • 时间基准timer.c、可选 hrtimer.cpcm_timer.cSND_PCM_TIMER):节拍器 / PCM 定时对齐;
  • 其它 ALSA 子设备rawmidi.chwdep.ccompress_offload.cseq/(序列器)、jack.c/ctljack.c(上报插头)、可选 control_led.c
  • 观测与调试info.c/proc/asound 相关信息,SND_PROC_FS)、pcm_drm_eld.cpcm_iec958.c
  • 历史兼容sound_oss.coss/ 目录:/dev/dsp/dev/mixer 等仿真路径;
  • 入口 gluesound.c:全局 snd_major、minor 表、打开 /dev/snd/* 时的 snd_lookup_minor_data、模块按需 request_module

0.2 明确解决的工程问题

问题 sound/core 中的典型位置
多块卡、多 PCM、minor 冲突与索引混乱 sound.c minor 路由 + snd_cards[]
热插拔/卸载时仍打开的 /dev/snd/* init.csnd_monitor_fileshutdown_list、断开后再收尾
PCM DMA buffer / coherent / mmap pcm_memory.cmemalloc.cmemory.cmisc.c
SoC DMAEngine 通用 PCM pcm_dmaengine.c(独立 snd-pcm-dmaengine.oCONFIG_SND_DMAENGINE_PCM
虚拟主音量链路(共享控件聚合) vmaster.c
Legacy ISA DMA isadma.cCONFIG_ISA_DMA_API

1. 顶层 Makefile 产物映射(节选)

来自 sound/core/Makefile

内核对象 组成文件(节选) CONFIG
snd.o sound.oinit.omemory.ocontrol.omisc.odevice.o;可选 info[_oss].oisadma.osound_oss.ovmaster.ojack/ctljack CONFIG_SND
snd-pcm.o pcm.opcm_native.opcm_lib.opcm_misc.opcm_memory.omemalloc.o;可选 pcm_timerpcm_drm_eldpcm_iec958 CONFIG_SND_PCM
snd-pcm-dmaengine.o pcm_dmaengine.c CONFIG_SND_DMAENGINE_PCM
snd-timer.o timer.c(及 timer_compat CONFIG_SND_TIMER
snd-hrtimer.o hrtimer.c CONFIG_SND_HRTIMER
snd-rawmidi.o rawmidi.c CONFIG_SND_RAWMIDI
snd-hwdep.o hwdep[_compat].c CONFIG_SND_HWDEP
snd-seq-device.o seq_device.c CONFIG_SND_SEQ_DEVICE
snd-compress.o compress_offload.c CONFIG_SND_COMPRESS_OFFLOAD
oss/seq/ OSS PCM/Mixer、MIDI 序列器全栈 SND_OSSEMULSND_SEQUENCER

Kconfig 位于 sound/core/Kconfig,细分 PCM IEC958、OSS emulation、compress design(参见 Documentation/sound/designs/*.rst)。


2. 关键源文件职责一览(不含 seq/oss 全部枚举)

文件 职责概要
sound.c ALSA 字符设备 major/minor、设备节点查找、snd_request_card
init.c snd_card/snd_cardsslots、监控打开的 file 以防卸下过早释放
device.c snd_device_*:PCM/control 等设备链表及注册顺序
memory.c Core 侧内存助手(与 PCM pcm_memory 分工协作)
misc.c 各式共用 helper
control.c Mixer/control ioctl、kctl 链表与用户控件限额
control_compat.c/control_led.c 兼容 ioctl、LED 类控件扩展
pcm.c snd_pcm_new/链表,绑定 snd_device
pcm_native.c 面向用户的 PCM ioctl、mmappoll 主路径
pcm_lib.c 缓冲区、boundaryavail、XRUN、snd_pcm_period_elapsed 等中段逻辑
pcm_misc.c PCM 杂项
pcm_compat.c 32 位 ioctl 兼容
pcm_timer.c PCM 关联 snd_timer
timer.c 内核节拍定时器抽象(兼容代码通过 #include "timer_compat.c" 并入)
hrtimer.c 高精度定时后端
rawmidi.c 原始 MIDI 流
hwdep.c 硬件依赖 / FW loader / vendor ioctl 通道
compress_offload.c 压缩音频 offload(Documentation/sound/designs/compress-offload.rst
info.cinfo_oss.c /proc/asound 相关信息
jack.cctljack.c 声学插头开关与用户控件映射
seq/ ALSA Sequencer:端口、队列、FIFO、dummy、synth、seq/oss 兼容

3. 子目录 seq/oss/

  • seq/:MIDI 与时间驱动的序列协议栈(内核 sequencer)。依赖 CONFIG_SND_SEQUENCER(上层 sound/core/Makefile 引入)。
  • oss/oss/dev/dspmixerseq 仿真(设计:Documentation/sound/designs/oss-emulation.rst)。

二者与现代 PulseAudio/PipeWire 经由 ALSA lib 的路径并行存在:BSP 常按需裁剪 OSS。


4. 与 RK3588 的关系

CONFIG_SND=y(RK BSP 典型),snd-* 核心模块即会被链接或模块化;Rockchip ASoC(sound/soc/rockchip/* 最终在 snd_card_register() 前后通过这些 Core API 暴露 PCM/control。调试 aplay -lamixer 所见节点,背后均由 sound/coresnd_pcm_*snd_ctl_*/dev/snd/* minor 管理支撑。


5. 小结

sound/core 提供 ALSA 「一张卡 + 多子设备 + 统一字符接口」 的中层实现:PCM 负责音频样本路径,control 负责配置与通知,timer/rawmidi/hwdep/compress/seq 覆盖附属能力,OSS 维持旧 API。RK3588 音频驱动开发主要工作在 sound/soc,但理解 sound/core 才能理清 snd_pcm_open → ops → DMAsnd_ctl_notify 的全链路。

RK3588 kernel-6.1:sound/core 源码级详细分析

RK3588 kernel-6.1:sound/core 源码级详细分析

源码基准:/home/work2/SeagullYpcEncode/rk3588/kernel-6.1/sound/core/

本文在 linuxDoc/sound/rk3588-kernel-6.1-sound-core分析.md(模块清单与 Makefile 映射)之上,按 调用链、状态机、锁与资源 做更深一层的代码走读。整机 sound 与 ASoC/Rockchip 关系仍以 rk3588-kernel-6.1-sound声卡架构分析.md 为准。


1. 总体数据与控制通路

平面 典型入口 内核对象 用户可见形态
控制面 snd_ctl_* snd_kcontrolsnd_card.controls /dev/snd/controlC*SNDRV_CTL_IOCTL_*
PCM 数据面 snd_pcm_* snd_pcmsnd_pcm_substreamsnd_pcm_runtime /dev/snd/pcmC*D*i/pcmC*D*o
字符设备入口 sound.c snd_minors[]struct snd_minor major CONFIG_SND_MAJOR(116)
Compress offload compress_offload.c snd_comprsnd_compr_stream /dev/snd/comprC*D*

底层 SoC 驱动(如 sound/soc/rockchip/*)主要在 snd_pcm_ops(或 dmaengine 封装)里衔接 DMA;sound/core 负责 统一 minor、ioctl、缓冲模型、XRUN/avail 逻辑与安全断开


2. 字符设备与 minor:sound.c

2.1 打开设备的二次分发

所有 /dev/snd/* 节点共享同一 snd_fops,仅在 open 时根据 minor 取出事先注册的 struct snd_minor,再 replace_fops 成具体子系统的 file_operations

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
static int snd_open(struct inode *inode, struct file *file)
{
unsigned int minor = iminor(inode);
struct snd_minor *mptr = NULL;
const struct file_operations *new_fops;
int err = 0;

if (minor >= ARRAY_SIZE(snd_minors))
return -ENODEV;
mutex_lock(&sound_mutex);
mptr = snd_minors[minor];
if (mptr == NULL) {
mptr = autoload_device(minor);
if (!mptr) {
mutex_unlock(&sound_mutex);
return -ENODEV;
}
}
new_fops = fops_get(mptr->f_ops);
mutex_unlock(&sound_mutex);
if (!new_fops)
return -ENODEV;
replace_fops(file, new_fops);

if (file->f_op->open)
err = file->f_op->open(inode, file);
return err;
}

要点

  • CONFIG_MODULESautoload_device 可对 aloadC* 触发 request_module("snd-card-%d") 或对 sequencer/timer 触发 snd-seq/snd-timer,实现「打开节点再按需加载」的旧式自动装载路径。
  • CONFIG_SND_DYNAMIC_MINORSsnd_find_free_minor 跳过仍为 autoload 保留的 static minor,其余动态分配。

2.2 snd_lookup_minor_data 与声卡引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void *snd_lookup_minor_data(unsigned int minor, int type)
{
struct snd_minor *mreg;
void *private_data;

if (minor >= ARRAY_SIZE(snd_minors))
return NULL;
mutex_lock(&sound_mutex);
mreg = snd_minors[minor];
if (mreg && mreg->type == type) {
private_data = mreg->private_data;
if (private_data && mreg->card_ptr)
get_device(&mreg->card_ptr->card_dev);
} else
private_data = NULL;
mutex_unlock(&sound_mutex);
return private_data;
}

子驱动 open(如 compress)在匹配 SNDRV_DEVICE_TYPE_* 成功后持有 card 引用,须在出口 snd_card_unref,避免 snd_card_disconnect 期间 UAF


3. 声卡生命周期与热插拔:init.c

3.1 snd_card_register 末尾才真正对用户开放控制面

注释写明:在调用本函数之前,ALSA 控制接口对外阻塞;函数内先 device_add(&card->card_dev),再 snd_device_register_all,最后把 card 填入全局 snd_cards[]snd_info_card_register

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
* snd_card_register - register the soundcard
* @card: soundcard structure
*
* This function registers all the devices assigned to the soundcard.
* Until calling this, the ALSA control interface is blocked from the
* external accesses. Thus, you should call this function at the end
* of the initialization of the soundcard.
*
* Return: Zero otherwise a negative error code if the registration failed.
*/
int snd_card_register(struct snd_card *card)
{
int err;

if (snd_BUG_ON(!card))
return -EINVAL;

if (!card->registered) {
err = device_add(&card->card_dev);
if (err < 0)
return err;
card->registered = true;
} else {
if (card->managed)
devm_remove_action(card->dev, trigger_card_free, card);
}

if (card->managed) {
err = devm_add_action(card->dev, trigger_card_free, card);
if (err < 0)
return err;
}

err = snd_device_register_all(card);

驱动编写含义:先 snd_pcm_new/snd_ctl_add 等把逻辑对象挂上 snd_card,最后 snd_card_register;否则会违背「控制设备尚未完整注册」的假设。

3.2 监控打开的 struct filesnd_monitor_file

init.c 维护 shutdown_files:在 snd_card_disconnect 路径上,对已打开的 file 替换为 snd_disconnect_f_ops,避免用户进程在设备释放后仍调用原 read/write/ioctl。这是 USB 声卡热插拔 / 模块卸载 不 panic 的关键之一(细节见 init.csnd_shutdownregister_shutdown_file 等)。


4. 设备链表与注册顺序:device.c

4.1 snd_device_type 决定排序与 register_all 次序

include/sound/core.h 中枚举顺序 同时定义调用顺序SNDRV_DEV_CONTROL 必须排在最后(注释),以便其它 PCM/rawmidi 等先注册, mixer 最后再挂钩:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
enum snd_device_type {
SNDRV_DEV_LOWLEVEL,
SNDRV_DEV_INFO,
SNDRV_DEV_BUS,
SNDRV_DEV_CODEC,
SNDRV_DEV_PCM,
SNDRV_DEV_COMPRESS,
SNDRV_DEV_RAWMIDI,
SNDRV_DEV_TIMER,
SNDRV_DEV_SEQUENCER,
SNDRV_DEV_HWDEP,
SNDRV_DEV_JACK,
SNDRV_DEV_CONTROL, /* NOTE: this must be the last one */
};

snd_device_newtype 递增插入链表:

1
2
3
4
5
6
7
8
/* insert the entry in an incrementally sorted list */
list_for_each_prev(p, &card->devices, list) {
struct snd_device *pdev = list_entry(p, struct snd_device, list);
if ((unsigned int)pdev->type <= (unsigned int)type)
break;
}

list_add(&dev->list, p);

4.2 disconnect_allfree_all 的顺序

  • snd_device_disconnect_all反向遍历,先断开高层可见设备。
  • snd_device_free_all两阶段——先释放除 SNDRV_DEV_CONTROLSNDRV_DEV_LOWLEVEL 外的节点,再收尾剩余(避免 control 过早拆除引用仍在的 PCM):
1
2
3
4
5
6
7
8
9
10
11
list_for_each_entry_safe_reverse(dev, next, &card->devices, list) {
/* exception: free ctl and lowlevel stuff later */
if (dev->type == SNDRV_DEV_CONTROL ||
dev->type == SNDRV_DEV_LOWLEVEL)
continue;
__snd_device_free(dev);
}

/* free all */
list_for_each_entry_safe_reverse(dev, next, &card->devices, list)
__snd_device_free(dev);

5. PCM 核心:pcm.c / pcm_native.c / pcm_lib.c

5.1 中层职责划分(概念)

文件 职责
pcm.c 创建 snd_pcm、挂 snd_device、全局 snd_pcm_devices 链表、与 snd_minor 绑定
pcm_native.c ioctlmmappollread/write(mmap 路径为主)等 用户态 ABI
pcm_lib.c hw_ptr/appl_ptr 更新、availboundaryXRUNsnd_pcm_period_elapsed、阻塞唤醒 tsleep
pcm_memory.c + memalloc.c 环形缓冲分配、预分配、mmap 页映射
pcm_dmaengine.c 通用 dmaengine PCMdma_slave_configsnd_soc_* 常用封装

5.2 snd_pcm_period_elapsed(中断上下文典型路径)

DMA 每完成一个 period,驱动应调用 snd_pcm_period_elapsed(内部抢 substream);若在 **snd_pcm_ops 回调里已持锁,则用 snd_pcm_period_elapsed_under_stream_lock。内核注释说明了 pointer/trigger/get_time_info 可能被间接触发:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
* snd_pcm_period_elapsed_under_stream_lock() - update the status of runtime for the next period
* under acquired lock of PCM substream.
* ...
* Developer should pay enough attention that some callbacks in &snd_pcm_ops are done by the call of
* function:
*
* - .pointer - to retrieve current position of audio data transmission by frame count or XRUN state.
* - .trigger - with SNDRV_PCM_TRIGGER_STOP at XRUN or DRAINING state.
* - .get_time_info - to retrieve audio time stamp if needed.
*
* Even if more than one periods have elapsed since the last call, you have to call this only once.
*/
void snd_pcm_period_elapsed_under_stream_lock(struct snd_pcm_substream *substream)
{
...
if (!snd_pcm_running(substream) ||
snd_pcm_update_hw_ptr0(substream, 1) < 0)
goto _end;

#ifdef CONFIG_SND_PCM_TIMER
if (substream->timer_running)
snd_timer_interrupt(substream->timer, 1);
#endif
_end:
snd_kill_fasync(runtime->fasync, SIGIO, POLL_IN);
}
...
void snd_pcm_period_elapsed(struct snd_pcm_substream *substream)
{
unsigned long flags;
...
snd_pcm_stream_lock_irqsave(substream, flags);
snd_pcm_period_elapsed_under_stream_lock(substream);
snd_pcm_stream_unlock_irqrestore(substream, flags);
}

实践要点:Rockchip I2S/TDM 一类驱动常在 DMA complete 回调snd_pcm_period_elapsed;漏报会导致 应用层 avail 不前进 / 时钟漂移感知异常

5.3 SoC 常用:pcm_dmaengine.c

提供 snd_hwparams_to_dma_slave_config 等,把 hw_params 物理位宽 映射到 dma_slave_buswidth,并与 snd_dmaengine_pcm_trigger 一类封装联动,减少每个 CPU DAI 重复的 prep/start/stop 样板代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* snd_hwparams_to_dma_slave_config - Convert hw_params to dma_slave_config
* @substream: PCM substream
* @params: hw_params
* @slave_config: DMA slave config
*
* This function can be used to initialize a dma_slave_config from a substream
* and hw_params in a dmaengine based PCM driver implementation.
*
* Return: zero if successful, or a negative error code
*/
int snd_hwparams_to_dma_slave_config(const struct snd_pcm_substream *substream,
const struct snd_pcm_hw_params *params,
struct dma_slave_config *slave_config)
{
enum dma_slave_buswidth buswidth;
int bits;

bits = params_physical_width(params);
...

6. 控制面:control.c

  • snd_ctl_addsnd_kcontrol 挂入 card,分配 numid,并在已注册声卡上通知用户态。
  • SNDRV_CTL_ELEM_IFACE_* 区分 mixer、PCM、POWER 等;ASoC DAPM 大量控件亦走此路径。
  • snd_ctl_notify/snd_ctl_notify_one:elem 值变化时 poll/epoll 就绪。
  • 模块参数 max_user_ctl_alloc_size(默认 8MiB)限制 用户态创建控件 的分配,减轻 DoS 风险。

7. Compress offload:compress_offload.c

面向 压缩码流(由 DSP/加速器解码而非 PCM ring),使用 与 PCM 类似但独立 的 ioctl 集(SNDRV_COMPRESS_*)。源码用大段注释约定 复用 snd_pcm_state 枚举语义 表达 compress 状态机:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*
* a note on stream states used:
* we use following states in the compressed core
* SNDRV_PCM_STATE_OPEN: When stream has been opened.
* SNDRV_PCM_STATE_SETUP: When stream has been initialized. This is done by
* calling SNDRV_COMPRESS_SET_PARAMS. Running streams will come to this
* state at stop by calling SNDRV_COMPRESS_STOP, or at end of drain.
* SNDRV_PCM_STATE_PREPARED: When a stream has been written to (for
* playback only). User after setting up stream writes the data buffer
* before starting the stream.
* SNDRV_PCM_STATE_RUNNING: When stream has been started and is
* decoding/encoding and rendering/capturing data.
* SNDRV_PCM_STATE_DRAINING: When stream is draining current data. This is done
* by calling SNDRV_COMPRESS_DRAIN.
* SNDRV_PCM_STATE_PAUSED: When stream is paused. This is done by calling
* SNDRV_COMPRESS_PAUSE. It can be stopped or resumed by calling
* SNDRV_COMPRESS_STOP or SNDRV_COMPRESS_RESUME respectively.
*/

snd_compr_open 根据 O_RDONLY/O_WRONLY 区分 capture/playback,并与 compr->direction 一致性校验;失败路径须 snd_card_unref


8. 其它 core 模块(简析)

模块 文件 说明
Timer timer.c(内含 #include "timer_compat.c")、hrtimer.c snd_timer 用户接口与高精度后端;pcm_timer.c 把 PCM 与 timer 中断联动
Raw MIDI rawmidi.c /dev/snd/midiC*D*;末尾 #include "rawmidi_compat.c"
Hwdep hwdep.c 厂商 ioctl、firewire、FW 下载等旁路
Jack jack.cctljack.c 上报插头事件;可与 input 子系统联动(SND_JACK_INPUT_DEV
VMaster vmaster.c 软音量 虚拟 master,聚合多路 slider
info info.c /proc/asound 信息树(CONFIG_SND_PROC_FS
OSS sound_oss.coss/ 旧 OSS API 兼容层
Sequencer seq/ ALSA sequencer 与 seq/oss
IEC958 / ELD pcm_iec958.cpcm_drm_eld.c S/PDIF、HDMI 音频元数据
ISA DMA isadma.c 遗留 ISA 声卡

9. 与 RK3588 开发的关联方式

  1. 绝大多数板级工作不在 sound/core,而在 sound/soc/rockchip 与 codec machine;但 PCM 回调契约trigger/pointer/hw_params)与 snd_pcm_period_elapsed 出自 core。
  2. DMAEngine PCM 路径:CONFIG_SND_SOC_GENERIC_DMAENGINE_PCM + pcm_dmaengine.c 导出符号,与 rockchip_i2s_tdm.c 等平台驱动组合常见。
  3. 调试:XRUN、延迟问题通常需同时看 pcm_lib 的 avail/wait 与驱动侧 DMA 回调频率 是否匹配 period_size

10. 参考文档

rk3588/kernel-6.1 sound 声卡架构分析

rk3588/kernel-6.1 sound 声卡架构分析

本文面向 rk3588/kernel-6.1/sound,重点分析 ALSA/ASoC 在内核中的分层、对象模型、主数据路径与 Rockchip 适配方式,帮助你把“声卡驱动代码”放进整体内核架构里理解。


1. sound 子系统总体分层

sound/Makefilesound/Kconfig 可以看到该目录有三层语义:

  1. 通用声卡核心层(ALSA Core)

    • 入口配置:CONFIG_SND
    • 关键目录:sound/core/
    • 提供 snd_card、control(混音器/控制)、PCM、timer、rawmidi 等统一抽象。
  2. SoC 音频框架层(ASoC)

    • 入口配置:CONFIG_SND_SOC
    • 关键目录:sound/soc/
    • 将 SoC 音频拆成 CPU DAI、Codec、Platform/Machine 三类组件,用 snd_soc_card + snd_soc_dai_link 进行装配。
  3. 平台/总线具体实现层

    • 目录:sound/soc/rockchip/sound/usb/sound/hda/sound/pci/
    • 这里放控制器驱动、编解码器绑定、板级 machine driver。

对 RK3588 而言,主路径通常是:
ASoC Core (sound/soc) + Rockchip CPU DAI 控制器 (sound/soc/rockchip/*) + 外部/内部 Codec + 板级 machine card。


2. ALSA Core 关键对象与生命周期

2.1 声卡对象 snd_card

sound/core/init.c 中,典型生命周期是:

  • snd_card_new():分配并初始化声卡对象(含控制链表、文件跟踪、sysfs 设备等)
  • 驱动创建并挂接各子设备(control/pcm/…)
  • snd_card_register():把 card 及其子设备真正注册给用户空间可见
  • 热插拔/卸载:snd_card_disconnect() / snd_card_free()

这说明 snd_card 是 ALSA 的“根设备容器”,其下挂所有控制与数据平面对象。

2.2 控制面(Mixer/Control)

sound/core/control.c 中核心是 snd_kcontrol

  • 创建控制项:snd_ctl_new1()
  • 加入声卡:snd_ctl_add()
  • 用户态通过 SNDRV_CTL_IOCTL_* 读写控制值
  • 变化通知:snd_ctl_notify()snd_ctl_notify_one()

因此控制面是“状态配置 + 事件通知”通道,不直接搬运音频样本数据。

2.3 数据面(PCM)

sound/core/pcm.c 中核心是 snd_pcm / snd_pcm_substream / snd_pcm_runtime

  • 创建设备:snd_pcm_new()
  • 设置流操作:snd_pcm_set_ops()
  • 注册字符设备:snd_pcm_dev_register(),映射到 /dev/snd/pcmC*D*[pc]
  • 运行时通过 open/hw_params/prepare/trigger/pointer 协议驱动 DMA/中断传输

3. ASoC 架构:把 SoC 音频“组装成卡”

sound/soc/soc-core.c 是 ASoC 主轴。核心对象关系:

  • snd_soc_component:可复用组件(CPU DAI 控制器、Codec、平台组件)
  • snd_soc_dai:具体数字音频接口端点
  • snd_soc_dai_link:描述 CPU DAI 与 Codec DAI 的连线关系
  • snd_soc_pcm_runtime (rtd):每条 link 的运行时对象
  • snd_soc_card:整卡聚合对象,最终绑定到 snd_card

3.1 绑定流程(核心调用链)

机器驱动调用 snd_soc_register_card() 后,进入:

  1. snd_soc_bind_card()
  2. snd_soc_add_pcm_runtime()(为每条 dai_link 创建 rtd
  3. snd_card_new()(创建 ALSA card)
  4. soc_probe_link_components() / soc_probe_link_dais()
  5. soc_init_pcm_runtime() -> soc_new_pcm()(创建 PCM 设备)
  6. snd_soc_dapm_*(构建音频电源图与路由)
  7. snd_card_register()(向用户空间发布声卡)

这条链路把“设备树 + 驱动组件”最终落地成 /proc/asound/dev/snd/* 可见设备。

3.2 DAPM 的作用

ASoC 不只关心数据传输,还通过 DAPM(Dynamic Audio Power Management)管理模拟/数字路径电源域,减少功耗并抑制 Pop/Click。
soc-core.c 里可看到 snd_soc_dapm_add_routes()snd_soc_dapm_sync()snd_soc_dapm_stream_event() 等关键流程。


4. Rockchip 声卡架构落点(sound/soc/rockchip

sound/soc/rockchip/Kconfig + Makefile 显示 Rockchip 体系可分为两类:

  1. 控制器/DAI 驱动(CPU DAI)
    例如 rockchip_i2s_tdm.crockchip_i2s.crockchip_pdm*.crockchip_spdif*.crockchip_sai.c

  2. Machine/Card 驱动(板级装配)
    例如 rockchip_hdmi.crockchip_rt5645.crockchip_multicodecs.c 等,负责拼接 cpu/codec/platform 并注册 snd_soc_card

RK3588 常见核心在 rockchip_i2s_tdm.c(控制器)与具体 machine 驱动组合。


5. 典型驱动解剖

5.1 CPU DAI:rockchip_i2s_tdm.c

这是 Rockchip I2S/TDM 控制器主驱动,关键点:

  • DAI opsrockchip_i2s_tdm_dai_ops
    实现 startup/shutdown/hw_params/hw_free/trigger/set_fmt/set_sysclk/set_tdm_slot
  • 组件注册devm_snd_soc_register_component(..., &rockchip_i2s_tdm_component, i2s_tdm->dai, 1)
    把 component + dai 暴露给 ASoC core。
  • 平台 PCM 对接rockchip_i2s_tdm_register_platform()
    按场景注册 snd_dmaengine_pcm、TRCM 或 DLP。
  • 中断与 XRUNrockchip_i2s_tdm_isr()
    处理 TX underrun / RX overrun,并调用 snd_pcm_stop_xrun()
  • PM 运行时电源i2s_tdm_runtime_suspend() / i2s_tdm_runtime_resume()
    维护时钟、regcache、pinctrl、DMA 状态。

说明该文件同时承担:寄存器控制 + DMA 数据平面 + ASoC DAI 接口 + PM。

5.2 Machine Driver:rockchip_hdmi.c

rk_hdmi_probe() 做的事很典型:

  • 从 DT 解析 card 名称、CPU/Codec 节点、dai_fmtmclk-fs
  • 组装 snd_soc_dai_link(cpus/codecs/platforms)
  • 设置 ops(如 rk_hdmi_hw_params,配置 CPU/Codec sysclk)
  • devm_snd_soc_register_card() 注册整卡

其中 rk_dailink_init() 还创建 HDMI jack 与 DAPM widget,体现 machine driver 对“板级音频拓扑”的职责。


6. 从用户态到硬件的数据路径

以播放为例,主链路可抽象为:

  1. 用户态 alsa-lib/tinyalsa 打开 pcmC*D*p
  2. sound/core/pcm.c 分配 snd_pcm_substream/runtime
  3. hw_params 下发到 ASoC runtime
  4. ASoC 调到 CPU DAI rockchip_i2s_tdm_hw_params()
  5. 配置格式/通道/时钟/slot/DMA 参数
  6. trigger(START) -> 使能 DMA 与 I2S/TDM XFER
  7. 中断/DMA 推进,pointer 上报 hw_ptr
  8. 异常时 IRQ 触发 XRUN 处理

控制面(音量、开关、路由)并行走 control.csnd_kcontrol 机制,不与 PCM 样本搬运混淆。


7. RK3588 视角下的配置与裁剪建议

  1. 最小可用链路
    CONFIG_SND + CONFIG_SND_SOC + CONFIG_SND_SOC_ROCKCHIP + 对应接口(如 SND_SOC_ROCKCHIP_I2S_TDM)+ 目标 codec/machine 配置。

  2. 按硬件接口启用
    仅启用实际使用的 I2S/TDM/PDM/SPDIF/SAI,减少镜像体积和 probe 噪音。

  3. DT 与驱动一致性
    sound-dairockchip,cpurockchip,codec、路由与时钟属性必须与 machine/cpu dai 预期一致,否则常见 -EPROBE_DEFER 或注册失败。


8. 调试抓手(实战)

  1. 设备枚举

    • /proc/asound/cards
    • /proc/asound/pcm
  2. ASoC 拓扑与 DAPM

    • debugfs: /sys/kernel/debug/asoc/
    • 检查 dai 列表、component 列表、路由与电源状态
  3. 关键日志定位

    • snd_soc_register_card failed
    • CODEC DAI ... not registered(通常是 probe 顺序或 DT 绑定问题)
    • TX FIFO Underrun / RX FIFO Overrun(带宽、时钟、DMA阈值、buffer 参数问题)
  4. 优先核对四件事

    • dai_fmt(I2S/LJ/RJ、主从、极性)
    • mclk/bclk/lrck 比例
    • DMA burst/period/buffer
    • DAPM route 与 pin switch

9. 结论

sound 子系统在 RK3588 内核中本质是“ALSA Core + ASoC 装配框架 + Rockchip CPU DAI/Machine 实现”的三层协作:

  • ALSA Core 提供统一用户态 ABI 与控制/PCM 抽象;
  • ASoC 负责组件发现、link 绑定、DAPM 电源与运行时 orchestration;
  • Rockchip 驱动将 I2S/TDM 等硬件细节映射为标准 DAI/PCM 操作。

理解 snd_card -> snd_soc_card -> dai_link/rtd -> cpu_dai_ops 这条主线后,定位声卡问题会明显更快:先判断是“卡没起来(绑定问题)”还是“流不通(时钟/格式/DMA问题)”。

sound/ 音频子系统机制与原理详解

sound/ 音频子系统机制与原理详解

源码路径rk3588/kernel-6.1/sound/
内核版本:Linux 6.1(RK3588 / ARM64)
文档目录linuxDoc/sound/

Linux 音频栈分为 ALSA Core(字符设备、PCM、控制接口)与 ASoC(SoC 嵌入式:CPU DAI + Codec + Machine)。RK3588 产品几乎走 ASoC + sound/soc/rockchip/,HDMI/扬声器/麦克风由 设备树声卡 描述。


目录


一、分层架构

用户态sound/coresound/socsoc/rockchip硬件/Codecalsa-lib / PulseAudio / tinyalsasound.c minorspcm.c control.csoc-core soc-pcmsoc-dapmi2s_tdm pdm hdmiI2S PDM SPDIF HDMI IPES8388 / HDMI codec / PDM mic
回答的问题
ALSA Core 如何暴露 /dev/snd/pcmC0D0p
ASoC 如何把 CPU、Codec、板级连线成 声卡
Rockchip DAI 如何操作 SoC 内置 I2S/PDM 寄存器与 DMA
Machine 哪条 DAI link、哪个 GPIO 控功放

二、目录职责

目录 体量 职责
core/ 声卡、PCM、混音、控制、proc
soc/ 最大 ASoC 框架 + 各 SoC 厂商 + codecs
soc/rockchip/ 49 文件 RK3588 音频 IP 驱动
usb/ USB Audio Class
hda/ Intel HD Audio(RK 不用)
pci/drivers/ 其它主机接口

sound_core.c:旧版 /dev/dsp 等,与 CONFIG_SOUND 相关;现代播放走 CONFIG_SND


三、RK3588 在树中的位置

  • CPU DAIrockchip,rk3588-i2s-tdmrk3588-pdmrk3588-spdif
  • 板级rockchip_multicodecsrockchip_hdmi 等平台驱动
  • 片上数字 Codecrockchip,rk3588-codec-digital(GRF 配置,非完整 AC97)
  • VADrockchip,rk3588-vad(语音检测,可接 I2S1/PDM0)
  • ASRC/DLP:树中包含采样率转换和环回驱动;其中本分支 Rockchip ASRC match table 不含 RK3588,DLP/ASRC 是否可用必须以 SoC compatible 和板级拓扑为准

独立 sound/drivers/rockchip;RK 代码集中在 soc/rockchip


四、数据流概览

播放(Playback)

1
用户态 write → ALSA PCM → soc-pcm → DAI startup → DMA 到 I2S FIFO → 外置 Codec / HDMI

采集(Capture)

1
PDM/I2S RX → DMA → PCM capture buffer → 用户态 read

DAPM:按 dapm_route 在 stream 开关时自动上电/静音相关 widget,降低 pop 与功耗。


功能组专题

编号 文档
01 ALSA-Core与PCM设备机制与实现详解.md
02 ASoC框架与DAPM机制与实现详解.md
03 Rockchip-DAI控制器机制与实现详解.md
04 Rockchip机器驱动与HDMI机制与实现详解.md
05 Codec与其它音频路径机制与实现详解.md
06 RK3588平台音频拓扑与使用场景详解.md

sound/ 源码目录与模块索引

sound/ 源码目录与模块索引

源码路径rk3588/kernel-6.1/sound/(约 2800 文件)
内核版本:Linux 6.1(RK3588 / ARM64)
文档目录linuxDoc/sound/


一、顶层 Makefile

1
2
obj-$(CONFIG_SOUND) += soundcore.o      # sound_core.c 旧 OSS 入口
obj-$(CONFIG_SND) += core/ i2c/ drivers/ isa/ pci/ arm/ ... soc/ hda/ usb/ ...
CONFIG 说明
CONFIG_SND 现代 ALSA
CONFIG_SND_SOC ASoC(嵌入式必选)
CONFIG_SND_SOC_ROCKCHIP RK 平台 DAI

二、一级子目录(RK 产品相关度)

目录 相关度 说明
core/ ALSA 核心:card、pcm、control、timer
soc/ 最高 ASoC + soc/rockchip/
soc/codecs/ 外置/内置 Codec(含 rk_codec_digital.c
usb/ USB 声卡
hda/ PC HD Audio(RK 一般无)
pci/isa/ 桌面遗留
drivers/ 杂项声卡驱动
firewire/virtio/ 特殊平台

soc/ 内其它厂商ti/qcom/samsung/sof/ 等(RK 树内保留上游代码)。


三、soc/rockchip/ 文件(49 个)

模块对象 源文件 功能
snd-soc-rockchip-i2s-tdm rockchip_i2s_tdm.c I2S/TDM 主控制器(RK3588 多路)
snd-soc-rockchip-i2s rockchip_i2s.c 旧版 I2S
snd-soc-rockchip-pdm rockchip_pdm.c PDM 数字麦
snd-soc-rockchip-pdm-v2 rockchip_pdm_v2.c PDM v2(match 不含 RK3588)
snd-soc-rockchip-sai rockchip_sai.c 通用 rockchip,sai-v1,需按 DTS 判断
snd-soc-rockchip-spdif rockchip_spdif.c SPDIF 发
snd-soc-rockchip-spdifrx rockchip_spdifrx.c SPDIF 收
snd-soc-rockchip-hdmi rockchip_hdmi.c HDMI 机器驱动
snd-soc-rockchip-multicodecs rockchip_multicodecs.c 多 Codec 板级
snd-soc-rockchip-vad rockchip_vad.c 语音活动检测
snd-soc-rockchip-asrc rockchip_asrc.c 异步采样率转换
snd-soc-rockchip-dlp rockchip_dlp.c 数字环回
snd-soc-rockchip-multi-dais rockchip_multi_dais.c 多 DAI 聚合
未编译遗留文件 rockchip_hdmi_dp.crockchip_cdndp.c 源码存在但未列入本目录 Makefile,不能作为 RK3588 活跃路径
rockchip_utils.c 公共工具

同类未进入 Makefile 的文件还包括 rockchip_hdmi_analog.crockchip_da7219.crockchip_rt5651.crockchip_audio_pwm.c。分析实际运行路径必须同时核对 Makefile/Kconfig,不能只按 .c 文件是否存在判断。


四、ASoC 核心(soc/ 根目录)

文件 作用
soc-core.c 组件/卡注册、PM、链路
soc-dapm.c 动态音频电源管理
soc-dai.c DAI 操作
soc-pcm.c ASoC PCM 中间层
soc-card.c snd_soc_card
soc-link.c DAI link
soc-generic-dmaengine-pcm.c DMAengine PCM

五、RK3588 设备树 compatible(驱动内)

compatible 驱动文件
rockchip,rk3588-i2s-tdm rockchip_i2s_tdm.c
rockchip,rk3588-pdm rockchip_pdm.c
rockchip,rk3588-spdif rockchip_spdif.c
rockchip,rk3588-vad rockchip_vad.c
rockchip,rk3588-codec-digital soc/codecs/rk_codec_digital.c
rockchip,rk3588-spdifrx, rockchip,rk3308-spdifrx DTS 以 fallback 匹配 rockchip_spdifrx.c

rockchip_asrc.c 在本分支仅匹配 RK3506/RK3576;rockchip_pdm_v2.c 仅匹配 RK3506/RK3576/RV1126B,二者都不能当作 RK3588 原生 compatible。

板级 sound 节点 多在 arch/arm64/boot/dts/rockchip/*.dts(i),非 sound/ 目录。


六、已有补充文档

文件 说明
rk3588-kernel-6.1-sound声卡架构分析.md 早期架构速览
rk3588-kernel-6.1-sound-core*.md core 子目录笔记

七、专题对照

主题 文档
ALSA Core 01
ASoC 02
RK DAI 03
RK Machine 04
Codec/USB 05
RK3588 06
PCM 状态机 07
ASoC 绑定/DAI Link 08
DAPM/Control/Jack 09
DMAengine/实时性 10
I2S/TDM 11
数字音频 IP 12
HDMI/DP 13
ALSA 其它接口 14
RK3588 调试调优 15

01 kvm_main 核心机制与实现详解

01 kvm_main 核心机制与实现详解

1. 文件角色

virt/kvm/kvm_main.c(约 6000+ 行)是 所有架构共享的 KVM 核心:字符设备、VM/VCPU 生命周期、Guest 内存槽、MMU notifier、调试fs、停机轮询(halt poll)、与 kvm_arch_* 协作的枢纽。

文件头注释仍提及 Intel VT-x 历史;现代 ARM64 同样依赖此文件的 通用 ioctl 与资源管理

2. 模块入口与 /dev/kvm

1
2
3
4
5
static struct miscdevice kvm_dev = {
KVM_MINOR,
"kvm",
&kvm_chardev_ops,
};

kvm_dev_ioctl 处理:

ioctl 行为
KVM_GET_API_VERSION 返回 KVM_API_VERSION
KVM_CREATE_VM kvm_dev_ioctl_create_vmanon_inode + kvm_vm_fops
KVM_CHECK_EXTENSION 能力探测(含 arch 扩展)
KVM_GET_VCPU_MMAP_SIZE kvm_run 映射大小(+ 可选 coalesced mmio 页)
其它 kvm_arch_dev_ioctl(架构特定)

KVM_CREATE_VM 流程:

  1. get_unused_fd_flags
  2. kvm_create_vm(type, fdname) → 分配 struct kvm,调用 kvm_arch_init_vm
  3. anon_inode_getfile("kvm-vm", &kvm_vm_fops, kvm, O_RDWR)
  4. fd_install 返回 VM fd。

3. 三级 file_operations

CREATE_VMCREATE_VCPU/dev/kvm kvm_chardev_opsVM fd kvm_vm_fopsVCPU fd kvm_vcpu_fops
  • VM ioctl:内存区域、irq 路由、寄存器级设备、dirty log 等;
  • VCPU ioctlKVM_RUN、寄存器 get/set、中断、调试;
  • KVM device ioctlkvm_device_ioctl):API 2.0 设备模型(如 VFIO 设备、架构 vGIC 设备在 arch 注册)。

4. VCPU 运行与请求机制

  • vcpu_load / vcpu_put:绑定 VCPU 到当前 CPU,准备进入 guest;
  • kvm_make_vcpu_request:设置 vcpu->requests 位(TLB flush、kick 等);
  • kvm_kick_many_cpus:IPI 唤醒执行 KVM_RUN 的线程;
  • halt_poll_ns:guest halt 时自旋轮询再阻塞,降低唤醒延迟(模块参数可调)。

ARM64:kvm_arch_vcpu_should_kickarm.c 根据 IN_GUEST_MODE 判断。

5. Guest 内存(memslots)

用户态通过 KVM_SET_USER_MEMORY_REGION 注册 GPA→HVA 区间:

  • 内核维护 address spacememory slots
  • mmu_notifier 挂钩:host 页面迁移/释放时 kvm_mmu_notifier_invalidate_range 使 guest 映射失效;
  • huge pageread-only memslotprivate memslot 等由通用层 + arch 支持。

kvm_pfn_to_refcounted_pagekvm_is_zone_device_page 处理特殊内存类型。

6. TLB 与影子页表

  • kvm_flush_remote_tlbs:请求所有 VCPU 刷新 guest TLB(调用 kvm_arch_flush_remote_tlbs);
  • kvm_flush_shadow_all:MMU shadow 相关(x86 为主,ARM 使用 Stage-2 由 mmu.c 实现)。

7. mmu_notifier

当 host 进程内存被 pin/unpin、migrate、munmap 时:

1
2
3
4
static void kvm_mmu_notifier_invalidate_range(struct mmu_notifier *mn, ...)
{
kvm_arch_mmu_notifier_invalidate_range(kvm, ...);
}

保证 QEMU 用户态内存guest 物理地址 一致性,是 KVM 正确性的基石。

8. 调试与统计

  • kvm_debugfs_dir:debugfs 入口(/sys/kernel/debug/kvm/);
  • binary_stats.c:向用户态导出二进制统计(配合 tools/perf 等生态);
  • tracepointstrace/events/kvm.hCREATE_TRACE_POINTSkvm_main.c

9. 弱符号扩展点

架构未实现时使用空 weak 函数,例如:

1
__weak void kvm_arch_mmu_notifier_invalidate_range(struct kvm *kvm, ...) { }

ARM64 在 mmu.c 等提供强符号覆盖。

10. 与 RK3588 的关系

  • RK3588 启用 KVM 时,所有 VM 创建/内存注册 均经 kvm_main.c
  • 异常退出、系统寄存器、VGIC 不在此文件;
  • 调优 halt poll 可影响虚拟 Android/VM 的 idle 延迟与 host CPU 占用。

11. 小结

kvm_main.c = KVM 资源管理与 ioctl 中枢;读 ARM KVM 须以此为基础,再进入 05-ARM64边界arm.c/handle_exit.c

02 中断、事件与 VFIO 机制与实现详解

02 中断、事件与 VFIO 机制与实现详解

1. eventfd.c — irqfd 与 ioeventfd

依赖CONFIG_HAVE_KVM_IRQFD(ARM64 KVM 配置中 select HAVE_KVM_EVENTFD)。

irqfd

用户态写 KVM_IRQFDeventfdGSI(Global System Interrupt)关联:

  • 工作队列 irqfd_inject 调用 kvm_set_irq(kvm, KVM_USERSPACE_IRQ_SOURCE_ID, gsi, 1, ...)
  • 支持 resampler(电平触发中断自动 deassert);
  • kvm_arch_irqfd_allowed:架构可拒绝非法组合(weak,ARM 默认可用)。

irqchip routing 配合:GSI → irqchip pin / MSI / ARM IRQ number。

ioeventfd

MMIO/PIO 写命中时 signal eventfd,用于 virtio 队列 kick 等高效通知,避免 VM exit 后用户态轮询。

2. irqchip.c — 中断路由

依赖CONFIG_HAVE_KVM_IRQ_ROUTING

核心 API:

函数 作用
kvm_irq_map_gsi GSI → 一条或多条 kvm_kernel_irq_routing_entry
kvm_irq_map_chip_pin 传统 irqchip 模型 pin → GSI
kvm_set_irq 向路由目标注入中断(导出给 eventfd、设备模型)

使用 SRCUkvm->irq_srcu)保护 kvm->irq_routing 表更新,读路径无锁并发。

ARM64:实际中断芯片为 in-kernel vGICarch/arm64/kvm/vgic/),irqchip.c 提供 通用路由框架,具体 set 实现在 arch。

3. vfio.c — KVM/VFIO 桥

依赖CONFIG_KVM_VFIO(ARM64 menu 默认 select)。

目的:将 VFIO 容器/设备 fdstruct kvm 关联,使:

  • IOMMU 组 DMA 映射与 guest 内存一致;
  • coherentnoncoherent 设备属性传播;
  • 通过 vfio_file_set_kvm(符号可选)通知 VFIO 子系统当前 VM。
1
2
3
4
5
struct kvm_vfio {
struct list_head file_list;
struct mutex lock;
bool noncoherent;
};

PowerPC 另有 CONFIG_SPAPR_TCE_IOMMU 分支;ARM64 走通用 VFIO-VFIO 路径 + irq bypass

典型场景:RK3588 作 host 时 PCIe 网卡/GPU 直通(需 IOMMU/SMMU 支持)。

4. IRQ bypass 协同

eventfd.c 包含 <linux/irqbypass.h>;VFIO 设备作 producer,KVM 作 consumer 时,可在硬件支持下 跳过 host 内核中断处理(ARM IRQ forwarding 等)。

匹配逻辑在 04-irqbypass

5. 数据流(MSI/irqfd → guest)

GuestvGIC archirqchip.ceventfd.cQEMUGuestvGIC archirqchip.ceventfd.cQEMUKVM_IRQFDkvm_set_irq GSIrouting entryvirtual IRQ

6. RK3588 注意点

  • SMMU 必须正确配置,VFIO 直通才安全;
  • 嵌入式 SoC 外设较少,VFIO 用例少于服务器;
  • vGICv3/v4 配置见 Documentation/virt/kvm/devices/arm-vgic-v3.rst
  • GIC ITS(MSI)在 vgic-its.c,与 irqchip 路由的 MSI 条目配合。

7. 小结

文件 层级
eventfd.c 用户态事件 ↔ KVM 中断/MMIO 唤醒
irqchip.c GSI 路由表
vfio.c KVM ↔ VFIO DMA/IRQ 协同

ARM 中断 语义arch/arm64/kvm/vgic/virt/ 提供 机制与路由容器

03 内存脏页与异步 PF 机制与实现详解

03 内存脏页与异步 PF 机制与实现详解

1. dirty_ring.c — 脏页环

依赖CONFIG_HAVE_KVM_DIRTY_RING(ARM64 可选 HAVE_KVM_DIRTY_RING_ACQ_REL,弱序架构需额外内存序语义)。

动机:传统 KVM_GET_DIRTY_LOG bitmap 在大内存 VM 上 扩展性差;脏页环提供 per-vCPU 环形缓冲区,用户态增量拉取脏 GPA。

要点:

  • kvm_dirty_ring_used / soft_limit / full 控制背压;
  • kvm_reset_dirty_gfn 清除 dirty 标记;
  • kvm_cpu_dirty_log_size() 为 weak,架构可扩展保留项数。

RK3588:作 host 跑大内存 Android VM 时,若启用 dirty ring 可降低 migration/live snapshot 开销;需 QEMU 与内核 同时支持

2. pfncache.c — PFN 缓存

依赖CONFIG_HAVE_KVM_PFNCACHE

缓存 GFN→PFN 解析结果,加速 MMIO/缺页路径中重复查找。架构通过 Kconfig 声明能力;与 ARM Stage-2 页表协作在 arch 层消费。

3. async_pf.c — 异步 Page Fault

依赖CONFIG_KVM_ASYNC_PF(x86 为主;ARM 支持情况视配置)。

流程概念:

  1. Guest 触发缺页 exit;
  2. 架构可投递 异步 PF 工作项 async_pf_execute
  3. 在 host mm 中 fault-in 页面后 kvm_async_pf_completion 唤醒 guest。
1
2
3
4
static void async_pf_execute(struct work_struct *work)
{
// 在 apf->mm 中处理缺页,再通知 vcpu
}

降低 KVM_RUN 线程阻塞;对 内存大、换页频繁 的 VM 有益。

4. coalesced_mmio.c — MMIO 合并

依赖CONFIG_KVM_MMIO(ARM64 KVM select)。

问题:高频 MMIO(如 virtio 环)每次写都 VM exit 开销大。

做法:在 专用页KVM_GET_VCPU_MMAP_SIZE 含一页)维护 ring buffer,批量提交 MMIO 写,减少 exit 次数。

头文件 coalesced_mmio.h 定义环格式;kvm_main 在 MMIO bus 上注册 coalesced zone。

5. kvm_mm.h

公共 MM 辅助声明,供 dirty_ringkvm_main 共享,非独立功能模块。

6. 与 kvm_main 内存路径关系

机制 触发场景
mmu_notifier host 换出/释放 guest RAM 页
dirty ring / dirty log 迁移、增量备份
async pf guest 缺页
coalesced mmio 批量设备寄存器写

ARM64 guest RAM 映射实现在 arch/arm64/kvm/mmu.c(Stage-2 IPA)。

7. RK3588 实践

场景 建议
桌面/服务器式 VM 迁移 评估 dirty ring + QEMU 新版本
嵌入式轻量 VM 常关闭,简化路径
virtio 性能 coalesced mmio + ioeventfd(见 02 文档)

8. 小结

virt/kvm 中内存相关文件侧重 性能与可扩展性页表与 Stage-2 仍在 arch。RK3588 host 调优 virtio/MMIO 时 coalesced_mmioeventfd 通常比 dirty ring 更常触及。