敞开成长之旅!这是我参与「日新方案 2 月更文应战」的第 3 天,点击查看活动概况

1、基本概念及逻辑关系

Linux ALSA驱动之三:PCM创建流程源码分析(基于Linux 5.18)

  如上图,经过上一节声卡的学习咱们现已知道PCM是声卡的一个子设备,或许表示一个PCM实例。

  每个声卡最多能够包括4个pcm的实例,每个pcm实例对应一个pcm设备文件。pcm实例数量的这种限制源于linux设备号所占用的位巨细,假如今后运用64位的设备号,咱们将能够创立更多的pcm实例。不过大多数情况下,在嵌入式设备中,一个pcm实例现已足够了。

  一个pcm实例由一个playback stream和一个capture stream组成,这两个stream又别离有一个或多个substreams组成。能够用如下图来表示他们直接的逻辑关系:

Linux ALSA驱动之三:PCM创建流程源码分析(基于Linux 5.18)

  当一个子流现已存在,而且现已被打开,当再次被打开的时分,会被堵塞。

  在实践的运用中,一般不会如上图这么杂乱,大多数情况下是一个声卡有一个PCM实例,PCM下面有一个playback和capture,而playback和capture各自有一个substream。

  PCM层有几个很重要的结构体,咱们经过如下的UML图来梳理他们直接的关系。

Linux ALSA驱动之三:PCM创建流程源码分析(基于Linux 5.18)

  1、snd_pcm: 挂在snd_card下面的一个snd_device。

  2、snd_pcm中的字段:streams[2] :该数组中的两个元素指向两个snd_pcm_str结构,别离代表playback stream和capture stream。

  3、snd_pcm_str中的substream字段:指向snd_pcm_substream结构。

  4、snd_pcm_substream是pcm中间层的核心,绝大部分任务都是在substream中处理,尤其是他的ops(snd_pcm_ops)字段,许多user空间的运用程序经过alsa-lib对驱动程序的请求都是由该结构中的函数处理。它的runtime字段则指向snd_pcm_runtime结构,snd_pcm_runtime记录这substream的一些重要的软件和硬件运转环境和参数。

2、PCM创立流程

  PCM的整个创立流程请参考如下时序图进行理解:

Linux ALSA驱动之三:PCM创建流程源码分析(基于Linux 5.18)

  alsa-driver的中间层现已提供新建PCM的API:

2.1、创立PCM实例

int snd_pcm_new(struct snd_card *card, const char *id, int device,
		int playback_count, int capture_count, struct snd_pcm **rpcm)

  card: 表示所属的声卡。

  ID: PCM实例的ID(名字)。

  device: 表示目前创立的是该声卡下的第几个PCM,榜首个PCM设备从0开端计数。

  playback_count: 表示该PCM播映流中将会有几个substream。

  capture_count :表示该PCM录音流中将会有几个substream。

  rpcm: 回来的PCM实例。

  该函数的首要作用是创立PCM逻辑设备,创立回放子流和录制子流实例,并初始化回放子流和录制子流的PCM操作函数(数据搬运时,需求调用这些函数来驱动 codec、codec_dai、cpu_dai、dma 设备作业)。

2.2、设置PCM设备的操作函数

void snd_pcm_set_ops(struct snd_pcm *pcm, int direction,
		     const struct snd_pcm_ops *ops)

  pcm: 上述snd_pcm_new 创立的PCM实例。

  direction: 是指SNDRV_PCM_STREAM_PLAYBACK或SNDRV_PCM_STREAM_CAPTURE,即设置为播映或许录音功能。

  snd_pcm_ops: 结构中的函数一般便是咱们驱动要完成的函数。

2.3、界说PCM的操作函数

  以AC97驱动(linux/sound/arm/pxa2xx-ac97.c)为例,在驱动中关于PCM进行了如下设置:

