11da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds/*
21da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds * Scatter-Gather buffer
31da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *
41da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *  Copyright (c) by Takashi Iwai <tiwai@suse.de>
51da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *
61da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *   This program is free software; you can redistribute it and/or modify
71da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *   it under the terms of the GNU General Public License as published by
81da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *   the Free Software Foundation; either version 2 of the License, or
91da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *   (at your option) any later version.
101da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *
111da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *   This program is distributed in the hope that it will be useful,
121da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *   but WITHOUT ANY WARRANTY; without even the implied warranty of
131da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
141da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *   GNU General Public License for more details.
151da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *
161da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *   You should have received a copy of the GNU General Public License
171da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *   along with this program; if not, write to the Free Software
181da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
191da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds *
201da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds */
211da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
221da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <linux/slab.h>
231da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <linux/mm.h>
241da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <linux/vmalloc.h>
251da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#include <sound/memalloc.h>
261da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
271da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
281da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds/* table entries are align to 32 */
291da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds#define SGBUF_TBL_ALIGN		32
307ab399262ee636d19db5163a35ac406d5b892a0aClemens Ladisch#define sgbuf_align_table(tbl)	ALIGN((tbl), SGBUF_TBL_ALIGN)
311da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
321da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsint snd_free_sgbuf_pages(struct snd_dma_buffer *dmab)
331da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
341da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	struct snd_sg_buf *sgbuf = dmab->private_data;
351da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	struct snd_dma_buffer tmpb;
361da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	int i;
371da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
381da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	if (! sgbuf)
391da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		return -EINVAL;
401da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
416af845e4eb36fb91b322aaf77ec1cab2220a48adTakashi Iwai	if (dmab->area)
426af845e4eb36fb91b322aaf77ec1cab2220a48adTakashi Iwai		vunmap(dmab->area);
436af845e4eb36fb91b322aaf77ec1cab2220a48adTakashi Iwai	dmab->area = NULL;
446af845e4eb36fb91b322aaf77ec1cab2220a48adTakashi Iwai
451da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	tmpb.dev.type = SNDRV_DMA_TYPE_DEV;
461da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	tmpb.dev.dev = sgbuf->dev;
471da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	for (i = 0; i < sgbuf->pages; i++) {
4851e9f2e665bf2b6a01be275d64c336d942c59a66Takashi Iwai		if (!(sgbuf->table[i].addr & ~PAGE_MASK))
4951e9f2e665bf2b6a01be275d64c336d942c59a66Takashi Iwai			continue; /* continuous pages */
501da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		tmpb.area = sgbuf->table[i].buf;
5151e9f2e665bf2b6a01be275d64c336d942c59a66Takashi Iwai		tmpb.addr = sgbuf->table[i].addr & PAGE_MASK;
5251e9f2e665bf2b6a01be275d64c336d942c59a66Takashi Iwai		tmpb.bytes = (sgbuf->table[i].addr & ~PAGE_MASK) << PAGE_SHIFT;
531da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		snd_dma_free_pages(&tmpb);
541da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	}
551da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
561da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	kfree(sgbuf->table);
571da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	kfree(sgbuf->page_table);
581da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	kfree(sgbuf);
591da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	dmab->private_data = NULL;
601da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
611da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	return 0;
621da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
631da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
6451e9f2e665bf2b6a01be275d64c336d942c59a66Takashi Iwai#define MAX_ALLOC_PAGES		32
6551e9f2e665bf2b6a01be275d64c336d942c59a66Takashi Iwai
661da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvaldsvoid *snd_malloc_sgbuf_pages(struct device *device,
671da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			     size_t size, struct snd_dma_buffer *dmab,
681da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			     size_t *res_size)
691da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds{
701da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	struct snd_sg_buf *sgbuf;
7151e9f2e665bf2b6a01be275d64c336d942c59a66Takashi Iwai	unsigned int i, pages, chunk, maxpages;
721da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	struct snd_dma_buffer tmpb;
7351e9f2e665bf2b6a01be275d64c336d942c59a66Takashi Iwai	struct snd_sg_page *table;
7451e9f2e665bf2b6a01be275d64c336d942c59a66Takashi Iwai	struct page **pgtable;
751da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
761da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	dmab->area = NULL;
771da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	dmab->addr = 0;
7859feddb25f9d925e86ee22596802405788bc050fPanagiotis Issaris	dmab->private_data = sgbuf = kzalloc(sizeof(*sgbuf), GFP_KERNEL);
791da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	if (! sgbuf)
801da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		return NULL;
811da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	sgbuf->dev = device;
821da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	pages = snd_sgbuf_aligned_pages(size);
831da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	sgbuf->tblsize = sgbuf_align_table(pages);
8451e9f2e665bf2b6a01be275d64c336d942c59a66Takashi Iwai	table = kcalloc(sgbuf->tblsize, sizeof(*table), GFP_KERNEL);
8551e9f2e665bf2b6a01be275d64c336d942c59a66Takashi Iwai	if (!table)
861da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		goto _failed;
8751e9f2e665bf2b6a01be275d64c336d942c59a66Takashi Iwai	sgbuf->table = table;
8851e9f2e665bf2b6a01be275d64c336d942c59a66Takashi Iwai	pgtable = kcalloc(sgbuf->tblsize, sizeof(*pgtable), GFP_KERNEL);
8951e9f2e665bf2b6a01be275d64c336d942c59a66Takashi Iwai	if (!pgtable)
901da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		goto _failed;
9151e9f2e665bf2b6a01be275d64c336d942c59a66Takashi Iwai	sgbuf->page_table = pgtable;
921da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
9351e9f2e665bf2b6a01be275d64c336d942c59a66Takashi Iwai	/* allocate pages */
9451e9f2e665bf2b6a01be275d64c336d942c59a66Takashi Iwai	maxpages = MAX_ALLOC_PAGES;
9551e9f2e665bf2b6a01be275d64c336d942c59a66Takashi Iwai	while (pages > 0) {
9651e9f2e665bf2b6a01be275d64c336d942c59a66Takashi Iwai		chunk = pages;
9751e9f2e665bf2b6a01be275d64c336d942c59a66Takashi Iwai		/* don't be too eager to take a huge chunk */
9851e9f2e665bf2b6a01be275d64c336d942c59a66Takashi Iwai		if (chunk > maxpages)
9951e9f2e665bf2b6a01be275d64c336d942c59a66Takashi Iwai			chunk = maxpages;
10051e9f2e665bf2b6a01be275d64c336d942c59a66Takashi Iwai		chunk <<= PAGE_SHIFT;
10151e9f2e665bf2b6a01be275d64c336d942c59a66Takashi Iwai		if (snd_dma_alloc_pages_fallback(SNDRV_DMA_TYPE_DEV, device,
10251e9f2e665bf2b6a01be275d64c336d942c59a66Takashi Iwai						 chunk, &tmpb) < 0) {
10351e9f2e665bf2b6a01be275d64c336d942c59a66Takashi Iwai			if (!sgbuf->pages)
10451e9f2e665bf2b6a01be275d64c336d942c59a66Takashi Iwai				return NULL;
10551e9f2e665bf2b6a01be275d64c336d942c59a66Takashi Iwai			if (!res_size)
1061da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds				goto _failed;
10751e9f2e665bf2b6a01be275d64c336d942c59a66Takashi Iwai			size = sgbuf->pages * PAGE_SIZE;
1081da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds			break;
1091da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		}
11051e9f2e665bf2b6a01be275d64c336d942c59a66Takashi Iwai		chunk = tmpb.bytes >> PAGE_SHIFT;
11151e9f2e665bf2b6a01be275d64c336d942c59a66Takashi Iwai		for (i = 0; i < chunk; i++) {
11251e9f2e665bf2b6a01be275d64c336d942c59a66Takashi Iwai			table->buf = tmpb.area;
11351e9f2e665bf2b6a01be275d64c336d942c59a66Takashi Iwai			table->addr = tmpb.addr;
11451e9f2e665bf2b6a01be275d64c336d942c59a66Takashi Iwai			if (!i)
11551e9f2e665bf2b6a01be275d64c336d942c59a66Takashi Iwai				table->addr |= chunk; /* mark head */
11651e9f2e665bf2b6a01be275d64c336d942c59a66Takashi Iwai			table++;
11751e9f2e665bf2b6a01be275d64c336d942c59a66Takashi Iwai			*pgtable++ = virt_to_page(tmpb.area);
11851e9f2e665bf2b6a01be275d64c336d942c59a66Takashi Iwai			tmpb.area += PAGE_SIZE;
11951e9f2e665bf2b6a01be275d64c336d942c59a66Takashi Iwai			tmpb.addr += PAGE_SIZE;
12051e9f2e665bf2b6a01be275d64c336d942c59a66Takashi Iwai		}
12151e9f2e665bf2b6a01be275d64c336d942c59a66Takashi Iwai		sgbuf->pages += chunk;
12251e9f2e665bf2b6a01be275d64c336d942c59a66Takashi Iwai		pages -= chunk;
12351e9f2e665bf2b6a01be275d64c336d942c59a66Takashi Iwai		if (chunk < maxpages)
12451e9f2e665bf2b6a01be275d64c336d942c59a66Takashi Iwai			maxpages = chunk;
1251da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	}
1261da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
1271da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	sgbuf->size = size;
1281da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	dmab->area = vmap(sgbuf->page_table, sgbuf->pages, VM_MAP, PAGE_KERNEL);
1291da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	if (! dmab->area)
1301da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds		goto _failed;
13151e9f2e665bf2b6a01be275d64c336d942c59a66Takashi Iwai	if (res_size)
13251e9f2e665bf2b6a01be275d64c336d942c59a66Takashi Iwai		*res_size = sgbuf->size;
1331da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	return dmab->area;
1341da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds
1351da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds _failed:
1361da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	snd_free_sgbuf_pages(dmab); /* free the table */
1371da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds	return NULL;
1381da177e4c3f41524e886b7f1b8a0c1fc7321cacLinus Torvalds}
139