diff options
Diffstat (limited to 'sound/core/pcm_memory.c')
-rw-r--r-- | sound/core/pcm_memory.c | 252 |
1 files changed, 123 insertions, 129 deletions
diff --git a/sound/core/pcm_memory.c b/sound/core/pcm_memory.c index 1bf6a3d9e0c2..56725d36825b 100644 --- a/sound/core/pcm_memory.c +++ b/sound/core/pcm_memory.c @@ -9,7 +9,6 @@ #include <linux/init.h> #include <linux/slab.h> #include <linux/moduleparam.h> -#include <linux/vmalloc.h> #include <linux/export.h> #include <sound/core.h> #include <sound/pcm.h> @@ -31,20 +30,52 @@ static unsigned long max_alloc_per_card = 32UL * 1024UL * 1024UL; module_param(max_alloc_per_card, ulong, 0644); MODULE_PARM_DESC(max_alloc_per_card, "Max total allocation bytes per card."); +static void __update_allocated_size(struct snd_card *card, ssize_t bytes) +{ + card->total_pcm_alloc_bytes += bytes; +} + +static void update_allocated_size(struct snd_card *card, ssize_t bytes) +{ + guard(mutex)(&card->memory_mutex); + __update_allocated_size(card, bytes); +} + +static void decrease_allocated_size(struct snd_card *card, size_t bytes) +{ + guard(mutex)(&card->memory_mutex); + WARN_ON(card->total_pcm_alloc_bytes < bytes); + __update_allocated_size(card, -(ssize_t)bytes); +} + static int do_alloc_pages(struct snd_card *card, int type, struct device *dev, - size_t size, struct snd_dma_buffer *dmab) + int str, size_t size, struct snd_dma_buffer *dmab) { + enum dma_data_direction dir; int err; - if (max_alloc_per_card && - card->total_pcm_alloc_bytes + size > max_alloc_per_card) - return -ENOMEM; + /* check and reserve the requested size */ + scoped_guard(mutex, &card->memory_mutex) { + if (max_alloc_per_card && + card->total_pcm_alloc_bytes + size > max_alloc_per_card) + return -ENOMEM; + __update_allocated_size(card, size); + } - err = snd_dma_alloc_pages(type, dev, size, dmab); + if (str == SNDRV_PCM_STREAM_PLAYBACK) + dir = DMA_TO_DEVICE; + else + dir = DMA_FROM_DEVICE; + err = snd_dma_alloc_dir_pages(type, dev, dir, size, dmab); if (!err) { - mutex_lock(&card->memory_mutex); - card->total_pcm_alloc_bytes += dmab->bytes; - mutex_unlock(&card->memory_mutex); + /* the actual allocation size might be bigger than requested, + * and we need to correct the account + */ + if (dmab->bytes != size) + update_allocated_size(card, dmab->bytes - size); + } else { + /* take back on allocation failure */ + decrease_allocated_size(card, size); } return err; } @@ -53,10 +84,7 @@ static void do_free_pages(struct snd_card *card, struct snd_dma_buffer *dmab) { if (!dmab->area) return; - mutex_lock(&card->memory_mutex); - WARN_ON(card->total_pcm_alloc_bytes < dmab->bytes); - card->total_pcm_alloc_bytes -= dmab->bytes; - mutex_unlock(&card->memory_mutex); + decrease_allocated_size(card, dmab->bytes); snd_dma_free_pages(dmab); dmab->area = NULL; } @@ -67,7 +95,8 @@ static void do_free_pages(struct snd_card *card, struct snd_dma_buffer *dmab) * * the minimum size is snd_minimum_buffer. it should be power of 2. */ -static int preallocate_pcm_pages(struct snd_pcm_substream *substream, size_t size) +static int preallocate_pcm_pages(struct snd_pcm_substream *substream, + size_t size, bool no_fallback) { struct snd_dma_buffer *dmab = &substream->dma_buffer; struct snd_card *card = substream->pcm->card; @@ -76,9 +105,11 @@ static int preallocate_pcm_pages(struct snd_pcm_substream *substream, size_t siz do { err = do_alloc_pages(card, dmab->dev.type, dmab->dev.dev, - size, dmab); + substream->stream, size, dmab); if (err != -ENOMEM) return err; + if (no_fallback) + break; size >>= 1; } while (size >= snd_minimum_buffer); dmab->bytes = 0; /* tell error */ @@ -86,15 +117,7 @@ static int preallocate_pcm_pages(struct snd_pcm_substream *substream, size_t siz substream->pcm->card->number, substream->pcm->device, substream->stream ? 'c' : 'p', substream->number, substream->pcm->name, orig_size); - return 0; -} - -/* - * release the preallocated buffer if not yet done. - */ -static void snd_pcm_lib_preallocate_dma_free(struct snd_pcm_substream *substream) -{ - do_free_pages(substream->pcm->card, &substream->dma_buffer); + return -ENOMEM; } /** @@ -105,7 +128,7 @@ static void snd_pcm_lib_preallocate_dma_free(struct snd_pcm_substream *substream */ void snd_pcm_lib_preallocate_free(struct snd_pcm_substream *substream) { - snd_pcm_lib_preallocate_dma_free(substream); + do_free_pages(substream->pcm->card, &substream->dma_buffer); } /** @@ -119,9 +142,8 @@ void snd_pcm_lib_preallocate_free_for_all(struct snd_pcm *pcm) struct snd_pcm_substream *substream; int stream; - for (stream = 0; stream < 2; stream++) - for (substream = pcm->streams[stream].substream; substream; substream = substream->next) - snd_pcm_lib_preallocate_free(substream); + for_each_pcm_substream(pcm, stream, substream) + snd_pcm_lib_preallocate_free(substream); } EXPORT_SYMBOL(snd_pcm_lib_preallocate_free_for_all); @@ -161,16 +183,20 @@ static void snd_pcm_lib_preallocate_proc_write(struct snd_info_entry *entry, struct snd_pcm_substream *substream = entry->private_data; struct snd_card *card = substream->pcm->card; char line[64], str[64]; - size_t size; + unsigned long size; struct snd_dma_buffer new_dmab; + guard(mutex)(&substream->pcm->open_mutex); if (substream->runtime) { buffer->error = -EBUSY; return; } if (!snd_info_get_line(buffer, line, sizeof(line))) { snd_info_get_str(str, line, sizeof(str)); - size = simple_strtoul(str, NULL, 10) * 1024; + buffer->error = kstrtoul(str, 10, &size); + if (buffer->error != 0) + return; + size *= 1024; if ((size != 0 && size < 8192) || size > substream->dma_max) { buffer->error = -EINVAL; return; @@ -183,8 +209,13 @@ static void snd_pcm_lib_preallocate_proc_write(struct snd_info_entry *entry, if (do_alloc_pages(card, substream->dma_buffer.dev.type, substream->dma_buffer.dev.dev, + substream->stream, size, &new_dmab) < 0) { buffer->error = -ENOMEM; + pr_debug("ALSA pcmC%dD%d%c,%d:%s: cannot preallocate for size %lu\n", + substream->pcm->card->number, substream->pcm->device, + substream->stream ? 'c' : 'p', substream->number, + substream->pcm->name, size); return; } substream->buffer_bytes_max = size; @@ -219,24 +250,39 @@ static inline void preallocate_info_init(struct snd_pcm_substream *substream) } #else /* !CONFIG_SND_VERBOSE_PROCFS */ -#define preallocate_info_init(s) +static inline void preallocate_info_init(struct snd_pcm_substream *substream) +{ +} #endif /* CONFIG_SND_VERBOSE_PROCFS */ /* * pre-allocate the buffer and create a proc file for the substream */ -static void preallocate_pages(struct snd_pcm_substream *substream, +static int preallocate_pages(struct snd_pcm_substream *substream, int type, struct device *data, size_t size, size_t max, bool managed) { + int err; + if (snd_BUG_ON(substream->dma_buffer.dev.type)) - return; + return -EINVAL; substream->dma_buffer.dev.type = type; substream->dma_buffer.dev.dev = data; - if (size > 0 && preallocate_dma && substream->number < maximum_substreams) - preallocate_pcm_pages(substream, size); + if (size > 0) { + if (!max) { + /* no fallback, only also inform -ENOMEM */ + err = preallocate_pcm_pages(substream, size, true); + if (err < 0) + return err; + } else if (preallocate_dma && + substream->number < maximum_substreams) { + err = preallocate_pcm_pages(substream, size, false); + if (err < 0 && err != -ENOMEM) + return err; + } + } if (substream->dma_buffer.bytes > 0) substream->buffer_bytes_max = substream->dma_buffer.bytes; @@ -245,20 +291,22 @@ static void preallocate_pages(struct snd_pcm_substream *substream, preallocate_info_init(substream); if (managed) substream->managed_buffer_alloc = 1; + return 0; } -static void preallocate_pages_for_all(struct snd_pcm *pcm, int type, +static int preallocate_pages_for_all(struct snd_pcm *pcm, int type, void *data, size_t size, size_t max, bool managed) { struct snd_pcm_substream *substream; - int stream; + int stream, err; - for (stream = 0; stream < 2; stream++) - for (substream = pcm->streams[stream].substream; substream; - substream = substream->next) - preallocate_pages(substream, type, data, size, max, - managed); + for_each_pcm_substream(pcm, stream, substream) { + err = preallocate_pages(substream, type, data, size, max, managed); + if (err < 0) + return err; + } + return 0; } /** @@ -315,11 +363,24 @@ EXPORT_SYMBOL(snd_pcm_lib_preallocate_pages_for_all); * When a buffer is actually allocated before the PCM hw_params call, it * turns on the runtime buffer_changed flag for drivers changing their h/w * parameters accordingly. + * + * When @size is non-zero and @max is zero, this tries to allocate for only + * the exact buffer size without fallback, and may return -ENOMEM. + * Otherwise, the function tries to allocate smaller chunks if the allocation + * fails. This is the behavior of snd_pcm_set_fixed_buffer(). + * + * When both @size and @max are zero, the function only sets up the buffer + * for later dynamic allocations. It's used typically for buffers with + * SNDRV_DMA_TYPE_VMALLOC type. + * + * Upon successful buffer allocation and setup, the function returns 0. + * + * Return: zero if successful, or a negative error code */ -void snd_pcm_set_managed_buffer(struct snd_pcm_substream *substream, int type, +int snd_pcm_set_managed_buffer(struct snd_pcm_substream *substream, int type, struct device *data, size_t size, size_t max) { - preallocate_pages(substream, type, data, size, max, true); + return preallocate_pages(substream, type, data, size, max, true); } EXPORT_SYMBOL(snd_pcm_set_managed_buffer); @@ -334,35 +395,16 @@ EXPORT_SYMBOL(snd_pcm_set_managed_buffer); * * Do pre-allocation to all substreams of the given pcm for the specified DMA * type and size, and set the managed_buffer_alloc flag to each substream. - */ -void snd_pcm_set_managed_buffer_all(struct snd_pcm *pcm, int type, - struct device *data, - size_t size, size_t max) -{ - preallocate_pages_for_all(pcm, type, data, size, max, true); -} -EXPORT_SYMBOL(snd_pcm_set_managed_buffer_all); - -#ifdef CONFIG_SND_DMA_SGBUF -/* - * snd_pcm_sgbuf_ops_page - get the page struct at the given offset - * @substream: the pcm substream instance - * @offset: the buffer offset - * - * Used as the page callback of PCM ops. * - * Return: The page struct at the given buffer offset. %NULL on failure. + * Return: zero if successful, or a negative error code */ -struct page *snd_pcm_sgbuf_ops_page(struct snd_pcm_substream *substream, unsigned long offset) +int snd_pcm_set_managed_buffer_all(struct snd_pcm *pcm, int type, + struct device *data, + size_t size, size_t max) { - struct snd_sg_buf *sgbuf = snd_pcm_substream_sgbuf(substream); - - unsigned int idx = offset >> PAGE_SHIFT; - if (idx >= (unsigned int)sgbuf->pages) - return NULL; - return sgbuf->page_table[idx]; + return preallocate_pages_for_all(pcm, type, data, size, max, true); } -#endif /* CONFIG_SND_DMA_SGBUF */ +EXPORT_SYMBOL(snd_pcm_set_managed_buffer_all); /** * snd_pcm_lib_malloc_pages - allocate the DMA buffer @@ -377,7 +419,7 @@ struct page *snd_pcm_sgbuf_ops_page(struct snd_pcm_substream *substream, unsigne */ int snd_pcm_lib_malloc_pages(struct snd_pcm_substream *substream, size_t size) { - struct snd_card *card = substream->pcm->card; + struct snd_card *card; struct snd_pcm_runtime *runtime; struct snd_dma_buffer *dmab = NULL; @@ -387,6 +429,7 @@ int snd_pcm_lib_malloc_pages(struct snd_pcm_substream *substream, size_t size) SNDRV_DMA_TYPE_UNKNOWN)) return -EINVAL; runtime = substream->runtime; + card = substream->pcm->card; if (runtime->dma_buffer_p) { /* perphaps, we might free the large DMA memory region @@ -402,6 +445,9 @@ int snd_pcm_lib_malloc_pages(struct snd_pcm_substream *substream, size_t size) substream->dma_buffer.bytes >= size) { dmab = &substream->dma_buffer; /* use the pre-allocated buffer */ } else { + /* dma_max=0 means the fixed size preallocation */ + if (substream->dma_buffer.area && !substream->dma_max) + return -ENOMEM; dmab = kzalloc(sizeof(*dmab), GFP_KERNEL); if (! dmab) return -ENOMEM; @@ -409,8 +455,13 @@ int snd_pcm_lib_malloc_pages(struct snd_pcm_substream *substream, size_t size) if (do_alloc_pages(card, substream->dma_buffer.dev.type, substream->dma_buffer.dev.dev, + substream->stream, size, dmab) < 0) { kfree(dmab); + pr_debug("ALSA pcmC%dD%d%c,%d:%s: cannot allocate for size %zu\n", + substream->pcm->card->number, substream->pcm->device, + substream->stream ? 'c' : 'p', substream->number, + substream->pcm->name, size); return -ENOMEM; } } @@ -430,7 +481,6 @@ EXPORT_SYMBOL(snd_pcm_lib_malloc_pages); */ int snd_pcm_lib_free_pages(struct snd_pcm_substream *substream) { - struct snd_card *card = substream->pcm->card; struct snd_pcm_runtime *runtime; if (PCM_RUNTIME_CHECK(substream)) @@ -439,6 +489,8 @@ int snd_pcm_lib_free_pages(struct snd_pcm_substream *substream) if (runtime->dma_area == NULL) return 0; if (runtime->dma_buffer_p != &substream->dma_buffer) { + struct snd_card *card = substream->pcm->card; + /* it's a newly allocated buffer. release it now. */ do_free_pages(card, runtime->dma_buffer_p); kfree(runtime->dma_buffer_p); @@ -447,61 +499,3 @@ int snd_pcm_lib_free_pages(struct snd_pcm_substream *substream) return 0; } EXPORT_SYMBOL(snd_pcm_lib_free_pages); - -int _snd_pcm_lib_alloc_vmalloc_buffer(struct snd_pcm_substream *substream, - size_t size, gfp_t gfp_flags) -{ - struct snd_pcm_runtime *runtime; - - if (PCM_RUNTIME_CHECK(substream)) - return -EINVAL; - runtime = substream->runtime; - if (runtime->dma_area) { - if (runtime->dma_bytes >= size) - return 0; /* already large enough */ - vfree(runtime->dma_area); - } - runtime->dma_area = __vmalloc(size, gfp_flags); - if (!runtime->dma_area) - return -ENOMEM; - runtime->dma_bytes = size; - return 1; -} -EXPORT_SYMBOL(_snd_pcm_lib_alloc_vmalloc_buffer); - -/** - * snd_pcm_lib_free_vmalloc_buffer - free vmalloc buffer - * @substream: the substream with a buffer allocated by - * snd_pcm_lib_alloc_vmalloc_buffer() - * - * Return: Zero if successful, or a negative error code on failure. - */ -int snd_pcm_lib_free_vmalloc_buffer(struct snd_pcm_substream *substream) -{ - struct snd_pcm_runtime *runtime; - - if (PCM_RUNTIME_CHECK(substream)) - return -EINVAL; - runtime = substream->runtime; - vfree(runtime->dma_area); - runtime->dma_area = NULL; - return 0; -} -EXPORT_SYMBOL(snd_pcm_lib_free_vmalloc_buffer); - -/** - * snd_pcm_lib_get_vmalloc_page - map vmalloc buffer offset to page struct - * @substream: the substream with a buffer allocated by - * snd_pcm_lib_alloc_vmalloc_buffer() - * @offset: offset in the buffer - * - * This function is to be used as the page callback in the PCM ops. - * - * Return: The page struct, or %NULL on failure. - */ -struct page *snd_pcm_lib_get_vmalloc_page(struct snd_pcm_substream *substream, - unsigned long offset) -{ - return vmalloc_to_page(substream->runtime->dma_area + offset); -} -EXPORT_SYMBOL(snd_pcm_lib_get_vmalloc_page); |