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_kcontrol、snd_card.controls |
/dev/snd/controlC*、SNDRV_CTL_IOCTL_* |
| PCM 数据面 | snd_pcm_* |
snd_pcm、snd_pcm_substream、snd_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_compr、snd_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 | static int snd_open(struct inode *inode, struct file *file) |
要点:
CONFIG_MODULES下autoload_device可对aloadC*触发request_module("snd-card-%d")或对 sequencer/timer 触发snd-seq/snd-timer,实现「打开节点再按需加载」的旧式自动装载路径。CONFIG_SND_DYNAMIC_MINORS时snd_find_free_minor跳过仍为 autoload 保留的 static minor,其余动态分配。
2.2 snd_lookup_minor_data 与声卡引用
1 | void *snd_lookup_minor_data(unsigned int minor, int type) |
子驱动 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 | /** |
驱动编写含义:先 snd_pcm_new/snd_ctl_add 等把逻辑对象挂上 snd_card,最后 snd_card_register;否则会违背「控制设备尚未完整注册」的假设。
3.2 监控打开的 struct file(snd_monitor_file)
init.c 维护 shutdown_files:在 snd_card_disconnect 路径上,对已打开的 file 替换为 snd_disconnect_f_ops,避免用户进程在设备释放后仍调用原 read/write/ioctl。这是 USB 声卡热插拔 / 模块卸载 不 panic 的关键之一(细节见 init.c 中 snd_shutdown、register_shutdown_file 等)。
4. 设备链表与注册顺序:device.c
4.1 snd_device_type 决定排序与 register_all 次序
include/sound/core.h 中枚举顺序 同时定义调用顺序;SNDRV_DEV_CONTROL 必须排在最后(注释),以便其它 PCM/rawmidi 等先注册, mixer 最后再挂钩:
1 | enum snd_device_type { |
snd_device_new 按 type 递增插入链表:
1 | /* insert the entry in an incrementally sorted list */ |
4.2 disconnect_all 与 free_all 的顺序
snd_device_disconnect_all:反向遍历,先断开高层可见设备。snd_device_free_all:两阶段——先释放除SNDRV_DEV_CONTROL与SNDRV_DEV_LOWLEVEL外的节点,再收尾剩余(避免 control 过早拆除引用仍在的 PCM):
1 | list_for_each_entry_safe_reverse(dev, next, &card->devices, list) { |
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 |
ioctl、mmap、poll、read/write(mmap 路径为主)等 用户态 ABI |
pcm_lib.c |
hw_ptr/appl_ptr 更新、avail、boundary、XRUN、snd_pcm_period_elapsed、阻塞唤醒 tsleep |
pcm_memory.c + memalloc.c |
环形缓冲分配、预分配、mmap 页映射 |
pcm_dmaengine.c |
通用 dmaengine PCM:dma_slave_config 与 snd_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 | /** |
实践要点: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 | /** |
6. 控制面:control.c
snd_ctl_add把snd_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 | /* |
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.c、ctljack.c |
上报插头事件;可与 input 子系统联动(SND_JACK_INPUT_DEV) |
| VMaster | vmaster.c |
软音量 虚拟 master,聚合多路 slider |
| info | info.c |
/proc/asound 信息树(CONFIG_SND_PROC_FS) |
| OSS | sound_oss.c、oss/ |
旧 OSS API 兼容层 |
| Sequencer | seq/ |
ALSA sequencer 与 seq/oss |
| IEC958 / ELD | pcm_iec958.c、pcm_drm_eld.c |
S/PDIF、HDMI 音频元数据 |
| ISA DMA | isadma.c |
遗留 ISA 声卡 |
9. 与 RK3588 开发的关联方式
- 绝大多数板级工作不在
sound/core,而在sound/soc/rockchip与 codec machine;但 PCM 回调契约(trigger/pointer/hw_params)与snd_pcm_period_elapsed出自 core。 - DMAEngine PCM 路径:
CONFIG_SND_SOC_GENERIC_DMAENGINE_PCM+pcm_dmaengine.c导出符号,与rockchip_i2s_tdm.c等平台驱动组合常见。 - 调试:XRUN、延迟问题通常需同时看
pcm_lib的 avail/wait 与驱动侧 DMA 回调频率 是否匹配 period_size。
正在加载留言…