// SPDX-License-Identifier: GPL-2.0 /* * AVIRT - ALSA Virtual Soundcard * * Copyright (c) 2010-2018 Fiberdyne Systems Pty Ltd * * pcm.c - AVIRT PCM interface */ #include "core.h" #define D_LOGNAME "pcm" #define D_INFOK(fmt, args...) DINFO(D_LOGNAME, fmt, ##args) #define D_PRINTK(fmt, args...) DDEBUG(D_LOGNAME, fmt, ##args) #define D_ERRORK(fmt, args...) DERROR(D_LOGNAME, fmt, ##args) #define AUDIOPATH_PCM_CB(ap, cb, ops, substream, ...) \ (((ap)->ops->cb) ? (ap)->ops->cb((substream), ##__VA_ARGS__) : 0) #define DO_AUDIOPATH_PCM_ROUTE_CBS(ap, cb, ops, substream, ...) \ ({ \ int __err = 0, __endpoint = substream->stream; \ struct snd_avirt_stream *__stream = \ snd_avirt_stream_find_by_device( \ substream->pcm->device); \ if (!IS_ERR_VALUE(__stream) && __stream) { \ struct snd_avirt_audiopath *__endpoint_ap; \ if (__stream->route) { \ __endpoint_ap = \ __stream->route \ ->endpoint_ap[__endpoint]; \ if ((__endpoint_ap)) { \ __err = AUDIOPATH_PCM_CB( \ __endpoint_ap, cb, ops, \ (substream), ##__VA_ARGS__); \ } \ } \ } \ (__err); \ }) /** * DO_AUDIOPATH_PCM_CBS - Call an Audio Path's ALSA PCM callbacks from AVIRT. * If the Audio Path has routing enabled, then we call the appropriate * from/to Audio Path's ALSA PCM callbacks */ #define DO_AUDIOPATH_PCM_CBS(ap, cb, substream, ...) \ ({ \ int __err = 0; \ if (substream->stream) { \ __err = AUDIOPATH_PCM_CB((ap), cb, pcm_capture_ops, \ (substream), ##__VA_ARGS__); \ if (__err >= 0) { \ __err = DO_AUDIOPATH_PCM_ROUTE_CBS( \ (ap), cb, pcm_playback_ops, \ (substream), ##__VA_ARGS__); \ } \ } else { \ __err = AUDIOPATH_PCM_CB((ap), cb, pcm_playback_ops, \ (substream), ##__VA_ARGS__); \ if (__err >= 0) { \ __err = DO_AUDIOPATH_PCM_ROUTE_CBS( \ (ap), cb, pcm_capture_ops, \ (substream), ##__VA_ARGS__); \ } \ } \ (__err); \ }) #define PRIVATE_DATA(substream) \ ((struct snd_avirt_private_data *)substream->private_data) /******************************************************************************* * ALSA PCM Callbacks ******************************************************************************/ /** * pcm_open - Implements 'open' callback for PCM middle layer * @substream: pointer to ALSA PCM substream * * This is called when an ALSA PCM substream is opened. The substream device * is configured here. * * Returns 0 on success or error code otherwise. */ static int pcm_open(struct snd_pcm_substream *substream) { struct snd_avirt_private_data *avirt_private_data; struct snd_avirt_audiopath *audiopath; struct snd_avirt_stream *stream; struct snd_pcm_hardware *hw; unsigned int chans = 0; if (!snd_avirt_streams_configured()) { D_ERRORK("Cannot open PCM. Card is not configured"); return -EPERM; } // Find the Audio Path mapped to this device stream = snd_avirt_stream_find_by_device(substream->pcm->device); if (IS_ERR_VALUE(stream) || !stream) return PTR_ERR(stream); audiopath = snd_avirt_audiopath_get(stream->map); CHK_NULL_V(audiopath, "Cannot find Audio Path uid: '%s'!", stream->map); // Set the private data's Audio Path avirt_private_data = substream->private_data; avirt_private_data->audiopath = audiopath; // Copy the hw params from the audiopath to the pcm hw = &substream->runtime->hw; memcpy(hw, audiopath->hw, sizeof(struct snd_pcm_hardware)); // Setup remaining hw properties chans = stream->channels; if (chans == 0) { D_ERRORK("Channels cannot be 0"); return -EINVAL; } hw->channels_min = chans; hw->channels_max = chans; // Do additional Audio Path 'open' callbacks return DO_AUDIOPATH_PCM_CBS(audiopath, open, substream); } /** * pcm_close - Implements 'close' callback for PCM middle layer * @substream: pointer to ALSA PCM substream * * This is called when a PCM substream is closed. * * Returns 0 on success or error code otherwise. */ static int pcm_close(struct snd_pcm_substream *substream) { // Do additional Audio Path 'close' callback return DO_AUDIOPATH_PCM_CBS( (struct snd_avirt_audiopath *)PRIVATE_DATA(substream)->audiopath, close, substream); } /** * pcm_close - Implements 'close' callback for PCM middle layer * @substream: pointer to ALSA PCM substream * * This is called when a PCM substream is closed. * * Returns 0 on success or error code otherwise. */ static int pcm_trigger(struct snd_pcm_substream *substream, int cmd) { // Do additional Audio Path 'trigger' callback return DO_AUDIOPATH_PCM_CBS( (struct snd_avirt_audiopath *)PRIVATE_DATA(substream)->audiopath, trigger, substream, cmd); } /** * pcm_close - Implements 'close' callback for PCM middle layer * @substream: pointer to ALSA PCM substream * * This is called when a PCM substream is closed. * * Returns 0 on success or error code otherwise. */ static int pcm_prepare(struct snd_pcm_substream *substream) { // Do additional Audio Path 'prepare' callback return DO_AUDIOPATH_PCM_CBS( (struct snd_avirt_audiopath *)PRIVATE_DATA(substream)->audiopath, prepare, substream); } /** * pcm_hw_params - Implements 'hw_params' callback for PCM middle layer * @substream: pointer to ALSA PCM substream * @hw_params: contains the hardware parameters for the PCM * * This is called when the hardware parameters are set, including buffer size, * the period size, the format, etc. The buffer allocation should be done here. * * Returns 0 on success or error code otherwise. */ static int pcm_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *hw_params) { int retval; /* Check supported channels */ if ((params_channels(hw_params) < substream->runtime->hw.channels_min) || (params_channels(hw_params) > substream->runtime->hw.channels_max)) { D_ERRORK("Requested number of channels: %d not supported", params_channels(hw_params)); return -EINVAL; } retval = snd_pcm_lib_alloc_vmalloc_buffer( substream, params_buffer_bytes(hw_params)); if (retval < 0) D_ERRORK("pcm: buffer allocation failed: %d", retval); return retval; } /** * pcm_hw_free - Implements 'hw_free' callback for PCM middle layer * @substream: pointer to ALSA PCM substream * * Release the resources allocated via 'hw_params'. * This function is always called before the 'close' callback . * * Returns 0 on success or error code otherwise. */ static int pcm_hw_free(struct snd_pcm_substream *substream) { int err; // Do additional Audio Path 'hw_free' callback err = DO_AUDIOPATH_PCM_CBS( (struct snd_avirt_audiopath *)PRIVATE_DATA(substream)->audiopath, hw_free, substream); if (err < 0) return err; return snd_pcm_lib_free_vmalloc_buffer(substream); } /** * Default PCM ops table. Selected PCM ops must pass through AVIRT before * calling back to their respective Audio Paths */ struct snd_pcm_ops pcm_ops_avirt = { .open = pcm_open, .close = pcm_close, .prepare = pcm_prepare, .trigger = pcm_trigger, .ioctl = snd_pcm_lib_ioctl, .hw_params = pcm_hw_params, .hw_free = pcm_hw_free, .page = snd_pcm_lib_get_vmalloc_page, };