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