linux 音频子系统代码分析
Posted tumaohe
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux 音频子系统代码分析相关的知识,希望对你有一定的参考价值。
目录:
1. 架构
整体框架
2. 文件组成
文件/文件夹 | 作用 |
---|---|
aoa | 苹果板载音频驱动 |
arm | arm音频设备支持 |
atmel | atmel ABDAC(音频字节流数模转换器)和ac97C(ac97控制器) |
core | alsa驱动中间层,是alsa驱动的核心 |
core/oss | 模拟旧OSS架构的PCM和Mixer模块 |
core/seq | 有关音序器相关代码 |
drivers | 与cpu、bus无关的公用代码 |
firewire | IEEE-1394/FireWire/iLink音频设备支持 |
hda | HD audio(高保真音频)支持 |
i2c | alsa的i2c控制代码 |
isa | 各种isa声卡的代码 |
mips | mips音频设备支持 |
oss | 对oss的兼容支持 |
parisc | 鸿蒙和PA-RISC架构的GSC音频设备支持 |
pci | pci音频设备支持 |
pcmcia | pcmcia音频设备支持 |
ppc | PowerPC音频设备支持 |
sh | SuperH架构音频设备支持 |
soc | system-on-chip体系的中间层代码 |
soc/codec | 针对asoc体系各种codec代码,与平台无关 |
sparc | SPARC架构音频设备支持 |
spi | spi音频设备支持 |
synth | 一些工具 |
usb | usb音频设备支持 |
ac97_bus.c | 实现ac97标准总线 |
last.c | 音频设备注册完成后打印”ALSA devices List” |
sound_core.c | 注册音频核心层子系统 |
sound_firmware.c | 加载音频驱动固件 |
3. ALSA核心层
3.1 alsa驱动的设备文件结构:
目前ALSA内核提供给用户空间的接口有:
-
信息接口(proc/asound)
-
控制接口(dev/snd/controlCX)
-
混音器接口(dev/snd/mixerCXDX)
-
PCM接口(dev/snd/pcmCXDX)
-
Raw迷笛接口(dev/snd/midiCXDX)
-
音序器接口(dev/snd/seq)
-
定时器接口(dev/snd/timer)
tree /proc/asound/
/proc/asound/
|-- Codec -> card0
|-- card0
| |-- id
| |-- oss_mixer
| |-- pcm0c
| | |-- info
| | |-- oss
| | |-- sub0
| | |-- hw_params
| | |-- info
| | |-- prealloc
| | |-- prealloc_max
| | |-- status
| | |-- sw_params
| |-- pcm0p
| |-- info
| |-- oss
| |-- sub0
| |-- hw_params
| |-- info
| |-- prealloc
| |-- prealloc_max
| |-- status
| |-- sw_params
|-- cards
|-- devices
|-- oss
| |-- devices
| |-- sndstat
|-- pcm
|-- timers
|-- version
7 directories, 25 files
# ls /dev/snd/ -l
total 0
crw------- 1 root root 116, 0 Jan 1 00:00 controlC0 // 用于声卡的控制,例如通道选择,混音,麦克风的控制等
crw------- 1 root root 116, 24 Jan 1 00:00 pcmC0D0c // 用于录音的pcm设备
crw------- 1 root root 116, 16 Jan 1 00:00 pcmC0D0p //用于播放的pcm设备
crw------- 1 root root 116, 33 Jan 1 00:00 timer //定时器
注:
部分设备未开启: midiC0D0(用于播放midi音频), 2. seq (音序器),等
设备文件注册代码, 以pcm设备为例:
int snd_register_device(int type, struct snd_card *card, int dev,
const struct file_operations *f_ops,
void *private_data, struct device *device)
static int snd_pcm_dev_register(struct snd_device *device)
static int _snd_pcm_new(struct snd_card *card, const char *id, int device,
int playback_count, int capture_count, bool internal,
struct snd_pcm **rpcm)
int snd_pcm_new(struct snd_card *card, const char *id, int device,
int playback_count, int capture_count, struct snd_pcm **rpcm)
------------------------------------------------------------------------
int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
static int soc_link_init(struct snd_soc_card *card,
struct snd_soc_pcm_runtime *rtd)
static int soc_link_init(struct snd_soc_card *card,
struct snd_soc_pcm_runtime *rtd)
static int snd_soc_instantiate_card(struct snd_soc_card *card)
static int snd_soc_bind_card(struct snd_soc_card *card)
int snd_soc_register_card(struct snd_soc_card *card)
3.2 数据结构和操作函数
3.2.1 声卡
直接接口: int snd_soc_register_card(struct snd_soc_card *card)(soc\\soc-core.c)
snd_soc_register_card -> snd_soc_bind_card -> snd_soc_instantiate_card -> snd_card_new
3.2.2 pcm设备
snd_pcm_new
snd_pcm_set_ops
3.2.2 控制接口
struct snd_kcontrol
struct snd_kcontrol_new
-> snd_pcm_new
3.3 alsa声卡设备
3.3.1 platform层
3.3.2 cpu-dai层
3.3.3 codec-dai层
4. ASoC框架
参考帖子 :ALSA-ASOC音频驱动框架简述
4.1 整体框架
4.2 主要接口
4.3 源码分析
machine/platform/codec代码分析从其他地方摘的图,链接:https://blog.csdn.net/baidu_36250852/article/details/120614976
注: 此图中应该未使用设备树,最新版本Linux应该会有细节区别
4.3.1 无外置codec芯片,直接输出模拟信号
4.3.1.1 配置
设备树:
&codec
allwinner,audio-routing =
"Headphone", "HP",
"Headphone", "HPCOM",
"LINEIN", "Line In",
"FMINL", "Left FM In",
"FMINR", "Right FM In",
"MIC", "Mic";
status = "okay";
;
codec: codec@1c23c00
compatible = "allwinner,suniv-f1c100s-codec";
reg = <0x01c23c00 0x400>;
interrupts = <21>;
clocks = <&ccu CLK_BUS_CODEC>,
<&ccu CLK_CODEC>;
clock-names = "apb", "codec";
resets = <&ccu RST_BUS_CODEC>;
dmas = <&dma SUN4I_DMA_NORMAL 0x0c>,
<&dma SUN4I_DMA_NORMAL 0x0c>;
dma-names = "rx", "tx";
status = "disabled";
;
4.3.1.2 源码摘录
sound\\soc\\sunxi\\sun4i-codec.c
static struct snd_soc_card *suniv_codec_create_card(struct device *dev)
struct snd_soc_card *card;
int ret;
card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL);
if (!card)
return ERR_PTR(-ENOMEM);
card->dai_link = sun4i_codec_create_link(dev, &card->num_links);
if (!card->dai_link)
return ERR_PTR(-ENOMEM);
card->dev = dev;
card->name = "F1C100s Audio Codec";
card->dapm_widgets = suniv_codec_card_dapm_widgets;
card->num_dapm_widgets = ARRAY_SIZE(suniv_codec_card_dapm_widgets);
card->dapm_routes = suniv_codec_card_routes;
card->num_dapm_routes = ARRAY_SIZE(suniv_codec_card_routes);
card->fully_routed = true;
ret = snd_soc_of_parse_audio_routing(card, "allwinner,audio-routing");
if (ret)
dev_warn(dev, "failed to parse audio-routing: %d\\n", ret);
return card;
;
static const struct sun4i_codec_quirks suniv_f1c100s_codec_quirks =
.regmap_config = &suniv_codec_regmap_config,
.codec = &suniv_codec_codec,
.create_card = suniv_codec_create_card,
.reg_adc_fifoc = REG_FIELD(SUNIV_CODEC_ADC_FIFOC, 0, 31),
.reg_dac_txdata = SUN4I_CODEC_DAC_TXDATA,
.reg_adc_rxdata = SUNIV_CODEC_ADC_RXDATA,
.has_reset = true,
.dma_max_burst = SUNIV_DMA_MAX_BURST,
;
static const struct of_device_id sun4i_codec_of_match[] =
.compatible = "allwinner,suniv-f1c100s-codec",
.data = &suniv_f1c100s_codec_quirks,
,
;
static int sun4i_codec_probe(struct platform_device *pdev)
struct snd_soc_card *card;
struct sun4i_codec *scodec;
const struct sun4i_codec_quirks *quirks;
struct resource *res;
void __iomem *base;
int ret;
scodec = devm_kzalloc(&pdev->dev, sizeof(*scodec), GFP_KERNEL);
if (!scodec)
return -ENOMEM;
scodec->dev = &pdev->dev;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(base))
dev_err(&pdev->dev, "Failed to map the registers\\n");
return PTR_ERR(base);
quirks = of_device_get_match_data(&pdev->dev);
if (quirks == NULL)
dev_err(&pdev->dev, "Failed to determine the quirks to use\\n");
return -ENODEV;
scodec->regmap = devm_regmap_init_mmio(&pdev->dev, base,
quirks->regmap_config);
if (IS_ERR(scodec->regmap))
dev_err(&pdev->dev, "Failed to create our regmap\\n");
return PTR_ERR(scodec->regmap);
/* Get the clocks from the DT */
scodec->clk_apb = devm_clk_get(&pdev->dev, "apb");
if (IS_ERR(scodec->clk_apb))
dev_err(&pdev->dev, "Failed to get the APB clock\\n");
return PTR_ERR(scodec->clk_apb);
scodec->clk_module = devm_clk_get(&pdev->dev, "codec");
if (IS_ERR(scodec->clk_module))
dev_err(&pdev->dev, "Failed to get the module clock\\n");
return PTR_ERR(scodec->clk_module);
if (quirks->has_reset)
scodec->rst = devm_reset_control_get_exclusive(&pdev->dev,
NULL);
if (IS_ERR(scodec->rst))
dev_err(&pdev->dev, "Failed to get reset control\\n");
return PTR_ERR(scodec->rst);
scodec->gpio_pa = devm_gpiod_get_optional(&pdev->dev, "allwinner,pa",
GPIOD_OUT_LOW);
if (IS_ERR(scodec->gpio_pa))
ret = PTR_ERR(scodec->gpio_pa);
if (ret != -EPROBE_DEFER)
dev_err(&pdev->dev, "Failed to get pa gpio: %d\\n", ret);
return ret;
/* reg_field setup */
scodec->reg_adc_fifoc = devm_regmap_field_alloc(&pdev->dev,
scodec->regmap,
quirks->reg_adc_fifoc);
if (IS_ERR(scodec->reg_adc_fifoc))
ret = PTR_ERR(scodec->reg_adc_fifoc);
dev_err(&pdev->dev, "Failed to create regmap fields: %d\\n",
ret);
return ret;
/* Enable the bus clock */
if (clk_prepare_enable(scodec->clk_apb))
dev_err(&pdev->dev, "Failed to enable the APB clock\\n");
return -EINVAL;
/* Deassert the reset control */
if (scodec->rst)
ret = reset_control_deassert(scodec->rst);
if (ret)
dev_err(&pdev->dev,
"Failed to deassert the reset control\\n");
goto err_clk_disable;
/* DMA configuration for TX FIFO */
scodec->playback_dma_data.addr = res->start + quirks->reg_dac_txdata;
scodec->playback_dma_data.maxburst = quirks->dma_max_burst;
scodec->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
/* DMA configuration for RX FIFO */
scodec->capture_dma_data.addr = res->start + quirks->reg_adc_rxdata;
scodec->capture_dma_data.maxburst = quirks->dma_max_burst;
scodec->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
ret = devm_snd_soc_register_component(&pdev->dev, quirks->codec,
&sun4i_codec_dai, 1);
if (ret)
dev_err(&pdev->dev, "Failed to register our codec\\n");
goto err_assert_reset;
ret = devm_snd_soc_register_component(&pdev->dev,
&sun4i_codec_component,
&dummy_cpu_dai, 1);
if (ret)
dev_err(&pdev->dev, "Failed to register our DAI\\n");
goto err_assert_reset;
ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
if (ret)
dev_err(&pdev->dev, "Failed to register against DMAEngine\\n");
goto err_assert_reset;
card = quirks->create_card(&pdev->dev);
if (IS_ERR(card))
ret = PTR_ERR(card);
dev_err(&pdev->dev, "Failed to create our card\\n");
goto err_assert_reset;
snd_soc_card_set_drvdata(card, scodec);
ret = snd_soc_register_card(card); //注册声卡
if (ret)
dev_err(&pdev->dev, "Failed to register our card\\n");
goto err_assert_reset;
return 0;
err_assert_reset:
if (scodec->rst)
reset_control_assert(scodec->rst);
err_clk_disable:
clk_disable_unprepare(scodec->clk_apb);
return ret;
static struct platform_driver sun4i_codec_driver =
.driver =
.name = "sun4i-codec",
.of_match_table = sun4i_codec_of_match,
,
.probe = sun4i_codec_probe,
.remove = sun4i_codec_remove,
;
module_platform_driver(sun4i_codec_driver);
5 测试
# tinymix contents
# tinymix set 2 1
# tinymix set 1 63
# tinymix set 13 1
# tinycap 1.wav
# tinyplay 1.wav
Number of controls: 25
ctl type num name value
0 INT 1 DAC Playback Volume 63 (range 0->63)
1 INT 1 Headphone Playback Volume 63 (range 0->63)
2 BOOL 2 Headphone Playback Switch On, On
3 INT 1 Line In Playback Volume 0 (range 0->7)
4 INT 1 FM In Playback Volume 0 (range 0->7)
5 INT 1 Mic In Playback Volume 3 (range 0->7)
6 INT 1 Mic Boost Volume 4 (range 0->7)
7 INT 1 ADC Capture Volume 3 (range 0->7)
8 BOOL 1 ADC Mixer Right Out Capture Switch Off
9 BOOL 1 ADC Mixer Left Out Capture Switch Off
10 BOOL 1 ADC Mixer Line In Capture Switch Off
11 BOOL 1 ADC Mixer Right FM In Capture Switch Off
12 BOOL 1 ADC Mixer Left FM In Capture Switch Off
13 BOOL 1 ADC Mixer Mic Capture Switch On
14 BOOL 1 Left Mixer Right DAC Playback Switch Off
15 BOOL 1 Left Mixer Left DAC Playback Switch Off
16 BOOL 1 Left Mixer FM In Playback Switch Off
17 BOOL 1 Left Mixer Line In Playback Switch Off
18 BOOL 1 Left Mixer Mic In Playback Switch Off
19 BOOL 1 Right Mixer Left DAC Playback Switch Off
20 BOOL 1 Right Mixer Right DAC Playback Switch Off
21 BOOL 1 Right Mixer FM In Playback Switch Off
22 BOOL 1 Right Mixer Line In Playback Switch Off
23 BOOL 1 Right Mixer Mic In Playback Switch Off
24 ENUM 2 Headphone Source Playback Route , DACMixer, , DACMixer
<Linux音频子系统 -代码分析(以YMU836为例)
1. 前言
数据流通过I2S的接口来传输,而I2c/Spi接口主要完成控制接口,例如控制声音的大小,功放的增益等操作。
platform 部分对应的代码在 kernel_3.10/sound/soc/fsl/fsl_ssi.c;
codec 部分对应的代码在 kernel_3.10/sound/soc/codecs/ymu836.c;
machine 部分对应的代码在 kernel_3.10/sound/soc/fsl/imx-ymu836.c。
2. platform部分
前面有介绍过,platform主要负责DMA的控制和I2S的控制,即PCM dma和cpu dai两个部分。
module_platform_driver(fsl_ssi_driver);
fsl_ssi_probe
snd_soc_register_component
snd_soc_register_dai
list_add(&dai->list, &dai_list);
imx_pcm_dma_init
snd_dmaengine_pcm_register
snd_soc_add_platform
list_add(&platform->list, &platform_list);
module_platform_driver是一个宏,最终调用的是platform_driver_register这个接口,注册一个平台驱动,所以直接从fsl_ssi_driver的probe回调函数开始分析。
static struct platform_driver fsl_ssi_driver = {
.driver = {
.name = "fsl-ssi-dai",
.owner = THIS_MODULE,
.of_match_table = fsl_ssi_ids,
.pm = &fsl_ssi_pm,
},
.probe = fsl_ssi_probe,
.remove = fsl_ssi_remove,
};
static int fsl_ssi_probe(struct platform_device *pdev)
{
······
pm_runtime_enable(&pdev->dev);
/* Register with ASoC */
dev_set_drvdata(&pdev->dev, ssi_private);
ret = snd_soc_register_component(&pdev->dev, &fsl_ssi_component, &ssi_private->cpu_dai_drv, 1); // cpu dai
if (ret) {
dev_err(&pdev->dev, "failed to register DAI: %d\\n", ret);
goto error_dev;
}
if (ssi_private->ssi_on_imx) {
ret = imx_pcm_dma_init(pdev, SND_DMAENGINE_PCM_FLAG_NO_RESIDUE, IMX_SSI_DMABUF_SIZE); // pcm dma
}
return ret;
}
cpu dai的注册过程如下:
// sound/soc/soc-core.c
int snd_soc_register_component(struct device *dev, const struct snd_soc_component_driver *cmpnt_drv,
struct snd_soc_dai_driver *dai_drv, int num_dai)
{
if (1 == num_dai)
ret = snd_soc_register_dai(dev, dai_drv); //注册 cpu dai
else
ret = snd_soc_register_dais(dev, dai_drv, num_dai);
if (ret < 0) {
dev_err(dev, "ASoC: Failed to regster DAIs: %d\\n", ret);
goto error_component_name;
}
list_add(&cmpnt->list, &component_list);
return ret;
}
// sound/soc/soc-core.c
static int snd_soc_register_dai(struct device *dev, struct snd_soc_dai_driver *dai_drv)
{
struct snd_soc_codec *codec;
struct snd_soc_dai *dai;
dev_dbg(dev, "ASoC: dai register %s\\n", dev_name(dev));
dai = kzalloc(sizeof(struct snd_soc_dai), GFP_KERNEL);
dai->dev = dev;
dai->driver = dai_drv;
dai->dapm.dev = dev;
list_for_each_entry(codec, &codec_list, list) {
if (codec->dev == dev) {
dev_dbg(dev, "ASoC: Mapped DAI %s to CODEC %s\\n",
dai->name, codec->name);
dai->codec = codec;
break;
}
}
list_add(&dai->list, &dai_list); //将cpu dai挂接到静态全局链表dai_list
dev_dbg(dev, "ASoC: Registered DAI '%s'\\n", dai->name);
return 0;
}
pcm dma的注册过程如下:
// sound/soc/fsl/imx-pcm-dma.c
int imx_pcm_dma_init(struct platform_device *pdev, unsigned int flags, size_t size)
{
······
return snd_dmaengine_pcm_register(&pdev->dev, config, flags); //--->
}
// sound/soc/soc-generic-dmaengine-pcm.c
int snd_dmaengine_pcm_register(struct device *dev,
const struct snd_dmaengine_pcm_config *config, unsigned int flags)
{
struct dmaengine_pcm *pcm;
pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);
dmaengine_pcm_request_chan_of(pcm, dev);
return snd_soc_add_platform(dev, &pcm->platform, &dmaengine_pcm_platform); // --->
}
// sound/soc/soc-core.c
int snd_soc_add_platform(struct device *dev, struct snd_soc_platform *platform,
const struct snd_soc_platform_driver *platform_drv)
{
/* create platform component name */
platform->name = fmt_single_name(dev, &platform->id);
platform->dev = dev;
platform->driver = platform_drv;
platform->dapm.dev = dev;
platform->dapm.platform = platform;
platform->dapm.stream_event = platform_drv->stream_event;
list_add(&platform->list, &platform_list); //将pcm dma挂接到静态全局链表platform_list
dev_dbg(dev, "ASoC: Registered platform '%s'\\n", platform->name);
return 0;
}
3. codec部分
因为Codec驱动的代码要做到平台无关性,要使得Machine驱动能够使用该Codec,Codec驱动的首要任务就是确定snd_soc_codec(音频的控制)和snd_soc_dai(数据处理)的实例,并把它们注册到系统中。
module_spi_driver(ymu836_spi_driver)
ymu836_spi_probe
snd_soc_register_codec(&spi->dev,&soc_codec_dev_ymu836, &ymu836_dai, 1);
list_add(&codec->list, &codec_list)
snd_soc_register_dais
list_add(&dai->list, &dai_list);
soc_codec_dev_ymu836
ymu836_probe //注册snd_soc_codec(音频的控制),在machine端会通过链表将snd_soc_codec结构体取出,并调用里面的初始化回调函数
module_spi_driver宏和module_platform_driver宏类似,就不做赘述了
static struct spi_driver ymu836_spi_driver = {
.driver = {
.name = "ymu836",
.owner = THIS_MODULE,
.of_match_table = ymu836_of_match,
},
.probe = ymu836_spi_probe,
.remove = ymu836_spi_remove,
.shutdown = ymu836_spi_shutdown,
.id_table = ymu836_spi_id,
};
static int ymu836_spi_probe(struct spi_device *spi)
{
int ret;
ymu836 = devm_kzalloc(&spi->dev, sizeof(struct ymu836_priv), GFP_KERNEL);
······
spi->mode=0;
ret = spi_setup(spi);
spi_set_drvdata(spi, ymu836);
spidev=spi;
ret = snd_soc_register_codec(&spi->dev,&soc_codec_dev_ymu836, &ymu836_dai, 1); //
return ret;
}
// sound/soc/soc-core.c
int snd_soc_register_codec(struct device *dev, const struct snd_soc_codec_driver *codec_drv,
struct snd_soc_dai_driver *dai_drv, int num_dai)
{
size_t reg_size;
struct snd_soc_codec *codec;
int ret, i;
dev_dbg(dev, "codec register %s\\n", dev_name(dev));
codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
/* create CODEC component name */
codec->name = fmt_single_name(dev, &codec->id);
codec->write = codec_drv->write;
codec->read = codec_drv->read;
codec->volatile_register = codec_drv->volatile_register;
codec->readable_register = codec_drv->readable_register;
codec->writable_register = codec_drv->writable_register;
codec->ignore_pmdown_time = codec_drv->ignore_pmdown_time;
codec->dapm.bias_level = SND_SOC_BIAS_OFF;
codec->dapm.dev = dev;
codec->dapm.codec = codec;
codec->dapm.seq_notifier = codec_drv->seq_notifier;
codec->dapm.stream_event = codec_drv->stream_event;
codec->dev = dev;
codec->driver = codec_drv; //传进来的soc_codec_dev_ymu836赋值给了codec
codec->num_dai = num_dai;
mutex_init(&codec->mutex);
list_add(&codec->list, &codec_list); //将ymu836 这款codec相关的信息挂接到静态全局链表codec_list
/* register any DAIs */
ret = snd_soc_register_dais(dev, dai_drv, num_dai); // 注册codec dai
dev_dbg(codec->dev, "ASoC: Registered codec '%s'\\n", codec->name);
return 0;
}
// sound/soc/soc-core.c
static int snd_soc_register_dais(struct device *dev, struct snd_soc_dai_driver *dai_drv, size_t count)
{
struct snd_soc_codec *codec;
struct snd_soc_dai *dai;
int i, ret = 0;
dev_dbg(dev, "ASoC: dai register %s #%Zu\\n", dev_name(dev), count);
list_for_each_entry(codec, &codec_list, list) {
if (codec->dev == dev) {
dev_dbg(dev, "ASoC: Mapped DAI %s to CODEC %s\\n", dai->name, codec->name);
dai->codec = codec;
break;
}
}
list_add(&dai->list, &dai_list); // 将codec dai挂接到静态全局链表dai_list
dev_dbg(dai->dev, "ASoC: Registered DAI '%s'\\n", dai->name);
}
return 0;
}
4. machine部分
通常有两种方法注册一个card:
第一种是在machine驱动中注册一个名为 “soc-audio” 的 platform device,并将 struct snd_soc_card 结构变量作为这个device的私有数据,在 soc-core.c 调用 名为 “soc-audio” 的 platform drv的probe函数,probe中调用 snd_soc_register_card 注册这个card;
第二种是直接在machine驱动中调用 snd_soc_register_card 函数注册card。
imx_ymu836_probe
snd_soc_register_card
snd_soc_instantiate_card //关键接口
soc_bind_dai_link //绑定dais
rtd->cpu_dai = cpu_dai; //从链表dai_list中找到cpu_dai并存储在rtd中
rtd->codec = codec; //从链表codec_list中找到codec并存储在rtd中
rtd->codec_dai = codec_dai; //从链表dai_list中找到codec_dai并存储在rtd中
rtd->platform = platform; //从链表platform_list中找到platform并存储在rtd中
codec->cache_init //调用回调函数cache_init进行初始化
snd_card_create
snd_ctl_create //.dev_register = snd_ctl_dev_register ===》疑问1
snd_ctl_dev_register
snd_register_device //生成dev/controlC%i设备文件,sound/core/control.c
snd_register_device_for_dev
preg->f_ops = f_ops;
snd_minors[minor] = preg; //将controlC设备对应的f_ops放到全局数组snd_minors[]中
card->probe //回调card的probe函数
soc_probe_link_components //针对rtd下的每个component,回调其初始化函数--->
soc_probe_codec(card, cpu_dai->codec) //调用platform侧codec层注册的driver中的probe函数 ===>该codec在platform部分注册
soc_probe_codec(card, codec_dai->codec) //调用codec侧codec层注册的driver中的probe函数 ===>该codec在codec部分注册
soc_probe_platform(card, platform) //调用platform侧dma层注册的driver中的probe函数 ===>该codec在platform部分注册
soc_probe_link_dais
soc_new_pcm
snd_pcm_new
_snd_pcm_new //.dev_register = snd_pcm_dev_register ===》疑问2
snd_pcm_dev_register //生成dev/pcmC0D0c或pcmC0D0p设备文件,sound/core/pcm.c
snd_register_device_for_dev
preg->f_ops = f_ops;
snd_minors[minor] = preg; //将pcmC0D0c设备或pcmC0D0p设备对应的f_ops放到全局数组snd_minors[]中
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &rtd->ops); //底层驱动需要实现rtd->ops这个结构体
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &rtd->ops); //用户空间打开pcm设备文件以后,会间接调用rtd->ops里的底层驱动回调函数
platform->driver->pcm_new(rtd) //此回调函数中会分配环形缓冲区
snd_soc_add_card_controls
snd_soc_dapm_add_routes
card->late_probe(card)
snd_soc_dapm_new_widgets
snd_card_register
snd_device_register_all
dev->ops->dev_register(dev) //调用疑问1和疑问2处赋值的dev_register函数指针
其实呢,machine部分才是关键部分,platform和codec部分的很多内容都可以通过分析machine部分来得到结论,所以machine部分的解析也会杂乱一些,详解如下:
static struct platform_driver imx_ymu836_driver = {
.driver = {
.name = "imx-ymu836",
.owner = THIS_MODULE,
.pm = &snd_soc_pm_ops,
.of_match_table = imx_ymu836_dt_ids,
},
.probe = imx_ymu836_probe,
.remove = imx_ymu836_remove,
};
module_platform_driver(imx_ymu836_driver);
static int imx_ymu836_probe(struct platform_device *pdev)
{
······
ret = snd_soc_register_card(&data->card); //--->
if (ret) {
dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\\n", ret);
goto fail;
}
priv->snd_card = data->card.snd_card;
return ret;
}
// sound/soc/soc-core.c
int snd_soc_register_card(struct snd_soc_card *card)
{
int i, ret;
······
ret = snd_soc_instantiate_card(card); //--->
return ret;
}
// sound/soc/soc-core.c
static int snd_soc_instantiate_card(struct snd_soc_card *card)
{
struct snd_soc_codec *codec;
struct snd_soc_codec_conf *codec_conf;
enum snd_soc_compress_type compress_type;
struct snd_soc_dai_link *dai_link;
int ret, i, order, dai_fmt;
/* bind DAIs */
for (i = 0; i < card->num_links; i++) {
ret = soc_bind_dai_link(card, i); //--->4-1代码
if (ret != 0)
goto base_error;
}
/* initialize the register cache for each available codec */
list_for_each_entry(codec, &codec_list, list) {
if (codec->cache_init) //调用回调函数cache_init进行初始化
continue;
······
}
/* card bind complete so register a sound card */
ret = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1, //创建声卡--->4-2代码
card->owner, 0, &card->snd_card);
if (ret < 0) {
dev_err(card->dev, "ASoC: can't create sound card for card %s: %d\\n", card->name, ret);
goto base_error;
}
card->snd_card->dev = card->dev;
card->dapm.bias_level = SND_SOC_BIAS_OFF;
card->dapm.dev = card->dev;
card->dapm.card = card;
list_add(&card->dapm.list, &card->dapm_list);
/* initialise the sound card only once */
if (card->probe) { //回调card的probe函数
ret = card->probe(card);
if (ret < 0)
goto card_probe_error;
}
/* probe all components used by DAI links on this card */
for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST; order++) {
for (i = 0; i < card->num_links; i++) {
ret = soc_probe_link_components(card, i, order); //针对rtd下的每个component,回调其初始化函数--->4-3代码
if (ret < 0) {
dev_err(card->dev, "ASoC: failed to instantiate card %d\\n", ret);
goto probe_dai_err;
}
}
}
/* probe all DAI links on this card */
for (order = SND_SOC_COMP_ORDER_FIRST; order <= SND_SOC_COMP_ORDER_LAST; order++) {
for (i = 0; i < card->num_links; i++) {
ret = soc_probe_link_dais(card, i, order); //针对rtd下的每个dai,回调其初始化函数--->4-4代码
if (ret < 0) {
dev_err(card->dev, "ASoC: failed to instantiate card %d\\n", ret);
goto probe_dai_err;
}
}
}
snd_soc_dapm_link_dai_widgets(card);
if (card->controls)
snd_soc_add_card_controls(card, card->controls, card->num_controls);
if (card->dapm_routes)
snd_soc_dapm_add_routes(&card->dapm, card->dapm_routes, card->num_dapm_routes);
if (card->late_probe)
ret = card->late_probe(card);
snd_soc_dapm_new_widgets(&card->dapm);
if (card->fully_routed)
list_for_each_entry(codec, &card->codec_dev_list, card_list)
snd_soc_dapm_auto_nc_codec_pins(codec);
ret = snd_card_register(card->snd_card); //注册声卡,此后用户空间出现设备节点,用户程序可与底层交互了--->4-5代码
if (ret < 0) {
dev_err(card->dev, "ASoC: failed to register soundcard %d\\n", ret);
goto probe_aux_dev_err;
}
return 0;
}
4-1代码详解
// sound/soc/soc-core.c
static int soc_bind_dai_link(struct snd_soc_card *card, int num)
{
struct snd_soc_dai_link *dai_link = &card->dai_link[num];
struct snd_soc_pcm_runtime *rtd = &card->rtd[num];
struct snd_soc_codec *codec;
struct snd_soc_platform *platform;
struct snd_soc_dai *codec_dai, *cpu_dai;
const char *platform_name;
dev_dbg(card->dev, "ASoC: binding %s at idx %d\\n", dai_link->name, num);
/* Find CPU DAI from registered DAIs*/
list_for_each_entry(cpu_dai, &dai_list, list) {
if (dai_link->cpu_of_node &&
(cpu_dai->dev->of_node != dai_link->cpu_of_node))
continue;
if (dai_link->cpu_name &&
strcmp(dev_name(cpu_dai->dev), dai_link->cpu_name))
continue;
if (dai_link->cpu_dai_name &&
strcmp(cpu_dai->name, dai_link->cpu_dai_name))
continue;
rtd->cpu_dai = cpu_dai; //找到cpu_dai并存储在rtd中
}
/* Find CODEC from registered CODECs */
list_for_each_entry(codec, &codec_list, list) {
if (dai_link->codec_of_node) {
if (codec->dev->of_node != dai_link->codec_of_node)
continue;
} else {
if (strcmp(codec->name, dai_link->codec_name))
continue;
}
rtd->codec = codec; //找到codec并存储在rtd中
/*
* CODEC found, so find CODEC DAI from registered DAIs from
* this CODEC
*/
list_for_each_entry(codec_dai, &dai_list, list) {
if (codec->dev == codec_dai->dev &&
!strcmp(codec_dai->name,
dai_link->codec_dai_name)) {
rtd->codec_dai = codec_dai; //找到codec_dai并存储在rtd中
}
}
}
/* if there's no platform we match on the empty platform */
platform_name = dai_link->platform_name; //目前为止,并没有看到有任何地方给platform_name赋值,所以此时的platform_name应该是空的
if (!platform_name && !dai_link->platform_of_node)
platform_name = "snd-soc-dummy";
/* find one from the set of registered platforms */
list_for_each_entry(platform, &platform_list, list) {
if (dai_link->platform_of_node) {
if (platform->dev->of_node !=
dai_link->platform_of_node)
continue;
} else {
if (strcmp(platform->name, platform_name))
continue;
}
rtd->platform = platform; //找到platform并存储在rtd中
}
card->num_rtd++;
return 0;
}
4-2 代码详解
// sound/core/init.c
int snd_card_create(int idx, const char *xid, struct module *module, int extra_size, struct snd_card **card_ret)
{
struct snd_card *card;
int err, idx2;
······
/* the control interface cannot be accessed from the user space until */
/* snd_cards_bitmask and snd_cards are set with snd_card_register */
err = snd_ctl_create(card);
if (err < 0) {
snd_printk(KERN_ERR "unable to register control minors\\n");
goto __error;
}
return 0;
}
// sound/core/control.c
int snd_ctl_create(struct snd_card *card)
{
static struct snd_device_ops ops = {
.dev_free = snd_ctl_dev_free,
.dev_register = snd_ctl_dev_register, // ===》疑问1
.dev_disconnect = snd_ctl_dev_disconnect,
};
return snd_device_new(card, SNDRV_DEV_CONTROL, card, &ops);
}
static int snd_ctl_dev_register(struct snd_device *device)
{
struct snd_card *card = device->device_data;
int err, cardnum;
char name[16];
cardnum = card->number;
sprintf(name, "controlC%i", cardnum);
//生成dev/controlC%i设备文件
if ((err = snd_register_device(SNDRV_DEVICE_TYPE_CONTROL, card, -1, &snd_ctl_f_ops, card, name)) < 0)
return err;
return 0;
}
static inline int snd_register_device(int type, struct snd_card *card, int dev,
const struct file_operations *f_ops,
void *private_data,
const char *name)
{
return snd_register_device_for_dev(type, card, dev, f_ops,
private_data, name,
snd_card_get_device_link(card));
}
int snd_register_device_for_dev(int type, struct snd_card *card, int dev,
const struct file_operations *f_ops,
void *private_data,
const char *name, struct device *device)
{
int minor;
struct snd_minor *preg;
preg = kmalloc(sizeof *preg, GFP_KERNEL);
preg->type = type;
preg->card = card ? card->number : -1;
preg->device = dev;
preg->f_ops = f_ops; //重点操作之一
preg->private_data = private_data;
preg->card_ptr = card;
//将controlC设备对应的f_ops放到全局数组snd_minors[]中,用户空间打开设备文件时会从snd_minors[]数组中取出对应的ops操作集
snd_minors[minor] = preg;
preg->dev = device_create(sound_class, device, MKDEV(major, minor),
private_data, "%s", name);
return 0;
}
4-3 代码详解
static int soc_probe_link_components(struct snd_soc_card *card, int num,
int order)
{
struct snd_soc_pcm_runtime *rtd = &card->rtd[num];
// 4-1代码中对cpu_dai、codec_dai、platform进行了赋值
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct snd_soc_dai *codec_dai = rtd->codec_dai;
struct snd_soc_platform *platform = rtd->platform;
int ret;
/* probe the CPU-side component, if it is a CODEC */
if (cpu_dai->codec &&
!cpu_dai->codec->probed &&
cpu_dai->codec->driver->probe_order == order) {
//调用platform侧codec层注册的driver中的probe函数 ===>该codec在platform部分注册
ret = soc_probe_codec(card, cpu_dai->codec);
if (ret < 0)
return ret;
}
/* probe the CODEC-side component */
if (!codec_dai->codec->probed &&
codec_dai->codec->driver->probe_order == order) {
//调用codec侧codec层注册的driver中的probe函数 ===>该codec在codec部分注册
ret = soc_probe_codec(card, codec_dai->codec);
if (ret < 0)
return ret;
}
/* probe the platform */
if (!platform->probed &&
platform->driver->probe_order == order) {
//调用platform侧dma层注册的driver中的probe函数 ===>该codec在platform部分注册
ret = soc_probe_platform(card, platform);
if (ret < 0)
return ret;
}
return 0;
}
static int soc_probe_codec(struct snd_soc_card *card, struct snd_soc_codec *codec)
{
int ret = 0;
const struct snd_soc_codec_driver *driver = codec->driver;
struct snd_soc_dai *dai;
codec->card = card;
codec->dapm.card = card;
soc_set_name_prefix(card, codec);
/* Create DAPM widgets for each DAI stream */
list_for_each_entry(dai, &dai_list, list) {
if (dai->dev != codec->dev)
continue;
snd_soc_dapm_new_dai_widgets(&codec->dapm, dai);
}
if (driver->probe) { // 调用driver里对应的probe回调函数
ret = driver->probe(codec);
if (ret < 0) {
dev_err(codec->dev, "ASoC: failed to probe CODEC %d\\n", ret);
goto err_probe;
}
}
/* mark codec as probed and add to card codec list */
codec->probed = 1;
list_add(&codec->card_list, &card->codec_dev_list);
list_add(&codec->dapm.list, &card->dapm_list);
return 0;
}
4-4代码详解
static int soc_probe_link_dais(struct snd_soc_card *card, int num, int order)
{
int ret;
if (!dai_link->params) {
/* create the pcm */
ret = soc_new_pcm(rtd, num); //针对每个rtd(也就是每个dai_link),创建一个PCM逻辑设备--->
if (ret < 0) {
dev_err(card->dev, "ASoC: can't create pcm %s :%d\\n",
dai_link->stream_name, ret);
return ret;
}
}
return 0;
}
// sound/soc/soc-pcm.c
int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
{
struct snd_soc_platform *platform = rtd->platform;
struct snd_soc_dai *codec_dai = rtd->codec_dai;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct snd_pcm *pcm;
char new_name[64];
int ret = 0, playback = 0, capture = 0;
if (rtd->dai_link->dynamic)
snprintf(new_name, sizeof(new_name), "%s (*)",
rtd->dai_link->stream_name);
else
snprintf(new_name, sizeof(new_name), "%s %s-%d",
rtd->dai_link->stream_name, codec_dai->name, num);
//创建PCM逻辑设备 --->
ret = snd_pcm_new(rtd->card->snd_card, new_name, num, playback, capture, &pcm);
}
if (ret < 0) {
dev_err(rtd->card->dev, "ASoC: can't create pcm for %s\\n",
rtd->dai_link->name);
return ret;
}
dev_dbg(rtd->card->dev, "ASoC: registered pcm #%d %s\\n",num, new_name);
/* DAPM dai link stream work */
INIT_DELAYED_WORK(&rtd->delayed_work, close_delayed_work);
if (playback)
//底层驱动需要实现rtd->ops这个结构体
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &rtd->ops);
if (capture)
//用户空间打开pcm设备文件以后,会间接调用rtd->ops里的底层驱动回调函数
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &rtd->ops);
if (platform->driver->pcm_new) { //此回调函数中会分配环形缓冲区
ret = platform->driver->pcm_new(rtd);
if (ret < 0) {
dev_err(platform->dev, "ASoC: pcm constructor failed: %d\\n", ret);
return ret;
}
}
return ret;
}
int snd_pcm_new(struct snd_card *card, const char *id, int device,
int playback_count, int capture_count, struct snd_pcm **rpcm)
{
return _snd_pcm_new(card, id, device, playback_count, capture_count, false, rpcm);
}
// sound/core/pcm.c
static int _snd_pcm_new(struct snd_card *card, const char *id, int device,
int playback_count, int capture_count, bool internal,
struct snd_pcm **rpcm)
{
struct snd_pcm *pcm;
int err;
static struct snd_device_ops ops = {
.dev_free = snd_pcm_dev_free,
.dev_register = snd_pcm_dev_register, // ===》疑问2
.dev_disconnect = snd_pcm_dev_disconnect,
};
······
if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK, playback_count)) < 0) {
snd_pcm_free(pcm);
return err;
}
if ((err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, capture_count)) < 0) {
snd_pcm_free(pcm);
return err;
}
return 0;
}
static int snd_pcm_dev_register(struct snd_device *device)
{
int cidx, err;
struct snd_pcm_substream *substream;
struct snd_pcm_notify *notify;
char str[16];
struct snd_pcm *pcm;
struct device *dev;
/* register pcm 生成dev/pcmC0D0c或pcmC0D0p设备文件*/
err = snd_register_device_for_dev(devtype, pcm->card,
pcm->device,
&snd_pcm_f_ops[cidx],
pcm, str, dev);
return 0;
}
int snd_register_device_for_dev(int type, struct snd_card *card, int dev,
const struct file_operations *f_ops,
void *private_data,
const char *name, struct device *device)
{
int minor;
struct snd_minor *preg;
preg = kmalloc(sizeof *preg, GFP_KERNEL);
preg->type = type;
preg->card = card ? card->number : -1;
preg->device = dev;
preg->f_ops = f_ops; //重点操作之一
preg->private_data = private_data;
preg->card_ptr = card;
//将pcmC0D0c设备或pcmC0D0p设备对应的f_ops放到全局数组snd_minors[]中,用户空间打开设备文件时会从snd_minors[]数组中取出对应的ops操作集
snd_minors[minor] = preg;
preg->dev = device_create(sound_class, device, MKDEV(major, minor),
private_data, "%s", name);
return 0;
}
4-5代码详解
// sound/core/init.c
int snd_card_register(struct snd_card *card)
{
int err;
if (!card->card_dev) {
card->card_dev = device_create(sound_class, card->dev,
MKDEV(0, 0), card,
"card%i", card->number);
}
if ((err = snd_device_register_all(card)) < 0) // --->
return err;
snd_cards[card->number] = card;
mutex_unlock(&snd_card_mutex);
init_info_for_card(card);
return 0;
}
// sound/core/device.c
int snd_device_register_all(struct snd_card *card)
{
struct snd_device *dev;
int err;
list_for_each_entry(dev, &card->devices, list) {
if (dev->state == SNDRV_DEV_BUILD && dev->ops->dev_register) {
//调用疑问1和疑问2处赋值的dev_register函数指针
if ((err = dev->ops->dev_register(dev)) < 0)
return err;
dev->state = SNDRV_DEV_REGISTERED;
}
}
return 0;
}
5. 与用户交互的接口
在snd_open中, 会根据次设备号(每个逻辑设备都有自己的次设备号), 选择对应的逻辑设备的ops函数, 然后替换file->f_op, 此后用户空间的任何操作都是直接与逻辑设备的ops函数交互的。
ALSA系统中有一个全局数组snd_minors[], 数组的下标就是次设备号, 每个元素对应一个逻辑设备, snd_minor[i]-> f_ops存储的就是逻辑设备自己的ops函数,用次设备号作为下标, 从snd_minors[]中找到对应的元素, 然后用元素的f_ops替换file->f_op即可。
//sound/core/sound.c
#define CONFIG_SND_MAJOR 116 /* standard configuration */
static int major = CONFIG_SND_MAJOR;
static int __init alsa_sound_init(void)
{
snd_major = major;
snd_ecards_limit = cards_limit;
if (register_chrdev(major, "alsa", &snd_fops)) { //注册字符设备,snd_fops是关键
snd_printk(KERN_ERR "unable to register native major device number %d\\n", major);
return -EIO;
}
snd_info_minor_register();
return 0;
}
subsys_initcall(alsa_sound_init);
static const struct file_operations snd_fops =
{
.owner = THIS_MODULE,
.open = snd_open,
.llseek = noop_llseek,
};
static int snd_open(struct inode *inode, struct file *file)
{
unsigned int minor = iminor(inode);
struct snd_minor *mptr = NULL;
const struct file_operations *old_fops;
int err = 0;
if (minor >= ARRAY_SIZE(snd_minors))
return -ENODEV;
//根据次设备号取出设备对应的file_operations结构体
mptr = snd_minors[minor];
old_fops = file->f_op;
//将file->f_op 函数操作集替换成对应逻辑设备的file_operations结构体,譬如dev/controlC0设备
file->f_op = fops_get(mptr->f_ops);
if (file->f_op == NULL) {
file->f_op = old_fops;
err = -ENODEV;
}
if (file->f_op->open) {
err = file->f_op->open(inode, file);
if (err) {
fops_put(file->f_op);
file->f_op = fops_get(old_fops);
}
}
fops_put(old_fops);
return err;
}
以上是关于linux 音频子系统代码分析的主要内容,如果未能解决你的问题,请参考以下文章