static const struct snd_pcm_ops pxa2xx_ac97_pcm_ops = {
    .open	= pxa2xx_ac97_pcm_open,
    .close	= pxa2xx_ac97_pcm_close,
    .hw_params	= pxa2xx_pcm_hw_params,
    .prepare	= pxa2xx_ac97_pcm_prepare,
    .trigger	= pxa2xx_pcm_trigger,
    .pointer	= pxa2xx_pcm_pointer,
};
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &pxa2xx_ac97_pcm_ops);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &pxa2xx_ac97_pcm_ops);

2.4、界说硬件参数

static const struct snd_pcm_hardware pxa2xx_pcm_hardware = {
    .info		    = SNDRV_PCM_INFO_MMAP |
                              SNDRV_PCM_INFO_MMAP_VALID |
                              SNDRV_PCM_INFO_INTERLEAVED |
                              SNDRV_PCM_INFO_PAUSE |
                              SNDRV_PCM_INFO_RESUME,
    .formats		    = SNDRV_PCM_FMTBIT_S16_LE |
                              SNDRV_PCM_FMTBIT_S24_LE |
                              SNDRV_PCM_FMTBIT_S32_LE,
    .period_bytes_min	= 32,
    .period_bytes_max	= 8192 - 32,
    .periods_min	= 1,
    .periods_max	= 256,
    .buffer_bytes_max	= 128 * 1024,
    .fifo_size		= 32,
};
int pxa2xx_pcm_open(struct snd_pcm_substream *substream)
{
	struct snd_soc_pcm_runtime *rtd = substream->private_data;
	struct snd_pcm_runtime *runtime = substream->runtime;
	struct snd_dmaengine_dai_dma_data *dma_params;
	int ret;
	runtime->hw = pxa2xx_pcm_hardware;
	dma_params = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0), substream);
	if (!dma_params)
            return 0;
	/*
	 * For mysterious reasons (and despite what the manual says)
	 * playback samples are lost if the DMA count is not a multiple
	 * of the DMA burst size.  Let's add a rule to enforce that.
	 */
	ret = snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 32);
	if (ret)
            return ret;
	ret = snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 32);
	if (ret)
            return ret;
	ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
	if (ret < 0)
            return ret;
	return snd_dmaengine_pcm_open(substream, dma_request_slave_channel(asoc_rtd_to_cpu(rtd, 0)->dev, dma_params->chan_name));
}

3、PCM相关源码剖析

3.1、snd_pcm_new

/**
 * snd_pcm_new - create a new PCM instance
 * @card: the card instance
 * @id: the id string
 * @device: the device index (zero based)
 * @playback_count: the number of substreams for playback
 * @capture_count: the number of substreams for capture
 * @rpcm: the pointer to store the new pcm instance
 *
 * Creates a new PCM instance.
 *
 * The pcm operators have to be set afterwards to the new instance
 * via snd_pcm_set_ops().
 *
 * Return: Zero if successful, or a negative error code on failure.
 */
int snd_pcm_new(struct snd_card *card, const char *id, int device,
		int playback_count, int capture_count, struct snd_pcm **rpcm)
{
    /* 直接调用函数_snd_pcm_new,参数internal传入false */
    return _snd_pcm_new(card, id, device, playback_count, capture_count, false, rpcm);
}
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;
    /* 1. 逻辑设备的操作函数结构体, 首要用于注册子设备 */
    static const struct snd_device_ops ops = {
        .dev_free = snd_pcm_dev_free,
        .dev_register =	snd_pcm_dev_register,
        .dev_disconnect = snd_pcm_dev_disconnect,
    };
    static const struct snd_device_ops internal_ops = {
        .dev_free = snd_pcm_dev_free,
    };
    if (snd_BUG_ON(!card))
        return -ENXIO;
    if (rpcm)
        *rpcm = NULL;
    /* 2. 为snd_pcm结构体分配空间,依据传入参数赋值 */
    pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);
    if (!pcm)
        return -ENOMEM;
    pcm->card = card;
    pcm->device = device;
    pcm->internal = internal;
    mutex_init(&pcm->open_mutex);
    init_waitqueue_head(&pcm->open_wait);
    INIT_LIST_HEAD(&pcm->list);
    if (id)
        strscpy(pcm->id, id, sizeof(pcm->id));
    /* 3. 依据传入的playback和capture的个数创立PCM流 snd_pcm_str */
    err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK,
                             playback_count);
    if (err < 0)
        goto free_pcm;
    err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, capture_count);
    if (err < 0)
        goto free_pcm;
    /* 4. 创立一个PCM逻辑设备,创立逻辑设备,并增加到逻辑设备链表 */
	err = snd_device_new(card, SNDRV_DEV_PCM, pcm, internal ? &internal_ops : &ops);
	if (err < 0)
            goto free_pcm;
	if (rpcm)
            *rpcm = pcm;
	return 0;
