forked from torvalds/linux
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge tag 'sound-fix-5.16-rc1' of git://git.kernel.org/pub/scm/linux/…
…kernel/git/tiwai/sound Pull sound fixes from Takashi Iwai: "A collection of fixes for 5.16-rc1, notably for a few regressions that were found in 5.15 and pre-rc1: - revert of the unification of SG-buffer helper functions on x86 and the relevant fix - regression fixes for mmap after the recent code refactoring - two NULL dereference fixes in HD-audio controller driver - UAF fixes in ALSA timer core - a few usual HD-audio and FireWire quirks" * tag 'sound-fix-5.16-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound: ALSA: fireworks: add support for Loud Onyx 1200f quirk ALSA: hda: fix general protection fault in azx_runtime_idle ALSA: hda: Free card instance properly at probe errors ALSA: hda/realtek: Add quirk for HP EliteBook 840 G7 mute LED ALSA: memalloc: Remove a stale comment ALSA: synth: missing check for possible NULL after the call to kstrdup ALSA: memalloc: Use proper SG helpers for noncontig allocations ALSA: pci: rme: Fix unaligned buffer addresses ALSA: firewire-motu: add support for MOTU Track 16 ALSA: PCM: Fix NULL dereference at mmap checks ALSA: hda/realtek: Add quirk for ASUS UX550VE ALSA: timer: Unconditionally unlink slave instances, too ALSA: memalloc: Catch call with NULL snd_dma_buffer pointer Revert "ALSA: memalloc: Convert x86 SG-buffer handling with non-contiguous type" ALSA: hda/realtek: Add a quirk for Acer Spin SP513-54N ALSA: firewire-motu: add support for MOTU Traveler mk3 ALSA: hda/realtek: Headset fixup for Clevo NH77HJQ ALSA: timer: Fix use-after-free problem
- Loading branch information
Showing
15 changed files
with
386 additions
and
102 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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, | ||
}; |
Oops, something went wrong.