18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Scatter-Gather buffer
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci *  Copyright (c) by Takashi Iwai <tiwai@suse.de>
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#include <linux/slab.h>
98c2ecf20Sopenharmony_ci#include <linux/mm.h>
108c2ecf20Sopenharmony_ci#include <linux/vmalloc.h>
118c2ecf20Sopenharmony_ci#include <linux/export.h>
128c2ecf20Sopenharmony_ci#include <sound/memalloc.h>
138c2ecf20Sopenharmony_ci
148c2ecf20Sopenharmony_ci
158c2ecf20Sopenharmony_ci/* table entries are align to 32 */
168c2ecf20Sopenharmony_ci#define SGBUF_TBL_ALIGN		32
178c2ecf20Sopenharmony_ci#define sgbuf_align_table(tbl)	ALIGN((tbl), SGBUF_TBL_ALIGN)
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ciint snd_free_sgbuf_pages(struct snd_dma_buffer *dmab)
208c2ecf20Sopenharmony_ci{
218c2ecf20Sopenharmony_ci	struct snd_sg_buf *sgbuf = dmab->private_data;
228c2ecf20Sopenharmony_ci	struct snd_dma_buffer tmpb;
238c2ecf20Sopenharmony_ci	int i;
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_ci	if (! sgbuf)
268c2ecf20Sopenharmony_ci		return -EINVAL;
278c2ecf20Sopenharmony_ci
288c2ecf20Sopenharmony_ci	vunmap(dmab->area);
298c2ecf20Sopenharmony_ci	dmab->area = NULL;
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_ci	tmpb.dev.type = SNDRV_DMA_TYPE_DEV;
328c2ecf20Sopenharmony_ci	if (dmab->dev.type == SNDRV_DMA_TYPE_DEV_UC_SG)
338c2ecf20Sopenharmony_ci		tmpb.dev.type = SNDRV_DMA_TYPE_DEV_UC;
348c2ecf20Sopenharmony_ci	tmpb.dev.dev = sgbuf->dev;
358c2ecf20Sopenharmony_ci	for (i = 0; i < sgbuf->pages; i++) {
368c2ecf20Sopenharmony_ci		if (!(sgbuf->table[i].addr & ~PAGE_MASK))
378c2ecf20Sopenharmony_ci			continue; /* continuous pages */
388c2ecf20Sopenharmony_ci		tmpb.area = sgbuf->table[i].buf;
398c2ecf20Sopenharmony_ci		tmpb.addr = sgbuf->table[i].addr & PAGE_MASK;
408c2ecf20Sopenharmony_ci		tmpb.bytes = (sgbuf->table[i].addr & ~PAGE_MASK) << PAGE_SHIFT;
418c2ecf20Sopenharmony_ci		snd_dma_free_pages(&tmpb);
428c2ecf20Sopenharmony_ci	}
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_ci	kfree(sgbuf->table);
458c2ecf20Sopenharmony_ci	kfree(sgbuf->page_table);
468c2ecf20Sopenharmony_ci	kfree(sgbuf);
478c2ecf20Sopenharmony_ci	dmab->private_data = NULL;
488c2ecf20Sopenharmony_ci
498c2ecf20Sopenharmony_ci	return 0;
508c2ecf20Sopenharmony_ci}
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_ci#define MAX_ALLOC_PAGES		32
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_civoid *snd_malloc_sgbuf_pages(struct device *device,
558c2ecf20Sopenharmony_ci			     size_t size, struct snd_dma_buffer *dmab,
568c2ecf20Sopenharmony_ci			     size_t *res_size)
578c2ecf20Sopenharmony_ci{
588c2ecf20Sopenharmony_ci	struct snd_sg_buf *sgbuf;
598c2ecf20Sopenharmony_ci	unsigned int i, pages, chunk, maxpages;
608c2ecf20Sopenharmony_ci	struct snd_dma_buffer tmpb;
618c2ecf20Sopenharmony_ci	struct snd_sg_page *table;
628c2ecf20Sopenharmony_ci	struct page **pgtable;
638c2ecf20Sopenharmony_ci	int type = SNDRV_DMA_TYPE_DEV;
648c2ecf20Sopenharmony_ci	pgprot_t prot = PAGE_KERNEL;
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_ci	dmab->area = NULL;
678c2ecf20Sopenharmony_ci	dmab->addr = 0;
688c2ecf20Sopenharmony_ci	dmab->private_data = sgbuf = kzalloc(sizeof(*sgbuf), GFP_KERNEL);
698c2ecf20Sopenharmony_ci	if (! sgbuf)
708c2ecf20Sopenharmony_ci		return NULL;
718c2ecf20Sopenharmony_ci	if (dmab->dev.type == SNDRV_DMA_TYPE_DEV_UC_SG) {
728c2ecf20Sopenharmony_ci		type = SNDRV_DMA_TYPE_DEV_UC;
738c2ecf20Sopenharmony_ci#ifdef pgprot_noncached
748c2ecf20Sopenharmony_ci		prot = pgprot_noncached(PAGE_KERNEL);
758c2ecf20Sopenharmony_ci#endif
768c2ecf20Sopenharmony_ci	}
778c2ecf20Sopenharmony_ci	sgbuf->dev = device;
788c2ecf20Sopenharmony_ci	pages = snd_sgbuf_aligned_pages(size);
798c2ecf20Sopenharmony_ci	sgbuf->tblsize = sgbuf_align_table(pages);
808c2ecf20Sopenharmony_ci	table = kcalloc(sgbuf->tblsize, sizeof(*table), GFP_KERNEL);
818c2ecf20Sopenharmony_ci	if (!table)
828c2ecf20Sopenharmony_ci		goto _failed;
838c2ecf20Sopenharmony_ci	sgbuf->table = table;
848c2ecf20Sopenharmony_ci	pgtable = kcalloc(sgbuf->tblsize, sizeof(*pgtable), GFP_KERNEL);
858c2ecf20Sopenharmony_ci	if (!pgtable)
868c2ecf20Sopenharmony_ci		goto _failed;
878c2ecf20Sopenharmony_ci	sgbuf->page_table = pgtable;
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_ci	/* allocate pages */
908c2ecf20Sopenharmony_ci	maxpages = MAX_ALLOC_PAGES;
918c2ecf20Sopenharmony_ci	while (pages > 0) {
928c2ecf20Sopenharmony_ci		chunk = pages;
938c2ecf20Sopenharmony_ci		/* don't be too eager to take a huge chunk */
948c2ecf20Sopenharmony_ci		if (chunk > maxpages)
958c2ecf20Sopenharmony_ci			chunk = maxpages;
968c2ecf20Sopenharmony_ci		chunk <<= PAGE_SHIFT;
978c2ecf20Sopenharmony_ci		if (snd_dma_alloc_pages_fallback(type, device,
988c2ecf20Sopenharmony_ci						 chunk, &tmpb) < 0) {
998c2ecf20Sopenharmony_ci			if (!sgbuf->pages)
1008c2ecf20Sopenharmony_ci				goto _failed;
1018c2ecf20Sopenharmony_ci			if (!res_size)
1028c2ecf20Sopenharmony_ci				goto _failed;
1038c2ecf20Sopenharmony_ci			size = sgbuf->pages * PAGE_SIZE;
1048c2ecf20Sopenharmony_ci			break;
1058c2ecf20Sopenharmony_ci		}
1068c2ecf20Sopenharmony_ci		chunk = tmpb.bytes >> PAGE_SHIFT;
1078c2ecf20Sopenharmony_ci		for (i = 0; i < chunk; i++) {
1088c2ecf20Sopenharmony_ci			table->buf = tmpb.area;
1098c2ecf20Sopenharmony_ci			table->addr = tmpb.addr;
1108c2ecf20Sopenharmony_ci			if (!i)
1118c2ecf20Sopenharmony_ci				table->addr |= chunk; /* mark head */
1128c2ecf20Sopenharmony_ci			table++;
1138c2ecf20Sopenharmony_ci			*pgtable++ = virt_to_page(tmpb.area);
1148c2ecf20Sopenharmony_ci			tmpb.area += PAGE_SIZE;
1158c2ecf20Sopenharmony_ci			tmpb.addr += PAGE_SIZE;
1168c2ecf20Sopenharmony_ci		}
1178c2ecf20Sopenharmony_ci		sgbuf->pages += chunk;
1188c2ecf20Sopenharmony_ci		pages -= chunk;
1198c2ecf20Sopenharmony_ci		if (chunk < maxpages)
1208c2ecf20Sopenharmony_ci			maxpages = chunk;
1218c2ecf20Sopenharmony_ci	}
1228c2ecf20Sopenharmony_ci
1238c2ecf20Sopenharmony_ci	sgbuf->size = size;
1248c2ecf20Sopenharmony_ci	dmab->area = vmap(sgbuf->page_table, sgbuf->pages, VM_MAP, prot);
1258c2ecf20Sopenharmony_ci	if (! dmab->area)
1268c2ecf20Sopenharmony_ci		goto _failed;
1278c2ecf20Sopenharmony_ci	if (res_size)
1288c2ecf20Sopenharmony_ci		*res_size = sgbuf->size;
1298c2ecf20Sopenharmony_ci	return dmab->area;
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_ci _failed:
1328c2ecf20Sopenharmony_ci	snd_free_sgbuf_pages(dmab); /* free the table */
1338c2ecf20Sopenharmony_ci	return NULL;
1348c2ecf20Sopenharmony_ci}
1358c2ecf20Sopenharmony_ci
1368c2ecf20Sopenharmony_ci/*
1378c2ecf20Sopenharmony_ci * compute the max chunk size with continuous pages on sg-buffer
1388c2ecf20Sopenharmony_ci */
1398c2ecf20Sopenharmony_ciunsigned int snd_sgbuf_get_chunk_size(struct snd_dma_buffer *dmab,
1408c2ecf20Sopenharmony_ci				      unsigned int ofs, unsigned int size)
1418c2ecf20Sopenharmony_ci{
1428c2ecf20Sopenharmony_ci	struct snd_sg_buf *sg = dmab->private_data;
1438c2ecf20Sopenharmony_ci	unsigned int start, end, pg;
1448c2ecf20Sopenharmony_ci
1458c2ecf20Sopenharmony_ci	if (!sg)
1468c2ecf20Sopenharmony_ci		return size;
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_ci	start = ofs >> PAGE_SHIFT;
1498c2ecf20Sopenharmony_ci	end = (ofs + size - 1) >> PAGE_SHIFT;
1508c2ecf20Sopenharmony_ci	/* check page continuity */
1518c2ecf20Sopenharmony_ci	pg = sg->table[start].addr >> PAGE_SHIFT;
1528c2ecf20Sopenharmony_ci	for (;;) {
1538c2ecf20Sopenharmony_ci		start++;
1548c2ecf20Sopenharmony_ci		if (start > end)
1558c2ecf20Sopenharmony_ci			break;
1568c2ecf20Sopenharmony_ci		pg++;
1578c2ecf20Sopenharmony_ci		if ((sg->table[start].addr >> PAGE_SHIFT) != pg)
1588c2ecf20Sopenharmony_ci			return (start << PAGE_SHIFT) - ofs;
1598c2ecf20Sopenharmony_ci	}
1608c2ecf20Sopenharmony_ci	/* ok, all on continuous pages */
1618c2ecf20Sopenharmony_ci	return size;
1628c2ecf20Sopenharmony_ci}
1638c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_sgbuf_get_chunk_size);
164