ALSA: Fix vunmap and free order in snd_free_sgbuf_pages()
[linux-2.6.git] / sound / core / sgbuf.c
index c30669f..4e7ec2b 100644 (file)
@@ -27,7 +27,7 @@
 
 /* table entries are align to 32 */
 #define SGBUF_TBL_ALIGN                32
-#define sgbuf_align_table(tbl) ((((tbl) + SGBUF_TBL_ALIGN - 1) / SGBUF_TBL_ALIGN) * SGBUF_TBL_ALIGN)
+#define sgbuf_align_table(tbl) ALIGN((tbl), SGBUF_TBL_ALIGN)
 
 int snd_free_sgbuf_pages(struct snd_dma_buffer *dmab)
 {
@@ -38,17 +38,20 @@ int snd_free_sgbuf_pages(struct snd_dma_buffer *dmab)
        if (! sgbuf)
                return -EINVAL;
 
+       if (dmab->area)
+               vunmap(dmab->area);
+       dmab->area = NULL;
+
        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;
-               tmpb.bytes = PAGE_SIZE;
+               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);
@@ -58,13 +61,17 @@ int snd_free_sgbuf_pages(struct snd_dma_buffer *dmab)
        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;
+       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;
@@ -74,31 +81,55 @@ void *snd_malloc_sgbuf_pages(struct device *device,
        sgbuf->dev = device;
        pages = snd_sgbuf_aligned_pages(size);
        sgbuf->tblsize = sgbuf_align_table(pages);
-       sgbuf->table = kcalloc(sgbuf->tblsize, sizeof(*sgbuf->table), GFP_KERNEL);
-       if (! sgbuf->table)
+       table = kcalloc(sgbuf->tblsize, sizeof(*table), GFP_KERNEL);
+       if (!table)
                goto _failed;
-       sgbuf->page_table = kcalloc(sgbuf->tblsize, sizeof(*sgbuf->page_table), GFP_KERNEL);
-       if (! sgbuf->page_table)
+       sgbuf->table = table;
+       pgtable = kcalloc(sgbuf->tblsize, sizeof(*pgtable), GFP_KERNEL);
+       if (!pgtable)
                goto _failed;
+       sgbuf->page_table = pgtable;
 
-       /* allocate each page */
-       for (i = 0; i < pages; i++) {
-               if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, device, PAGE_SIZE, &tmpb) < 0) {
-                       if (res_size == NULL)
+       /* 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;
-                       *res_size = size = sgbuf->pages * PAGE_SIZE;
+                       size = sgbuf->pages * PAGE_SIZE;
                        break;
                }
-               sgbuf->table[i].buf = tmpb.area;
-               sgbuf->table[i].addr = tmpb.addr;
-               sgbuf->page_table[i] = virt_to_page(tmpb.area);
-               sgbuf->pages++;
+               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: