Linux ALSA驱动之三:PCM创建流程源码分析(基于Linux 5.18)下
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(®ister_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(®ister_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宏被定义的时候,会起作用。
通过下图可以帮助你更好的理解各结构直接的乱讲关系。