linux/sound/core/sgbuf.c
<<
>>
Prefs
   1/*
   2 * Scatter-Gather buffer
   3 *
   4 *  Copyright (c) by Takashi Iwai <tiwai@suse.de>
   5 *
   6 *   This program is free software; you can redistribute it and/or modify
   7 *   it under the terms of the GNU General Public License as published by
   8 *   the Free Software Foundation; either version 2 of the License, or
   9 *   (at your option) any later version.
  10 *
  11 *   This program is distributed in the hope that it will be useful,
  12 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
  13 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14 *   GNU General Public License for more details.
  15 *
  16 *   You should have received a copy of the GNU General Public License
  17 *   along with this program; if not, write to the Free Software
  18 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
  19 *
  20 */
  21
  22#include <linux/slab.h>
  23#include <linux/mm.h>
  24#include <linux/vmalloc.h>
  25#include <linux/export.h>
  26#include <sound/memalloc.h>
  27
  28
  29/* table entries are align to 32 */
  30#define SGBUF_TBL_ALIGN         32
  31#define sgbuf_align_table(tbl)  ALIGN((tbl), SGBUF_TBL_ALIGN)
  32
  33int snd_free_sgbuf_pages(struct snd_dma_buffer *dmab)
  34{
  35        struct snd_sg_buf *sgbuf = dmab->private_data;
  36        struct snd_dma_buffer tmpb;
  37        int i;
  38
  39        if (! sgbuf)
  40                return -EINVAL;
  41
  42        if (dmab->area)
  43                vunmap(dmab->area);
  44        dmab->area = NULL;
  45
  46        tmpb.dev.type = SNDRV_DMA_TYPE_DEV;
  47        tmpb.dev.dev = sgbuf->dev;
  48        for (i = 0; i < sgbuf->pages; i++) {
  49                if (!(sgbuf->table[i].addr & ~PAGE_MASK))
  50                        continue; /* continuous pages */
  51                tmpb.area = sgbuf->table[i].buf;
  52                tmpb.addr = sgbuf->table[i].addr & PAGE_MASK;
  53                tmpb.bytes = (sgbuf->table[i].addr & ~PAGE_MASK) << PAGE_SHIFT;
  54                snd_dma_free_pages(&tmpb);
  55        }
  56
  57        kfree(sgbuf->table);
  58        kfree(sgbuf->page_table);
  59        kfree(sgbuf);
  60        dmab->private_data = NULL;
  61        
  62        return 0;
  63}
  64
  65#define MAX_ALLOC_PAGES         32
  66
  67void *snd_malloc_sgbuf_pages(struct device *device,
  68                             size_t size, struct snd_dma_buffer *dmab,
  69                             size_t *res_size)
  70{
  71        struct snd_sg_buf *sgbuf;
  72        unsigned int i, pages, chunk, maxpages;
  73        struct snd_dma_buffer tmpb;
  74        struct snd_sg_page *table;
  75        struct page **pgtable;
  76
  77        dmab->area = NULL;
  78        dmab->addr = 0;
  79        dmab->private_data = sgbuf = kzalloc(sizeof(*sgbuf), GFP_KERNEL);
  80        if (! sgbuf)
  81                return NULL;
  82        sgbuf->dev = device;
  83        pages = snd_sgbuf_aligned_pages(size);
  84        sgbuf->tblsize = sgbuf_align_table(pages);
  85        table = kcalloc(sgbuf->tblsize, sizeof(*table), GFP_KERNEL);
  86        if (!table)
  87                goto _failed;
  88        sgbuf->table = table;
  89        pgtable = kcalloc(sgbuf->tblsize, sizeof(*pgtable), GFP_KERNEL);
  90        if (!pgtable)
  91                goto _failed;
  92        sgbuf->page_table = pgtable;
  93
  94        /* allocate pages */
  95        maxpages = MAX_ALLOC_PAGES;
  96        while (pages > 0) {
  97                chunk = pages;
  98                /* don't be too eager to take a huge chunk */
  99                if (chunk > maxpages)
 100                        chunk = maxpages;
 101                chunk <<= PAGE_SHIFT;
 102                if (snd_dma_alloc_pages_fallback(SNDRV_DMA_TYPE_DEV, device,
 103                                                 chunk, &tmpb) < 0) {
 104                        if (!sgbuf->pages)
 105                                goto _failed;
 106                        if (!res_size)
 107                                goto _failed;
 108                        size = sgbuf->pages * PAGE_SIZE;
 109                        break;
 110                }
 111                chunk = tmpb.bytes >> PAGE_SHIFT;
 112                for (i = 0; i < chunk; i++) {
 113                        table->buf = tmpb.area;
 114                        table->addr = tmpb.addr;
 115                        if (!i)
 116                                table->addr |= chunk; /* mark head */
 117                        table++;
 118                        *pgtable++ = virt_to_page(tmpb.area);
 119                        tmpb.area += PAGE_SIZE;
 120                        tmpb.addr += PAGE_SIZE;
 121                }
 122                sgbuf->pages += chunk;
 123                pages -= chunk;
 124                if (chunk < maxpages)
 125                        maxpages = chunk;
 126        }
 127
 128        sgbuf->size = size;
 129        dmab->area = vmap(sgbuf->page_table, sgbuf->pages, VM_MAP, PAGE_KERNEL);
 130        if (! dmab->area)
 131                goto _failed;
 132        if (res_size)
 133                *res_size = sgbuf->size;
 134        return dmab->area;
 135
 136 _failed:
 137        snd_free_sgbuf_pages(dmab); /* free the table */
 138        return NULL;
 139}
 140
 141/*
 142 * compute the max chunk size with continuous pages on sg-buffer
 143 */
 144unsigned int snd_sgbuf_get_chunk_size(struct snd_dma_buffer *dmab,
 145                                      unsigned int ofs, unsigned int size)
 146{
 147        struct snd_sg_buf *sg = dmab->private_data;
 148        unsigned int start, end, pg;
 149
 150        start = ofs >> PAGE_SHIFT;
 151        end = (ofs + size - 1) >> PAGE_SHIFT;
 152        /* check page continuity */
 153        pg = sg->table[start].addr >> PAGE_SHIFT;
 154        for (;;) {
 155                start++;
 156                if (start > end)
 157                        break;
 158                pg++;
 159                if ((sg->table[start].addr >> PAGE_SHIFT) != pg)
 160                        return (start << PAGE_SHIFT) - ofs;
 161        }
 162        /* ok, all on continuous pages */
 163        return size;
 164}
 165EXPORT_SYMBOL(snd_sgbuf_get_chunk_size);
 166