• Linux ALSA驱动之PCM创建流程源码分析


    1、基本概念及逻辑关系

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

            每个声卡最多可以包含4个pcm的实例,每个pcm实例对应一个pcm设备文件。pcm实例数量的这种限制源于linux设备号所占用的位大小,如果以后使用64位的设备号,我们将可以创建更多的pcm实例。不过大多数情况下,在嵌入式设备中,一个pcm实例已经足够了。

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

            当一个子流已经存在,并且已经被打开,当再次被打开的时候,会被阻塞。        

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

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

             图片地址:http://hi.csdn.net/attachment/201104/2/0_1301728746sAUd.gif

            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的整个创建流程请参考如下时序图进行理解:

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

    2.1、创建PCM实例

    1. int snd_pcm_new(struct snd_card *card, const char *id, int device,
    2. 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设备的操作函数

    1. void snd_pcm_set_ops(struct snd_pcm *pcm, int direction,
    2. 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进行了如下设置:

    1. static const struct snd_pcm_ops pxa2xx_ac97_pcm_ops = {
    2. .open = pxa2xx_ac97_pcm_open,
    3. .close = pxa2xx_ac97_pcm_close,
    4. .hw_params = pxa2xx_pcm_hw_params,
    5. .prepare = pxa2xx_ac97_pcm_prepare,
    6. .trigger = pxa2xx_pcm_trigger,
    7. .pointer = pxa2xx_pcm_pointer,
    8. };
    9. snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &pxa2xx_ac97_pcm_ops);
    10. snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &pxa2xx_ac97_pcm_ops);

    2.4、定义硬件参数

    1. static const struct snd_pcm_hardware pxa2xx_pcm_hardware = {
    2. .info = SNDRV_PCM_INFO_MMAP |
    3. SNDRV_PCM_INFO_MMAP_VALID |
    4. SNDRV_PCM_INFO_INTERLEAVED |
    5. SNDRV_PCM_INFO_PAUSE |
    6. SNDRV_PCM_INFO_RESUME,
    7. .formats = SNDRV_PCM_FMTBIT_S16_LE |
    8. SNDRV_PCM_FMTBIT_S24_LE |
    9. SNDRV_PCM_FMTBIT_S32_LE,
    10. .period_bytes_min = 32,
    11. .period_bytes_max = 8192 - 32,
    12. .periods_min = 1,
    13. .periods_max = 256,
    14. .buffer_bytes_max = 128 * 1024,
    15. .fifo_size = 32,
    16. };
    17. int pxa2xx_pcm_open(struct snd_pcm_substream *substream)
    18. {
    19. struct snd_soc_pcm_runtime *rtd = substream->private_data;
    20. struct snd_pcm_runtime *runtime = substream->runtime;
    21. struct snd_dmaengine_dai_dma_data *dma_params;
    22. int ret;
    23. runtime->hw = pxa2xx_pcm_hardware;
    24. dma_params = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0), substream);
    25. if (!dma_params)
    26. return 0;
    27. /*
    28. * For mysterious reasons (and despite what the manual says)
    29. * playback samples are lost if the DMA count is not a multiple
    30. * of the DMA burst size. Let's add a rule to enforce that.
    31. */
    32. ret = snd_pcm_hw_constraint_step(runtime, 0,
    33. SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 32);
    34. if (ret)
    35. return ret;
    36. ret = snd_pcm_hw_constraint_step(runtime, 0,
    37. SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 32);
    38. if (ret)
    39. return ret;
    40. ret = snd_pcm_hw_constraint_integer(runtime,
    41. SNDRV_PCM_HW_PARAM_PERIODS);
    42. if (ret < 0)
    43. return ret;
    44. return snd_dmaengine_pcm_open(
    45. substream, dma_request_slave_channel(asoc_rtd_to_cpu(rtd, 0)->dev,
    46. dma_params->chan_name));
    47. }

    3、PCM相关源码分析

    3.1、snd_pcm_new

    1. /**
    2. * snd_pcm_new - create a new PCM instance
    3. * @card: the card instance
    4. * @id: the id string
    5. * @device: the device index (zero based)
    6. * @playback_count: the number of substreams for playback
    7. * @capture_count: the number of substreams for capture
    8. * @rpcm: the pointer to store the new pcm instance
    9. *
    10. * Creates a new PCM instance.
    11. *
    12. * The pcm operators have to be set afterwards to the new instance
    13. * via snd_pcm_set_ops().
    14. *
    15. * Return: Zero if successful, or a negative error code on failure.
    16. */
    17. int snd_pcm_new(struct snd_card *card, const char *id, int device,
    18. int playback_count, int capture_count, struct snd_pcm **rpcm)
    19. {
    20. /* 直接调用函数_snd_pcm_new,参数internal传入false */
    21. return _snd_pcm_new(card, id, device, playback_count, capture_count,
    22. false, rpcm);
    23. }
    24. static int _snd_pcm_new(struct snd_card *card, const char *id, int device,
    25. int playback_count, int capture_count, bool internal,
    26. struct snd_pcm **rpcm)
    27. {
    28. struct snd_pcm *pcm;
    29. int err;
    30. /* 1. 逻辑设备的操作函数结构体, 主要用于注册子设备 */
    31. static const struct snd_device_ops ops = {
    32. .dev_free = snd_pcm_dev_free,
    33. .dev_register = snd_pcm_dev_register,
    34. .dev_disconnect = snd_pcm_dev_disconnect,
    35. };
    36. static const struct snd_device_ops internal_ops = {
    37. .dev_free = snd_pcm_dev_free,
    38. };
    39. if (snd_BUG_ON(!card))
    40. return -ENXIO;
    41. if (rpcm)
    42. *rpcm = NULL;
    43. /* 2. 为snd_pcm结构体分配空间,根据传入参数赋值 */
    44. pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);
    45. if (!pcm)
    46. return -ENOMEM;
    47. pcm->card = card;
    48. pcm->device = device;
    49. pcm->internal = internal;
    50. mutex_init(&pcm->open_mutex);
    51. init_waitqueue_head(&pcm->open_wait);
    52. INIT_LIST_HEAD(&pcm->list);
    53. if (id)
    54. strscpy(pcm->id, id, sizeof(pcm->id));
    55. /* 3. 根据传入的playback和capture的个数创建PCM流 snd_pcm_str */
    56. err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_PLAYBACK,
    57. playback_count);
    58. if (err < 0)
    59. goto free_pcm;
    60. err = snd_pcm_new_stream(pcm, SNDRV_PCM_STREAM_CAPTURE, capture_count);
    61. if (err < 0)
    62. goto free_pcm;
    63. /* 4. 创建一个PCM逻辑设备,创建逻辑设备,并添加到逻辑设备链表 */
    64. err = snd_device_new(card, SNDRV_DEV_PCM, pcm,
    65. internal ? &internal_ops : &ops);
    66. if (err < 0)
    67. goto free_pcm;
    68. if (rpcm)
    69. *rpcm = pcm;
    70. return 0;
    71. free_pcm:
    72. snd_pcm_free(pcm);
    73. return err;
    74. }

    3.2、snd_pcm

    1. struct snd_pcm {
    2. struct snd_card *card;
    3. struct list_head list;
    4. int device; /* device number */
    5. unsigned int info_flags;
    6. unsigned short dev_class;
    7. unsigned short dev_subclass;
    8. char id[64];
    9. char name[80];
    10. struct snd_pcm_str streams[2];
    11. struct mutex open_mutex;
    12. wait_queue_head_t open_wait;
    13. void *private_data;
    14. void (*private_free) (struct snd_pcm *pcm);
    15. bool internal; /* pcm is for internal use only */
    16. bool nonatomic; /* whole PCM operations are in non-atomic context */
    17. bool no_device_suspend; /* don't invoke device PM suspend */
    18. #if IS_ENABLED(CONFIG_SND_PCM_OSS)
    19. struct snd_pcm_oss oss;
    20. #endif
    21. };

             这里重要的变量有两个streams与private_data。streams有两个,是因为一个指向播放设备,一个指向录音设备。private_data在很多结构里都可以看到,和面象对象里的继承有点类似,如果将snd_pcm理解为基类的话,private_data指向的就是它的继承类,也就是真正的实现者。

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

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

    1. struct snd_pcm_str {
    2. int stream; /* stream (direction) */
    3. struct snd_pcm *pcm;
    4. /* -- substreams -- */
    5. unsigned int substream_count;
    6. unsigned int substream_opened;
    7. struct snd_pcm_substream *substream;
    8. #if IS_ENABLED(CONFIG_SND_PCM_OSS)
    9. /* -- OSS things -- */
    10. struct snd_pcm_oss_stream oss;
    11. #endif
    12. #ifdef CONFIG_SND_VERBOSE_PROCFS
    13. struct snd_info_entry *proc_root;
    14. #ifdef CONFIG_SND_PCM_XRUN_DEBUG
    15. unsigned int xrun_debug; /* 0 = disabled, 1 = verbose, 2 = stacktrace */
    16. #endif
    17. #endif
    18. struct snd_kcontrol *chmap_kctl; /* channel-mapping controls */
    19. struct device dev;
    20. };

             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

    1. /**
    2. * snd_pcm_new_stream - create a new PCM stream
    3. * @pcm: the pcm instance
    4. * @stream: the stream direction, SNDRV_PCM_STREAM_XXX
    5. * @substream_count: the number of substreams
    6. *
    7. * Creates a new stream for the pcm.
    8. * The corresponding stream on the pcm must have been empty before
    9. * calling this, i.e. zero must be given to the argument of
    10. * snd_pcm_new().
    11. *
    12. * Return: Zero if successful, or a negative error code on failure.
    13. */
    14. int snd_pcm_new_stream(struct snd_pcm *pcm, int stream, int substream_count)
    15. {
    16. int idx, err;
    17. /* 3.1 根据传入的参数,为PCM流(snd_pcm_str)赋值:方向,所属的PCM,PCM子流的个数 */
    18. struct snd_pcm_str *pstr = &pcm->streams[stream];
    19. struct snd_pcm_substream *substream, *prev;
    20. #if IS_ENABLED(CONFIG_SND_PCM_OSS)
    21. mutex_init(&pstr->oss.setup_mutex);
    22. #endif
    23. pstr->stream = stream;
    24. pstr->pcm = pcm;
    25. pstr->substream_count = substream_count;
    26. if (!substream_count)
    27. return 0;
    28. snd_device_initialize(&pstr->dev, pcm->card);
    29. pstr->dev.groups = pcm_dev_attr_groups;
    30. pstr->dev.type = &pcm_dev_type;
    31. dev_set_name(&pstr->dev, "pcmC%iD%i%c", pcm->card->number, pcm->device,
    32. stream == SNDRV_PCM_STREAM_PLAYBACK ? 'p' : 'c');
    33. /* proc */
    34. if (!pcm->internal) {
    35. err = snd_pcm_stream_proc_init(pstr);
    36. if (err < 0) {
    37. pcm_err(pcm, "Error in snd_pcm_stream_proc_init\n");
    38. return err;
    39. }
    40. }
    41. prev = NULL;
    42. for (idx = 0, prev = NULL; idx < substream_count; idx++) {
    43. /* 为子流分配空间,赋值(pcm,pcm流,ID, 方向.....) */
    44. substream = kzalloc(sizeof(*substream), GFP_KERNEL);
    45. if (!substream)
    46. return -ENOMEM;
    47. substream->pcm = pcm;
    48. substream->pstr = pstr;
    49. substream->number = idx;
    50. substream->stream = stream;
    51. sprintf(substream->name, "subdevice #%i", idx);
    52. substream->buffer_bytes_max = UINT_MAX;
    53. /* 添加子流到子流的链表 */
    54. if (prev == NULL) /* 第一个子流 */
    55. pstr->substream = substream;
    56. else
    57. prev->next = substream; /* 非第一个子流,添加到前一个子流后部 */
    58. /* proc */
    59. if (!pcm->internal) {
    60. err = snd_pcm_substream_proc_init(substream);
    61. if (err < 0) {
    62. pcm_err(pcm,
    63. "Error in snd_pcm_stream_proc_init\n");
    64. if (prev == NULL)
    65. pstr->substream = NULL;
    66. else
    67. prev->next = NULL;
    68. kfree(substream);
    69. return err;
    70. }
    71. }
    72. /* 结构体初始化 */
    73. substream->group = &substream->self_group;
    74. snd_pcm_group_init(&substream->self_group);
    75. list_add_tail(&substream->link_list, &substream->self_group.substreams);
    76. atomic_set(&substream->mmap_count, 0);
    77. prev = substream;
    78. }
    79. return 0;
    80. }

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

    1. enum {
    2. SNDRV_PCM_STREAM_PLAYBACK = 0,
    3. SNDRV_PCM_STREAM_CAPTURE,
    4. SNDRV_PCM_STREAM_LAST = SNDRV_PCM_STREAM_CAPTURE,
    5. };

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

    1. void snd_device_initialize(struct device *dev, struct snd_card *card)
    2. {
    3. device_initialize(dev);
    4. if (card)
    5. dev->parent = &card->card_dev;
    6. dev->class = sound_class;
    7. dev->release = default_release;
    8. }

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

    3.4、snd_pcm_substream

    1. struct snd_pcm_substream {
    2. struct snd_pcm *pcm;
    3. struct snd_pcm_str *pstr;
    4. void *private_data; /* copied from pcm->private_data */
    5. int number;
    6. char name[32]; /* substream name */
    7. int stream; /* stream (direction) */
    8. struct pm_qos_request latency_pm_qos_req; /* pm_qos request */
    9. size_t buffer_bytes_max; /* limit ring buffer size */
    10. struct snd_dma_buffer dma_buffer;
    11. size_t dma_max;
    12. /* -- hardware operations -- */
    13. const struct snd_pcm_ops *ops;
    14. /* -- runtime information -- */
    15. struct snd_pcm_runtime *runtime;
    16. /* -- timer section -- */
    17. struct snd_timer *timer; /* timer */
    18. unsigned timer_running: 1; /* time is running */
    19. long wait_time; /* time in ms for R/W to wait for avail */
    20. /* -- next substream -- */
    21. struct snd_pcm_substream *next;
    22. /* -- linked substreams -- */
    23. struct list_head link_list; /* linked list member */
    24. struct snd_pcm_group self_group; /* fake group for non linked substream (with substream lock inside) */
    25. struct snd_pcm_group *group; /* pointer to current group */
    26. /* -- assigned files -- */
    27. int ref_count;
    28. atomic_t mmap_count;
    29. unsigned int f_flags;
    30. void (*pcm_release)(struct snd_pcm_substream *);
    31. struct pid *pid;
    32. #if IS_ENABLED(CONFIG_SND_PCM_OSS)
    33. /* -- OSS things -- */
    34. struct snd_pcm_oss_substream oss;
    35. #endif
    36. #ifdef CONFIG_SND_VERBOSE_PROCFS
    37. struct snd_info_entry *proc_root;
    38. #endif /* CONFIG_SND_VERBOSE_PROCFS */
    39. /* misc flags */
    40. unsigned int hw_opened: 1;
    41. unsigned int managed_buffer_alloc:1;
    42. };

            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

    1. /**
    2. * snd_pcm_set_ops - set the PCM operators
    3. * @pcm: the pcm instance
    4. * @direction: stream direction, SNDRV_PCM_STREAM_XXX
    5. * @ops: the operator table
    6. *
    7. * Sets the given PCM operators to the pcm instance.
    8. */
    9. void snd_pcm_set_ops(struct snd_pcm *pcm, int direction,
    10. const struct snd_pcm_ops *ops)
    11. {
    12. struct snd_pcm_str *stream = &pcm->streams[direction];
    13. struct snd_pcm_substream *substream;
    14. for (substream = stream->substream; substream != NULL; substream = substream->next)
    15. substream->ops = ops;
    16. }
    17. EXPORT_SYMBOL(snd_pcm_set_ops);

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

    3.6、snd_pcm_dev_register

            在继续分析snd_pcm_dev_register函数之前需要先介绍一个结构体。struct snd_minor。

    1. struct snd_minor {
    2. int type; /* SNDRV_DEVICE_TYPE_XXX */
    3. int card; /* card number */
    4. int device; /* device number */
    5. const struct file_operations *f_ops; /* file operations */
    6. void *private_data; /* private data for f_ops->open */
    7. struct device *dev; /* device for sysfs */
    8. struct snd_card *card_ptr; /* assigned card instance */
    9. };

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

            card_number: 所属的card。

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

            f_ops: 具体设备的文件操作集合。

            private_data: open函数的私有数据。

            card_ptr: 所属的card。

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

    1. static int snd_pcm_dev_register(struct snd_device *device)
    2. {
    3. /* 1、添加pcm结构体到全局链表snd_pcm_devices */
    4. int cidx, err;
    5. struct snd_pcm_substream *substream;
    6. struct snd_pcm *pcm;
    7. if (snd_BUG_ON(!device || !device->device_data))
    8. return -ENXIO;
    9. /* snd_devcie保存的是snd_pcm对象 */
    10. pcm = device->device_data;
    11. mutex_lock(®ister_mutex);
    12. /* snd_pcm对象将被保存到全局变量snd_pcm_devices中,用于枚举设备等操作 */
    13. err = snd_pcm_add(pcm);
    14. if (err)
    15. goto unlock;
    16. for (cidx = 0; cidx < 2; cidx++) {
    17. /* 2、确定PCM设备节点名字 */
    18. int devtype = -1;
    19. if (pcm->streams[cidx].substream == NULL)
    20. continue;
    21. switch (cidx) {
    22. case SNDRV_PCM_STREAM_PLAYBACK:
    23. devtype = SNDRV_DEVICE_TYPE_PCM_PLAYBACK;
    24. break;
    25. case SNDRV_PCM_STREAM_CAPTURE:
    26. devtype = SNDRV_DEVICE_TYPE_PCM_CAPTURE;
    27. break;
    28. }
    29. /* register pcm */
    30. /* 将设备添加到文件系统,将snd_pcm_f_ops传入,将被设置给snd_minor对象 */
    31. err = snd_register_device(devtype, pcm->card, pcm->device,
    32. &snd_pcm_f_ops[cidx], pcm,
    33. &pcm->streams[cidx].dev);
    34. if (err < 0) {
    35. list_del_init(&pcm->list);
    36. goto unlock;
    37. }
    38. for (substream = pcm->streams[cidx].substream; substream; substream = substream->next)
    39. /* 设定CONFIG_SND_PCM_TIMER宏的时候,会去设置substream的时间 */
    40. snd_pcm_timer_init(substream);
    41. }
    42. pcm_call_notify(pcm, n_register);
    43. unlock:
    44. mutex_unlock(®ister_mutex);
    45. return err;
    46. }
    47. /**
    48. * snd_register_device - Register the ALSA device file for the card
    49. * @type: the device type, SNDRV_DEVICE_TYPE_XXX
    50. * @card: the card instance
    51. * @dev: the device index
    52. * @f_ops: the file operations
    53. * @private_data: user pointer for f_ops->open()
    54. * @device: the device to register
    55. *
    56. * Registers an ALSA device file for the given card.
    57. * The operators have to be set in reg parameter.
    58. *
    59. * Return: Zero if successful, or a negative error code on failure.
    60. */
    61. int snd_register_device(int type, struct snd_card *card, int dev,
    62. const struct file_operations *f_ops,
    63. void *private_data, struct device *device)
    64. {
    65. int minor;
    66. int err = 0;
    67. struct snd_minor *preg;
    68. if (snd_BUG_ON(!device))
    69. return -EINVAL;
    70. preg = kmalloc(sizeof *preg, GFP_KERNEL);
    71. if (preg == NULL)
    72. return -ENOMEM;
    73. /* 创建一个snd_minor,并添加到全局结构体 snd_minors */
    74. preg->type = type;
    75. preg->card = card ? card->number : -1;
    76. preg->device = dev;
    77. preg->f_ops = f_ops;
    78. preg->private_data = private_data;
    79. preg->card_ptr = card;
    80. mutex_lock(&sound_mutex);
    81. /* 4、注册一个设备节点 */
    82. minor = snd_find_free_minor(type, card, dev);
    83. if (minor < 0) {
    84. err = minor;
    85. goto error;
    86. }
    87. preg->dev = device;
    88. device->devt = MKDEV(major, minor);
    89. err = device_add(device);
    90. if (err < 0)
    91. goto error;
    92. snd_minors[minor] = preg;
    93. error:
    94. mutex_unlock(&sound_mutex);
    95. if (err < 0)
    96. kfree(preg);
    97. return err;
    98. }

            当声卡被注册时,会注册所有的逻辑设备。主要的工作是创建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宏被定义的时候,会起作用。

            通过下图可以帮助你更好的理解各结构直接的乱讲关系。

  • 相关阅读:
    Azure Devops(十五) 使用Azure的私有Maven仓库
    ResNet论文精读,代码实现与拓展知识
    LAYUI-FROM
    基于java实现责任链进行参数校验
    2分钟彻底搞懂JS的版本演进
    恭喜磊哥喜提n+1
    IT业称霸应届生薪酬榜,调查超27万毕业生后,有了这些发现
    前后端分离项目验证码实现
    win10自带SSH免密登录Linux
    一文1800字解读性能指标与性能分析
  • 原文地址:https://blog.csdn.net/code_lyb/article/details/126144107