mirror of
https://github.com/torvalds/linux.git
synced 2024-12-21 10:31:54 +00:00
51e9f2e665
Most hardwares have limited buffer-descriptor table length. This also restricts the max buffer size of the sound driver. For example, snd-hda-intel has 1MB buffer size limit, and this is because it can have at most 256 BDL entries. For supporting larger buffers, we need to allocate larger pages even for sg-buffers. This patch changes the sgbuf allocation code to try to allocate larger pages first. At each head of the allocated pages, the number of allocated pages is stored in the lowest bits of the corresponding entry of the table addr field. This change isn't visible as long as the driver uses snd_sgbuf_get_addr() helper. Also, the patch adds a new function, snd_pcm_sgbuf_get_chunk_size(). This returns the size of the chunk on continuous pages starting at the given position offset. If the chunk reaches to a non-continuous page, it returns the size to the boundary. Signed-off-by: Takashi Iwai <tiwai@suse.de> Signed-off-by: Jaroslav Kysela <perex@perex.cz>
138 lines
3.5 KiB
C
138 lines
3.5 KiB
C
/*
|
|
* Scatter-Gather buffer
|
|
*
|
|
* Copyright (c) by Takashi Iwai <tiwai@suse.de>
|
|
*
|
|
* 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/vmalloc.h>
|
|
#include <sound/memalloc.h>
|
|
|
|
|
|
/* table entries are align to 32 */
|
|
#define SGBUF_TBL_ALIGN 32
|
|
#define sgbuf_align_table(tbl) ALIGN((tbl), SGBUF_TBL_ALIGN)
|
|
|
|
int snd_free_sgbuf_pages(struct snd_dma_buffer *dmab)
|
|
{
|
|
struct snd_sg_buf *sgbuf = dmab->private_data;
|
|
struct snd_dma_buffer tmpb;
|
|
int i;
|
|
|
|
if (! sgbuf)
|
|
return -EINVAL;
|
|
|
|
tmpb.dev.type = SNDRV_DMA_TYPE_DEV;
|
|
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);
|
|
}
|
|
if (dmab->area)
|
|
vunmap(dmab->area);
|
|
dmab->area = NULL;
|
|
|
|
kfree(sgbuf->table);
|
|
kfree(sgbuf->page_table);
|
|
kfree(sgbuf);
|
|
dmab->private_data = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define MAX_ALLOC_PAGES 32
|
|
|
|
void *snd_malloc_sgbuf_pages(struct device *device,
|
|
size_t size, struct snd_dma_buffer *dmab,
|
|
size_t *res_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;
|
|
|
|
dmab->area = NULL;
|
|
dmab->addr = 0;
|
|
dmab->private_data = sgbuf = kzalloc(sizeof(*sgbuf), GFP_KERNEL);
|
|
if (! sgbuf)
|
|
return NULL;
|
|
sgbuf->dev = device;
|
|
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(SNDRV_DMA_TYPE_DEV, device,
|
|
chunk, &tmpb) < 0) {
|
|
if (!sgbuf->pages)
|
|
return NULL;
|
|
if (!res_size)
|
|
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;
|
|
dmab->area = vmap(sgbuf->page_table, sgbuf->pages, VM_MAP, PAGE_KERNEL);
|
|
if (! dmab->area)
|
|
goto _failed;
|
|
if (res_size)
|
|
*res_size = sgbuf->size;
|
|
return dmab->area;
|
|
|
|
_failed:
|
|
snd_free_sgbuf_pages(dmab); /* free the table */
|
|
return NULL;
|
|
}
|