diff options
Diffstat (limited to 'sound/core')
| -rw-r--r-- | sound/core/Makefile | 1 | ||||
| -rw-r--r-- | sound/core/control_compat.c | 3 | ||||
| -rw-r--r-- | sound/core/memalloc.c | 105 | ||||
| -rw-r--r-- | sound/core/oss/pcm_oss.c | 37 | ||||
| -rw-r--r-- | sound/core/sgbuf.c | 201 | ||||
| -rw-r--r-- | sound/core/timer.c | 17 |
6 files changed, 300 insertions, 64 deletions
diff --git a/sound/core/Makefile b/sound/core/Makefile index 350d704ced98..79e1407cd0de 100644 --- a/sound/core/Makefile +++ b/sound/core/Makefile @@ -19,6 +19,7 @@ snd-$(CONFIG_SND_JACK) += ctljack.o jack.o snd-pcm-y := pcm.o pcm_native.o pcm_lib.o pcm_misc.o \ pcm_memory.o memalloc.o snd-pcm-$(CONFIG_SND_PCM_TIMER) += pcm_timer.o +snd-pcm-$(CONFIG_SND_DMA_SGBUF) += sgbuf.o snd-pcm-$(CONFIG_SND_PCM_ELD) += pcm_drm_eld.o snd-pcm-$(CONFIG_SND_PCM_IEC958) += pcm_iec958.o diff --git a/sound/core/control_compat.c b/sound/core/control_compat.c index 470dabc60aa0..edff063e088d 100644 --- a/sound/core/control_compat.c +++ b/sound/core/control_compat.c @@ -264,6 +264,7 @@ static int copy_ctl_value_to_user(void __user *userdata, struct snd_ctl_elem_value *data, int type, int count) { + struct snd_ctl_elem_value32 __user *data32 = userdata; int i, size; if (type == SNDRV_CTL_ELEM_TYPE_BOOLEAN || @@ -280,6 +281,8 @@ static int copy_ctl_value_to_user(void __user *userdata, if (copy_to_user(valuep, data->value.bytes.data, size)) return -EFAULT; } + if (copy_to_user(&data32->id, &data->id, sizeof(data32->id))) + return -EFAULT; return 0; } diff --git a/sound/core/memalloc.c b/sound/core/memalloc.c index 99cd0f67daa1..9fc971a704a9 100644 --- a/sound/core/memalloc.c +++ b/sound/core/memalloc.c @@ -183,8 +183,11 @@ EXPORT_SYMBOL_GPL(snd_devm_alloc_dir_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); + const struct snd_malloc_ops *ops; + if (!dmab) + return -ENOENT; + ops = snd_dma_get_ops(dmab); if (ops && ops->mmap) return ops->mmap(dmab, area); else @@ -549,60 +552,73 @@ static void snd_dma_noncontig_sync(struct snd_dma_buffer *dmab, } } -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, -}; +static inline void snd_dma_noncontig_iter_set(struct snd_dma_buffer *dmab, + struct sg_page_iter *piter, + size_t offset) +{ + struct sg_table *sgt = dmab->private_data; -/* x86-specific SG-buffer with WC pages */ -#ifdef CONFIG_SND_DMA_SGBUF -#define vmalloc_to_virt(v) (unsigned long)page_to_virt(vmalloc_to_page(v)) + __sg_page_iter_start(piter, sgt->sgl, sgt->orig_nents, + offset >> PAGE_SHIFT); +} -static void *snd_dma_sg_wc_alloc(struct snd_dma_buffer *dmab, size_t size) +static dma_addr_t snd_dma_noncontig_get_addr(struct snd_dma_buffer *dmab, + size_t offset) { - void *p = snd_dma_noncontig_alloc(dmab, size); - size_t ofs; + struct sg_dma_page_iter iter; - if (!p) - return NULL; - for (ofs = 0; ofs < size; ofs += PAGE_SIZE) - set_memory_uc(vmalloc_to_virt(p + ofs), 1); - return p; + snd_dma_noncontig_iter_set(dmab, &iter.base, offset); + __sg_page_iter_dma_next(&iter); + return sg_page_iter_dma_address(&iter) + offset % PAGE_SIZE; } -static void snd_dma_sg_wc_free(struct snd_dma_buffer *dmab) +static struct page *snd_dma_noncontig_get_page(struct snd_dma_buffer *dmab, + size_t offset) { - size_t ofs; + struct sg_page_iter iter; - for (ofs = 0; ofs < dmab->bytes; ofs += PAGE_SIZE) - set_memory_wb(vmalloc_to_virt(dmab->area + ofs), 1); - snd_dma_noncontig_free(dmab); + snd_dma_noncontig_iter_set(dmab, &iter, offset); + __sg_page_iter_next(&iter); + return sg_page_iter_page(&iter); } -static int snd_dma_sg_wc_mmap(struct snd_dma_buffer *dmab, - struct vm_area_struct *area) +static unsigned int +snd_dma_noncontig_get_chunk_size(struct snd_dma_buffer *dmab, + unsigned int ofs, unsigned int size) { - area->vm_page_prot = pgprot_writecombine(area->vm_page_prot); - /* FIXME: dma_mmap_noncontiguous() works? */ - return -ENOENT; /* continue with the default mmap handler */ + struct sg_dma_page_iter iter; + unsigned int start, end; + unsigned long addr; + + start = ALIGN_DOWN(ofs, PAGE_SIZE); + end = ofs + size - 1; /* the last byte address */ + snd_dma_noncontig_iter_set(dmab, &iter.base, start); + if (!__sg_page_iter_dma_next(&iter)) + return 0; + /* check page continuity */ + addr = sg_page_iter_dma_address(&iter); + for (;;) { + start += PAGE_SIZE; + if (start > end) + break; + addr += PAGE_SIZE; + if (!__sg_page_iter_dma_next(&iter) || + sg_page_iter_dma_address(&iter) != addr) + return start - ofs; + } + /* ok, all on continuous pages */ + return size; } -const struct snd_malloc_ops snd_dma_sg_wc_ops = { - .alloc = snd_dma_sg_wc_alloc, - .free = snd_dma_sg_wc_free, - .mmap = snd_dma_sg_wc_mmap, +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, - .get_addr = snd_dma_vmalloc_get_addr, - .get_page = snd_dma_vmalloc_get_page, - .get_chunk_size = snd_dma_vmalloc_get_chunk_size, + .get_addr = snd_dma_noncontig_get_addr, + .get_page = snd_dma_noncontig_get_page, + .get_chunk_size = snd_dma_noncontig_get_chunk_size, }; -#endif /* CONFIG_SND_DMA_SGBUF */ /* * Non-coherent pages allocator @@ -663,17 +679,20 @@ static const struct snd_malloc_ops *dma_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_SND_DMA_SGBUF - [SNDRV_DMA_TYPE_DEV_WC_SG] = &snd_dma_sg_wc_ops, -#endif #ifdef CONFIG_GENERIC_ALLOCATOR [SNDRV_DMA_TYPE_DEV_IRAM] = &snd_dma_iram_ops, #endif /* CONFIG_GENERIC_ALLOCATOR */ #endif /* CONFIG_HAS_DMA */ +#ifdef CONFIG_SND_DMA_SGBUF + [SNDRV_DMA_TYPE_DEV_SG] = &snd_dma_sg_ops, + [SNDRV_DMA_TYPE_DEV_WC_SG] = &snd_dma_sg_ops, +#endif }; static const struct snd_malloc_ops *snd_dma_get_ops(struct snd_dma_buffer *dmab) { + if (WARN_ON_ONCE(!dmab)) + return NULL; if (WARN_ON_ONCE(dmab->dev.type <= SNDRV_DMA_TYPE_UNKNOWN || dmab->dev.type >= ARRAY_SIZE(dma_ops))) return NULL; diff --git a/sound/core/oss/pcm_oss.c b/sound/core/oss/pcm_oss.c index 82a818734a5f..20a0a4771b9a 100644 --- a/sound/core/oss/pcm_oss.c +++ b/sound/core/oss/pcm_oss.c @@ -147,7 +147,7 @@ snd_pcm_hw_param_value_min(const struct snd_pcm_hw_params *params, * * Return the maximum value for field PAR. */ -static unsigned int +static int snd_pcm_hw_param_value_max(const struct snd_pcm_hw_params *params, snd_pcm_hw_param_t var, int *dir) { @@ -682,18 +682,24 @@ static int snd_pcm_oss_period_size(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *oss_params, struct snd_pcm_hw_params *slave_params) { - size_t s; - size_t oss_buffer_size, oss_period_size, oss_periods; - size_t min_period_size, max_period_size; + ssize_t s; + ssize_t oss_buffer_size; + ssize_t oss_period_size, oss_periods; + ssize_t min_period_size, max_period_size; struct snd_pcm_runtime *runtime = substream->runtime; size_t oss_frame_size; oss_frame_size = snd_pcm_format_physical_width(params_format(oss_params)) * params_channels(oss_params) / 8; + oss_buffer_size = snd_pcm_hw_param_value_max(slave_params, + SNDRV_PCM_HW_PARAM_BUFFER_SIZE, + NULL); + if (oss_buffer_size <= 0) + return -EINVAL; oss_buffer_size = snd_pcm_plug_client_size(substream, - snd_pcm_hw_param_value_max(slave_params, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, NULL)) * oss_frame_size; - if (!oss_buffer_size) + oss_buffer_size * oss_frame_size); + if (oss_buffer_size <= 0) return -EINVAL; oss_buffer_size = rounddown_pow_of_two(oss_buffer_size); if (atomic_read(&substream->mmap_count)) { @@ -730,7 +736,7 @@ static int snd_pcm_oss_period_size(struct snd_pcm_substream *substream, min_period_size = snd_pcm_plug_client_size(substream, snd_pcm_hw_param_value_min(slave_params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, NULL)); - if (min_period_size) { + if (min_period_size > 0) { min_period_size *= oss_frame_size; min_period_size = roundup_pow_of_two(min_period_size); if (oss_period_size < min_period_size) @@ -739,7 +745,7 @@ static int snd_pcm_oss_period_size(struct snd_pcm_substream *substream, max_period_size = snd_pcm_plug_client_size(substream, snd_pcm_hw_param_value_max(slave_params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, NULL)); - if (max_period_size) { + if (max_period_size > 0) { max_period_size *= oss_frame_size; max_period_size = rounddown_pow_of_two(max_period_size); if (oss_period_size > max_period_size) @@ -752,7 +758,7 @@ static int snd_pcm_oss_period_size(struct snd_pcm_substream *substream, oss_periods = substream->oss.setup.periods; s = snd_pcm_hw_param_value_max(slave_params, SNDRV_PCM_HW_PARAM_PERIODS, NULL); - if (runtime->oss.maxfrags && s > runtime->oss.maxfrags) + if (s > 0 && runtime->oss.maxfrags && s > runtime->oss.maxfrags) s = runtime->oss.maxfrags; if (oss_periods > s) oss_periods = s; @@ -878,8 +884,15 @@ static int snd_pcm_oss_change_params_locked(struct snd_pcm_substream *substream) err = -EINVAL; goto failure; } - choose_rate(substream, sparams, runtime->oss.rate); - snd_pcm_hw_param_near(substream, sparams, SNDRV_PCM_HW_PARAM_CHANNELS, runtime->oss.channels, NULL); + + err = choose_rate(substream, sparams, runtime->oss.rate); + if (err < 0) + goto failure; + err = snd_pcm_hw_param_near(substream, sparams, + SNDRV_PCM_HW_PARAM_CHANNELS, + runtime->oss.channels, NULL); + if (err < 0) + goto failure; format = snd_pcm_oss_format_from(runtime->oss.format); @@ -1956,7 +1969,7 @@ static int snd_pcm_oss_set_fragment1(struct snd_pcm_substream *substream, unsign if (runtime->oss.subdivision || runtime->oss.fragshift) return -EINVAL; fragshift = val & 0xffff; - if (fragshift >= 31) + if (fragshift >= 25) /* should be large enough */ return -EINVAL; runtime->oss.fragshift = fragshift; runtime->oss.maxfrags = (val >> 16) & 0xffff; diff --git a/sound/core/sgbuf.c b/sound/core/sgbuf.c new file mode 100644 index 000000000000..8352a5cdb19f --- /dev/null +++ b/sound/core/sgbuf.c @@ -0,0 +1,201 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Scatter-Gather buffer + * + * Copyright (c) by Takashi Iwai <[email protected]> + */ + +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/vmalloc.h> +#include <linux/export.h> +#include <sound/memalloc.h> +#include "memalloc_local.h" + +struct snd_sg_page { + void *buf; + dma_addr_t addr; +}; + +struct snd_sg_buf { + int size; /* allocated byte size */ + int pages; /* allocated pages */ + int tblsize; /* allocated table size */ + struct snd_sg_page *table; /* address table */ + struct page **page_table; /* page table (for vmap/vunmap) */ + struct device *dev; +}; + +/* table entries are align to 32 */ +#define SGBUF_TBL_ALIGN 32 +#define sgbuf_align_table(tbl) ALIGN((tbl), SGBUF_TBL_ALIGN) + +static void snd_dma_sg_free(struct snd_dma_buffer *dmab) +{ + struct snd_sg_buf *sgbuf = dmab->private_data; + struct snd_dma_buffer tmpb; + int i; + + if (!sgbuf) + return; + + vunmap(dmab->area); + dmab->area = NULL; + + tmpb.dev.type = SNDRV_DMA_TYPE_DEV; + if (dmab->dev.type == SNDRV_DMA_TYPE_DEV_WC_SG) + tmpb.dev.type = SNDRV_DMA_TYPE_DEV_WC; + tmpb.dev.dev = sgbuf->dev; + for (i = 0; i < sgbuf->pages; i++) { + if (!(sgbuf->table[i].addr & ~PAGE_MASK)) + continue; /* continuous pages */ + tmpb.area = sgbuf->table[i].buf; + tmpb.addr = sgbuf->table[i].addr & PAGE_MASK; + tmpb.bytes = (sgbuf->table[i].addr & ~PAGE_MASK) << PAGE_SHIFT; + snd_dma_free_pages(&tmpb); + } + + kfree(sgbuf->table); + kfree(sgbuf->page_table); + kfree(sgbuf); + dmab->private_data = NULL; +} + +#define MAX_ALLOC_PAGES 32 + +static void *snd_dma_sg_alloc(struct snd_dma_buffer *dmab, size_t size) +{ + struct snd_sg_buf *sgbuf; + unsigned int i, pages, chunk, maxpages; + struct snd_dma_buffer tmpb; + struct snd_sg_page *table; + struct page **pgtable; + int type = SNDRV_DMA_TYPE_DEV; + pgprot_t prot = PAGE_KERNEL; + void *area; + + dmab->private_data = sgbuf = kzalloc(sizeof(*sgbuf), GFP_KERNEL); + if (!sgbuf) + return NULL; + if (dmab->dev.type == SNDRV_DMA_TYPE_DEV_WC_SG) { + type = SNDRV_DMA_TYPE_DEV_WC; +#ifdef pgprot_noncached + prot = pgprot_noncached(PAGE_KERNEL); +#endif + } + sgbuf->dev = dmab->dev.dev; + pages = snd_sgbuf_aligned_pages(size); + sgbuf->tblsize = sgbuf_align_table(pages); + table = kcalloc(sgbuf->tblsize, sizeof(*table), GFP_KERNEL); + if (!table) + goto _failed; + sgbuf->table = table; + pgtable = kcalloc(sgbuf->tblsize, sizeof(*pgtable), GFP_KERNEL); + if (!pgtable) + goto _failed; + sgbuf->page_table = pgtable; + + /* allocate pages */ + maxpages = MAX_ALLOC_PAGES; + while (pages > 0) { + chunk = pages; + /* don't be too eager to take a huge chunk */ + if (chunk > maxpages) + chunk = maxpages; + chunk <<= PAGE_SHIFT; + if (snd_dma_alloc_pages_fallback(type, dmab->dev.dev, + chunk, &tmpb) < 0) { + if (!sgbuf->pages) + goto _failed; + size = sgbuf->pages * PAGE_SIZE; + break; + } + chunk = tmpb.bytes >> PAGE_SHIFT; + for (i = 0; i < chunk; i++) { + table->buf = tmpb.area; + table->addr = tmpb.addr; + if (!i) + table->addr |= chunk; /* mark head */ + table++; + *pgtable++ = virt_to_page(tmpb.area); + tmpb.area += PAGE_SIZE; + tmpb.addr += PAGE_SIZE; + } + sgbuf->pages += chunk; + pages -= chunk; + if (chunk < maxpages) + maxpages = chunk; + } + + sgbuf->size = size; + area = vmap(sgbuf->page_table, sgbuf->pages, VM_MAP, prot); + if (!area) + goto _failed; + return area; + + _failed: + snd_dma_sg_free(dmab); /* free the table */ + return NULL; +} + +static dma_addr_t snd_dma_sg_get_addr(struct snd_dma_buffer *dmab, + size_t offset) +{ + struct snd_sg_buf *sgbuf = dmab->private_data; + dma_addr_t addr; + + addr = sgbuf->table[offset >> PAGE_SHIFT].addr; + addr &= ~((dma_addr_t)PAGE_SIZE - 1); + return addr + offset % PAGE_SIZE; +} + +static struct page *snd_dma_sg_get_page(struct snd_dma_buffer *dmab, + size_t offset) +{ + struct snd_sg_buf *sgbuf = dmab->private_data; + unsigned int idx = offset >> PAGE_SHIFT; + + if (idx >= (unsigned int)sgbuf->pages) + return NULL; + return sgbuf->page_table[idx]; +} + +static unsigned int snd_dma_sg_get_chunk_size(struct snd_dma_buffer *dmab, + unsigned int ofs, + unsigned int size) +{ + struct snd_sg_buf *sg = dmab->private_data; + unsigned int start, end, pg; + + start = ofs >> PAGE_SHIFT; + end = (ofs + size - 1) >> PAGE_SHIFT; + /* check page continuity */ + pg = sg->table[start].addr >> PAGE_SHIFT; + for (;;) { + start++; + if (start > end) + break; + pg++; + if ((sg->table[start].addr >> PAGE_SHIFT) != pg) + return (start << PAGE_SHIFT) - ofs; + } + /* ok, all on continuous pages */ + return size; +} + +static int snd_dma_sg_mmap(struct snd_dma_buffer *dmab, + struct vm_area_struct *area) +{ + if (dmab->dev.type == SNDRV_DMA_TYPE_DEV_WC_SG) + area->vm_page_prot = pgprot_writecombine(area->vm_page_prot); + return -ENOENT; /* continue with the default mmap handler */ +} + +const struct snd_malloc_ops snd_dma_sg_ops = { + .alloc = snd_dma_sg_alloc, + .free = snd_dma_sg_free, + .get_addr = snd_dma_sg_get_addr, + .get_page = snd_dma_sg_get_page, + .get_chunk_size = snd_dma_sg_get_chunk_size, + .mmap = snd_dma_sg_mmap, +}; diff --git a/sound/core/timer.c b/sound/core/timer.c index 92b7008fcdb8..b3214baa8919 100644 --- a/sound/core/timer.c +++ b/sound/core/timer.c @@ -624,13 +624,13 @@ static int snd_timer_stop1(struct snd_timer_instance *timeri, bool stop) if (!timer) return -EINVAL; spin_lock_irqsave(&timer->lock, flags); + list_del_init(&timeri->ack_list); + list_del_init(&timeri->active_list); if (!(timeri->flags & (SNDRV_TIMER_IFLG_RUNNING | SNDRV_TIMER_IFLG_START))) { result = -EBUSY; goto unlock; } - list_del_init(&timeri->ack_list); - list_del_init(&timeri->active_list); if (timer->card && timer->card->shutdown) goto unlock; if (stop) { @@ -665,23 +665,22 @@ static int snd_timer_stop1(struct snd_timer_instance *timeri, bool stop) static int snd_timer_stop_slave(struct snd_timer_instance *timeri, bool stop) { unsigned long flags; + bool running; spin_lock_irqsave(&slave_active_lock, flags); - if (!(timeri->flags & SNDRV_TIMER_IFLG_RUNNING)) { - spin_unlock_irqrestore(&slave_active_lock, flags); - return -EBUSY; - } + running = timeri->flags & SNDRV_TIMER_IFLG_RUNNING; timeri->flags &= ~SNDRV_TIMER_IFLG_RUNNING; if (timeri->timer) { spin_lock(&timeri->timer->lock); list_del_init(&timeri->ack_list); list_del_init(&timeri->active_list); - snd_timer_notify1(timeri, stop ? SNDRV_TIMER_EVENT_STOP : - SNDRV_TIMER_EVENT_PAUSE); + if (running) + snd_timer_notify1(timeri, stop ? SNDRV_TIMER_EVENT_STOP : + SNDRV_TIMER_EVENT_PAUSE); spin_unlock(&timeri->timer->lock); } spin_unlock_irqrestore(&slave_active_lock, flags); - return 0; + return running ? 0 : -EBUSY; } /* |