diff options
Diffstat (limited to 'sound/core/pcm_memory.c')
| -rw-r--r-- | sound/core/pcm_memory.c | 429 |
1 files changed, 229 insertions, 200 deletions
diff --git a/sound/core/pcm_memory.c b/sound/core/pcm_memory.c index 0af622c34e19..56725d36825b 100644 --- a/sound/core/pcm_memory.c +++ b/sound/core/pcm_memory.c @@ -1,35 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Digital Audio (PCM) abstract layer * Copyright (c) by Jaroslav Kysela <perex@perex.cz> - * - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * */ -#include <asm/io.h> +#include <linux/io.h> #include <linux/time.h> #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> #include <sound/info.h> #include <sound/initval.h> +#include "pcm_local.h" static int preallocate_dma = 1; module_param(preallocate_dma, int, 0444); @@ -41,6 +26,68 @@ MODULE_PARM_DESC(maximum_substreams, "Maximum substreams with preallocated DMA m static const size_t snd_minimum_buffer = 16384; +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, + int str, size_t size, struct snd_dma_buffer *dmab) +{ + enum dma_data_direction dir; + int err; + + /* 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); + } + + 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) { + /* 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; +} + +static void do_free_pages(struct snd_card *card, struct snd_dma_buffer *dmab) +{ + if (!dmab->area) + return; + decrease_allocated_size(card, dmab->bytes); + snd_dma_free_pages(dmab); + dmab->area = NULL; +} /* * try to allocate as the large pages as possible. @@ -48,45 +95,29 @@ static const size_t snd_minimum_buffer = 16384; * * 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; + size_t orig_size = size; int err; - /* already reserved? */ - if (snd_dma_get_reserved_buf(dmab, substream->dma_buf_id) > 0) { - if (dmab->bytes >= size) - return 0; /* yes */ - /* no, free the reserved block */ - snd_dma_free_pages(dmab); - dmab->bytes = 0; - } - do { - if ((err = snd_dma_alloc_pages(dmab->dev.type, dmab->dev.dev, - size, dmab)) < 0) { - if (err != -ENOMEM) - return err; /* fatal error */ - } else - return 0; + err = do_alloc_pages(card, dmab->dev.type, dmab->dev.dev, + 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 */ - return 0; -} - -/* - * release the preallocated buffer if not yet done. - */ -static void snd_pcm_lib_preallocate_dma_free(struct snd_pcm_substream *substream) -{ - if (substream->dma_buffer.area == NULL) - return; - if (substream->dma_buf_id) - snd_dma_reserve_buf(&substream->dma_buffer, substream->dma_buf_id); - else - snd_dma_free_pages(&substream->dma_buffer); - substream->dma_buffer.area = NULL; + pr_warn("ALSA pcmC%dD%d%c,%d:%s: cannot preallocate for size %zu\n", + substream->pcm->card->number, substream->pcm->device, + substream->stream ? 'c' : 'p', substream->number, + substream->pcm->name, orig_size); + return -ENOMEM; } /** @@ -94,19 +125,10 @@ static void snd_pcm_lib_preallocate_dma_free(struct snd_pcm_substream *substream * @substream: the pcm substream instance * * Releases the pre-allocated buffer of the given substream. - * - * Return: Zero if successful, or a negative error code on failure. */ -int snd_pcm_lib_preallocate_free(struct snd_pcm_substream *substream) +void snd_pcm_lib_preallocate_free(struct snd_pcm_substream *substream) { - snd_pcm_lib_preallocate_dma_free(substream); -#ifdef CONFIG_SND_VERBOSE_PROCFS - snd_info_free_entry(substream->proc_prealloc_max_entry); - substream->proc_prealloc_max_entry = NULL; - snd_info_free_entry(substream->proc_prealloc_entry); - substream->proc_prealloc_entry = NULL; -#endif - return 0; + do_free_pages(substream->pcm->card, &substream->dma_buffer); } /** @@ -114,20 +136,15 @@ int snd_pcm_lib_preallocate_free(struct snd_pcm_substream *substream) * @pcm: the pcm instance * * Releases all the pre-allocated buffers on the given pcm. - * - * Return: Zero if successful, or a negative error code on failure. */ -int snd_pcm_lib_preallocate_free_for_all(struct snd_pcm *pcm) +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); - return 0; + for_each_pcm_substream(pcm, stream, substream) + snd_pcm_lib_preallocate_free(substream); } - EXPORT_SYMBOL(snd_pcm_lib_preallocate_free_for_all); #ifdef CONFIG_SND_VERBOSE_PROCFS @@ -164,17 +181,22 @@ static void snd_pcm_lib_preallocate_proc_write(struct snd_info_entry *entry, struct snd_info_buffer *buffer) { 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; @@ -184,10 +206,16 @@ static void snd_pcm_lib_preallocate_proc_write(struct snd_info_entry *entry, memset(&new_dmab, 0, sizeof(new_dmab)); new_dmab.dev = substream->dma_buffer.dev; if (size > 0) { - if (snd_dma_alloc_pages(substream->dma_buffer.dev.type, - substream->dma_buffer.dev.dev, - size, &new_dmab) < 0) { + 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; @@ -195,7 +223,7 @@ static void snd_pcm_lib_preallocate_proc_write(struct snd_info_entry *entry, substream->buffer_bytes_max = UINT_MAX; } if (substream->dma_buffer.area) - snd_dma_free_pages(&substream->dma_buffer); + do_free_pages(card, &substream->dma_buffer); substream->dma_buffer = new_dmab; } else { buffer->error = -EINVAL; @@ -206,49 +234,80 @@ static inline void preallocate_info_init(struct snd_pcm_substream *substream) { struct snd_info_entry *entry; - if ((entry = snd_info_create_card_entry(substream->pcm->card, "prealloc", substream->proc_root)) != NULL) { - entry->c.text.read = snd_pcm_lib_preallocate_proc_read; + entry = snd_info_create_card_entry(substream->pcm->card, "prealloc", + substream->proc_root); + if (entry) { + snd_info_set_text_ops(entry, substream, + snd_pcm_lib_preallocate_proc_read); entry->c.text.write = snd_pcm_lib_preallocate_proc_write; - entry->mode |= S_IWUSR; - entry->private_data = substream; - if (snd_info_register(entry) < 0) { - snd_info_free_entry(entry); - entry = NULL; - } + entry->mode |= 0200; } - substream->proc_prealloc_entry = entry; - if ((entry = snd_info_create_card_entry(substream->pcm->card, "prealloc_max", substream->proc_root)) != NULL) { - entry->c.text.read = snd_pcm_lib_preallocate_max_proc_read; - entry->private_data = substream; - if (snd_info_register(entry) < 0) { - snd_info_free_entry(entry); - entry = NULL; - } - } - substream->proc_prealloc_max_entry = entry; + entry = snd_info_create_card_entry(substream->pcm->card, "prealloc_max", + substream->proc_root); + if (entry) + snd_info_set_text_ops(entry, substream, + snd_pcm_lib_preallocate_max_proc_read); } #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 int snd_pcm_lib_preallocate_pages1(struct snd_pcm_substream *substream, - size_t size, size_t max) +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 (size > 0 && preallocate_dma && substream->number < maximum_substreams) - preallocate_pcm_pages(substream, size); + if (snd_BUG_ON(substream->dma_buffer.dev.type)) + return -EINVAL; + + substream->dma_buffer.dev.type = type; + substream->dma_buffer.dev.dev = data; + + 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; substream->dma_max = max; - preallocate_info_init(substream); + if (max > 0) + preallocate_info_init(substream); + if (managed) + substream->managed_buffer_alloc = 1; return 0; } +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, err; + + for_each_pcm_substream(pcm, stream, substream) { + err = preallocate_pages(substream, type, data, size, max, managed); + if (err < 0) + return err; + } + return 0; +} /** * snd_pcm_lib_preallocate_pages - pre-allocation for the given DMA type @@ -259,23 +318,13 @@ static int snd_pcm_lib_preallocate_pages1(struct snd_pcm_substream *substream, * @max: the max. allowed pre-allocation size * * Do pre-allocation for the given DMA buffer type. - * - * When substream->dma_buf_id is set, the function tries to look for - * the reserved buffer, and the buffer is not freed but reserved at - * destruction time. The dma_buf_id must be unique for all systems - * (in the same DMA buffer type) e.g. using snd_dma_pci_buf_id(). - * - * Return: Zero if successful, or a negative error code on failure. */ -int snd_pcm_lib_preallocate_pages(struct snd_pcm_substream *substream, +void snd_pcm_lib_preallocate_pages(struct snd_pcm_substream *substream, int type, struct device *data, size_t size, size_t max) { - substream->dma_buffer.dev.type = type; - substream->dma_buffer.dev.dev = data; - return snd_pcm_lib_preallocate_pages1(substream, size, max); + preallocate_pages(substream, type, data, size, max, false); } - EXPORT_SYMBOL(snd_pcm_lib_preallocate_pages); /** @@ -288,47 +337,74 @@ EXPORT_SYMBOL(snd_pcm_lib_preallocate_pages); * * Do pre-allocation to all substreams of the given pcm for the * specified DMA type. - * - * Return: Zero if successful, or a negative error code on failure. */ -int snd_pcm_lib_preallocate_pages_for_all(struct snd_pcm *pcm, +void snd_pcm_lib_preallocate_pages_for_all(struct snd_pcm *pcm, int type, void *data, size_t size, size_t max) { - struct snd_pcm_substream *substream; - int stream, err; - - for (stream = 0; stream < 2; stream++) - for (substream = pcm->streams[stream].substream; substream; substream = substream->next) - if ((err = snd_pcm_lib_preallocate_pages(substream, type, data, size, max)) < 0) - return err; - return 0; + preallocate_pages_for_all(pcm, type, data, size, max, false); } - EXPORT_SYMBOL(snd_pcm_lib_preallocate_pages_for_all); -#ifdef CONFIG_SND_DMA_SGBUF /** - * snd_pcm_sgbuf_ops_page - get the page struct at the given offset + * snd_pcm_set_managed_buffer - set up buffer management for a substream * @substream: the pcm substream instance - * @offset: the buffer offset + * @type: DMA type (SNDRV_DMA_TYPE_*) + * @data: DMA type dependent data + * @size: the requested pre-allocation size in bytes + * @max: the max. allowed pre-allocation size + * + * Do pre-allocation for the given DMA buffer type, and set the managed + * buffer allocation mode to the given substream. + * In this mode, PCM core will allocate a buffer automatically before PCM + * hw_params ops call, and release the buffer after PCM hw_free ops call + * as well, so that the driver doesn't need to invoke the allocation and + * the release explicitly in its callback. + * 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. * - * Used as the page callback of PCM ops. + * Upon successful buffer allocation and setup, the function returns 0. * - * 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(struct snd_pcm_substream *substream, 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(substream, type, data, size, max, true); } +EXPORT_SYMBOL(snd_pcm_set_managed_buffer); -EXPORT_SYMBOL(snd_pcm_sgbuf_ops_page); -#endif /* CONFIG_SND_DMA_SGBUF */ +/** + * snd_pcm_set_managed_buffer_all - set up buffer management for all substreams + * for all substreams + * @pcm: the pcm instance + * @type: DMA type (SNDRV_DMA_TYPE_*) + * @data: DMA type dependent data + * @size: the requested pre-allocation size in bytes + * @max: the max. allowed pre-allocation size + * + * 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. + * + * Return: zero if successful, or a negative error code + */ +int snd_pcm_set_managed_buffer_all(struct snd_pcm *pcm, int type, + struct device *data, + size_t size, size_t max) +{ + return preallocate_pages_for_all(pcm, type, data, size, max, true); +} +EXPORT_SYMBOL(snd_pcm_set_managed_buffer_all); /** * snd_pcm_lib_malloc_pages - allocate the DMA buffer @@ -343,6 +419,7 @@ EXPORT_SYMBOL(snd_pcm_sgbuf_ops_page); */ int snd_pcm_lib_malloc_pages(struct snd_pcm_substream *substream, size_t size) { + struct snd_card *card; struct snd_pcm_runtime *runtime; struct snd_dma_buffer *dmab = NULL; @@ -352,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 @@ -367,14 +445,23 @@ 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; dmab->dev = substream->dma_buffer.dev; - if (snd_dma_alloc_pages(substream->dma_buffer.dev.type, - substream->dma_buffer.dev.dev, - size, dmab) < 0) { + 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; } } @@ -382,7 +469,6 @@ int snd_pcm_lib_malloc_pages(struct snd_pcm_substream *substream, size_t size) runtime->dma_bytes = size; return 1; /* area was changed */ } - EXPORT_SYMBOL(snd_pcm_lib_malloc_pages); /** @@ -403,70 +489,13 @@ 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. */ - snd_dma_free_pages(runtime->dma_buffer_p); + do_free_pages(card, runtime->dma_buffer_p); kfree(runtime->dma_buffer_p); } snd_pcm_set_runtime_buffer(substream, NULL); 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, PAGE_KERNEL); - 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); |