free_pcm:
    snd_pcm_free(pcm);
    return err;
}

3.2、snd_pcm

struct snd_pcm {
    struct snd_card *card;
    struct list_head list;
    int device; /* device number */
    unsigned int info_flags;
    unsigned short dev_class;
    unsigned short dev_subclass;
    char id[64];
    char name[80];
    struct snd_pcm_str streams[2];
    struct mutex open_mutex;
    wait_queue_head_t open_wait;
    void *private_data;
    void (*private_free) (struct snd_pcm *pcm);
    bool internal; /* pcm is for internal use only */
    bool nonatomic; /* whole PCM operations are in non-atomic context */
    bool no_device_suspend; /* don't invoke device PM suspend */
#if IS_ENABLED(CONFIG_SND_PCM_OSS)
    struct snd_pcm_oss oss;
#endif
};

  这儿重要的变量有两个streams与private_data。streams有两个,是因为一个指向播映设备,一个指向录音设备。private_data在很多结构里都能够看到,和面象目标里的承继有点类似,假如将snd_pcm理解为基类的话,private_data指向的便是它的承继类,也便是真实的完成者。

  list,在pcm.c中有一个大局变量snd_pcm_devices,将一切的snd_pcm目标链接起来,目的是外部提供一些可供枚举一切设备的接口,看起来并不怎样被用到。

  别的还有info_flags、dev_class等变量看起来是为一些特殊设备预留的,对待一些特殊操作。

struct snd_pcm_str {
    int stream;				/* stream (direction) */
    struct snd_pcm *pcm;
    /* -- substreams -- */
    unsigned int substream_count;
    unsigned int substream_opened;
    struct snd_pcm_substream *substream;
#if IS_ENABLED(CONFIG_SND_PCM_OSS)
    /* -- OSS things -- */
    struct snd_pcm_oss_stream oss;
#endif
#ifdef CONFIG_SND_VERBOSE_PROCFS
    struct snd_info_entry *proc_root;
#ifdef CONFIG_SND_PCM_XRUN_DEBUG
    unsigned int xrun_debug;	/* 0 = disabled, 1 = verbose, 2 = stacktrace */
#endif
#endif
    struct snd_kcontrol *chmap_kctl; /* channel-mapping controls */
    struct device dev;
};

  snd_pcm_str的首要作用是指向snd_pcm_substream,而snd_pcm_substream能够有多个,这也是snd_pcm_str存在的原因,否则snd_pcm直接指向snd_pcm_substream就能够了。

  这儿的dev是将pcm加入到文件体系时要用到。包括的信息,在下面介绍的snd_pcm_new_stream中会看到。

3.3、snd_pcm_new_stream

/**
 * snd_pcm_new_stream - create a new PCM stream
 * @pcm: the pcm instance
 * @stream: the stream direction, SNDRV_PCM_STREAM_XXX
 * @substream_count: the number of substreams
 *
 * Creates a new stream for the pcm.
 * The corresponding stream on the pcm must have been empty before
 * calling this, i.e. zero must be given to the argument of
 * snd_pcm_new().
 *
 * Return: Zero if successful, or a negative error code on failure.
 */
