ALSA: Allocate larger pages in sgbuf
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>
This commit is contained in:
parent
4e184f8fc0
commit
51e9f2e665
|
@ -97,7 +97,9 @@ static inline unsigned int snd_sgbuf_aligned_pages(size_t size)
|
||||||
*/
|
*/
|
||||||
static inline dma_addr_t snd_sgbuf_get_addr(struct snd_sg_buf *sgbuf, size_t offset)
|
static inline dma_addr_t snd_sgbuf_get_addr(struct snd_sg_buf *sgbuf, size_t offset)
|
||||||
{
|
{
|
||||||
return sgbuf->table[offset >> PAGE_SHIFT].addr + offset % PAGE_SIZE;
|
dma_addr_t addr = sgbuf->table[offset >> PAGE_SHIFT].addr;
|
||||||
|
addr &= PAGE_MASK;
|
||||||
|
return addr + offset % PAGE_SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -996,7 +996,8 @@ snd_pcm_sgbuf_get_ptr(struct snd_pcm_substream *substream, unsigned int ofs)
|
||||||
|
|
||||||
struct page *snd_pcm_sgbuf_ops_page(struct snd_pcm_substream *substream,
|
struct page *snd_pcm_sgbuf_ops_page(struct snd_pcm_substream *substream,
|
||||||
unsigned long offset);
|
unsigned long offset);
|
||||||
|
unsigned int snd_pcm_sgbuf_get_chunk_size(struct snd_pcm_substream *substream,
|
||||||
|
unsigned int ofs, unsigned int size);
|
||||||
|
|
||||||
/* handle mmap counter - PCM mmap callback should handle this counter properly */
|
/* handle mmap counter - PCM mmap callback should handle this counter properly */
|
||||||
static inline void snd_pcm_mmap_data_open(struct vm_area_struct *area)
|
static inline void snd_pcm_mmap_data_open(struct vm_area_struct *area)
|
||||||
|
|
|
@ -324,6 +324,32 @@ struct page *snd_pcm_sgbuf_ops_page(struct snd_pcm_substream *substream, unsigne
|
||||||
|
|
||||||
EXPORT_SYMBOL(snd_pcm_sgbuf_ops_page);
|
EXPORT_SYMBOL(snd_pcm_sgbuf_ops_page);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* compute the max chunk size with continuous pages on sg-buffer
|
||||||
|
*/
|
||||||
|
unsigned int snd_pcm_sgbuf_get_chunk_size(struct snd_pcm_substream *substream,
|
||||||
|
unsigned int ofs, unsigned int size)
|
||||||
|
{
|
||||||
|
struct snd_sg_buf *sg = snd_pcm_substream_sgbuf(substream);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL(snd_pcm_sgbuf_get_chunk_size);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* snd_pcm_lib_malloc_pages - allocate the DMA buffer
|
* snd_pcm_lib_malloc_pages - allocate the DMA buffer
|
||||||
* @substream: the substream to allocate the DMA buffer to
|
* @substream: the substream to allocate the DMA buffer to
|
||||||
|
|
|
@ -41,9 +41,11 @@ int snd_free_sgbuf_pages(struct snd_dma_buffer *dmab)
|
||||||
tmpb.dev.type = SNDRV_DMA_TYPE_DEV;
|
tmpb.dev.type = SNDRV_DMA_TYPE_DEV;
|
||||||
tmpb.dev.dev = sgbuf->dev;
|
tmpb.dev.dev = sgbuf->dev;
|
||||||
for (i = 0; i < sgbuf->pages; i++) {
|
for (i = 0; i < sgbuf->pages; i++) {
|
||||||
|
if (!(sgbuf->table[i].addr & ~PAGE_MASK))
|
||||||
|
continue; /* continuous pages */
|
||||||
tmpb.area = sgbuf->table[i].buf;
|
tmpb.area = sgbuf->table[i].buf;
|
||||||
tmpb.addr = sgbuf->table[i].addr;
|
tmpb.addr = sgbuf->table[i].addr & PAGE_MASK;
|
||||||
tmpb.bytes = PAGE_SIZE;
|
tmpb.bytes = (sgbuf->table[i].addr & ~PAGE_MASK) << PAGE_SHIFT;
|
||||||
snd_dma_free_pages(&tmpb);
|
snd_dma_free_pages(&tmpb);
|
||||||
}
|
}
|
||||||
if (dmab->area)
|
if (dmab->area)
|
||||||
|
@ -58,13 +60,17 @@ int snd_free_sgbuf_pages(struct snd_dma_buffer *dmab)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define MAX_ALLOC_PAGES 32
|
||||||
|
|
||||||
void *snd_malloc_sgbuf_pages(struct device *device,
|
void *snd_malloc_sgbuf_pages(struct device *device,
|
||||||
size_t size, struct snd_dma_buffer *dmab,
|
size_t size, struct snd_dma_buffer *dmab,
|
||||||
size_t *res_size)
|
size_t *res_size)
|
||||||
{
|
{
|
||||||
struct snd_sg_buf *sgbuf;
|
struct snd_sg_buf *sgbuf;
|
||||||
unsigned int i, pages;
|
unsigned int i, pages, chunk, maxpages;
|
||||||
struct snd_dma_buffer tmpb;
|
struct snd_dma_buffer tmpb;
|
||||||
|
struct snd_sg_page *table;
|
||||||
|
struct page **pgtable;
|
||||||
|
|
||||||
dmab->area = NULL;
|
dmab->area = NULL;
|
||||||
dmab->addr = 0;
|
dmab->addr = 0;
|
||||||
|
@ -74,31 +80,55 @@ void *snd_malloc_sgbuf_pages(struct device *device,
|
||||||
sgbuf->dev = device;
|
sgbuf->dev = device;
|
||||||
pages = snd_sgbuf_aligned_pages(size);
|
pages = snd_sgbuf_aligned_pages(size);
|
||||||
sgbuf->tblsize = sgbuf_align_table(pages);
|
sgbuf->tblsize = sgbuf_align_table(pages);
|
||||||
sgbuf->table = kcalloc(sgbuf->tblsize, sizeof(*sgbuf->table), GFP_KERNEL);
|
table = kcalloc(sgbuf->tblsize, sizeof(*table), GFP_KERNEL);
|
||||||
if (! sgbuf->table)
|
if (!table)
|
||||||
goto _failed;
|
goto _failed;
|
||||||
sgbuf->page_table = kcalloc(sgbuf->tblsize, sizeof(*sgbuf->page_table), GFP_KERNEL);
|
sgbuf->table = table;
|
||||||
if (! sgbuf->page_table)
|
pgtable = kcalloc(sgbuf->tblsize, sizeof(*pgtable), GFP_KERNEL);
|
||||||
|
if (!pgtable)
|
||||||
goto _failed;
|
goto _failed;
|
||||||
|
sgbuf->page_table = pgtable;
|
||||||
|
|
||||||
/* allocate each page */
|
/* allocate pages */
|
||||||
for (i = 0; i < pages; i++) {
|
maxpages = MAX_ALLOC_PAGES;
|
||||||
if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, device, PAGE_SIZE, &tmpb) < 0) {
|
while (pages > 0) {
|
||||||
if (res_size == NULL)
|
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;
|
goto _failed;
|
||||||
*res_size = size = sgbuf->pages * PAGE_SIZE;
|
size = sgbuf->pages * PAGE_SIZE;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
sgbuf->table[i].buf = tmpb.area;
|
chunk = tmpb.bytes >> PAGE_SHIFT;
|
||||||
sgbuf->table[i].addr = tmpb.addr;
|
for (i = 0; i < chunk; i++) {
|
||||||
sgbuf->page_table[i] = virt_to_page(tmpb.area);
|
table->buf = tmpb.area;
|
||||||
sgbuf->pages++;
|
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;
|
sgbuf->size = size;
|
||||||
dmab->area = vmap(sgbuf->page_table, sgbuf->pages, VM_MAP, PAGE_KERNEL);
|
dmab->area = vmap(sgbuf->page_table, sgbuf->pages, VM_MAP, PAGE_KERNEL);
|
||||||
if (! dmab->area)
|
if (! dmab->area)
|
||||||
goto _failed;
|
goto _failed;
|
||||||
|
if (res_size)
|
||||||
|
*res_size = sgbuf->size;
|
||||||
return dmab->area;
|
return dmab->area;
|
||||||
|
|
||||||
_failed:
|
_failed:
|
||||||
|
|
Loading…
Reference in New Issue
Block a user