alsa compress driver
https://elixir.bootlin.com/linux/v5.4.240/source/sound/soc/soc-compress.c#L856
platform driver 例子:https://elixir.bootlin.com/linux/v5.4.240/source/sound/soc/intel/atom/sst-mfld-platform-pcm.c#L525
static struct snd_soc_dai_driver sst_platform_dai[] = { ... { .name = "compress-cpu-dai", .compress_new = snd_soc_new_compress, .ops = &sst_compr_dai_ops, .playback = { .stream_name = "Compress Playback", .channels_min = 1, }, }, ... }; static const struct snd_soc_dai_ops sst_media_dai_ops = { .startup = sst_media_open, .shutdown = sst_media_close, .prepare = sst_media_prepare, .hw_params = sst_media_hw_params, .hw_free = sst_media_hw_free, .mute_stream = sst_media_digital_mute, }; static const struct snd_soc_component_driver sst_soc_platform_drv = { .name = DRV_NAME, .probe = sst_soc_probe, .remove = sst_soc_remove, .ops = &sst_platform_ops, .compr_ops = &sst_platform_compr_ops, .pcm_new = sst_pcm_new, }; const struct snd_compr_ops sst_platform_compr_ops = { .open = sst_platform_compr_open, .free = sst_platform_compr_free, .set_params = sst_platform_compr_set_params, .set_metadata = sst_platform_compr_set_metadata, .trigger = sst_platform_compr_trigger, .pointer = sst_platform_compr_pointer, .ack = sst_platform_compr_ack, .get_caps = sst_platform_compr_get_caps, .get_codec_caps = sst_platform_compr_get_codec_caps, }; static int sst_platform_probe(struct platform_device *pdev) { struct sst_data *drv; int ret; struct sst_platform_data *pdata; drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL); if (drv == NULL) { return -ENOMEM; } pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); if (pdata == NULL) { return -ENOMEM; } pdata->pdev_strm_map = dpcm_strm_map; pdata->strm_map_size = ARRAY_SIZE(dpcm_strm_map); drv->pdata = pdata; drv->pdev = pdev; mutex_init(&drv->lock); dev_set_drvdata(&pdev->dev, drv); ret = devm_snd_soc_register_component(&pdev->dev, &sst_soc_platform_drv, sst_platform_dai, ARRAY_SIZE(sst_platform_dai)); if (ret) dev_err(&pdev->dev, "registering cpu dais failed\n"); return ret; }
machine driver例子:https://elixir.bootlin.com/linux/v5.4.240/source/sound/soc/intel/boards/cht_bsw_nau8824.c#L191
SND_SOC_DAILINK_DEF(compress, DAILINK_COMP_ARRAY(COMP_CPU("compress-cpu-dai"))); static struct snd_soc_dai_link cht_dailink[] = { ... [MERR_DPCM_COMPR] = { .name = "Compressed Port", .stream_name = "Compress", SND_SOC_DAILINK_REG(compress, dummy, platform), }, ... }; /* SoC card */ static struct snd_soc_card snd_soc_card_cht = { .name = "chtnau8824", .owner = THIS_MODULE, .dai_link = cht_dailink, .num_links = ARRAY_SIZE(cht_dailink), .dapm_widgets = cht_dapm_widgets, .num_dapm_widgets = ARRAY_SIZE(cht_dapm_widgets), .dapm_routes = cht_audio_map, .num_dapm_routes = ARRAY_SIZE(cht_audio_map), .controls = cht_mc_controls, .num_controls = ARRAY_SIZE(cht_mc_controls), }; static int snd_cht_mc_probe(struct platform_device *pdev) { ..... /* register the soc card */ ret_val = devm_snd_soc_register_card(&pdev->dev, &snd_soc_card_cht); if (ret_val) { dev_err(&pdev->dev, "snd_soc_register_card failed %d\n", ret_val); return ret_val; } platform_set_drvdata(pdev, &snd_soc_card_cht); return ret_val; }
在platform driver的snd_soc_dai_driver定义了compress dai driver, 并将.compress_new=snd_soc_new_compress.
compress_new在注册声卡时会被call到。
static int snd_soc_instantiate_card(struct snd_soc_card *card) { .... for_each_card_rtds(card, rtd) soc_link_init(card, rtd); .... } static int soc_link_init(struct snd_soc_card *card, struct snd_soc_pcm_runtime *rtd) { ... num = rtd->num; /* create compress_device if possible */ ret = snd_soc_dai_compress_new(cpu_dai, rtd, num); ... } int snd_soc_dai_compress_new(struct snd_soc_dai *dai, struct snd_soc_pcm_runtime *rtd, int num) { if (dai->driver->compress_new) return dai->driver->compress_new(rtd, num); return -ENOTSUPP; }
snd_soc_new_compress创建一个新的struct snd_compr *compr结构,赋值compr->ops, 并且使用snd_compress_new创建compress device
/** * snd_soc_new_compress - create a new compress. * * @rtd: The runtime for which we will create compress * @num: the device index number (zero based - shared with normal PCMs) * * Return: 0 for success, else error. */ int snd_soc_new_compress(struct snd_soc_pcm_runtime *rtd, int num) { struct snd_soc_component *component; struct snd_soc_rtdcom_list *rtdcom; struct snd_soc_dai *codec_dai = rtd->codec_dai; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; struct snd_compr *compr; struct snd_pcm *be_pcm; char new_name[64]; int ret = 0, direction = 0; int playback = 0, capture = 0; if (rtd->num_codecs > 1) { dev_err(rtd->card->dev, "Compress ASoC: Multicodec not supported\n"); return -EINVAL; } if (!codec_dai) { dev_err(rtd->card->dev, "Missing codec\n"); return -EINVAL; } /* check client and interface hw capabilities */ if (snd_soc_dai_stream_valid(codec_dai, SNDRV_PCM_STREAM_PLAYBACK) && snd_soc_dai_stream_valid(cpu_dai, SNDRV_PCM_STREAM_PLAYBACK)) playback = 1; if (snd_soc_dai_stream_valid(codec_dai, SNDRV_PCM_STREAM_CAPTURE) && snd_soc_dai_stream_valid(cpu_dai, SNDRV_PCM_STREAM_CAPTURE)) capture = 1; /* * Compress devices are unidirectional so only one of the directions * should be set, check for that (xor) */ if (playback + capture != 1) { dev_err(rtd->card->dev, "Compress ASoC: Invalid direction for P %d, C %d\n", playback, capture); return -EINVAL; } if (playback) direction = SND_COMPRESS_PLAYBACK; else direction = SND_COMPRESS_CAPTURE; compr = devm_kzalloc(rtd->card->dev, sizeof(*compr), GFP_KERNEL); if (!compr) return -ENOMEM; compr->ops = devm_kzalloc(rtd->card->dev, sizeof(soc_compr_ops), GFP_KERNEL); if (!compr->ops) return -ENOMEM; if (rtd->dai_link->dynamic) { snprintf(new_name, sizeof(new_name), "(%s)", rtd->dai_link->stream_name); ret = snd_pcm_new_internal(rtd->card->snd_card, new_name, num, rtd->dai_link->dpcm_playback, rtd->dai_link->dpcm_capture, &be_pcm); if (ret < 0) { dev_err(rtd->card->dev, "Compress ASoC: can't create compressed for %s: %d\n", rtd->dai_link->name, ret); return ret; } rtd->pcm = be_pcm; rtd->fe_compr = 1; if (rtd->dai_link->dpcm_playback) be_pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream->private_data = rtd; if (rtd->dai_link->dpcm_capture) be_pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream->private_data = rtd; memcpy(compr->ops, &soc_compr_dyn_ops, sizeof(soc_compr_dyn_ops)); } else { snprintf(new_name, sizeof(new_name), "%s %s-%d", rtd->dai_link->stream_name, codec_dai->name, num); memcpy(compr->ops, &soc_compr_ops, sizeof(soc_compr_ops)); } for_each_rtdcom(rtd, rtdcom) { component = rtdcom->component; if (!component->driver->compr_ops || !component->driver->compr_ops->copy) continue; compr->ops->copy = soc_compr_copy; break; } mutex_init(&compr->lock); ret = snd_compress_new(rtd->card->snd_card, num, direction, new_name, compr); if (ret < 0) { component = rtd->codec_dai->component; dev_err(component->dev, "Compress ASoC: can't create compress for codec %s: %d\n", component->name, ret); return ret; } /* DAPM dai link stream work */ INIT_DELAYED_WORK(&rtd->delayed_work, close_delayed_work); rtd->compr = compr; compr->private_data = rtd; dev_info(rtd->card->dev, "Compress ASoC: %s <-> %s mapping ok\n", codec_dai->name, cpu_dai->name); return 0; } EXPORT_SYMBOL_GPL(snd_soc_new_compress);
compr->ops:(如果component driver有定义copy函数则compr->ops->copy = soc_compr_copy)
/* ASoC Compress operations */ static struct snd_compr_ops soc_compr_ops = { .open = soc_compr_open, .free = soc_compr_free, .set_params = soc_compr_set_params, .set_metadata = soc_compr_set_metadata, .get_metadata = soc_compr_get_metadata, .get_params = soc_compr_get_params, .trigger = soc_compr_trigger, .pointer = soc_compr_pointer, .ack = soc_compr_ack, .get_caps = soc_compr_get_caps, .get_codec_caps = soc_compr_get_codec_caps }; /* ASoC Dynamic Compress operations */ static struct snd_compr_ops soc_compr_dyn_ops = { .open = soc_compr_open_fe, .free = soc_compr_free_fe, .set_params = soc_compr_set_params_fe, .get_params = soc_compr_get_params, .set_metadata = soc_compr_set_metadata, .get_metadata = soc_compr_get_metadata, .trigger = soc_compr_trigger_fe, .pointer = soc_compr_pointer, .ack = soc_compr_ack, .get_caps = soc_compr_get_caps, .get_codec_caps = soc_compr_get_codec_caps };
snd_compress_new与snd_pcm_new类似,call snd_device_new创建compress device(struct snd_device *dev), 并将snd_device加到card->devices的list中。
/* * snd_compress_new: create new compress device * @card: sound card pointer * @device: device number * @dirn: device direction, should be of type enum snd_compr_direction * @compr: compress device pointer */ int snd_compress_new(struct snd_card *card, int device, int dirn, const char *id, struct snd_compr *compr) { static struct snd_device_ops ops = { .dev_free = snd_compress_dev_free, .dev_register = snd_compress_dev_register, .dev_disconnect = snd_compress_dev_disconnect, }; int ret; compr->card = card; compr->device = device; compr->direction = dirn; snd_compress_set_id(compr, id); snd_device_initialize(&compr->dev, card); dev_set_name(&compr->dev, "comprC%iD%i", card->number, device); ret = snd_device_new(card, SNDRV_DEV_COMPRESS, compr, &ops); if (ret == 0) snd_compress_proc_init(compr); return ret; } EXPORT_SYMBOL_GPL(snd_compress_new); /** * snd_device_new - create an ALSA device component * @card: the card instance * @type: the device type, SNDRV_DEV_XXX * @device_data: the data pointer of this device * @ops: the operator table * * Creates a new device component for the given data pointer. * The device will be assigned to the card and managed together * by the card. * * The data pointer plays a role as the identifier, too, so the * pointer address must be unique and unchanged. * * Return: Zero if successful, or a negative error code on failure. */ int snd_device_new(struct snd_card *card, enum snd_device_type type, void *device_data, struct snd_device_ops *ops) { struct snd_device *dev; struct list_head *p; if (snd_BUG_ON(!card || !device_data || !ops)) return -ENXIO; dev = kzalloc(sizeof(*dev), GFP_KERNEL); if (!dev) return -ENOMEM; INIT_LIST_HEAD(&dev->list); dev->card = card; dev->type = type; dev->state = SNDRV_DEV_BUILD; dev->device_data = device_data; dev->ops = ops; /* insert the entry in an incrementally sorted list */ list_for_each_prev(p, &card->devices) { struct snd_device *pdev = list_entry(p, struct snd_device, list); if ((unsigned int)pdev->type <= (unsigned int)type) break; } list_add(&dev->list, p); return 0; } EXPORT_SYMBOL(snd_device_new);
创建struct snd_device *dev时,其ops如下,在后续注册card会遍历card->devices调用ops.dev_register来将struct snd_compr注册进snd_minors[minor]中。
static struct snd_device_ops ops = { .dev_free = snd_compress_dev_free, .dev_register = snd_compress_dev_register, .dev_disconnect = snd_compress_dev_disconnect, };static int snd_compress_dev_register(struct snd_device *device) { int ret = -EINVAL; struct snd_compr *compr; if (snd_BUG_ON(!device || !device->device_data)) return -EBADFD; compr = device->device_data; pr_debug("reg device %s, direction %d\n", compr->name, compr->direction); /* register compressed device */ ret = snd_register_device(SNDRV_DEVICE_TYPE_COMPRESS, compr->card, compr->device, &snd_compr_file_ops, compr, &compr->dev); if (ret < 0) { pr_err("snd_register_device failed %d\n", ret); return ret; } return ret; } /** * 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; 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); 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; } EXPORT_SYMBOL(snd_register_device);
在snd_compress_dev_register内,snd_minors[minor]->f_ops:
static const struct file_operations snd_compr_file_ops = { .owner = THIS_MODULE, .open = snd_compr_open, .release = snd_compr_free, .write = snd_compr_write, .read = snd_compr_read, .unlocked_ioctl = snd_compr_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl = snd_compr_ioctl_compat, #endif .mmap = snd_compr_mmap, .poll = snd_compr_poll, };
在user space 调用tinycompress lib open compress device (/dev/snd/comprCXDX)时, 会调用到f_ops.open()函数,即snd_compr_open()。
在snd_compr_open透过file的inode找到major和minor, 进而找到注册到snd_minors[minor]中对应的struct snd_compr *compr.将compr->ops赋值给stream->ops
(data->stream.ops = compr->ops;)
/* * a note on stream states used: * we use following states in the compressed core * SNDRV_PCM_STATE_OPEN: When stream has been opened. * SNDRV_PCM_STATE_SETUP: When stream has been initialized. This is done by * calling SNDRV_COMPRESS_SET_PARAMS. Running streams will come to this * state at stop by calling SNDRV_COMPRESS_STOP, or at end of drain. * SNDRV_PCM_STATE_PREPARED: When a stream has been written to (for * playback only). User after setting up stream writes the data buffer * before starting the stream. * SNDRV_PCM_STATE_RUNNING: When stream has been started and is * decoding/encoding and rendering/capturing data. * SNDRV_PCM_STATE_DRAINING: When stream is draining current data. This is done * by calling SNDRV_COMPRESS_DRAIN. * SNDRV_PCM_STATE_PAUSED: When stream is paused. This is done by calling * SNDRV_COMPRESS_PAUSE. It can be stopped or resumed by calling * SNDRV_COMPRESS_STOP or SNDRV_COMPRESS_RESUME respectively. */ static int snd_compr_open(struct inode *inode, struct file *f) { struct snd_compr *compr; struct snd_compr_file *data; struct snd_compr_runtime *runtime; enum snd_compr_direction dirn; int maj = imajor(inode); int ret; if ((f->f_flags & O_ACCMODE) == O_WRONLY) dirn = SND_COMPRESS_PLAYBACK; else if ((f->f_flags & O_ACCMODE) == O_RDONLY) dirn = SND_COMPRESS_CAPTURE; else return -EINVAL; if (maj == snd_major) compr = snd_lookup_minor_data(iminor(inode), SNDRV_DEVICE_TYPE_COMPRESS); else return -EBADFD; if (compr == NULL) { pr_err("no device data!!!\n"); return -ENODEV; } if (dirn != compr->direction) { pr_err("this device doesn't support this direction\n"); snd_card_unref(compr->card); return -EINVAL; } data = kzalloc(sizeof(*data), GFP_KERNEL); if (!data) { snd_card_unref(compr->card); return -ENOMEM; } INIT_DELAYED_WORK(&data->stream.error_work, error_delayed_work); data->stream.ops = compr->ops; data->stream.direction = dirn; data->stream.private_data = compr->private_data; data->stream.device = compr; runtime = kzalloc(sizeof(*runtime), GFP_KERNEL); if (!runtime) { kfree(data); snd_card_unref(compr->card); return -ENOMEM; } runtime->state = SNDRV_PCM_STATE_OPEN; init_waitqueue_head(&runtime->sleep); data->stream.runtime = runtime; f->private_data = (void *)data; mutex_lock(&compr->lock); ret = compr->ops->open(&data->stream); mutex_unlock(&compr->lock); if (ret) { kfree(runtime); kfree(data); } snd_card_unref(compr->card); return ret; }
在snd_compr_open中找到struct snd_compr *compr后,调用compr->ops->open(), compr->ops即snd_soc_new_compress中赋值的soc_compr_ops.compr->open为soc_compr_open()
static int soc_compr_open(struct snd_compr_stream *cstream) { struct snd_soc_pcm_runtime *rtd = cstream->private_data; struct snd_soc_component *component; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; int ret; mutex_lock_nested(&rtd->card->pcm_mutex, rtd->card->pcm_subclass); if (cpu_dai->driver->cops && cpu_dai->driver->cops->startup) { ret = cpu_dai->driver->cops->startup(cstream, cpu_dai); if (ret < 0) { dev_err(cpu_dai->dev, "Compress ASoC: can't open interface %s: %d\n", cpu_dai->name, ret); goto out; } } ret = soc_compr_components_open(cstream, &component); if (ret < 0) goto machine_err; if (rtd->dai_link->compr_ops && rtd->dai_link->compr_ops->startup) { ret = rtd->dai_link->compr_ops->startup(cstream); if (ret < 0) { dev_err(rtd->dev, "Compress ASoC: %s startup failed: %d\n", rtd->dai_link->name, ret); goto machine_err; } } snd_soc_runtime_activate(rtd, cstream->direction); mutex_unlock(&rtd->card->pcm_mutex); return 0; machine_err: soc_compr_components_free(cstream, component); if (cpu_dai->driver->cops && cpu_dai->driver->cops->shutdown) cpu_dai->driver->cops->shutdown(cstream, cpu_dai); out: mutex_unlock(&rtd->card->pcm_mutex); return ret; }
在soc_compr_open中,会依次调用cpu_dai->driver->cops->startup, soc_compr_components_open(), dai_link->compr_ops->startup.
在soc_compr_components_open()中,调用component->driver->compr_ops->open()即platform driver的compr_ops->open().
static int soc_compr_components_open(struct snd_compr_stream *cstream, struct snd_soc_component **last) { struct snd_soc_pcm_runtime *rtd = cstream->private_data; struct snd_soc_component *component; struct snd_soc_rtdcom_list *rtdcom; int ret; for_each_rtdcom(rtd, rtdcom) { component = rtdcom->component; if (!component->driver->compr_ops || !component->driver->compr_ops->open) continue; ret = component->driver->compr_ops->open(cstream); if (ret < 0) { dev_err(component->dev, "Compress ASoC: can't open platform %s: %d\n", component->name, ret); *last = component; return ret; } } *last = NULL; return 0; }
user space 通过tinycompress set param时会ioctl到compress_offload.c的snd_compr_ioctl
static long snd_compr_ioctl(struct file *f, unsigned int cmd, unsigned long arg) { struct snd_compr_file *data = f->private_data; struct snd_compr_stream *stream; int retval = -ENOTTY; if (snd_BUG_ON(!data)) return -EFAULT; stream = &data->stream; mutex_lock(&stream->device->lock); switch (_IOC_NR(cmd)) { case _IOC_NR(SNDRV_COMPRESS_IOCTL_VERSION): retval = put_user(SNDRV_COMPRESS_VERSION, (int __user *)arg) ? -EFAULT : 0; break; case _IOC_NR(SNDRV_COMPRESS_GET_CAPS): retval = snd_compr_get_caps(stream, arg); break; #ifndef COMPR_CODEC_CAPS_OVERFLOW case _IOC_NR(SNDRV_COMPRESS_GET_CODEC_CAPS): retval = snd_compr_get_codec_caps(stream, arg); break; #endif case _IOC_NR(SNDRV_COMPRESS_SET_PARAMS): retval = snd_compr_set_params(stream, arg); break; case _IOC_NR(SNDRV_COMPRESS_GET_PARAMS): retval = snd_compr_get_params(stream, arg); break; case _IOC_NR(SNDRV_COMPRESS_SET_METADATA): retval = snd_compr_set_metadata(stream, arg); break; case _IOC_NR(SNDRV_COMPRESS_GET_METADATA): retval = snd_compr_get_metadata(stream, arg); break; case _IOC_NR(SNDRV_COMPRESS_TSTAMP): retval = snd_compr_tstamp(stream, arg); break; case _IOC_NR(SNDRV_COMPRESS_AVAIL): retval = snd_compr_ioctl_avail(stream, arg); break; case _IOC_NR(SNDRV_COMPRESS_PAUSE): retval = snd_compr_pause(stream); break; case _IOC_NR(SNDRV_COMPRESS_RESUME): retval = snd_compr_resume(stream); break; case _IOC_NR(SNDRV_COMPRESS_START): retval = snd_compr_start(stream); break; case _IOC_NR(SNDRV_COMPRESS_STOP): retval = snd_compr_stop(stream); break; case _IOC_NR(SNDRV_COMPRESS_DRAIN): retval = snd_compr_drain(stream); break; case _IOC_NR(SNDRV_COMPRESS_PARTIAL_DRAIN): retval = snd_compr_partial_drain(stream); break; case _IOC_NR(SNDRV_COMPRESS_NEXT_TRACK): retval = snd_compr_next_track(stream); break; } mutex_unlock(&stream->device->lock); return retval; }
snd_compr_set_params, 调用snd_compr_allocate_buffer分配buffer,并调用stream->ops->setparams
static int snd_compr_set_params(struct snd_compr_stream *stream, unsigned long arg) { struct snd_compr_params *params; int retval; if (stream->runtime->state == SNDRV_PCM_STATE_OPEN) { /* * we should allow parameter change only when stream has been * opened not in other cases */ params = memdup_user((void __user *)arg, sizeof(*params)); if (IS_ERR(params)) return PTR_ERR(params); retval = snd_compress_check_input(params); if (retval) goto out; retval = snd_compr_allocate_buffer(stream, params); if (retval) { retval = -ENOMEM; goto out; } retval = stream->ops->set_params(stream, params); if (retval) goto out; stream->metadata_set = false; stream->next_track = false; stream->runtime->state = SNDRV_PCM_STATE_SETUP; } else { return -EPERM; } out: kfree(params); return retval; }
如果stream->ops->copy没有define并且stream->runtime->dma_buffer_p为NULL时,则通过kmalloc申请buffer.
/* revisit this with snd_pcm_preallocate_xxx */ static int snd_compr_allocate_buffer(struct snd_compr_stream *stream, struct snd_compr_params *params) { unsigned int buffer_size; void *buffer = NULL; buffer_size = params->buffer.fragment_size * params->buffer.fragments; if (stream->ops->copy) { buffer = NULL; /* if copy is defined the driver will be required to copy * the data from core */ } else { if (stream->runtime->dma_buffer_p) { if (buffer_size > stream->runtime->dma_buffer_p->bytes) dev_err(&stream->device->dev, "Not enough DMA buffer"); else buffer = stream->runtime->dma_buffer_p->area; } else { buffer = kmalloc(buffer_size, GFP_KERNEL); } if (!buffer) return -ENOMEM; } stream->runtime->fragment_size = params->buffer.fragment_size; stream->runtime->fragments = params->buffer.fragments; stream->runtime->buffer = buffer; stream->runtime->buffer_size = buffer_size; return 0; }
stream->ops->set_params即为compr->ops,即snd_soc_new_compress中赋值的soc_compr_ops.set_params(即soc_compr_set_params)
在soc_compr_set_params,会依次调用cpu_dai->driver->cops->set_params, soc_compr_components_set_params(), dai_link->compr_ops->set_params.
static int soc_compr_set_params(struct snd_compr_stream *cstream, struct snd_compr_params *params) { struct snd_soc_pcm_runtime *rtd = cstream->private_data; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; int ret; mutex_lock_nested(&rtd->card->pcm_mutex, rtd->card->pcm_subclass); /* * First we call set_params for the CPU DAI, then the component * driver this should configure the SoC side. If the machine has * compressed ops then we call that as well. The expectation is * that these callbacks will configure everything for this compress * path, like configuring a PCM port for a CODEC. */ if (cpu_dai->driver->cops && cpu_dai->driver->cops->set_params) { ret = cpu_dai->driver->cops->set_params(cstream, params, cpu_dai); if (ret < 0) goto err; } ret = soc_compr_components_set_params(cstream, params); if (ret < 0) goto err; if (rtd->dai_link->compr_ops && rtd->dai_link->compr_ops->set_params) { ret = rtd->dai_link->compr_ops->set_params(cstream); if (ret < 0) goto err; } if (cstream->direction == SND_COMPRESS_PLAYBACK) snd_soc_dapm_stream_event(rtd, SNDRV_PCM_STREAM_PLAYBACK, SND_SOC_DAPM_STREAM_START); else snd_soc_dapm_stream_event(rtd, SNDRV_PCM_STREAM_CAPTURE, SND_SOC_DAPM_STREAM_START); /* cancel any delayed stream shutdown that is pending */ rtd->pop_wait = 0; mutex_unlock(&rtd->card->pcm_mutex); cancel_delayed_work_sync(&rtd->delayed_work); return 0; err: mutex_unlock(&rtd->card->pcm_mutex); return ret; }
在soc_compr_components_set_params()中,调用component->driver->compr_ops->set_params()即platform driver的compr_ops->set_params().
static int soc_compr_components_set_params(struct snd_compr_stream *cstream, struct snd_compr_params *params) { struct snd_soc_pcm_runtime *rtd = cstream->private_data; struct snd_soc_component *component; struct snd_soc_rtdcom_list *rtdcom; int ret; for_each_rtdcom(rtd, rtdcom) { component = rtdcom->component; if (!component->driver->compr_ops || !component->driver->compr_ops->set_params) continue; ret = component->driver->compr_ops->set_params(cstream, params); if (ret < 0) return ret; } return 0; }
当user space 通过tinycompress write数据时,会调用到compress_offload.c的snd_compr_write:
static ssize_t snd_compr_write(struct file *f, const char __user *buf, size_t count, loff_t *offset) { struct snd_compr_file *data = f->private_data; struct snd_compr_stream *stream; size_t avail; int retval; if (snd_BUG_ON(!data)) return -EFAULT; stream = &data->stream; mutex_lock(&stream->device->lock); /* write is allowed when stream is running or has been steup */ switch (stream->runtime->state) { case SNDRV_PCM_STATE_SETUP: case SNDRV_PCM_STATE_PREPARED: case SNDRV_PCM_STATE_RUNNING: break; default: mutex_unlock(&stream->device->lock); return -EBADFD; } avail = snd_compr_get_avail(stream); pr_debug("avail returned %ld\n", (unsigned long)avail); /* calculate how much we can write to buffer */ if (avail > count) avail = count; if (stream->ops->copy) { char __user* cbuf = (char __user*)buf; retval = stream->ops->copy(stream, cbuf, avail); } else { retval = snd_compr_write_data(stream, buf, avail); } if (retval > 0) stream->runtime->total_bytes_available += retval; /* while initiating the stream, write should be called before START * call, so in setup move state */ if (stream->runtime->state == SNDRV_PCM_STATE_SETUP) { stream->runtime->state = SNDRV_PCM_STATE_PREPARED; pr_debug("stream prepared, Houston we are good to go\n"); } mutex_unlock(&stream->device->lock); return retval; }
如果platform driver的compr ops有定义copy函数时,则stream->ops->copy即为compr->ops->copy,即为soc_compr_copy.
static int soc_compr_copy(struct snd_compr_stream *cstream, char __user *buf, size_t count) { struct snd_soc_pcm_runtime *rtd = cstream->private_data; struct snd_soc_component *component; struct snd_soc_rtdcom_list *rtdcom; int ret = 0; mutex_lock_nested(&rtd->card->pcm_mutex, rtd->card->pcm_subclass); for_each_rtdcom(rtd, rtdcom) { component = rtdcom->component; if (!component->driver->compr_ops || !component->driver->compr_ops->copy) continue; ret = component->driver->compr_ops->copy(cstream, buf, count); break; } mutex_unlock(&rtd->card->pcm_mutex); return ret; }
如果没有定义copy则调用snd_compr_write_data
static int snd_compr_write_data(struct snd_compr_stream *stream, const char __user *buf, size_t count) { void *dstn; size_t copy; struct snd_compr_runtime *runtime = stream->runtime; /* 64-bit Modulus */ u64 app_pointer = div64_u64(runtime->total_bytes_available, runtime->buffer_size); app_pointer = runtime->total_bytes_available - (app_pointer * runtime->buffer_size); dstn = runtime->buffer + app_pointer; pr_debug("copying %ld at %lld\n", (unsigned long)count, app_pointer); if (count < runtime->buffer_size - app_pointer) { if (copy_from_user(dstn, buf, count)) return -EFAULT; } else { copy = runtime->buffer_size - app_pointer; if (copy_from_user(dstn, buf, copy)) return -EFAULT; if (copy_from_user(runtime->buffer, buf + copy, count - copy)) return -EFAULT; } /* if DSP cares, let it know data has been written */ if (stream->ops->ack) stream->ops->ack(stream, count); return count; }