int snd_pcm_new_stream(struct snd_pcm *pcm, int stream, int substream_count)
{
    int idx, err;
    /* 3.1 依据传入的参数,为PCM流(snd_pcm_str)赋值:方向,所属的PCM,PCM子流的个数 */
    struct snd_pcm_str *pstr = &pcm->streams[stream];
    struct snd_pcm_substream *substream, *prev;
#if IS_ENABLED(CONFIG_SND_PCM_OSS)
    mutex_init(&pstr->oss.setup_mutex);
#endif
    pstr->stream = stream;
    pstr->pcm = pcm;
    pstr->substream_count = substream_count;
    if (!substream_count)
        return 0;
    snd_device_initialize(&pstr->dev, pcm->card);
    pstr->dev.groups = pcm_dev_attr_groups;
    pstr->dev.type = &pcm_dev_type;
    dev_set_name(&pstr->dev, "pcmC%iD%i%c", pcm->card->number, pcm->device, stream == SNDRV_PCM_STREAM_PLAYBACK ? 'p' : 'c');
    /* proc */
    if (!pcm->internal) {
        err = snd_pcm_stream_proc_init(pstr);
        if (err < 0) {
            pcm_err(pcm, "Error in snd_pcm_stream_proc_init\n");
            return err;
        }
    }
    prev = NULL;
    for (idx = 0, prev = NULL; idx < substream_count; idx++) {
        /* 为子流分配空间,赋值(pcm,pcm流,ID, 方向.....) */
        substream = kzalloc(sizeof(*substream), GFP_KERNEL);
        if (!substream)
            return -ENOMEM;
        substream->pcm = pcm;
        substream->pstr = pstr;
        substream->number = idx;
        substream->stream = stream;
        sprintf(substream->name, "subdevice #%i", idx);
        substream->buffer_bytes_max = UINT_MAX;
        /* 增加子流到子流的链表 */
        if (prev == NULL)    /* 榜首个子流 */
            pstr->substream = substream;
        else
            prev->next = substream;    /* 非榜首个子流,增加到前一个子流后部 */
        /* proc */
        if (!pcm->internal) {
            err = snd_pcm_substream_proc_init(substream);
            if (err < 0) {
                pcm_err(pcm, "Error in snd_pcm_stream_proc_init\n");
                if (prev == NULL)
                    pstr->substream = NULL;
                else
                    prev->next = NULL;
                kfree(substream);
                return err;
            }
        }
        /* 结构体初始化 */
        substream->group = &substream->self_group;
        snd_pcm_group_init(&substream->self_group);
        list_add_tail(&substream->link_list, &substream->self_group.substreams);
        atomic_set(&substream->mmap_count, 0);
        prev = substream;
    }
    return 0;
}

  函数参数中的int stream,是一个枚举类型:

enum {
    SNDRV_PCM_STREAM_PLAYBACK = 0,
    SNDRV_PCM_STREAM_CAPTURE,
    SNDRV_PCM_STREAM_LAST = SNDRV_PCM_STREAM_CAPTURE,
};

  从snd_device_initialize(&pstr->dev, pcm->card);开端。dev终究会被传入device_add函数中,用来构建文件体系。

void snd_device_initialize(struct device *dev, struct snd_card *card)
{
    device_initialize(dev);
    if (card)
            dev->parent = &card->card_dev;
    dev->class = sound_class;
    dev->release = default_release;
}

  这段函数中能够看到dev->class被设置成sound_class,这个是咱们之前说到的文件放到snd目录的原因。

3.4、snd_pcm_substream

