diff options
Diffstat (limited to 'sound/core/memalloc.c')
-rw-r--r-- | sound/core/memalloc.c | 299 |
1 files changed, 299 insertions, 0 deletions
diff --git a/sound/core/memalloc.c b/sound/core/memalloc.c new file mode 100644 index 000000000..082509eb8 --- /dev/null +++ b/sound/core/memalloc.c @@ -0,0 +1,299 @@ +/* + * Copyright (c) by Jaroslav Kysela <perex@perex.cz> + * Takashi Iwai <tiwai@suse.de> + * + * Generic memory allocators + * + * + * 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 <linux/slab.h> +#include <linux/mm.h> +#include <linux/dma-mapping.h> +#include <linux/genalloc.h> +#include <sound/memalloc.h> + +/* + * + * Generic memory allocators + * + */ + +/** + * snd_malloc_pages - allocate pages with the given size + * @size: the size to allocate in bytes + * @gfp_flags: the allocation conditions, GFP_XXX + * + * Allocates the physically contiguous pages with the given size. + * + * Return: The pointer of the buffer, or %NULL if no enough memory. + */ +void *snd_malloc_pages(size_t size, gfp_t gfp_flags) +{ + int pg; + + if (WARN_ON(!size)) + return NULL; + if (WARN_ON(!gfp_flags)) + return NULL; + gfp_flags |= __GFP_COMP; /* compound page lets parts be mapped */ + pg = get_order(size); + return (void *) __get_free_pages(gfp_flags, pg); +} + +/** + * snd_free_pages - release the pages + * @ptr: the buffer pointer to release + * @size: the allocated buffer size + * + * Releases the buffer allocated via snd_malloc_pages(). + */ +void snd_free_pages(void *ptr, size_t size) +{ + int pg; + + if (ptr == NULL) + return; + pg = get_order(size); + free_pages((unsigned long) ptr, pg); +} + +/* + * + * Bus-specific memory allocators + * + */ + +#ifdef CONFIG_HAS_DMA +/* allocate the coherent DMA pages */ +static void *snd_malloc_dev_pages(struct device *dev, size_t size, dma_addr_t *dma) +{ + int pg; + gfp_t gfp_flags; + + if (WARN_ON(!dma)) + return NULL; + pg = get_order(size); + gfp_flags = GFP_KERNEL + | __GFP_COMP /* compound page lets parts be mapped */ + | __GFP_NORETRY /* don't trigger OOM-killer */ + | __GFP_NOWARN; /* no stack trace print - this call is non-critical */ + return dma_alloc_coherent(dev, PAGE_SIZE << pg, dma, gfp_flags); +} + +/* free the coherent DMA pages */ +static void snd_free_dev_pages(struct device *dev, size_t size, void *ptr, + dma_addr_t dma) +{ + int pg; + + if (ptr == NULL) + return; + pg = get_order(size); + dma_free_coherent(dev, PAGE_SIZE << pg, ptr, dma); +} + +#ifdef CONFIG_GENERIC_ALLOCATOR +/** + * snd_malloc_dev_iram - allocate memory from on-chip internal ram + * @dmab: buffer allocation record to store the allocated data + * @size: number of bytes to allocate from the iram + * + * This function requires iram phandle provided via of_node + */ +static void snd_malloc_dev_iram(struct snd_dma_buffer *dmab, size_t size) +{ + struct device *dev = dmab->dev.dev; + struct gen_pool *pool = NULL; + + dmab->area = NULL; + dmab->addr = 0; + + if (dev->of_node) + pool = of_get_named_gen_pool(dev->of_node, "iram", 0); + + if (!pool) + return; + + /* Assign the pool into private_data field */ + dmab->private_data = pool; + + dmab->area = gen_pool_dma_alloc(pool, size, &dmab->addr); +} + +/** + * snd_free_dev_iram - free allocated specific memory from on-chip internal ram + * @dmab: buffer allocation record to store the allocated data + */ +static void snd_free_dev_iram(struct snd_dma_buffer *dmab) +{ + struct gen_pool *pool = dmab->private_data; + + if (pool && dmab->area) + gen_pool_free(pool, (unsigned long)dmab->area, dmab->bytes); +} +#endif /* CONFIG_GENERIC_ALLOCATOR */ +#endif /* CONFIG_HAS_DMA */ + +/* + * + * ALSA generic memory management + * + */ + + +/** + * snd_dma_alloc_pages - allocate the buffer area according to the given type + * @type: the DMA buffer type + * @device: the device pointer + * @size: the buffer size to allocate + * @dmab: buffer allocation record to store the allocated data + * + * Calls the memory-allocator function for the corresponding + * buffer type. + * + * Return: Zero if the buffer with the given size is allocated successfully, + * otherwise a negative value on error. + */ +int snd_dma_alloc_pages(int type, struct device *device, size_t size, + struct snd_dma_buffer *dmab) +{ + if (WARN_ON(!size)) + return -ENXIO; + if (WARN_ON(!dmab)) + return -ENXIO; + + dmab->dev.type = type; + dmab->dev.dev = device; + dmab->bytes = 0; + switch (type) { + case SNDRV_DMA_TYPE_CONTINUOUS: + dmab->area = snd_malloc_pages(size, + (__force gfp_t)(unsigned long)device); + dmab->addr = 0; + break; +#ifdef CONFIG_HAS_DMA +#ifdef CONFIG_GENERIC_ALLOCATOR + case SNDRV_DMA_TYPE_DEV_IRAM: + snd_malloc_dev_iram(dmab, size); + if (dmab->area) + break; + /* Internal memory might have limited size and no enough space, + * so if we fail to malloc, try to fetch memory traditionally. + */ + dmab->dev.type = SNDRV_DMA_TYPE_DEV; +#endif /* CONFIG_GENERIC_ALLOCATOR */ + case SNDRV_DMA_TYPE_DEV: + dmab->area = snd_malloc_dev_pages(device, size, &dmab->addr); + break; +#endif +#ifdef CONFIG_SND_DMA_SGBUF + case SNDRV_DMA_TYPE_DEV_SG: + snd_malloc_sgbuf_pages(device, size, dmab, NULL); + break; +#endif + default: + pr_err("snd-malloc: invalid device type %d\n", type); + dmab->area = NULL; + dmab->addr = 0; + return -ENXIO; + } + if (! dmab->area) + return -ENOMEM; + dmab->bytes = size; + return 0; +} + +/** + * snd_dma_alloc_pages_fallback - allocate the buffer area according to the given type with fallback + * @type: the DMA buffer type + * @device: the device pointer + * @size: the buffer size to allocate + * @dmab: buffer allocation record to store the allocated data + * + * Calls the memory-allocator function for the corresponding + * buffer type. When no space is left, this function reduces the size and + * tries to allocate again. The size actually allocated is stored in + * res_size argument. + * + * Return: Zero if the buffer with the given size is allocated successfully, + * otherwise a negative value on error. + */ +int snd_dma_alloc_pages_fallback(int type, struct device *device, size_t size, + struct snd_dma_buffer *dmab) +{ + int err; + + while ((err = snd_dma_alloc_pages(type, device, size, dmab)) < 0) { + size_t aligned_size; + if (err != -ENOMEM) + return err; + if (size <= PAGE_SIZE) + return -ENOMEM; + aligned_size = PAGE_SIZE << get_order(size); + if (size != aligned_size) + size = aligned_size; + else + size >>= 1; + } + if (! dmab->area) + return -ENOMEM; + return 0; +} + + +/** + * snd_dma_free_pages - release the allocated buffer + * @dmab: the buffer allocation record to release + * + * Releases the allocated buffer via snd_dma_alloc_pages(). + */ +void snd_dma_free_pages(struct snd_dma_buffer *dmab) +{ + switch (dmab->dev.type) { + case SNDRV_DMA_TYPE_CONTINUOUS: + snd_free_pages(dmab->area, dmab->bytes); + break; +#ifdef CONFIG_HAS_DMA +#ifdef CONFIG_GENERIC_ALLOCATOR + case SNDRV_DMA_TYPE_DEV_IRAM: + snd_free_dev_iram(dmab); + break; +#endif /* CONFIG_GENERIC_ALLOCATOR */ + case SNDRV_DMA_TYPE_DEV: + snd_free_dev_pages(dmab->dev.dev, dmab->bytes, dmab->area, dmab->addr); + break; +#endif +#ifdef CONFIG_SND_DMA_SGBUF + case SNDRV_DMA_TYPE_DEV_SG: + snd_free_sgbuf_pages(dmab); + break; +#endif + default: + pr_err("snd-malloc: invalid device type %d\n", dmab->dev.type); + } +} + +/* + * exports + */ +EXPORT_SYMBOL(snd_dma_alloc_pages); +EXPORT_SYMBOL(snd_dma_alloc_pages_fallback); +EXPORT_SYMBOL(snd_dma_free_pages); + +EXPORT_SYMBOL(snd_malloc_pages); +EXPORT_SYMBOL(snd_free_pages); |