[alsa-devel] [PATCH 1/2] ALSA: pcm: Set per-card upper limit of PCM buffer allocations
Currently, the available buffer allocation size for a PCM stream depends on the preallocated size; when a buffer has been preallocated, the max buffer size is set to that size, so that application won't re-allocate too much memory. OTOH, when no preallocation is done, each substream may allocate arbitrary size of buffers as long as snd_pcm_hardware.buffer_bytes_max allows -- which can be quite high, HD-audio sets 1GB there.
It means that the system may consume a high amount of pages for PCM buffers, and they are pinned and never swapped out. This can lead to OOM easily.
For avoiding such a situation, this patch adds the upper limit per card. Each snd_pcm_lib_malloc_pages() and _free_pages() calls are tracked and it will return an error if the total amount of buffers goes over the defined upper limit. The default value is set to 32MB, which should be really large enough for usual operations.
If larger buffers are needed for any specific usage, it can be adjusted (also dynamically) via snd_pcm.max_alloc_per_card option. Setting zero there means no chceck is performed, and again, unlimited amount of buffers are allowed.
Signed-off-by: Takashi Iwai tiwai@suse.de --- include/sound/core.h | 3 +++ sound/core/init.c | 1 + sound/core/pcm_memory.c | 69 ++++++++++++++++++++++++++++++++++++------------- 3 files changed, 55 insertions(+), 18 deletions(-)
diff --git a/include/sound/core.h b/include/sound/core.h index 0e14b7a3e67b..ac8b692b69b4 100644 --- a/include/sound/core.h +++ b/include/sound/core.h @@ -120,6 +120,9 @@ struct snd_card { int sync_irq; /* assigned irq, used for PCM sync */ wait_queue_head_t remove_sleep;
+ size_t total_pcm_alloc_bytes; /* total amount of allocated buffers */ + struct mutex memory_mutex; /* protection for the above */ + #ifdef CONFIG_PM unsigned int power_state; /* power state */ wait_queue_head_t power_sleep; diff --git a/sound/core/init.c b/sound/core/init.c index faa9f03c01ca..b02a99766351 100644 --- a/sound/core/init.c +++ b/sound/core/init.c @@ -211,6 +211,7 @@ int snd_card_new(struct device *parent, int idx, const char *xid, INIT_LIST_HEAD(&card->ctl_files); spin_lock_init(&card->files_lock); INIT_LIST_HEAD(&card->files_list); + mutex_init(&card->memory_mutex); #ifdef CONFIG_PM init_waitqueue_head(&card->power_sleep); #endif diff --git a/sound/core/pcm_memory.c b/sound/core/pcm_memory.c index d4702cc1d376..a18ebf2341ca 100644 --- a/sound/core/pcm_memory.c +++ b/sound/core/pcm_memory.c @@ -27,6 +27,38 @@ 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 * 1024UL; +module_param(max_alloc_per_card, ulong, 0644); +MODULE_PARM_DESC(max_alloc_per_card, "Max total allocation bytes per card."); + +static int do_alloc_pages(struct snd_card *card, int type, struct device *dev, + size_t size, struct snd_dma_buffer *dmab) +{ + int err; + + if (max_alloc_per_card && + card->total_pcm_alloc_bytes + size > max_alloc_per_card) + return -ENOMEM; + err = snd_dma_alloc_pages(type, dev, size, dmab); + if (!err) { + mutex_lock(&card->memory_mutex); + card->total_pcm_alloc_bytes += dmab->bytes; + mutex_unlock(&card->memory_mutex); + } + return err; +} + +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); + snd_dma_free_pages(dmab); + dmab->area = NULL; +}
/* * try to allocate as the large pages as possible. @@ -37,16 +69,15 @@ static const size_t snd_minimum_buffer = 16384; static int preallocate_pcm_pages(struct snd_pcm_substream *substream, size_t size) { struct snd_dma_buffer *dmab = &substream->dma_buffer; + struct snd_card *card = substream->pcm->card; size_t orig_size = size; int err;
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, + size, dmab); + if (err != -ENOMEM) + return err; size >>= 1; } while (size >= snd_minimum_buffer); dmab->bytes = 0; /* tell error */ @@ -62,10 +93,7 @@ static int preallocate_pcm_pages(struct snd_pcm_substream *substream, size_t siz */ static void snd_pcm_lib_preallocate_dma_free(struct snd_pcm_substream *substream) { - if (substream->dma_buffer.area == NULL) - return; - snd_dma_free_pages(&substream->dma_buffer); - substream->dma_buffer.area = NULL; + do_free_pages(substream->pcm->card, &substream->dma_buffer); }
/** @@ -130,6 +158,7 @@ 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; struct snd_dma_buffer new_dmab; @@ -150,9 +179,10 @@ 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, + size, &new_dmab) < 0) { buffer->error = -ENOMEM; return; } @@ -161,7 +191,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; @@ -346,6 +376,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_pcm_runtime *runtime; struct snd_dma_buffer *dmab = NULL;
@@ -374,9 +405,10 @@ int snd_pcm_lib_malloc_pages(struct snd_pcm_substream *substream, size_t size) 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, + size, dmab) < 0) { kfree(dmab); return -ENOMEM; } @@ -397,6 +429,7 @@ 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)) @@ -406,7 +439,7 @@ int snd_pcm_lib_free_pages(struct snd_pcm_substream *substream) return 0; if (runtime->dma_buffer_p != &substream->dma_buffer) { /* 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);
Like many other drivers, HD-audio drivers also do PCM buffer preallocation to assure the buffer pages allocated at the early boot stage. This step is useful for platforms that may fail to allocate the PCM hardware buffers -- which is mostly for either large continuous pages or with the specific DMA mask (like emu10k1).
OTOH, when a buffer is allocated as SG-buffer and the DMA mask is either 32 or 64 bits, the allocation almost never fails unless it hits the real OOM situation. In such a case, we don't need the preallocation inevitably unlike the cases above.
That said, we may drop the preallocation for HD-audio that does allocate via SG-buffers, and the patch achieves it.
However, there is one caveat: the buffer allocation behavior depends on CONFIG_SND_DMA_SGBUF, and it falls back to the continuous pages when it's not set. And, currently this SG buffer allocation is enabled only on x86 platforms. So, covering those fall-outs, the patch adjusts CONFIG_SND_HDA_PREALLOC_SIZE depending on the condition, and keeps the old behavior as-is for non-x86 platforms.
On x86, the kconfig item is no longer adjustable but always set to zero for disabling the preallocation. You can still enable the preallocation via procfs interface at any time later, too.
Signed-off-by: Takashi Iwai tiwai@suse.de --- sound/hda/Kconfig | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/sound/hda/Kconfig b/sound/hda/Kconfig index b0c88fe040ee..4ca6b09056f3 100644 --- a/sound/hda/Kconfig +++ b/sound/hda/Kconfig @@ -21,14 +21,16 @@ config SND_HDA_EXT_CORE select SND_HDA_CORE
config SND_HDA_PREALLOC_SIZE - int "Pre-allocated buffer size for HD-audio driver" + int "Pre-allocated buffer size for HD-audio driver" if !SND_DMA_SGBUF range 0 32768 - default 64 + default 0 if SND_DMA_SGBUF + default 64 if !SND_DMA_SGBUF help Specifies the default pre-allocated buffer-size in kB for the HD-audio driver. A larger buffer (e.g. 2048) is preferred for systems using PulseAudio. The default 64 is chosen just for compatibility reasons. + On x86 systems, the default is zero as we need no preallocation.
Note that the pre-allocation size can be changed dynamically via a proc file (/proc/asound/card*/pcm*/sub*/prealloc), too.
On 2020/1/20 下午8:44, Takashi Iwai wrote:
Currently, the available buffer allocation size for a PCM stream depends on the preallocated size; when a buffer has been preallocated, the max buffer size is set to that size, so that application won't re-allocate too much memory. OTOH, when no preallocation is done, each substream may allocate arbitrary size of buffers as long as snd_pcm_hardware.buffer_bytes_max allows -- which can be quite high, HD-audio sets 1GB there.
It means that the system may consume a high amount of pages for PCM buffers, and they are pinned and never swapped out. This can lead to OOM easily.
For avoiding such a situation, this patch adds the upper limit per card. Each snd_pcm_lib_malloc_pages() and _free_pages() calls are tracked and it will return an error if the total amount of buffers goes over the defined upper limit. The default value is set to 32MB,
Is this typo here? In the patch, "max_alloc_per_card = 32UL * 1024UL * 1024UL * 1024UL", should be 32GB?
~Keyon
which should be really large enough for usual operations.
If larger buffers are needed for any specific usage, it can be adjusted (also dynamically) via snd_pcm.max_alloc_per_card option. Setting zero there means no chceck is performed, and again, unlimited amount of buffers are allowed.
Signed-off-by: Takashi Iwai tiwai@suse.de
include/sound/core.h | 3 +++ sound/core/init.c | 1 + sound/core/pcm_memory.c | 69 ++++++++++++++++++++++++++++++++++++------------- 3 files changed, 55 insertions(+), 18 deletions(-)
diff --git a/include/sound/core.h b/include/sound/core.h index 0e14b7a3e67b..ac8b692b69b4 100644 --- a/include/sound/core.h +++ b/include/sound/core.h @@ -120,6 +120,9 @@ struct snd_card { int sync_irq; /* assigned irq, used for PCM sync */ wait_queue_head_t remove_sleep;
- size_t total_pcm_alloc_bytes; /* total amount of allocated buffers */
- struct mutex memory_mutex; /* protection for the above */
- #ifdef CONFIG_PM unsigned int power_state; /* power state */ wait_queue_head_t power_sleep;
diff --git a/sound/core/init.c b/sound/core/init.c index faa9f03c01ca..b02a99766351 100644 --- a/sound/core/init.c +++ b/sound/core/init.c @@ -211,6 +211,7 @@ int snd_card_new(struct device *parent, int idx, const char *xid, INIT_LIST_HEAD(&card->ctl_files); spin_lock_init(&card->files_lock); INIT_LIST_HEAD(&card->files_list);
- mutex_init(&card->memory_mutex); #ifdef CONFIG_PM init_waitqueue_head(&card->power_sleep); #endif
diff --git a/sound/core/pcm_memory.c b/sound/core/pcm_memory.c index d4702cc1d376..a18ebf2341ca 100644 --- a/sound/core/pcm_memory.c +++ b/sound/core/pcm_memory.c @@ -27,6 +27,38 @@ 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 * 1024UL; +module_param(max_alloc_per_card, ulong, 0644); +MODULE_PARM_DESC(max_alloc_per_card, "Max total allocation bytes per card.");
+static int do_alloc_pages(struct snd_card *card, int type, struct device *dev,
size_t size, struct snd_dma_buffer *dmab)
+{
- int err;
- if (max_alloc_per_card &&
card->total_pcm_alloc_bytes + size > max_alloc_per_card)
return -ENOMEM;
- err = snd_dma_alloc_pages(type, dev, size, dmab);
- if (!err) {
mutex_lock(&card->memory_mutex);
card->total_pcm_alloc_bytes += dmab->bytes;
mutex_unlock(&card->memory_mutex);
- }
- return err;
+}
+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);
- snd_dma_free_pages(dmab);
- dmab->area = NULL;
+}
/*
- try to allocate as the large pages as possible.
@@ -37,16 +69,15 @@ static const size_t snd_minimum_buffer = 16384; static int preallocate_pcm_pages(struct snd_pcm_substream *substream, size_t size) { struct snd_dma_buffer *dmab = &substream->dma_buffer;
struct snd_card *card = substream->pcm->card; size_t orig_size = size; int err;
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,
size, dmab);
if (err != -ENOMEM)
size >>= 1; } while (size >= snd_minimum_buffer); dmab->bytes = 0; /* tell error */return err;
@@ -62,10 +93,7 @@ static int preallocate_pcm_pages(struct snd_pcm_substream *substream, size_t siz */ static void snd_pcm_lib_preallocate_dma_free(struct snd_pcm_substream *substream) {
- if (substream->dma_buffer.area == NULL)
return;
- snd_dma_free_pages(&substream->dma_buffer);
- substream->dma_buffer.area = NULL;
do_free_pages(substream->pcm->card, &substream->dma_buffer); }
/**
@@ -130,6 +158,7 @@ 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; struct snd_dma_buffer new_dmab;
@@ -150,9 +179,10 @@ 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,
size, &new_dmab) < 0) { buffer->error = -ENOMEM; return; }
@@ -161,7 +191,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);
substream->dma_buffer = new_dmab; } else { buffer->error = -EINVAL;do_free_pages(card, &substream->dma_buffer);
@@ -346,6 +376,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_pcm_runtime *runtime; struct snd_dma_buffer *dmab = NULL;
@@ -374,9 +405,10 @@ int snd_pcm_lib_malloc_pages(struct snd_pcm_substream *substream, size_t size) 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,
}size, dmab) < 0) { kfree(dmab); return -ENOMEM;
@@ -397,6 +429,7 @@ 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))
@@ -406,7 +439,7 @@ int snd_pcm_lib_free_pages(struct snd_pcm_substream *substream) return 0; if (runtime->dma_buffer_p != &substream->dma_buffer) { /* it's a newly allocated buffer. release it now. */
snd_dma_free_pages(runtime->dma_buffer_p);
kfree(runtime->dma_buffer_p); } snd_pcm_set_runtime_buffer(substream, NULL);do_free_pages(card, runtime->dma_buffer_p);
On Tue, 21 Jan 2020 03:13:20 +0100, Keyon Jie wrote:
On 2020/1/20 下午8:44, Takashi Iwai wrote:
Currently, the available buffer allocation size for a PCM stream depends on the preallocated size; when a buffer has been preallocated, the max buffer size is set to that size, so that application won't re-allocate too much memory. OTOH, when no preallocation is done, each substream may allocate arbitrary size of buffers as long as snd_pcm_hardware.buffer_bytes_max allows -- which can be quite high, HD-audio sets 1GB there.
It means that the system may consume a high amount of pages for PCM buffers, and they are pinned and never swapped out. This can lead to OOM easily.
For avoiding such a situation, this patch adds the upper limit per card. Each snd_pcm_lib_malloc_pages() and _free_pages() calls are tracked and it will return an error if the total amount of buffers goes over the defined upper limit. The default value is set to 32MB,
Is this typo here? In the patch, "max_alloc_per_card = 32UL * 1024UL * 1024UL * 1024UL", should be 32GB?
Oh yes, it's an obvious typo. Fixed now.
Thanks!
Takashi
participants (2)
-
Keyon Jie
-
Takashi Iwai