struct snd_pcm_substream {
    struct snd_pcm *pcm;
    struct snd_pcm_str *pstr;
    void *private_data;		/* copied from pcm->private_data */
    int number;
    char name[32];			/* substream name */
    int stream;			/* stream (direction) */
    struct pm_qos_request latency_pm_qos_req; /* pm_qos request */
    size_t buffer_bytes_max;	/* limit ring buffer size */
    struct snd_dma_buffer dma_buffer;
    size_t dma_max;
    /* -- hardware operations -- */
    const struct snd_pcm_ops *ops;
    /* -- runtime information -- */
    struct snd_pcm_runtime *runtime;
    /* -- timer section -- */
    struct snd_timer *timer;		/* timer */
    unsigned timer_running: 1;	/* time is running */
    long wait_time;	/* time in ms for R/W to wait for avail */
    /* -- next substream -- */
    struct snd_pcm_substream *next;
    /* -- linked substreams -- */
    struct list_head link_list;	/* linked list member */
    struct snd_pcm_group self_group;	/* fake group for non linked substream (with substream lock inside) */
    struct snd_pcm_group *group;		/* pointer to current group */
    /* -- assigned files -- */
    int ref_count;
    atomic_t mmap_count;
    unsigned int f_flags;
    void (*pcm_release)(struct snd_pcm_substream *);
    struct pid *pid;
#if IS_ENABLED(CONFIG_SND_PCM_OSS)
    /* -- OSS things -- */
    struct snd_pcm_oss_substream oss;
#endif
#ifdef CONFIG_SND_VERBOSE_PROCFS
    struct snd_info_entry *proc_root;
#endif /* CONFIG_SND_VERBOSE_PROCFS */
    /* misc flags */
    unsigned int hw_opened: 1;
    unsigned int managed_buffer_alloc:1;
};

  snd_pcm_substream的内容有些多,此处只需求重要的进行介绍。

  private_data: 从snd_pcm中的private_data复制过来的,指向完成者的结构。

  const struct snd_pcm_ops *ops: 这部分是框架的内容,详细的操作需求完成者的参与,留给完成者的函数指针集。这个和文件操作的规划战略是一致的。

  struct snd_pcm_runtime *runtime: 读写数据的时分由它来控制。到剖析读写代码的时分,会要点重视它。

  struct snd_pcm_substream *next: 将多个snd_pcm_substream目标链接起来,它便是snd_pcm_str指向的链接。

  group: 在用户空间能够经过SNDRV_PCM_IOCTL_LINK将多个substream链接起来。然后就能够对这些目标进行一致的操作。我没遇到过详细的运用场景。

3.5、snd_pcm_set_ops

/**
 * snd_pcm_set_ops - set the PCM operators
 * @pcm: the pcm instance
 * @direction: stream direction, SNDRV_PCM_STREAM_XXX
 * @ops: the operator table
 *
 * Sets the given PCM operators to the pcm instance.
 */
void snd_pcm_set_ops(struct snd_pcm *pcm, int direction,
		     const struct snd_pcm_ops *ops)
{
    struct snd_pcm_str *stream = &pcm->streams[direction];
    struct snd_pcm_substream *substream;
    for (substream = stream->substream; substream != NULL; substream = substream->next)
            substream->ops = ops;
}
EXPORT_SYMBOL(snd_pcm_set_ops);

  此函数是提供给调用侧运用的。设置的内容能够参考pcm文件结构简图。

3.6、snd_pcm_dev_register

  在持续剖析snd_pcm_dev_register函数之前需求先介绍一个结构体。struct snd_minor。

struct snd_minor {
    int type;			/* SNDRV_DEVICE_TYPE_XXX */
    int card;			/* card number */
    int device;			/* device number */
    const struct file_operations *f_ops;	/* file operations */
    void *private_data;		/* private data for f_ops->open */
    struct device *dev;		/* device for sysfs */
    struct snd_card *card_ptr;	/* assigned card instance */
};

  type: 设备类型,比如是pcm, control, timer等设备。

  card_number: 所属的card。

  device: 当时设备类型下的设备编号。

  f_ops: 详细设备的文件操作集合。

  private_data: open函数的私有数据。

  card_ptr: 所属的card。

此结构体是用来保存当时设备的上下文信息,该card下一切逻辑设备都存在此结构。

