dma_fifo.c (7660B)
1// SPDX-License-Identifier: GPL-2.0+ 2/* 3 * DMA-able FIFO implementation 4 * 5 * Copyright (C) 2012 Peter Hurley <peter@hurleysoftware.com> 6 */ 7 8#include <linux/kernel.h> 9#include <linux/slab.h> 10#include <linux/list.h> 11#include <linux/bug.h> 12 13#include "dma_fifo.h" 14 15#ifdef DEBUG_TRACING 16#define df_trace(s, args...) pr_debug(s, ##args) 17#else 18#define df_trace(s, args...) 19#endif 20 21#define FAIL(fifo, condition, format...) ({ \ 22 fifo->corrupt = !!(condition); \ 23 WARN(fifo->corrupt, format); \ 24}) 25 26/* 27 * private helper fn to determine if check is in open interval (lo,hi) 28 */ 29static bool addr_check(unsigned int check, unsigned int lo, unsigned int hi) 30{ 31 return check - (lo + 1) < (hi - 1) - lo; 32} 33 34/** 35 * dma_fifo_init: initialize the fifo to a valid but inoperative state 36 * @fifo: address of in-place "struct dma_fifo" object 37 */ 38void dma_fifo_init(struct dma_fifo *fifo) 39{ 40 memset(fifo, 0, sizeof(*fifo)); 41 INIT_LIST_HEAD(&fifo->pending); 42} 43 44/** 45 * dma_fifo_alloc - initialize and allocate dma_fifo 46 * @fifo: address of in-place "struct dma_fifo" object 47 * @size: 'apparent' size, in bytes, of fifo 48 * @align: dma alignment to maintain (should be at least cpu cache alignment), 49 * must be power of 2 50 * @tx_limit: maximum # of bytes transmissible per dma (rounded down to 51 * multiple of alignment, but at least align size) 52 * @open_limit: maximum # of outstanding dma transactions allowed 53 * @gfp_mask: get_free_pages mask, passed to kmalloc() 54 * 55 * The 'apparent' size will be rounded up to next greater aligned size. 56 * Returns 0 if no error, otherwise an error code 57 */ 58int dma_fifo_alloc(struct dma_fifo *fifo, int size, unsigned int align, 59 int tx_limit, int open_limit, gfp_t gfp_mask) 60{ 61 int capacity; 62 63 if (!is_power_of_2(align) || size < 0) 64 return -EINVAL; 65 66 size = round_up(size, align); 67 capacity = size + align * open_limit + align * DMA_FIFO_GUARD; 68 fifo->data = kmalloc(capacity, gfp_mask); 69 if (!fifo->data) 70 return -ENOMEM; 71 72 fifo->in = 0; 73 fifo->out = 0; 74 fifo->done = 0; 75 fifo->size = size; 76 fifo->avail = size; 77 fifo->align = align; 78 fifo->tx_limit = max_t(int, round_down(tx_limit, align), align); 79 fifo->open = 0; 80 fifo->open_limit = open_limit; 81 fifo->guard = size + align * open_limit; 82 fifo->capacity = capacity; 83 fifo->corrupt = 0; 84 85 return 0; 86} 87 88/** 89 * dma_fifo_free - frees the fifo 90 * @fifo: address of in-place "struct dma_fifo" to free 91 * 92 * Also reinits the fifo to a valid but inoperative state. This 93 * allows the fifo to be reused with a different target requiring 94 * different fifo parameters. 95 */ 96void dma_fifo_free(struct dma_fifo *fifo) 97{ 98 struct dma_pending *pending, *next; 99 100 if (!fifo->data) 101 return; 102 103 list_for_each_entry_safe(pending, next, &fifo->pending, link) 104 list_del_init(&pending->link); 105 kfree(fifo->data); 106 fifo->data = NULL; 107} 108 109/** 110 * dma_fifo_reset - dumps the fifo contents and reinits for reuse 111 * @fifo: address of in-place "struct dma_fifo" to reset 112 */ 113void dma_fifo_reset(struct dma_fifo *fifo) 114{ 115 struct dma_pending *pending, *next; 116 117 if (!fifo->data) 118 return; 119 120 list_for_each_entry_safe(pending, next, &fifo->pending, link) 121 list_del_init(&pending->link); 122 fifo->in = 0; 123 fifo->out = 0; 124 fifo->done = 0; 125 fifo->avail = fifo->size; 126 fifo->open = 0; 127 fifo->corrupt = 0; 128} 129 130/** 131 * dma_fifo_in - copies data into the fifo 132 * @fifo: address of in-place "struct dma_fifo" to write to 133 * @src: buffer to copy from 134 * @n: # of bytes to copy 135 * 136 * Returns the # of bytes actually copied, which can be less than requested if 137 * the fifo becomes full. If < 0, return is error code. 138 */ 139int dma_fifo_in(struct dma_fifo *fifo, const void *src, int n) 140{ 141 int ofs, l; 142 143 if (!fifo->data) 144 return -ENOENT; 145 if (fifo->corrupt) 146 return -ENXIO; 147 148 if (n > fifo->avail) 149 n = fifo->avail; 150 if (n <= 0) 151 return 0; 152 153 ofs = fifo->in % fifo->capacity; 154 l = min(n, fifo->capacity - ofs); 155 memcpy(fifo->data + ofs, src, l); 156 memcpy(fifo->data, src + l, n - l); 157 158 if (FAIL(fifo, addr_check(fifo->done, fifo->in, fifo->in + n) || 159 fifo->avail < n, 160 "fifo corrupt: in:%u out:%u done:%u n:%d avail:%d", 161 fifo->in, fifo->out, fifo->done, n, fifo->avail)) 162 return -ENXIO; 163 164 fifo->in += n; 165 fifo->avail -= n; 166 167 df_trace("in:%u out:%u done:%u n:%d avail:%d", fifo->in, fifo->out, 168 fifo->done, n, fifo->avail); 169 170 return n; 171} 172 173/** 174 * dma_fifo_out_pend - gets address/len of next avail read and marks as pended 175 * @fifo: address of in-place "struct dma_fifo" to read from 176 * @pended: address of structure to fill with read address/len 177 * The data/len fields will be NULL/0 if no dma is pended. 178 * 179 * Returns the # of used bytes remaining in fifo (ie, if > 0, more data 180 * remains in the fifo that was not pended). If < 0, return is error code. 181 */ 182int dma_fifo_out_pend(struct dma_fifo *fifo, struct dma_pending *pended) 183{ 184 unsigned int len, n, ofs, l, limit; 185 186 if (!fifo->data) 187 return -ENOENT; 188 if (fifo->corrupt) 189 return -ENXIO; 190 191 pended->len = 0; 192 pended->data = NULL; 193 pended->out = fifo->out; 194 195 len = fifo->in - fifo->out; 196 if (!len) 197 return -ENODATA; 198 if (fifo->open == fifo->open_limit) 199 return -EAGAIN; 200 201 n = len; 202 ofs = fifo->out % fifo->capacity; 203 l = fifo->capacity - ofs; 204 limit = min_t(unsigned int, l, fifo->tx_limit); 205 if (n > limit) { 206 n = limit; 207 fifo->out += limit; 208 } else if (ofs + n > fifo->guard) { 209 fifo->out += l; 210 fifo->in = fifo->out; 211 } else { 212 fifo->out += round_up(n, fifo->align); 213 fifo->in = fifo->out; 214 } 215 216 df_trace("in: %u out: %u done: %u n: %d len: %u avail: %d", fifo->in, 217 fifo->out, fifo->done, n, len, fifo->avail); 218 219 pended->len = n; 220 pended->data = fifo->data + ofs; 221 pended->next = fifo->out; 222 list_add_tail(&pended->link, &fifo->pending); 223 ++fifo->open; 224 225 if (FAIL(fifo, fifo->open > fifo->open_limit, 226 "past open limit:%d (limit:%d)", 227 fifo->open, fifo->open_limit)) 228 return -ENXIO; 229 if (FAIL(fifo, fifo->out & (fifo->align - 1), 230 "fifo out unaligned:%u (align:%u)", 231 fifo->out, fifo->align)) 232 return -ENXIO; 233 234 return len - n; 235} 236 237/** 238 * dma_fifo_out_complete - marks pended dma as completed 239 * @fifo: address of in-place "struct dma_fifo" which was read from 240 * @complete: address of structure for previously pended dma to mark completed 241 */ 242int dma_fifo_out_complete(struct dma_fifo *fifo, struct dma_pending *complete) 243{ 244 struct dma_pending *pending, *next, *tmp; 245 246 if (!fifo->data) 247 return -ENOENT; 248 if (fifo->corrupt) 249 return -ENXIO; 250 if (list_empty(&fifo->pending) && fifo->open == 0) 251 return -EINVAL; 252 253 if (FAIL(fifo, list_empty(&fifo->pending) != (fifo->open == 0), 254 "pending list disagrees with open count:%d", 255 fifo->open)) 256 return -ENXIO; 257 258 tmp = complete->data; 259 *tmp = *complete; 260 list_replace(&complete->link, &tmp->link); 261 dp_mark_completed(tmp); 262 263 /* Only update the fifo in the original pended order */ 264 list_for_each_entry_safe(pending, next, &fifo->pending, link) { 265 if (!dp_is_completed(pending)) { 266 df_trace("still pending: saved out: %u len: %d", 267 pending->out, pending->len); 268 break; 269 } 270 271 if (FAIL(fifo, pending->out != fifo->done || 272 addr_check(fifo->in, fifo->done, pending->next), 273 "in:%u out:%u done:%u saved:%u next:%u", 274 fifo->in, fifo->out, fifo->done, pending->out, 275 pending->next)) 276 return -ENXIO; 277 278 list_del_init(&pending->link); 279 fifo->done = pending->next; 280 fifo->avail += pending->len; 281 --fifo->open; 282 283 df_trace("in: %u out: %u done: %u len: %u avail: %d", fifo->in, 284 fifo->out, fifo->done, pending->len, fifo->avail); 285 } 286 287 if (FAIL(fifo, fifo->open < 0, "open dma:%d < 0", fifo->open)) 288 return -ENXIO; 289 if (FAIL(fifo, fifo->avail > fifo->size, "fifo avail:%d > size:%d", 290 fifo->avail, fifo->size)) 291 return -ENXIO; 292 293 return 0; 294}