Blame view
sound/core/sgbuf.c
4.2 KB
1da177e4c
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
/* * 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 * */ |
1da177e4c
|
21 22 23 |
#include <linux/slab.h> #include <linux/mm.h> #include <linux/vmalloc.h> |
9d069dc00
|
24 |
#include <linux/export.h> |
1da177e4c
|
25 26 27 28 29 |
#include <sound/memalloc.h> /* table entries are align to 32 */ #define SGBUF_TBL_ALIGN 32 |
7ab399262
|
30 |
#define sgbuf_align_table(tbl) ALIGN((tbl), SGBUF_TBL_ALIGN) |
1da177e4c
|
31 32 33 34 35 36 37 38 39 |
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; |
6af845e4e
|
40 41 42 |
if (dmab->area) vunmap(dmab->area); dmab->area = NULL; |
1da177e4c
|
43 44 45 |
tmpb.dev.type = SNDRV_DMA_TYPE_DEV; tmpb.dev.dev = sgbuf->dev; for (i = 0; i < sgbuf->pages; i++) { |
51e9f2e66
|
46 47 |
if (!(sgbuf->table[i].addr & ~PAGE_MASK)) continue; /* continuous pages */ |
1da177e4c
|
48 |
tmpb.area = sgbuf->table[i].buf; |
51e9f2e66
|
49 50 |
tmpb.addr = sgbuf->table[i].addr & PAGE_MASK; tmpb.bytes = (sgbuf->table[i].addr & ~PAGE_MASK) << PAGE_SHIFT; |
1da177e4c
|
51 52 |
snd_dma_free_pages(&tmpb); } |
1da177e4c
|
53 54 55 56 57 58 59 60 |
kfree(sgbuf->table); kfree(sgbuf->page_table); kfree(sgbuf); dmab->private_data = NULL; return 0; } |
51e9f2e66
|
61 |
#define MAX_ALLOC_PAGES 32 |
1da177e4c
|
62 63 64 65 66 |
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; |
51e9f2e66
|
67 |
unsigned int i, pages, chunk, maxpages; |
1da177e4c
|
68 |
struct snd_dma_buffer tmpb; |
51e9f2e66
|
69 70 |
struct snd_sg_page *table; struct page **pgtable; |
1da177e4c
|
71 72 73 |
dmab->area = NULL; dmab->addr = 0; |
59feddb25
|
74 |
dmab->private_data = sgbuf = kzalloc(sizeof(*sgbuf), GFP_KERNEL); |
1da177e4c
|
75 76 |
if (! sgbuf) return NULL; |
1da177e4c
|
77 78 79 |
sgbuf->dev = device; pages = snd_sgbuf_aligned_pages(size); sgbuf->tblsize = sgbuf_align_table(pages); |
51e9f2e66
|
80 81 |
table = kcalloc(sgbuf->tblsize, sizeof(*table), GFP_KERNEL); if (!table) |
1da177e4c
|
82 |
goto _failed; |
51e9f2e66
|
83 84 85 |
sgbuf->table = table; pgtable = kcalloc(sgbuf->tblsize, sizeof(*pgtable), GFP_KERNEL); if (!pgtable) |
1da177e4c
|
86 |
goto _failed; |
51e9f2e66
|
87 |
sgbuf->page_table = pgtable; |
1da177e4c
|
88 |
|
51e9f2e66
|
89 90 91 92 93 94 95 96 97 98 99 |
/* 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) |
c810f9039
|
100 |
goto _failed; |
51e9f2e66
|
101 |
if (!res_size) |
1da177e4c
|
102 |
goto _failed; |
51e9f2e66
|
103 |
size = sgbuf->pages * PAGE_SIZE; |
1da177e4c
|
104 105 |
break; } |
51e9f2e66
|
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
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; |
1da177e4c
|
121 122 123 124 125 126 |
} sgbuf->size = size; dmab->area = vmap(sgbuf->page_table, sgbuf->pages, VM_MAP, PAGE_KERNEL); if (! dmab->area) goto _failed; |
51e9f2e66
|
127 128 |
if (res_size) *res_size = sgbuf->size; |
1da177e4c
|
129 130 131 132 133 134 |
return dmab->area; _failed: snd_free_sgbuf_pages(dmab); /* free the table */ return NULL; } |
9d069dc00
|
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 |
/* * compute the max chunk size with continuous pages on sg-buffer */ unsigned int snd_sgbuf_get_chunk_size(struct snd_dma_buffer *dmab, unsigned int ofs, unsigned int size) { struct snd_sg_buf *sg = dmab->private_data; 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_sgbuf_get_chunk_size); |