static int snd_pcm_dev_register(struct snd_device *device)
{
    /* 1、增加pcm结构体到大局链表snd_pcm_devices */
    int cidx, err;
    struct snd_pcm_substream *substream;
    struct snd_pcm *pcm;
    if (snd_BUG_ON(!device || !device->device_data))
        return -ENXIO;
    /* snd_devcie保存的是snd_pcm目标 */
    pcm = device->device_data;
    mutex_lock(&register_mutex);
    /* snd_pcm目标将被保存到大局变量snd_pcm_devices中,用于枚举设备等操作 */
    err = snd_pcm_add(pcm);
    if (err)
        goto unlock;
    for (cidx = 0; cidx < 2; cidx++) {
        /* 2、确认PCM设备节点名字 */
        int devtype = -1;
        if (pcm->streams[cidx].substream == NULL)
            continue;
        switch (cidx) {
        case SNDRV_PCM_STREAM_PLAYBACK:
            devtype = SNDRV_DEVICE_TYPE_PCM_PLAYBACK;
            break;
        case SNDRV_PCM_STREAM_CAPTURE:
            devtype = SNDRV_DEVICE_TYPE_PCM_CAPTURE;
            break;
        }
        /* register pcm */
        /* 将设备增加到文件体系,将snd_pcm_f_ops传入,将被设置给snd_minor目标 */
        err = snd_register_device(devtype, pcm->card, pcm->device,
                                  &snd_pcm_f_ops[cidx], pcm,
                                  &pcm->streams[cidx].dev);
        if (err < 0) {
            list_del_init(&pcm->list);
            goto unlock;
        }
        for (substream = pcm->streams[cidx].substream; substream; substream = substream->next)
            /* 设定CONFIG_SND_PCM_TIMER宏的时分,会去设置substream的时间 */
            snd_pcm_timer_init(substream);
    }
    pcm_call_notify(pcm, n_register);
 unlock:
    mutex_unlock(&register_mutex);
    return err;
}
/**
 * snd_register_device - Register the ALSA device file for the card
 * @type: the device type, SNDRV_DEVICE_TYPE_XXX
 * @card: the card instance
 * @dev: the device index
 * @f_ops: the file operations
 * @private_data: user pointer for f_ops->open()
 * @device: the device to register
 *
 * Registers an ALSA device file for the given card.
 * The operators have to be set in reg parameter.
 *
 * Return: Zero if successful, or a negative error code on failure.
 */
int snd_register_device(int type, struct snd_card *card, int dev,
			const struct file_operations *f_ops,
			void *private_data, struct device *device)
{
    int minor;
    int err = 0;
    struct snd_minor *preg;
    if (snd_BUG_ON(!device))
        return -EINVAL;
    preg = kmalloc(sizeof *preg, GFP_KERNEL);
    if (preg == NULL)
        return -ENOMEM;
    /* 创立一个snd_minor,并增加到大局结构体 snd_minors */
    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;
    mutex_lock(&sound_mutex);
    /* 4、注册一个设备节点 */
    minor = snd_find_free_minor(type, card, dev);
    if (minor < 0) {
        err = minor;
        goto error;
    }
    preg->dev = device;
    device->devt = MKDEV(major, minor);
    err = device_add(device);
    if (err < 0)
        goto error;
    snd_minors[minor] = preg;
 error:
    mutex_unlock(&sound_mutex);
    if (err < 0)
        kfree(preg);
    return err;
}

  当声卡被注册时,会注册一切的逻辑设备。首要的作业是创立PCM设备节点详细的流程:

    1、增加pcm结构体到大局链表snd_pcm_devices。

    2、确认PCM设备节点名字。

    3、创立一个snd_minor,并增加到大局结构体 snd_minors。

    4、注册一个设备节点

  能够看到增加到文件体系的是播映设备和录音设备,依据snd_pcm_str指向的内容来设定的。代码中看到snd_pcm也被界说为SNDRV_DEV_PCM设备,可是文件体系中并不会保存这个类型的设备。

  snd_pcm_timer_init是在CONFIG_SND_PCM_TIMER宏被界说的时分,会起作用。

  经过下图能够帮助你更好的理解各结构直接的逻辑关系。

Linux ALSA驱动之三:PCM创建流程源码分析(基于Linux 5.18)