Merge back staging AVS changes for v4.21.
[sfrench/cifs-2.6.git] / sound / core / sgbuf.c
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 <asm/pgtable.h>
27 #include <sound/memalloc.h>
28
29
30 /* table entries are align to 32 */
31 #define SGBUF_TBL_ALIGN         32
32 #define sgbuf_align_table(tbl)  ALIGN((tbl), SGBUF_TBL_ALIGN)
33
34 int snd_free_sgbuf_pages(struct snd_dma_buffer *dmab)
35 {
36         struct snd_sg_buf *sgbuf = dmab->private_data;
37         struct snd_dma_buffer tmpb;
38         int i;
39
40         if (! sgbuf)
41                 return -EINVAL;
42
43         vunmap(dmab->area);
44         dmab->area = NULL;
45
46         tmpb.dev.type = SNDRV_DMA_TYPE_DEV;
47         if (dmab->dev.type == SNDRV_DMA_TYPE_DEV_UC_SG)
48                 tmpb.dev.type = SNDRV_DMA_TYPE_DEV_UC;
49         tmpb.dev.dev = sgbuf->dev;
50         for (i = 0; i < sgbuf->pages; i++) {
51                 if (!(sgbuf->table[i].addr & ~PAGE_MASK))
52                         continue; /* continuous pages */
53                 tmpb.area = sgbuf->table[i].buf;
54                 tmpb.addr = sgbuf->table[i].addr & PAGE_MASK;
55                 tmpb.bytes = (sgbuf->table[i].addr & ~PAGE_MASK) << PAGE_SHIFT;
56                 snd_dma_free_pages(&tmpb);
57         }
58
59         kfree(sgbuf->table);
60         kfree(sgbuf->page_table);
61         kfree(sgbuf);
62         dmab->private_data = NULL;
63         
64         return 0;
65 }
66
67 #define MAX_ALLOC_PAGES         32
68
69 void *snd_malloc_sgbuf_pages(struct device *device,
70                              size_t size, struct snd_dma_buffer *dmab,
71                              size_t *res_size)
72 {
73         struct snd_sg_buf *sgbuf;
74         unsigned int i, pages, chunk, maxpages;
75         struct snd_dma_buffer tmpb;
76         struct snd_sg_page *table;
77         struct page **pgtable;
78         int type = SNDRV_DMA_TYPE_DEV;
79         pgprot_t prot = PAGE_KERNEL;
80
81         dmab->area = NULL;
82         dmab->addr = 0;
83         dmab->private_data = sgbuf = kzalloc(sizeof(*sgbuf), GFP_KERNEL);
84         if (! sgbuf)
85                 return NULL;
86         if (dmab->dev.type == SNDRV_DMA_TYPE_DEV_UC_SG) {
87                 type = SNDRV_DMA_TYPE_DEV_UC;
88 #ifdef pgprot_noncached
89                 prot = pgprot_noncached(PAGE_KERNEL);
90 #endif
91         }
92         sgbuf->dev = device;
93         pages = snd_sgbuf_aligned_pages(size);
94         sgbuf->tblsize = sgbuf_align_table(pages);
95         table = kcalloc(sgbuf->tblsize, sizeof(*table), GFP_KERNEL);
96         if (!table)
97                 goto _failed;
98         sgbuf->table = table;
99         pgtable = kcalloc(sgbuf->tblsize, sizeof(*pgtable), GFP_KERNEL);
100         if (!pgtable)
101                 goto _failed;
102         sgbuf->page_table = pgtable;
103
104         /* allocate pages */
105         maxpages = MAX_ALLOC_PAGES;
106         while (pages > 0) {
107                 chunk = pages;
108                 /* don't be too eager to take a huge chunk */
109                 if (chunk > maxpages)
110                         chunk = maxpages;
111                 chunk <<= PAGE_SHIFT;
112                 if (snd_dma_alloc_pages_fallback(type, device,
113                                                  chunk, &tmpb) < 0) {
114                         if (!sgbuf->pages)
115                                 goto _failed;
116                         if (!res_size)
117                                 goto _failed;
118                         size = sgbuf->pages * PAGE_SIZE;
119                         break;
120                 }
121                 chunk = tmpb.bytes >> PAGE_SHIFT;
122                 for (i = 0; i < chunk; i++) {
123                         table->buf = tmpb.area;
124                         table->addr = tmpb.addr;
125                         if (!i)
126                                 table->addr |= chunk; /* mark head */
127                         table++;
128                         *pgtable++ = virt_to_page(tmpb.area);
129                         tmpb.area += PAGE_SIZE;
130                         tmpb.addr += PAGE_SIZE;
131                 }
132                 sgbuf->pages += chunk;
133                 pages -= chunk;
134                 if (chunk < maxpages)
135                         maxpages = chunk;
136         }
137
138         sgbuf->size = size;
139         dmab->area = vmap(sgbuf->page_table, sgbuf->pages, VM_MAP, prot);
140         if (! dmab->area)
141                 goto _failed;
142         if (res_size)
143                 *res_size = sgbuf->size;
144         return dmab->area;
145
146  _failed:
147         snd_free_sgbuf_pages(dmab); /* free the table */
148         return NULL;
149 }
150
151 /*
152  * compute the max chunk size with continuous pages on sg-buffer
153  */
154 unsigned int snd_sgbuf_get_chunk_size(struct snd_dma_buffer *dmab,
155                                       unsigned int ofs, unsigned int size)
156 {
157         struct snd_sg_buf *sg = dmab->private_data;
158         unsigned int start, end, pg;
159
160         start = ofs >> PAGE_SHIFT;
161         end = (ofs + size - 1) >> PAGE_SHIFT;
162         /* check page continuity */
163         pg = sg->table[start].addr >> PAGE_SHIFT;
164         for (;;) {
165                 start++;
166                 if (start > end)
167                         break;
168                 pg++;
169                 if ((sg->table[start].addr >> PAGE_SHIFT) != pg)
170                         return (start << PAGE_SHIFT) - ofs;
171         }
172         /* ok, all on continuous pages */
173         return size;
174 }
175 EXPORT_SYMBOL(snd_sgbuf_get_chunk_size);