ALSA: pcm: More unification of PCM transfer codes
This patch proceeds more abstraction of PCM read/write loop codes. For both interleaved and non-interleaved transfers, the same copy or silence transfer code (which is defined as pcm_transfer_f) is used now. This became possible since we switched to byte size to copy_* and fill_silence ops argument instead of frames. And, for both read and write, we can use the same copy function (which is defined as pcm_copy_f), just depending on whether interleaved or non-interleaved mode. The transfer function is determined at the beginning of the loop, depending on whether the driver gives the specific copy ops or it's the standard read/write. Another bonus by this change is that we now guarantee the silencing behavior when NULL buffer is passed to write helpers. It'll simplify some codes later. Reviewed-by: Takashi Sakamoto <o-takashi@sakamocchi.jp> Signed-off-by: Takashi Iwai <tiwai@suse.de>
This commit is contained in:
parent
c48f12ee0a
commit
9f60063094
|
@ -1993,77 +1993,100 @@ static int wait_for_avail(struct snd_pcm_substream *substream,
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef int (*transfer_f)(struct snd_pcm_substream *substream, unsigned int hwoff,
|
typedef int (*pcm_transfer_f)(struct snd_pcm_substream *substream,
|
||||||
void *data, unsigned int off,
|
int channel, unsigned long hwoff,
|
||||||
snd_pcm_uframes_t size);
|
void *buf, unsigned long bytes);
|
||||||
|
|
||||||
static int snd_pcm_lib_write_transfer(struct snd_pcm_substream *substream,
|
typedef int (*pcm_copy_f)(struct snd_pcm_substream *, snd_pcm_uframes_t, void *,
|
||||||
unsigned int hwoff,
|
snd_pcm_uframes_t, snd_pcm_uframes_t, pcm_transfer_f);
|
||||||
void *data, unsigned int off,
|
|
||||||
snd_pcm_uframes_t frames)
|
/* calculate the target DMA-buffer position to be written/read */
|
||||||
|
static void *get_dma_ptr(struct snd_pcm_runtime *runtime,
|
||||||
|
int channel, unsigned long hwoff)
|
||||||
{
|
{
|
||||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
return runtime->dma_area + hwoff +
|
||||||
int err;
|
channel * (runtime->dma_bytes / runtime->channels);
|
||||||
char __user *buf = (char __user *) data + frames_to_bytes(runtime, off);
|
}
|
||||||
if (substream->ops->copy_user) {
|
|
||||||
hwoff = frames_to_bytes(runtime, hwoff);
|
/* default copy_user ops for write */
|
||||||
frames = frames_to_bytes(runtime, frames);
|
static int default_write_copy_user(struct snd_pcm_substream *substream,
|
||||||
err = substream->ops->copy_user(substream, 0, hwoff, buf, frames);
|
int channel, unsigned long hwoff,
|
||||||
if (err < 0)
|
void __user *buf, unsigned long bytes)
|
||||||
return err;
|
{
|
||||||
} else {
|
if (copy_from_user(get_dma_ptr(substream->runtime, channel, hwoff),
|
||||||
char *hwbuf = runtime->dma_area + frames_to_bytes(runtime, hwoff);
|
buf, bytes))
|
||||||
if (copy_from_user(hwbuf, buf, frames_to_bytes(runtime, frames)))
|
return -EFAULT;
|
||||||
return -EFAULT;
|
|
||||||
}
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int snd_pcm_lib_writev_transfer(struct snd_pcm_substream *substream,
|
/* fill silence instead of copy data; called as a transfer helper
|
||||||
unsigned int hwoff,
|
* from __snd_pcm_lib_write() or directly from noninterleaved_copy() when
|
||||||
void *data, unsigned int off,
|
* a NULL buffer is passed
|
||||||
snd_pcm_uframes_t frames)
|
*/
|
||||||
|
static int fill_silence(struct snd_pcm_substream *substream, int channel,
|
||||||
|
unsigned long hwoff, void *buf, unsigned long bytes)
|
||||||
{
|
{
|
||||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||||
int err;
|
|
||||||
void __user **bufs = (void __user **)data;
|
|
||||||
int channels = runtime->channels;
|
|
||||||
char __user *buf;
|
|
||||||
int c;
|
|
||||||
|
|
||||||
if (substream->ops->copy_user) {
|
if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK)
|
||||||
hwoff = samples_to_bytes(runtime, hwoff);
|
return 0;
|
||||||
off = samples_to_bytes(runtime, off);
|
if (substream->ops->fill_silence)
|
||||||
frames = samples_to_bytes(runtime, frames);
|
return substream->ops->fill_silence(substream, channel,
|
||||||
for (c = 0; c < channels; ++c, ++bufs) {
|
hwoff, bytes);
|
||||||
buf = *bufs + off;
|
|
||||||
if (!*bufs) {
|
snd_pcm_format_set_silence(runtime->format,
|
||||||
if (snd_BUG_ON(!substream->ops->fill_silence))
|
get_dma_ptr(runtime, channel, hwoff),
|
||||||
return -EINVAL;
|
bytes_to_samples(runtime, bytes));
|
||||||
err = substream->ops->fill_silence(substream, c,
|
return 0;
|
||||||
hwoff,
|
}
|
||||||
frames);
|
|
||||||
} else {
|
/* call transfer function with the converted pointers and sizes;
|
||||||
err = substream->ops->copy_user(substream, c,
|
* for interleaved mode, it's one shot for all samples
|
||||||
hwoff, buf,
|
*/
|
||||||
frames);
|
static int interleaved_copy(struct snd_pcm_substream *substream,
|
||||||
}
|
snd_pcm_uframes_t hwoff, void *data,
|
||||||
if (err < 0)
|
snd_pcm_uframes_t off,
|
||||||
return err;
|
snd_pcm_uframes_t frames,
|
||||||
}
|
pcm_transfer_f transfer)
|
||||||
} else {
|
{
|
||||||
/* default transfer behaviour */
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||||
size_t dma_csize = runtime->dma_bytes / channels;
|
|
||||||
for (c = 0; c < channels; ++c, ++bufs) {
|
/* convert to bytes */
|
||||||
char *hwbuf = runtime->dma_area + (c * dma_csize) + samples_to_bytes(runtime, hwoff);
|
hwoff = frames_to_bytes(runtime, hwoff);
|
||||||
if (*bufs == NULL) {
|
off = frames_to_bytes(runtime, off);
|
||||||
snd_pcm_format_set_silence(runtime->format, hwbuf, frames);
|
frames = frames_to_bytes(runtime, frames);
|
||||||
} else {
|
return transfer(substream, 0, hwoff, data + off, frames);
|
||||||
char __user *buf = *bufs + samples_to_bytes(runtime, off);
|
}
|
||||||
if (copy_from_user(hwbuf, buf, samples_to_bytes(runtime, frames)))
|
|
||||||
return -EFAULT;
|
/* call transfer function with the converted pointers and sizes for each
|
||||||
}
|
* non-interleaved channel; when buffer is NULL, silencing instead of copying
|
||||||
}
|
*/
|
||||||
|
static int noninterleaved_copy(struct snd_pcm_substream *substream,
|
||||||
|
snd_pcm_uframes_t hwoff, void *data,
|
||||||
|
snd_pcm_uframes_t off,
|
||||||
|
snd_pcm_uframes_t frames,
|
||||||
|
pcm_transfer_f transfer)
|
||||||
|
{
|
||||||
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||||
|
int channels = runtime->channels;
|
||||||
|
void **bufs = data;
|
||||||
|
int c, err;
|
||||||
|
|
||||||
|
/* convert to bytes; note that it's not frames_to_bytes() here.
|
||||||
|
* in non-interleaved mode, we copy for each channel, thus
|
||||||
|
* each copy is n_samples bytes x channels = whole frames.
|
||||||
|
*/
|
||||||
|
off = samples_to_bytes(runtime, off);
|
||||||
|
frames = samples_to_bytes(runtime, frames);
|
||||||
|
hwoff = samples_to_bytes(runtime, hwoff);
|
||||||
|
for (c = 0; c < channels; ++c, ++bufs) {
|
||||||
|
if (!data || !*bufs)
|
||||||
|
err = fill_silence(substream, c, hwoff, NULL, frames);
|
||||||
|
else
|
||||||
|
err = transfer(substream, c, hwoff, *bufs + off,
|
||||||
|
frames);
|
||||||
|
if (err < 0)
|
||||||
|
return err;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -2106,24 +2129,33 @@ snd_pcm_sframes_t __snd_pcm_lib_write(struct snd_pcm_substream *substream,
|
||||||
snd_pcm_uframes_t xfer = 0;
|
snd_pcm_uframes_t xfer = 0;
|
||||||
snd_pcm_uframes_t offset = 0;
|
snd_pcm_uframes_t offset = 0;
|
||||||
snd_pcm_uframes_t avail;
|
snd_pcm_uframes_t avail;
|
||||||
transfer_f transfer;
|
pcm_copy_f writer;
|
||||||
|
pcm_transfer_f transfer;
|
||||||
bool nonblock;
|
bool nonblock;
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
err = pcm_sanity_check(substream);
|
err = pcm_sanity_check(substream);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
return err;
|
return err;
|
||||||
runtime = substream->runtime;
|
|
||||||
|
|
||||||
if (interleaved) {
|
if (interleaved) {
|
||||||
if (runtime->access != SNDRV_PCM_ACCESS_RW_INTERLEAVED &&
|
if (runtime->access != SNDRV_PCM_ACCESS_RW_INTERLEAVED &&
|
||||||
runtime->channels > 1)
|
runtime->channels > 1)
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
transfer = snd_pcm_lib_write_transfer;
|
writer = interleaved_copy;
|
||||||
} else {
|
} else {
|
||||||
if (runtime->access != SNDRV_PCM_ACCESS_RW_NONINTERLEAVED)
|
if (runtime->access != SNDRV_PCM_ACCESS_RW_NONINTERLEAVED)
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
transfer = snd_pcm_lib_writev_transfer;
|
writer = noninterleaved_copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
transfer = fill_silence;
|
||||||
|
} else {
|
||||||
|
if (substream->ops->copy_user)
|
||||||
|
transfer = (pcm_transfer_f)substream->ops->copy_user;
|
||||||
|
else
|
||||||
|
transfer = default_write_copy_user;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (size == 0)
|
if (size == 0)
|
||||||
|
@ -2166,7 +2198,8 @@ snd_pcm_sframes_t __snd_pcm_lib_write(struct snd_pcm_substream *substream,
|
||||||
appl_ptr = runtime->control->appl_ptr;
|
appl_ptr = runtime->control->appl_ptr;
|
||||||
appl_ofs = appl_ptr % runtime->buffer_size;
|
appl_ofs = appl_ptr % runtime->buffer_size;
|
||||||
snd_pcm_stream_unlock_irq(substream);
|
snd_pcm_stream_unlock_irq(substream);
|
||||||
err = transfer(substream, appl_ofs, data, offset, frames);
|
err = writer(substream, appl_ofs, data, offset, frames,
|
||||||
|
transfer);
|
||||||
snd_pcm_stream_lock_irq(substream);
|
snd_pcm_stream_lock_irq(substream);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
goto _end_unlock;
|
goto _end_unlock;
|
||||||
|
@ -2200,65 +2233,15 @@ snd_pcm_sframes_t __snd_pcm_lib_write(struct snd_pcm_substream *substream,
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(__snd_pcm_lib_write);
|
EXPORT_SYMBOL(__snd_pcm_lib_write);
|
||||||
|
|
||||||
static int snd_pcm_lib_read_transfer(struct snd_pcm_substream *substream,
|
/* default copy_user ops for read */
|
||||||
unsigned int hwoff,
|
static int default_read_copy_user(struct snd_pcm_substream *substream,
|
||||||
void *data, unsigned int off,
|
int channel, unsigned long hwoff,
|
||||||
snd_pcm_uframes_t frames)
|
void *buf, unsigned long bytes)
|
||||||
{
|
{
|
||||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
if (copy_to_user((void __user *)buf,
|
||||||
int err;
|
get_dma_ptr(substream->runtime, channel, hwoff),
|
||||||
char __user *buf = (char __user *) data + frames_to_bytes(runtime, off);
|
bytes))
|
||||||
if (substream->ops->copy_user) {
|
return -EFAULT;
|
||||||
hwoff = frames_to_bytes(runtime, hwoff);
|
|
||||||
frames = frames_to_bytes(runtime, frames);
|
|
||||||
err = substream->ops->copy_user(substream, 0, hwoff, buf, frames);
|
|
||||||
if (err < 0)
|
|
||||||
return err;
|
|
||||||
} else {
|
|
||||||
char *hwbuf = runtime->dma_area + frames_to_bytes(runtime, hwoff);
|
|
||||||
if (copy_to_user(buf, hwbuf, frames_to_bytes(runtime, frames)))
|
|
||||||
return -EFAULT;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int snd_pcm_lib_readv_transfer(struct snd_pcm_substream *substream,
|
|
||||||
unsigned int hwoff,
|
|
||||||
void *data, unsigned int off,
|
|
||||||
snd_pcm_uframes_t frames)
|
|
||||||
{
|
|
||||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
||||||
int err;
|
|
||||||
void __user **bufs = (void __user **)data;
|
|
||||||
int channels = runtime->channels;
|
|
||||||
char __user *buf;
|
|
||||||
char *hwbuf;
|
|
||||||
int c;
|
|
||||||
|
|
||||||
if (substream->ops->copy_user) {
|
|
||||||
hwoff = samples_to_bytes(runtime, hwoff);
|
|
||||||
off = samples_to_bytes(runtime, off);
|
|
||||||
frames = samples_to_bytes(runtime, frames);
|
|
||||||
for (c = 0; c < channels; ++c, ++bufs) {
|
|
||||||
if (!*bufs)
|
|
||||||
continue;
|
|
||||||
err = substream->ops->copy_user(substream, c, hwoff,
|
|
||||||
*bufs + off, frames);
|
|
||||||
if (err < 0)
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
snd_pcm_uframes_t dma_csize = runtime->dma_bytes / channels;
|
|
||||||
for (c = 0; c < channels; ++c, ++bufs) {
|
|
||||||
if (*bufs == NULL)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
hwbuf = runtime->dma_area + (c * dma_csize) + samples_to_bytes(runtime, hwoff);
|
|
||||||
buf = *bufs + samples_to_bytes(runtime, off);
|
|
||||||
if (copy_to_user(buf, hwbuf, samples_to_bytes(runtime, frames)))
|
|
||||||
return -EFAULT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2270,26 +2253,34 @@ snd_pcm_sframes_t __snd_pcm_lib_read(struct snd_pcm_substream *substream,
|
||||||
snd_pcm_uframes_t xfer = 0;
|
snd_pcm_uframes_t xfer = 0;
|
||||||
snd_pcm_uframes_t offset = 0;
|
snd_pcm_uframes_t offset = 0;
|
||||||
snd_pcm_uframes_t avail;
|
snd_pcm_uframes_t avail;
|
||||||
transfer_f transfer;
|
pcm_copy_f reader;
|
||||||
|
pcm_transfer_f transfer;
|
||||||
bool nonblock;
|
bool nonblock;
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
err = pcm_sanity_check(substream);
|
err = pcm_sanity_check(substream);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
return err;
|
return err;
|
||||||
runtime = substream->runtime;
|
|
||||||
|
if (!data)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
if (interleaved) {
|
if (interleaved) {
|
||||||
if (runtime->access != SNDRV_PCM_ACCESS_RW_INTERLEAVED &&
|
if (runtime->access != SNDRV_PCM_ACCESS_RW_INTERLEAVED &&
|
||||||
runtime->channels > 1)
|
runtime->channels > 1)
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
transfer = snd_pcm_lib_read_transfer;
|
reader = interleaved_copy;
|
||||||
} else {
|
} else {
|
||||||
if (runtime->access != SNDRV_PCM_ACCESS_RW_NONINTERLEAVED)
|
if (runtime->access != SNDRV_PCM_ACCESS_RW_NONINTERLEAVED)
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
transfer = snd_pcm_lib_readv_transfer;
|
reader = noninterleaved_copy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (substream->ops->copy_user)
|
||||||
|
transfer = (pcm_transfer_f)substream->ops->copy_user;
|
||||||
|
else
|
||||||
|
transfer = default_read_copy_user;
|
||||||
|
|
||||||
if (size == 0)
|
if (size == 0)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
@ -2343,7 +2334,8 @@ snd_pcm_sframes_t __snd_pcm_lib_read(struct snd_pcm_substream *substream,
|
||||||
appl_ptr = runtime->control->appl_ptr;
|
appl_ptr = runtime->control->appl_ptr;
|
||||||
appl_ofs = appl_ptr % runtime->buffer_size;
|
appl_ofs = appl_ptr % runtime->buffer_size;
|
||||||
snd_pcm_stream_unlock_irq(substream);
|
snd_pcm_stream_unlock_irq(substream);
|
||||||
err = transfer(substream, appl_ofs, data, offset, frames);
|
err = reader(substream, appl_ofs, data, offset, frames,
|
||||||
|
transfer);
|
||||||
snd_pcm_stream_lock_irq(substream);
|
snd_pcm_stream_lock_irq(substream);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
goto _end_unlock;
|
goto _end_unlock;
|
||||||
|
|
Loading…
Reference in New Issue