[PATCH RFC 0/5] ALSA: Support for non-coherent and non-contiguous page allocation
Hi,
this is an experimental patch set to add the support for PCM buffers with non-coherent and non-contiguous pages, typically useful for non-x86 architectures. The first patch improves the SG-buffer handling, then add a new PCM info flag that disables the control and status mmap, and implements two new buffer types, SNDRV_DMA_TYPE_NONCONTIG and SNDRV_DMA_TYPE_NONCOHERENT. The former is the SG-buffer and the latter is the continuous page allocation, corresponding to SNDRV_DMA_TYPE_DEV_SG and SNDRV_DMA_TYPE_DEV on x86.
Unlike other page types, those are directional (that need the DMA direction at allocation time) and require the explicit sync of buffers around the data transfer (flushing, invalidating). The sync is implemented inside ALSA PCM core and automatically applied at updating the applptr and hwsync via SYNC_PTR ioctl, which should be issued during mmap operation, so it should work transparently as long as applications are running with alsa-lib. For tinyALSA, we might need to revisit the implementation.
This is currently an RFC -- more exactly, CALL FOR TESTERS, as I have no Arm machine with the sound device for now. The needed change for an existing driver is simple:
* Replace the buffer type from SNDRV_DMA_TYPE_DEV to SNDRV_DMA_TYPE_NONCOHERENT (or from SNDRV_DMA_TYPE_DEV_SG to SNDRV_DMA_TYPE_NONCONTIG), which is set up typically in snd_pcm_set_managed_buffer*() call.
* Add SNDRV_PCM_INFO_EXPLICIT_SYNC flag to the PCM runtime hardware.info field
It'd be greatly appreciated if anyone can test and try the changes, and help debugging. Again, the code is faily untested.
The latest code is found in topic/memalloc-noncontig branch of my sound git tree: git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound.git
The patch set is based on for-next branch, i.e. changes for 5.15 are included.
thanks,
Takashi
===
Takashi Iwai (5): ALSA: memalloc: Count continuous pages in vmalloc buffer handler ALSA: pcm: Add SNDRV_PCM_INFO_EXPLICIT_SYNC flag ALSA: memalloc: Assign ops field to snd_dma_buffer ALSA: memalloc: Support for non-contiguous page allocation ALSA: memalloc: Support for non-coherent page allocation
include/sound/memalloc.h | 48 +++++++- include/uapi/sound/asound.h | 1 + sound/core/memalloc.c | 223 ++++++++++++++++++++++++++++-------- sound/core/memalloc_local.h | 1 + sound/core/pcm_lib.c | 9 ++ sound/core/pcm_memory.c | 13 ++- sound/core/pcm_native.c | 22 ++++ 7 files changed, 263 insertions(+), 54 deletions(-)
This is an enhancement for the SG-style page handling in vmalloc buffer handler to calculate the continuous pages. When snd_sgbuf_get_chunk_size() is called for a vmalloc buffer, currently we return only the size that fits into a single page. However, this API call is rather supposed for obtaining the continuous pages and most of vmalloc or noncontig buffers do have lots of continuous pages indeed. So, in this patch, the callback now calculates the possibly continuous pages up to the given size limit.
Note that the end address in the function is calculated from the last byte, hence it's one byte shorter. This is because ofs + size can be above the actual buffer size boundary.
Until now, this feature isn't really used, but it'll become useful in a later patch that adds the non-contiguous buffer type that shares the same callback function as vmalloc.
Signed-off-by: Takashi Iwai tiwai@suse.de --- sound/core/memalloc.c | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-)
diff --git a/sound/core/memalloc.c b/sound/core/memalloc.c index 1cea8cb9668f..c7c943c661e6 100644 --- a/sound/core/memalloc.c +++ b/sound/core/memalloc.c @@ -290,11 +290,13 @@ static int snd_dma_vmalloc_mmap(struct snd_dma_buffer *dmab, return remap_vmalloc_range(area, dmab->area, 0); }
+#define get_vmalloc_page_addr(dmab, offset) \ + page_to_phys(vmalloc_to_page((dmab)->area + (offset))) + static dma_addr_t snd_dma_vmalloc_get_addr(struct snd_dma_buffer *dmab, size_t offset) { - return page_to_phys(vmalloc_to_page(dmab->area + offset)) + - offset % PAGE_SIZE; + return get_vmalloc_page_addr(dmab, offset) + offset % PAGE_SIZE; }
static struct page *snd_dma_vmalloc_get_page(struct snd_dma_buffer *dmab, @@ -307,11 +309,23 @@ static unsigned int snd_dma_vmalloc_get_chunk_size(struct snd_dma_buffer *dmab, unsigned int ofs, unsigned int size) { - ofs %= PAGE_SIZE; - size += ofs; - if (size > PAGE_SIZE) - size = PAGE_SIZE; - return size - ofs; + unsigned int start, end; + unsigned long addr; + + start = ALIGN_DOWN(ofs, PAGE_SIZE); + end = ofs + size - 1; /* the last byte address */ + /* check page continuity */ + addr = get_vmalloc_page_addr(dmab, start); + for (;;) { + start += PAGE_SIZE; + if (start > end) + break; + addr += PAGE_SIZE; + if (get_vmalloc_page_addr(dmab, start) != addr) + return start - ofs; + } + /* ok, all on continuous pages */ + return size; }
static const struct snd_malloc_ops snd_dma_vmalloc_ops = {
ALSA PCM core has an optimized way to communicate with user-space for its control and status data via mmap on the supported architectures like x86. Depending on the situation, however, we'd rather want to enforce user-space notifying the applptr or hwptr change explicitly via ioctl. For example, the upcoming non-contig and non-coherent buffer handling would need an explicit sync, and this needs to catch the applptr and hwptr changes.
This patch adds the new PCM hardware info flag, SNDRV_PCM_INFO_EXPLICIT_SYNC. When this flag is set, PCM core disables both the control and the status mmap, which enforces user-space to update via SYNC_PTR ioctl. In that way, drivers can catch the applptr and hwptr update and apply the sync operation if needed.
Signed-off-by: Takashi Iwai tiwai@suse.de --- include/uapi/sound/asound.h | 1 + sound/core/pcm_native.c | 9 +++++++++ 2 files changed, 10 insertions(+)
diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h index d17c061950df..1d84ec9db93b 100644 --- a/include/uapi/sound/asound.h +++ b/include/uapi/sound/asound.h @@ -299,6 +299,7 @@ typedef int __bitwise snd_pcm_subformat_t; #define SNDRV_PCM_INFO_HAS_LINK_ABSOLUTE_ATIME 0x02000000 /* report absolute hardware link audio time, not reset on startup */ #define SNDRV_PCM_INFO_HAS_LINK_ESTIMATED_ATIME 0x04000000 /* report estimated link audio time */ #define SNDRV_PCM_INFO_HAS_LINK_SYNCHRONIZED_ATIME 0x08000000 /* report synchronized audio/system time */ +#define SNDRV_PCM_INFO_EXPLICIT_SYNC 0x10000000 /* needs explicit sync of pointers and data */
#define SNDRV_PCM_INFO_DRAIN_TRIGGER 0x40000000 /* internal kernel flag - trigger in drain */ #define SNDRV_PCM_INFO_FIFO_IN_FRAMES 0x80000000 /* internal kernel flag - FIFO size is in frames */ diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index dc9fa312fadd..d233cb3b41d8 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -3621,6 +3621,12 @@ static int snd_pcm_mmap_control(struct snd_pcm_substream *substream, struct file
static bool pcm_status_mmap_allowed(struct snd_pcm_file *pcm_file) { + /* If drivers require the explicit sync (typically for non-coherent + * pages), we have to disable the mmap of status and control data + * to enforce the control via SYNC_PTR ioctl. + */ + if (pcm_file->substream->runtime->hw.info & SNDRV_PCM_INFO_EXPLICIT_SYNC) + return false; /* See pcm_control_mmap_allowed() below. * Since older alsa-lib requires both status and control mmaps to be * coupled, we have to disable the status mmap for old alsa-lib, too. @@ -3635,6 +3641,9 @@ static bool pcm_control_mmap_allowed(struct snd_pcm_file *pcm_file) { if (pcm_file->no_compat_mmap) return false; + /* see above */ + if (pcm_file->substream->runtime->hw.info & SNDRV_PCM_INFO_EXPLICIT_SYNC) + return false; /* Disallow the control mmap when SYNC_APPLPTR flag is set; * it enforces the user-space to fall back to snd_pcm_sync_ptr(), * thus it effectively assures the manual update of appl_ptr.
This is for performance optimization. Instead of deducing the ops from the type at each time, store the ops table directly in snd_dma_buffer struct and refer to it. The upcoming sync ops will be in a hot path, hence some optimization is required.
Signed-off-by: Takashi Iwai tiwai@suse.de --- include/sound/memalloc.h | 2 ++ sound/core/memalloc.c | 53 ++++++++++++++++------------------------ 2 files changed, 23 insertions(+), 32 deletions(-)
diff --git a/include/sound/memalloc.h b/include/sound/memalloc.h index b197e3f431c1..f5f0d32f6d61 100644 --- a/include/sound/memalloc.h +++ b/include/sound/memalloc.h @@ -13,6 +13,7 @@
struct device; struct vm_area_struct; +struct snd_malloc_ops;
/* * buffer device info @@ -55,6 +56,7 @@ struct snd_dma_buffer { dma_addr_t addr; /* physical address */ size_t bytes; /* buffer size in bytes */ void *private_data; /* private for allocator; don't touch */ + const struct snd_malloc_ops *ops; /* assigned ops */ };
/* diff --git a/sound/core/memalloc.c b/sound/core/memalloc.c index c7c943c661e6..3f42376dfb0b 100644 --- a/sound/core/memalloc.c +++ b/sound/core/memalloc.c @@ -29,15 +29,6 @@ static inline gfp_t snd_mem_get_gfp_flags(const struct snd_dma_buffer *dmab, return (__force gfp_t)(unsigned long)dmab->dev.dev; }
-static void *__snd_dma_alloc_pages(struct snd_dma_buffer *dmab, size_t size) -{ - const struct snd_malloc_ops *ops = snd_dma_get_ops(dmab); - - if (WARN_ON_ONCE(!ops || !ops->alloc)) - return NULL; - return ops->alloc(dmab, size); -} - /** * snd_dma_alloc_pages - allocate the buffer area according to the given type * @type: the DMA buffer type @@ -54,6 +45,7 @@ static void *__snd_dma_alloc_pages(struct snd_dma_buffer *dmab, size_t size) int snd_dma_alloc_pages(int type, struct device *device, size_t size, struct snd_dma_buffer *dmab) { + const struct snd_malloc_ops *ops; if (WARN_ON(!size)) return -ENXIO; if (WARN_ON(!dmab)) @@ -65,9 +57,15 @@ int snd_dma_alloc_pages(int type, struct device *device, size_t size, dmab->bytes = 0; dmab->addr = 0; dmab->private_data = NULL; - dmab->area = __snd_dma_alloc_pages(dmab, size); - if (!dmab->area) + ops = snd_dma_get_ops(dmab); + if (WARN_ON(!ops || !ops->alloc)) + return -ENXIO; + dmab->ops = ops; + dmab->area = ops->alloc(dmab, size); + if (!dmab->area) { + dmab->ops = NULL; return -ENOMEM; + } dmab->bytes = size; return 0; } @@ -115,10 +113,8 @@ EXPORT_SYMBOL(snd_dma_alloc_pages_fallback); */ void snd_dma_free_pages(struct snd_dma_buffer *dmab) { - const struct snd_malloc_ops *ops = snd_dma_get_ops(dmab); - - if (ops && ops->free) - ops->free(dmab); + if (dmab->ops && dmab->ops->free) + dmab->ops->free(dmab); } EXPORT_SYMBOL(snd_dma_free_pages);
@@ -176,10 +172,8 @@ EXPORT_SYMBOL_GPL(snd_devm_alloc_pages); int snd_dma_buffer_mmap(struct snd_dma_buffer *dmab, struct vm_area_struct *area) { - const struct snd_malloc_ops *ops = snd_dma_get_ops(dmab); - - if (ops && ops->mmap) - return ops->mmap(dmab, area); + if (dmab && dmab->ops && dmab->ops->mmap) + return dmab->ops->mmap(dmab, area); else return -ENOENT; } @@ -192,10 +186,8 @@ EXPORT_SYMBOL(snd_dma_buffer_mmap); */ dma_addr_t snd_sgbuf_get_addr(struct snd_dma_buffer *dmab, size_t offset) { - const struct snd_malloc_ops *ops = snd_dma_get_ops(dmab); - - if (ops && ops->get_addr) - return ops->get_addr(dmab, offset); + if (dmab->ops && dmab->ops->get_addr) + return dmab->ops->get_addr(dmab, offset); else return dmab->addr + offset; } @@ -208,10 +200,8 @@ EXPORT_SYMBOL(snd_sgbuf_get_addr); */ struct page *snd_sgbuf_get_page(struct snd_dma_buffer *dmab, size_t offset) { - const struct snd_malloc_ops *ops = snd_dma_get_ops(dmab); - - if (ops && ops->get_page) - return ops->get_page(dmab, offset); + if (dmab->ops && dmab->ops->get_page) + return dmab->ops->get_page(dmab, offset); else return virt_to_page(dmab->area + offset); } @@ -227,10 +217,8 @@ EXPORT_SYMBOL(snd_sgbuf_get_page); unsigned int snd_sgbuf_get_chunk_size(struct snd_dma_buffer *dmab, unsigned int ofs, unsigned int size) { - const struct snd_malloc_ops *ops = snd_dma_get_ops(dmab); - - if (ops && ops->get_chunk_size) - return ops->get_chunk_size(dmab, ofs, size); + if (dmab->ops && dmab->ops->get_chunk_size) + return dmab->ops->get_chunk_size(dmab, ofs, size); else return size; } @@ -362,7 +350,8 @@ static void *snd_dma_iram_alloc(struct snd_dma_buffer *dmab, size_t size) * so if we fail to malloc, try to fetch memory traditionally. */ dmab->dev.type = SNDRV_DMA_TYPE_DEV; - return __snd_dma_alloc_pages(dmab, size); + dmab->ops = snd_dma_get_ops(dmab); + return dmab->ops->alloc(dmab, size); }
static void snd_dma_iram_free(struct snd_dma_buffer *dmab)
This patch adds the support for allocation of non-contiguous DMA pages in the common memalloc helper. It's another SG-buffer type, but unlike the existing one, this is directional and requires the explicit sync / invalidation of dirty pages on non-coherent architectures.
For this enhancement, the following points are changed: - snd_dma_device stores the DMA direction. - A new variant, snd_dma_alloc_dir_pages() and *_all() are introduced; snd_dma_alloc_pages() and *_all() just wrap with DMA_BIDIRECTIONAL. - A new helper snd_dma_buffer_sync() is introduced; this gets called in the appropriate places. - A new allocation type, SNDRV_DMA_TYPE_NONCONTIG, is introduced.
The driver needs to call the allocation with the new type, and it likely needs to add SNDRV_PCM_INFO_EXPLICIT_SYNC flag to the PCM hardware.info for disabling the mmap of control and status data. Otherwise it'd miss explicit sync in the mmap mode.
The explicit sync is performed in the points before and after read/write transfer as well as and applptr/hwptr syncptr ioctl. In the case of mmap mode, user-space is supposed to call the syncptr ioctl with the hwptr flag to update and fetch the status at first. This corresponds to CPU-sync. Then user-space advances the applptr via syncptr ioctl again with applptr flag, and this corresponds to the device sync with flushing.
Other than the DMA direction and the explicit sync, the usage of this buffer type is almost equivalent with the existing SNDRV_DMA_TYPE_DEV_SG; you can get the page and the address via snd_sgbuf_get_page() and snd_sgbuf_get_addr(), also calculate the continuous pages via snd_sgbuf_get_chunk_size().
For those SG-handling, the non-contig type shares the same ops with the vmalloc handler. As we do always vmap the SG pages at first, the actual address can be deduced from the vmapped address easily without iterating the SG-list.
Signed-off-by: Takashi Iwai tiwai@suse.de --- include/sound/memalloc.h | 45 +++++++++++++++-- sound/core/memalloc.c | 99 ++++++++++++++++++++++++++++++++++--- sound/core/memalloc_local.h | 1 + sound/core/pcm_lib.c | 9 ++++ sound/core/pcm_memory.c | 13 +++-- sound/core/pcm_native.c | 13 +++++ 6 files changed, 165 insertions(+), 15 deletions(-)
diff --git a/include/sound/memalloc.h b/include/sound/memalloc.h index f5f0d32f6d61..ed54f9dfb2b3 100644 --- a/include/sound/memalloc.h +++ b/include/sound/memalloc.h @@ -9,10 +9,12 @@ #ifndef __SOUND_MEMALLOC_H #define __SOUND_MEMALLOC_H
+#include <linux/dma-direction.h> #include <asm/page.h>
struct device; struct vm_area_struct; +struct sg_table; struct snd_malloc_ops;
/* @@ -20,6 +22,7 @@ struct snd_malloc_ops; */ struct snd_dma_device { int type; /* SNDRV_DMA_TYPE_XXX */ + enum dma_data_direction dir; /* DMA direction */ struct device *dev; /* generic device */ };
@@ -46,6 +49,7 @@ struct snd_dma_device { #define SNDRV_DMA_TYPE_DEV_IRAM SNDRV_DMA_TYPE_DEV #endif #define SNDRV_DMA_TYPE_VMALLOC 7 /* vmalloc'ed buffer */ +#define SNDRV_DMA_TYPE_NONCONTIG 8 /* non-coherent SG buffer */
/* * info for buffer allocation @@ -68,22 +72,55 @@ static inline unsigned int snd_sgbuf_aligned_pages(size_t size) }
/* allocate/release a buffer */ -int snd_dma_alloc_pages(int type, struct device *dev, size_t size, - struct snd_dma_buffer *dmab); +int snd_dma_alloc_dir_pages(int type, struct device *dev, + enum dma_data_direction dir, size_t size, + struct snd_dma_buffer *dmab); + +static inline int snd_dma_alloc_pages(int type, struct device *dev, + size_t size, struct snd_dma_buffer *dmab) +{ + return snd_dma_alloc_dir_pages(type, dev, DMA_BIDIRECTIONAL, size, dmab); +} + int snd_dma_alloc_pages_fallback(int type, struct device *dev, size_t size, struct snd_dma_buffer *dmab); void snd_dma_free_pages(struct snd_dma_buffer *dmab); int snd_dma_buffer_mmap(struct snd_dma_buffer *dmab, struct vm_area_struct *area);
+enum snd_dma_sync_mode { SNDRV_DMA_SYNC_CPU, SNDRV_DMA_SYNC_DEVICE }; +#ifdef CONFIG_HAS_DMA +void snd_dma_buffer_sync(struct snd_dma_buffer *dmab, + enum snd_dma_sync_mode mode); +#else +static inline void snd_dma_buffer_sync(struct snd_dma_buffer *dmab, + enum snd_dma_sync_mode mode) {} +#endif + +void snd_dma_buffer_sync(struct snd_dma_buffer *dmab, + enum snd_dma_sync_mode mode); + dma_addr_t snd_sgbuf_get_addr(struct snd_dma_buffer *dmab, size_t offset); struct page *snd_sgbuf_get_page(struct snd_dma_buffer *dmab, size_t offset); unsigned int snd_sgbuf_get_chunk_size(struct snd_dma_buffer *dmab, unsigned int ofs, unsigned int size);
/* device-managed memory allocator */ -struct snd_dma_buffer *snd_devm_alloc_pages(struct device *dev, int type, - size_t size); +struct snd_dma_buffer *snd_devm_alloc_dir_pages(struct device *dev, int type, + enum dma_data_direction dir, + size_t size); + +static inline struct snd_dma_buffer * +snd_devm_alloc_pages(struct device *dev, int type, size_t size) +{ + return snd_devm_alloc_dir_pages(dev, type, DMA_BIDIRECTIONAL, size); +} + +static inline struct sg_table * +snd_dma_noncontig_sg_table(struct snd_dma_buffer *dmab) +{ + return dmab->private_data; +}
#endif /* __SOUND_MEMALLOC_H */
diff --git a/sound/core/memalloc.c b/sound/core/memalloc.c index 3f42376dfb0b..5ffd356e0327 100644 --- a/sound/core/memalloc.c +++ b/sound/core/memalloc.c @@ -10,6 +10,7 @@ #include <linux/mm.h> #include <linux/dma-mapping.h> #include <linux/genalloc.h> +#include <linux/highmem.h> #include <linux/vmalloc.h> #ifdef CONFIG_X86 #include <asm/set_memory.h> @@ -30,9 +31,11 @@ static inline gfp_t snd_mem_get_gfp_flags(const struct snd_dma_buffer *dmab, }
/** - * snd_dma_alloc_pages - allocate the buffer area according to the given type + * snd_dma_alloc_dir_pages - allocate the buffer area according to the given + * type and direction * @type: the DMA buffer type * @device: the device pointer + * @dir: DMA direction * @size: the buffer size to allocate * @dmab: buffer allocation record to store the allocated data * @@ -42,8 +45,9 @@ static inline gfp_t snd_mem_get_gfp_flags(const struct snd_dma_buffer *dmab, * 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) +int snd_dma_alloc_dir_pages(int type, struct device *device, + enum dma_data_direction dir, size_t size, + struct snd_dma_buffer *dmab) { const struct snd_malloc_ops *ops; if (WARN_ON(!size)) @@ -54,6 +58,7 @@ int snd_dma_alloc_pages(int type, struct device *device, size_t size, size = PAGE_ALIGN(size); dmab->dev.type = type; dmab->dev.dev = device; + dmab->dev.dir = dir; dmab->bytes = 0; dmab->addr = 0; dmab->private_data = NULL; @@ -69,7 +74,7 @@ int snd_dma_alloc_pages(int type, struct device *device, size_t size, dmab->bytes = size; return 0; } -EXPORT_SYMBOL(snd_dma_alloc_pages); +EXPORT_SYMBOL(snd_dma_alloc_dir_pages);
/** * snd_dma_alloc_pages_fallback - allocate the buffer area according to the given type with fallback @@ -125,9 +130,10 @@ static void __snd_release_pages(struct device *dev, void *res) }
/** - * snd_devm_alloc_pages - allocate the buffer and manage with devres + * snd_devm_alloc_dir_pages - allocate the buffer and manage with devres * @dev: the device pointer * @type: the DMA buffer type + * @dir: DMA direction * @size: the buffer size to allocate * * Allocate buffer pages depending on the given type and manage using devres. @@ -140,7 +146,8 @@ static void __snd_release_pages(struct device *dev, void *res) * The function returns the snd_dma_buffer object at success, or NULL if failed. */ struct snd_dma_buffer * -snd_devm_alloc_pages(struct device *dev, int type, size_t size) +snd_devm_alloc_dir_pages(struct device *dev, int type, + enum dma_data_direction dir, size_t size) { struct snd_dma_buffer *dmab; int err; @@ -153,7 +160,7 @@ snd_devm_alloc_pages(struct device *dev, int type, size_t size) if (!dmab) return NULL;
- err = snd_dma_alloc_pages(type, dev, size, dmab); + err = snd_dma_alloc_dir_pages(type, dev, dir, size, dmab); if (err < 0) { devres_free(dmab); return NULL; @@ -162,7 +169,7 @@ snd_devm_alloc_pages(struct device *dev, int type, size_t size) devres_add(dev, dmab); return dmab; } -EXPORT_SYMBOL_GPL(snd_devm_alloc_pages); +EXPORT_SYMBOL_GPL(snd_devm_alloc_dir_pages);
/** * snd_dma_buffer_mmap - perform mmap of the given DMA buffer @@ -179,6 +186,21 @@ int snd_dma_buffer_mmap(struct snd_dma_buffer *dmab, } EXPORT_SYMBOL(snd_dma_buffer_mmap);
+#ifdef CONFIG_HAS_DMA +/** + * snd_dma_buffer_sync - sync DMA buffer between CPU and device + * @dmab: buffer allocation information + * @mod: sync mode + */ +void snd_dma_buffer_sync(struct snd_dma_buffer *dmab, + enum snd_dma_sync_mode mode) +{ + if (dmab && dmab->ops && dmab->ops->sync) + dmab->ops->sync(dmab, mode); +} +EXPORT_SYMBOL_GPL(snd_dma_buffer_sync); +#endif /* CONFIG_HAS_DMA */ + /** * snd_sgbuf_get_addr - return the physical address at the corresponding offset * @dmab: buffer allocation information @@ -457,6 +479,66 @@ static const struct snd_malloc_ops snd_dma_wc_ops = { .mmap = snd_dma_wc_mmap, }; #endif /* CONFIG_X86 */ + +/* + * Non-contiguous pages allocator + */ +static void *snd_dma_noncontig_alloc(struct snd_dma_buffer *dmab, size_t size) +{ + struct sg_table *sgt; + void *p; + + sgt = dma_alloc_noncontiguous(dmab->dev.dev, size, dmab->dev.dir, + DEFAULT_GFP, 0); + if (!sgt) + return NULL; + p = dma_vmap_noncontiguous(dmab->dev.dev, size, sgt); + if (p) + dmab->private_data = sgt; + else + dma_free_noncontiguous(dmab->dev.dev, size, sgt, dmab->dev.dir); + return p; +} + +static void snd_dma_noncontig_free(struct snd_dma_buffer *dmab) +{ + dma_vunmap_noncontiguous(dmab->dev.dev, dmab->area); + dma_free_noncontiguous(dmab->dev.dev, dmab->bytes, dmab->private_data, + dmab->dev.dir); +} + +static int snd_dma_noncontig_mmap(struct snd_dma_buffer *dmab, + struct vm_area_struct *area) +{ + return dma_mmap_noncontiguous(dmab->dev.dev, area, + dmab->bytes, dmab->private_data); +} + +static void snd_dma_noncontig_sync(struct snd_dma_buffer *dmab, + enum snd_dma_sync_mode mode) +{ + if (mode == SNDRV_DMA_SYNC_CPU) { + dma_sync_sgtable_for_cpu(dmab->dev.dev, dmab->private_data, + dmab->dev.dir); + invalidate_kernel_vmap_range(dmab->area, dmab->bytes); + } else { + flush_kernel_vmap_range(dmab->area, dmab->bytes); + dma_sync_sgtable_for_device(dmab->dev.dev, dmab->private_data, + dmab->dev.dir); + } +} + +static const struct snd_malloc_ops snd_dma_noncontig_ops = { + .alloc = snd_dma_noncontig_alloc, + .free = snd_dma_noncontig_free, + .mmap = snd_dma_noncontig_mmap, + .sync = snd_dma_noncontig_sync, + /* re-use vmalloc helpers for get_* ops */ + .get_addr = snd_dma_vmalloc_get_addr, + .get_page = snd_dma_vmalloc_get_page, + .get_chunk_size = snd_dma_vmalloc_get_chunk_size, +}; + #endif /* CONFIG_HAS_DMA */
/* @@ -468,6 +550,7 @@ static const struct snd_malloc_ops *dma_ops[] = { #ifdef CONFIG_HAS_DMA [SNDRV_DMA_TYPE_DEV] = &snd_dma_dev_ops, [SNDRV_DMA_TYPE_DEV_WC] = &snd_dma_wc_ops, + [SNDRV_DMA_TYPE_NONCONTIG] = &snd_dma_noncontig_ops, #ifdef CONFIG_GENERIC_ALLOCATOR [SNDRV_DMA_TYPE_DEV_IRAM] = &snd_dma_iram_ops, #endif /* CONFIG_GENERIC_ALLOCATOR */ diff --git a/sound/core/memalloc_local.h b/sound/core/memalloc_local.h index 9f2e0a608b49..a6f3a87194da 100644 --- a/sound/core/memalloc_local.h +++ b/sound/core/memalloc_local.h @@ -10,6 +10,7 @@ struct snd_malloc_ops { unsigned int (*get_chunk_size)(struct snd_dma_buffer *dmab, unsigned int ofs, unsigned int size); int (*mmap)(struct snd_dma_buffer *dmab, struct vm_area_struct *area); + void (*sync)(struct snd_dma_buffer *dmab, enum snd_dma_sync_mode mode); };
#ifdef CONFIG_SND_DMA_SGBUF diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c index 7d5883432085..e263dfaefbf4 100644 --- a/sound/core/pcm_lib.c +++ b/sound/core/pcm_lib.c @@ -106,6 +106,9 @@ void snd_pcm_playback_silence(struct snd_pcm_substream *substream, snd_pcm_ufram frames -= transfer; ofs = 0; } + if (runtime->info & SNDRV_PCM_INFO_EXPLICIT_SYNC) + snd_dma_buffer_sync(snd_pcm_get_dma_buf(substream), + SNDRV_DMA_SYNC_DEVICE); }
#ifdef CONFIG_SND_DEBUG @@ -2256,8 +2259,14 @@ snd_pcm_sframes_t __snd_pcm_lib_xfer(struct snd_pcm_substream *substream, goto _end_unlock; } snd_pcm_stream_unlock_irq(substream); + if (runtime->info & SNDRV_PCM_INFO_EXPLICIT_SYNC) + snd_dma_buffer_sync(snd_pcm_get_dma_buf(substream), + SNDRV_DMA_SYNC_CPU); err = writer(substream, appl_ofs, data, offset, frames, transfer); + if (runtime->info & SNDRV_PCM_INFO_EXPLICIT_SYNC) + snd_dma_buffer_sync(snd_pcm_get_dma_buf(substream), + SNDRV_DMA_SYNC_DEVICE); snd_pcm_stream_lock_irq(substream); if (err < 0) goto _end_unlock; diff --git a/sound/core/pcm_memory.c b/sound/core/pcm_memory.c index 7fbd1ccbb5b0..b70ce3b69ab4 100644 --- a/sound/core/pcm_memory.c +++ b/sound/core/pcm_memory.c @@ -32,15 +32,20 @@ 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 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;
- 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; @@ -77,7 +82,7 @@ static int preallocate_pcm_pages(struct snd_pcm_substream *substream,
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) @@ -177,6 +182,7 @@ 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 %zu\n", @@ -418,6 +424,7 @@ 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 preallocate for size %zu\n", diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index d233cb3b41d8..3a94882b1422 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -2958,6 +2958,13 @@ static int snd_pcm_delay(struct snd_pcm_substream *substream, return err; } +static void snd_pcm_dma_buffer_sync(struct snd_pcm_substream *substream, + enum snd_dma_sync_mode mode) +{ + if (substream->runtime->info & SNDRV_PCM_INFO_EXPLICIT_SYNC) + snd_dma_buffer_sync(snd_pcm_get_dma_buf(substream), mode); +} + static int snd_pcm_sync_ptr(struct snd_pcm_substream *substream, struct snd_pcm_sync_ptr __user *_sync_ptr) { @@ -2978,6 +2985,7 @@ static int snd_pcm_sync_ptr(struct snd_pcm_substream *substream, err = snd_pcm_hwsync(substream); if (err < 0) return err; + snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_CPU); } snd_pcm_stream_lock_irq(substream); if (!(sync_ptr.flags & SNDRV_PCM_SYNC_PTR_APPL)) { @@ -3000,6 +3008,8 @@ static int snd_pcm_sync_ptr(struct snd_pcm_substream *substream, sync_ptr.s.status.suspended_state = status->suspended_state; sync_ptr.s.status.audio_tstamp = status->audio_tstamp; snd_pcm_stream_unlock_irq(substream); + if (!(sync_ptr.flags & SNDRV_PCM_SYNC_PTR_APPL)) + snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_DEVICE); if (copy_to_user(_sync_ptr, &sync_ptr, sizeof(sync_ptr))) return -EFAULT; return 0; @@ -3069,6 +3079,7 @@ static int snd_pcm_ioctl_sync_ptr_compat(struct snd_pcm_substream *substream, err = snd_pcm_hwsync(substream); if (err < 0) return err; + snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_CPU); } status = runtime->status; control = runtime->control; @@ -3096,6 +3107,8 @@ static int snd_pcm_ioctl_sync_ptr_compat(struct snd_pcm_substream *substream, sstatus.suspended_state = status->suspended_state; sstatus.audio_tstamp = status->audio_tstamp; snd_pcm_stream_unlock_irq(substream); + if (!(sflags & SNDRV_PCM_SYNC_PTR_APPL)) + snd_pcm_dma_buffer_sync(substream, SNDRV_DMA_SYNC_DEVICE); if (put_user(sstatus.state, &src->s.status.state) || put_user(sstatus.hw_ptr, &src->s.status.hw_ptr) || put_user(sstatus.tstamp.tv_sec, &src->s.status.tstamp_sec) ||
This patch adds the new non-coherent contiguous page allocation to the standard memalloc helper. Like the previous patch to add the non-contig SG-buffer support, this non-coherent type is also direction and requires the explicit sync, too. Hence the driver using this type of buffer would have to set SNDRV_PCM_INFO_EXPLICIT_SYNC flag to the PCM hardware.info as well.
Signed-off-by: Takashi Iwai tiwai@suse.de --- include/sound/memalloc.h | 1 + sound/core/memalloc.c | 43 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+)
diff --git a/include/sound/memalloc.h b/include/sound/memalloc.h index ed54f9dfb2b3..3b03e3266e90 100644 --- a/include/sound/memalloc.h +++ b/include/sound/memalloc.h @@ -50,6 +50,7 @@ struct snd_dma_device { #endif #define SNDRV_DMA_TYPE_VMALLOC 7 /* vmalloc'ed buffer */ #define SNDRV_DMA_TYPE_NONCONTIG 8 /* non-coherent SG buffer */ +#define SNDRV_DMA_TYPE_NONCOHERENT 9 /* non-coherent buffer */
/* * info for buffer allocation diff --git a/sound/core/memalloc.c b/sound/core/memalloc.c index 5ffd356e0327..ad092986c7a8 100644 --- a/sound/core/memalloc.c +++ b/sound/core/memalloc.c @@ -539,6 +539,48 @@ static const struct snd_malloc_ops snd_dma_noncontig_ops = { .get_chunk_size = snd_dma_vmalloc_get_chunk_size, };
+/* + * Non-coherent pages allocator + */ +static void *snd_dma_noncoherent_alloc(struct snd_dma_buffer *dmab, size_t size) +{ + return dma_alloc_noncoherent(dmab->dev.dev, size, &dmab->addr, + dmab->dev.dir, DEFAULT_GFP); +} + +static void snd_dma_noncoherent_free(struct snd_dma_buffer *dmab) +{ + dma_free_noncoherent(dmab->dev.dev, dmab->bytes, dmab->area, + dmab->addr, dmab->dev.dir); +} + +static int snd_dma_noncoherent_mmap(struct snd_dma_buffer *dmab, + struct vm_area_struct *area) +{ + area->vm_page_prot = vm_get_page_prot(area->vm_flags); + return dma_mmap_pages(dmab->dev.dev, area, + area->vm_end - area->vm_start, + virt_to_page(dmab->area)); +} + +static void snd_dma_noncoherent_sync(struct snd_dma_buffer *dmab, + enum snd_dma_sync_mode mode) +{ + if (mode == SNDRV_DMA_SYNC_CPU) + dma_sync_single_for_cpu(dmab->dev.dev, dmab->addr, + dmab->bytes, dmab->dev.dir); + else + dma_sync_single_for_device(dmab->dev.dev, dmab->addr, + dmab->bytes, dmab->dev.dir); +} + +static const struct snd_malloc_ops snd_dma_noncoherent_ops = { + .alloc = snd_dma_noncoherent_alloc, + .free = snd_dma_noncoherent_free, + .mmap = snd_dma_noncoherent_mmap, + .sync = snd_dma_noncoherent_sync, +}; + #endif /* CONFIG_HAS_DMA */
/* @@ -551,6 +593,7 @@ static const struct snd_malloc_ops *dma_ops[] = { [SNDRV_DMA_TYPE_DEV] = &snd_dma_dev_ops, [SNDRV_DMA_TYPE_DEV_WC] = &snd_dma_wc_ops, [SNDRV_DMA_TYPE_NONCONTIG] = &snd_dma_noncontig_ops, + [SNDRV_DMA_TYPE_NONCOHERENT] = &snd_dma_noncoherent_ops, #ifdef CONFIG_GENERIC_ALLOCATOR [SNDRV_DMA_TYPE_DEV_IRAM] = &snd_dma_iram_ops, #endif /* CONFIG_GENERIC_ALLOCATOR */
participants (1)
-
Takashi Iwai