Linux音频子系统 -代码分析(以YMU836为例)
Posted 四季帆
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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音频子系统 -代码分析(以YMU836为例)的主要内容,如果未能解决你的问题,请参考以下文章
linux驱动由浅入深系列:tinyalsa(tinymix/tinycap/tinyplay/tinypcminfo)音频子系统之一