Branch data Line data Source code
1 : : // SPDX-License-Identifier: GPL-2.0-or-later 2 : : /* 3 : : * Scatter-Gather buffer 4 : : * 5 : : * Copyright (c) by Takashi Iwai <tiwai@suse.de> 6 : : */ 7 : : 8 : : #include <linux/slab.h> 9 : : #include <linux/mm.h> 10 : : #include <linux/vmalloc.h> 11 : : #include <linux/export.h> 12 : : #include <asm/pgtable.h> 13 : : #include <sound/memalloc.h> 14 : : 15 : : 16 : : /* table entries are align to 32 */ 17 : : #define SGBUF_TBL_ALIGN 32 18 : : #define sgbuf_align_table(tbl) ALIGN((tbl), SGBUF_TBL_ALIGN) 19 : : 20 : 0 : int snd_free_sgbuf_pages(struct snd_dma_buffer *dmab) 21 : : { 22 : 0 : struct snd_sg_buf *sgbuf = dmab->private_data; 23 : 0 : struct snd_dma_buffer tmpb; 24 : 0 : int i; 25 : : 26 [ # # ]: 0 : if (! sgbuf) 27 : : return -EINVAL; 28 : : 29 : 0 : vunmap(dmab->area); 30 : 0 : dmab->area = NULL; 31 : : 32 : 0 : tmpb.dev.type = SNDRV_DMA_TYPE_DEV; 33 [ # # ]: 0 : if (dmab->dev.type == SNDRV_DMA_TYPE_DEV_UC_SG) 34 : 0 : tmpb.dev.type = SNDRV_DMA_TYPE_DEV_UC; 35 : 0 : tmpb.dev.dev = sgbuf->dev; 36 [ # # ]: 0 : for (i = 0; i < sgbuf->pages; i++) { 37 [ # # ]: 0 : if (!(sgbuf->table[i].addr & ~PAGE_MASK)) 38 : 0 : continue; /* continuous pages */ 39 : 0 : tmpb.area = sgbuf->table[i].buf; 40 : 0 : tmpb.addr = sgbuf->table[i].addr & PAGE_MASK; 41 : 0 : tmpb.bytes = (sgbuf->table[i].addr & ~PAGE_MASK) << PAGE_SHIFT; 42 : 0 : snd_dma_free_pages(&tmpb); 43 : : } 44 : : 45 : 0 : kfree(sgbuf->table); 46 : 0 : kfree(sgbuf->page_table); 47 : 0 : kfree(sgbuf); 48 : 0 : dmab->private_data = NULL; 49 : : 50 : 0 : return 0; 51 : : } 52 : : 53 : : #define MAX_ALLOC_PAGES 32 54 : : 55 : 0 : void *snd_malloc_sgbuf_pages(struct device *device, 56 : : size_t size, struct snd_dma_buffer *dmab, 57 : : size_t *res_size) 58 : : { 59 : 0 : struct snd_sg_buf *sgbuf; 60 : 0 : unsigned int i, pages, chunk, maxpages; 61 : 0 : struct snd_dma_buffer tmpb; 62 : 0 : struct snd_sg_page *table; 63 : 0 : struct page **pgtable; 64 : 0 : int type = SNDRV_DMA_TYPE_DEV; 65 : 0 : pgprot_t prot = PAGE_KERNEL; 66 : : 67 : 0 : dmab->area = NULL; 68 : 0 : dmab->addr = 0; 69 : 0 : dmab->private_data = sgbuf = kzalloc(sizeof(*sgbuf), GFP_KERNEL); 70 [ # # ]: 0 : if (! sgbuf) 71 : : return NULL; 72 [ # # ]: 0 : if (dmab->dev.type == SNDRV_DMA_TYPE_DEV_UC_SG) { 73 : 0 : type = SNDRV_DMA_TYPE_DEV_UC; 74 : : #ifdef pgprot_noncached 75 [ # # ]: 0 : prot = pgprot_noncached(PAGE_KERNEL); 76 : : #endif 77 : : } 78 : 0 : sgbuf->dev = device; 79 : 0 : pages = snd_sgbuf_aligned_pages(size); 80 : 0 : sgbuf->tblsize = sgbuf_align_table(pages); 81 : 0 : table = kcalloc(sgbuf->tblsize, sizeof(*table), GFP_KERNEL); 82 [ # # ]: 0 : if (!table) 83 : 0 : goto _failed; 84 : 0 : sgbuf->table = table; 85 : 0 : pgtable = kcalloc(sgbuf->tblsize, sizeof(*pgtable), GFP_KERNEL); 86 [ # # ]: 0 : if (!pgtable) 87 : 0 : goto _failed; 88 : 0 : sgbuf->page_table = pgtable; 89 : : 90 : : /* allocate pages */ 91 : 0 : maxpages = MAX_ALLOC_PAGES; 92 [ # # ]: 0 : while (pages > 0) { 93 : 0 : chunk = pages; 94 : : /* don't be too eager to take a huge chunk */ 95 : 0 : if (chunk > maxpages) 96 : : chunk = maxpages; 97 : 0 : chunk <<= PAGE_SHIFT; 98 [ # # ]: 0 : if (snd_dma_alloc_pages_fallback(type, device, 99 : : chunk, &tmpb) < 0) { 100 [ # # ]: 0 : if (!sgbuf->pages) 101 : 0 : goto _failed; 102 [ # # ]: 0 : if (!res_size) 103 : 0 : goto _failed; 104 : 0 : size = sgbuf->pages * PAGE_SIZE; 105 : 0 : break; 106 : : } 107 : 0 : chunk = tmpb.bytes >> PAGE_SHIFT; 108 [ # # ]: 0 : for (i = 0; i < chunk; i++) { 109 : 0 : table->buf = tmpb.area; 110 : 0 : table->addr = tmpb.addr; 111 [ # # ]: 0 : if (!i) 112 : 0 : table->addr |= chunk; /* mark head */ 113 : 0 : table++; 114 [ # # ]: 0 : *pgtable++ = virt_to_page(tmpb.area); 115 : 0 : tmpb.area += PAGE_SIZE; 116 : 0 : tmpb.addr += PAGE_SIZE; 117 : : } 118 : 0 : sgbuf->pages += chunk; 119 : 0 : pages -= chunk; 120 : 0 : if (chunk < maxpages) 121 : : maxpages = chunk; 122 : : } 123 : : 124 : 0 : sgbuf->size = size; 125 : 0 : dmab->area = vmap(sgbuf->page_table, sgbuf->pages, VM_MAP, prot); 126 [ # # ]: 0 : if (! dmab->area) 127 : 0 : goto _failed; 128 [ # # ]: 0 : if (res_size) 129 : 0 : *res_size = sgbuf->size; 130 : 0 : return dmab->area; 131 : : 132 : 0 : _failed: 133 : 0 : snd_free_sgbuf_pages(dmab); /* free the table */ 134 : 0 : return NULL; 135 : : } 136 : : 137 : : /* 138 : : * compute the max chunk size with continuous pages on sg-buffer 139 : : */ 140 : 0 : unsigned int snd_sgbuf_get_chunk_size(struct snd_dma_buffer *dmab, 141 : : unsigned int ofs, unsigned int size) 142 : : { 143 : 0 : struct snd_sg_buf *sg = dmab->private_data; 144 : 0 : unsigned int start, end, pg; 145 : : 146 : 0 : start = ofs >> PAGE_SHIFT; 147 : 0 : end = (ofs + size - 1) >> PAGE_SHIFT; 148 : : /* check page continuity */ 149 : 0 : pg = sg->table[start].addr >> PAGE_SHIFT; 150 : 0 : for (;;) { 151 : 0 : start++; 152 [ # # ]: 0 : if (start > end) 153 : : break; 154 : 0 : pg++; 155 [ # # ]: 0 : if ((sg->table[start].addr >> PAGE_SHIFT) != pg) 156 : 0 : return (start << PAGE_SHIFT) - ofs; 157 : : } 158 : : /* ok, all on continuous pages */ 159 : : return size; 160 : : } 161 : : EXPORT_SYMBOL(snd_sgbuf_get_chunk_size);