| /* |
| * 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) ((((tbl) + SGBUF_TBL_ALIGN - 1) / SGBUF_TBL_ALIGN) * 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++) { |
| tmpb.area = sgbuf->table[i].buf; |
| tmpb.addr = sgbuf->table[i].addr; |
| tmpb.bytes = PAGE_SIZE; |
| 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; |
| } |
| |
| 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; |
| struct snd_dma_buffer tmpb; |
| |
| dmab->area = NULL; |
| dmab->addr = 0; |
| dmab->private_data = sgbuf = kmalloc(sizeof(*sgbuf), GFP_KERNEL); |
| if (! sgbuf) |
| return NULL; |
| memset(sgbuf, 0, sizeof(*sgbuf)); |
| sgbuf->dev = device; |
| pages = snd_sgbuf_aligned_pages(size); |
| sgbuf->tblsize = sgbuf_align_table(pages); |
| sgbuf->table = kmalloc(sizeof(*sgbuf->table) * sgbuf->tblsize, GFP_KERNEL); |
| if (! sgbuf->table) |
| goto _failed; |
| memset(sgbuf->table, 0, sizeof(*sgbuf->table) * sgbuf->tblsize); |
| sgbuf->page_table = kmalloc(sizeof(*sgbuf->page_table) * sgbuf->tblsize, GFP_KERNEL); |
| if (! sgbuf->page_table) |
| goto _failed; |
| memset(sgbuf->page_table, 0, sizeof(*sgbuf->page_table) * sgbuf->tblsize); |
| |
| /* 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) |
| goto _failed; |
| *res_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++; |
| } |
| |
| sgbuf->size = size; |
| dmab->area = vmap(sgbuf->page_table, sgbuf->pages, VM_MAP, PAGE_KERNEL); |
| if (! dmab->area) |
| goto _failed; |
| return dmab->area; |
| |
| _failed: |
| snd_free_sgbuf_pages(dmab); /* free the table */ |
| return NULL; |
| } |