1 /*
2  * Copyright (c) 2018-2024 Nordic Semiconductor ASA
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 /**
8  * Memory FIFO permitting enqueue at tail (last) and dequeue from head (first).
9  *
10  * Implemented as a circular queue over buffers. Buffers lie contiguously in
11  * the backing storage.
12  *
13  * Enqueuing is a 2 step procedure: Alloc and commit. We say an allocated
14  * buffer yet to be committed, exists in a limbo state - until committed.
15  * It is only safe to write to a buffer while it is in limbo.
16  *
17  * Invariant: last-index refers to the buffer that is safe to write while in
18  * limbo-state. Outside limbo state, last-index refers one buffer ahead of what
19  * has been enqueued.
20  *
21  * There are essentially two APIs, distinguished by the buffer-type:
22  *  API 1 Value-type   : MFIFO_DEFINE(name1, sizeof(struct foo), cnt1);
23  *  API 2 Pointer-type : MFIFO_DEFINE(name2, sizeof(void *),     cnt2);
24  *
25  * Enqueuing is performed differently between APIs:
26  *         | Allocate               | Commit
27  *   ------+------------------------+----------------------
28  *   API 1 | MFIFO_ENQUEUE_GET      | MFIFO_ENQUEUE
29  *   API 2 | MFIFO_ENQUEUE_IDX_GET  | MFIFO_BY_IDX_ENQUEUE
30  *
31  * TODO: Reduce size: All functions except mfifo_enqueue should not be inline
32  */
33 
34 /**
35  * @brief   Define a Memory FIFO implemented as a circular queue.
36  * @details API 1 and 2.
37  *   Contains one-more buffer than needed.
38  *
39  * TODO: We can avoid string-concat macros below by setting type in
40  *       MFIFO_DEFINE struct or use a typedef. Yes the size of field 'm' may be
41  *       different, but it is trailing and sizeof is not applied here, so it can
42  *       be a flexible array member.
43  */
44 #define MFIFO_DEFINE(name, sz, cnt) \
45 		const struct { \
46 			uint16_t s; /* Stride between elements */ \
47 			uint16_t n; /* Number of buffers */ \
48 		} mfifo_##name = { \
49 			.s = MROUND(sz), \
50 			.n = ((cnt) + 1), \
51 		}; \
52 		static struct { \
53 			uint8_t MALIGN(4) m[MROUND(sz) * ((cnt) + 1)]; \
54 			uint8_t f; /* First. Read index */ \
55 			uint8_t l; /* Last. Write index */ \
56 		} mfifo_fifo_##name;
57 
58 /**
59  * @brief   Initialize an MFIFO to be empty
60  * @details API 1 and 2. An MFIFO is empty if first == last
61  */
62 #define MFIFO_INIT(name) \
63 	mfifo_fifo_##name.f = mfifo_fifo_##name.l = 0
64 
65 /**
66  * @brief   Non-destructive: Allocate buffer from the queue's tail, by index
67  * @details API 2. Used internally by API 1.
68  *   Note that enqueue is split in 2 parts, allocation and commit:
69  *   1. Allocation: Buffer allocation from tail. May fail.
70  *   2. Commit:     If allocation was successful, the enqueue can be committed.
71  *   Committing can not fail, as only allocation can fail.
72  *   Allocation is non-destructive as operations are performed on index copies,
73  *   however the buffer remains in a state of limbo until committed.
74  *   Note: The limbo state opens up a potential race where successive
75  *   un-committed allocations returns the same buffer index.
76  *
77  * @param idx[out]  Index of newly allocated buffer
78  * @return  True if buffer could be allocated; otherwise false
79  */
mfifo_enqueue_idx_get(uint8_t count,uint8_t first,uint8_t last,uint8_t * idx)80 static inline bool mfifo_enqueue_idx_get(uint8_t count, uint8_t first,
81 					 uint8_t last, uint8_t *idx)
82 {
83 	/* Non-destructive: Advance write-index modulo 'count' */
84 	last = last + 1;
85 	if (last == count) {
86 		last = 0U;
87 	}
88 
89 	/* Is queue full?
90 	 * We want to maintain the invariant of emptiness defined by
91 	 * first == last, but we just advanced a copy of the write-index before
92 	 * and may have wrapped. So if first == last the queue is full and we
93 	 * can not continue
94 	 */
95 	if (last == first) {
96 		return false; /* Queue is full */
97 	}
98 
99 	*idx = last; /* Emit the allocated buffer's index */
100 	return true; /* Successfully allocated new buffer */
101 }
102 
103 /**
104  * @brief   Non-destructive: Allocate buffer from named queue
105  * @details API 2.
106  * @param   i[out]  Index of newly allocated buffer
107  * @return  True if buffer could be allocated; otherwise false
108  */
109 #define MFIFO_ENQUEUE_IDX_GET(name, i) \
110 		mfifo_enqueue_idx_get(mfifo_##name.n, mfifo_fifo_##name.f, \
111 				      mfifo_fifo_##name.l, (i))
112 
113 /**
114  * @brief   Commit a previously allocated buffer (=void-ptr)
115  * @details API 2
116  */
mfifo_by_idx_enqueue(uint8_t * fifo,uint8_t size,uint8_t idx,void * mem,uint8_t * last)117 static inline void mfifo_by_idx_enqueue(uint8_t *fifo, uint8_t size,
118 					uint8_t idx, void *mem, uint8_t *last)
119 {
120 	/* API 2: fifo is array of void-ptrs */
121 	void **p = (void **)(fifo + (*last) * size); /* buffer preceding idx */
122 	*p = mem; /* store the payload which for API 2 is only a void-ptr */
123 
124 	cpu_dmb(); /* Ensure data accesses are synchronized */
125 	*last = idx; /* Commit: Update write index */
126 }
127 
128 /**
129  * @brief   Commit a previously allocated buffer (=void-ptr)
130  * @details API 2
131  */
132 #define MFIFO_BY_IDX_ENQUEUE(name, i, mem) \
133 		mfifo_by_idx_enqueue(mfifo_fifo_##name.m, mfifo_##name.s, (i), \
134 				     (mem), &mfifo_fifo_##name.l)
135 
136 /**
137  * @brief   Non-destructive: Allocate buffer from named queue
138  * @details API 1.
139  *   The allocated buffer exists in limbo until committed.
140  *   To commit the enqueue process, mfifo_enqueue() must be called afterwards
141  * @return  Index of newly allocated buffer; only valid if mem != NULL
142  */
mfifo_enqueue_get(uint8_t * fifo,uint8_t size,uint8_t count,uint8_t first,uint8_t last,void ** mem)143 static inline uint8_t mfifo_enqueue_get(uint8_t *fifo, uint8_t size,
144 					uint8_t count, uint8_t first,
145 					uint8_t last, void **mem)
146 {
147 	uint8_t idx;
148 
149 	/* Attempt to allocate new buffer (idx) */
150 	if (!mfifo_enqueue_idx_get(count, first, last, &idx)) {
151 		/* Buffer could not be allocated */
152 		*mem = NULL; /* Signal the failure */
153 		return 0;    /* DontCare */
154 	}
155 
156 	/* We keep idx as the always-one-free, so we return preceding
157 	 * buffer (last). Recall that last has not been updated,
158 	 * so idx != last
159 	 */
160 	*mem = (void *)(fifo + last * size); /* preceding buffer */
161 
162 	return idx;
163 }
164 
165 /**
166  * @brief   Non-destructive: Allocate buffer from named queue
167  * @details API 1.
168  *   The allocated buffer exists in limbo until committed.
169  *   To commit the enqueue process, MFIFO_ENQUEUE() must be called afterwards
170  * @param mem[out] Pointer to newly allocated buffer; NULL if allocation failed
171  * @return Index to the buffer one-ahead of allocated buffer
172  */
173 #define MFIFO_ENQUEUE_GET(name, mem) \
174 		mfifo_enqueue_get(mfifo_fifo_##name.m, mfifo_##name.s, \
175 				  mfifo_##name.n, mfifo_fifo_##name.f, \
176 				  mfifo_fifo_##name.l, (mem))
177 
178 /**
179  * @brief   Atomically commit a previously allocated buffer
180  * @details API 1.
181  *   Destructive update: Update the queue, bringing the allocated buffer out of
182  *   limbo state -- thus completing its enqueue.
183  *   Can not fail.
184  *   The buffer must have been allocated using mfifo_enqueue_idx_get() or
185  *   mfifo_enqueue_get().
186  *
187  * @param idx[in]   Index one-ahead of previously allocated buffer
188  * @param last[out] Write-index
189  */
mfifo_enqueue(uint8_t idx,uint8_t * last)190 static inline void mfifo_enqueue(uint8_t idx, uint8_t *last)
191 {
192 	cpu_dmb(); /* Ensure data accesses are synchronized */
193 	*last = idx; /* Commit: Update write index */
194 }
195 
196 /**
197  * @brief   Atomically commit a previously allocated buffer
198  * @details API 1
199  *   The buffer should have been allocated using MFIFO_ENQUEUE_GET
200  * @param idx[in]  Index one-ahead of previously allocated buffer
201  */
202 #define MFIFO_ENQUEUE(name, idx) \
203 		mfifo_enqueue((idx), &mfifo_fifo_##name.l)
204 
205 /**
206  * @brief Number of available buffers
207  * @details API 1 and 2
208  *   Empty if first == last
209  */
mfifo_avail_count_get(uint8_t count,uint8_t first,uint8_t last)210 static inline uint8_t mfifo_avail_count_get(uint8_t count, uint8_t first,
211 					    uint8_t last)
212 {
213 	if (last >= first) {
214 		return last - first;
215 	} else {
216 		return count - first + last;
217 	}
218 }
219 
220 /**
221  * @brief Number of available buffers
222  * @details API 1 and 2
223  */
224 #define MFIFO_AVAIL_COUNT_GET(name) \
225 		mfifo_avail_count_get(mfifo_##name.n, mfifo_fifo_##name.f, \
226 				      mfifo_fifo_##name.l)
227 
228 /**
229  * @brief Non-destructive peek
230  * @details API 1
231  */
mfifo_dequeue_get(uint8_t * fifo,uint8_t size,uint8_t first,uint8_t last)232 static inline void *mfifo_dequeue_get(uint8_t *fifo, uint8_t size,
233 				      uint8_t first, uint8_t last)
234 {
235 	if (first == last) {
236 		return NULL;
237 	}
238 
239 	/* API 1: fifo is array of some value type */
240 	return (void *)(fifo + first * size);
241 }
242 
243 /**
244  * @details API 1
245  */
246 #define MFIFO_DEQUEUE_GET(name) \
247 		mfifo_dequeue_get(mfifo_fifo_##name.m, mfifo_##name.s, \
248 				   mfifo_fifo_##name.f, mfifo_fifo_##name.l)
249 
250 /**
251  * @brief Non-destructive: Peek at head (oldest) buffer
252  * @details API 2
253  */
mfifo_dequeue_peek(uint8_t * fifo,uint8_t size,uint8_t first,uint8_t last)254 static inline void *mfifo_dequeue_peek(uint8_t *fifo, uint8_t size,
255 				       uint8_t first, uint8_t last)
256 {
257 	if (first == last) {
258 		return NULL; /* Queue is empty */
259 	}
260 
261 	/* API 2: fifo is array of void-ptrs */
262 	return *((void **)(fifo + first * size));
263 }
264 
265 /**
266  * @brief Non-destructive: Peek at head (oldest) buffer
267  * @details API 2
268  */
269 #define MFIFO_DEQUEUE_PEEK(name) \
270 		mfifo_dequeue_peek(mfifo_fifo_##name.m, mfifo_##name.s, \
271 				   mfifo_fifo_##name.f, mfifo_fifo_##name.l)
272 
mfifo_dequeue_iter_get(uint8_t * fifo,uint8_t size,uint8_t count,uint8_t first,uint8_t last,uint8_t * idx)273 static inline void *mfifo_dequeue_iter_get(uint8_t *fifo, uint8_t size,
274 					   uint8_t count, uint8_t first,
275 					   uint8_t last, uint8_t *idx)
276 {
277 	void *p;
278 	uint8_t i;
279 
280 	if (*idx >= count) {
281 		*idx = first;
282 	}
283 
284 	if (*idx == last) {
285 		return NULL;
286 	}
287 
288 	i = *idx + 1;
289 	if (i == count) {
290 		i = 0U;
291 	}
292 
293 	p = (void *)(fifo + (*idx) * size);
294 
295 	*idx = i;
296 
297 	return p;
298 }
299 
300 #define MFIFO_DEQUEUE_ITER_GET(name, idx) \
301 		mfifo_dequeue_iter_get(mfifo_fifo_##name.m, mfifo_##name.s, \
302 				       mfifo_##name.n, mfifo_fifo_##name.f, \
303 				       mfifo_fifo_##name.l, (idx))
304 
305 /**
306  * @brief Dequeue head-buffer from queue of buffers
307  *
308  * @param fifo[in]      Contiguous memory holding the circular queue
309  * @param size[in]      Size of each buffer in circular queue
310  * @param count[in]     Number of buffers in circular queue
311  * @param last[in]      Tail index, Span: [0 .. count-1]
312  * @param first[in,out] Head index, Span: [0 .. count-1]. Will be updated
313  * @return              Head buffer; or NULL if queue was empty
314  */
mfifo_dequeue(uint8_t * fifo,uint8_t size,uint8_t count,uint8_t last,uint8_t * first)315 static inline void *mfifo_dequeue(uint8_t *fifo, uint8_t size, uint8_t count,
316 				  uint8_t last, uint8_t *first)
317 {
318 	uint8_t _first = *first; /* Copy read-index */
319 	void *mem;
320 
321 	/* Queue is empty if first == last */
322 	if (_first == last) {
323 		return NULL;
324 	}
325 
326 	/* Obtain address of head buffer.
327 	 * API 2: fifo is array of void-ptrs
328 	 */
329 	mem = *((void **)(fifo + _first * size));
330 
331 	/* Circular buffer increment read-index modulo 'count' */
332 	_first += 1U;
333 	if (_first == count) {
334 		_first = 0U;
335 	}
336 
337 	*first = _first; /* Write back read-index */
338 
339 	return mem;
340 }
341 
342 /**
343  * @brief   Dequeue head-buffer from named queue of buffers
344  *
345  * @param name[in]  Name-fragment of circular queue
346  * @return          Head buffer; or NULL if queue was empty
347  */
348 #define MFIFO_DEQUEUE(name) \
349 		mfifo_dequeue(mfifo_fifo_##name.m, mfifo_##name.s, \
350 			      mfifo_##name.n, mfifo_fifo_##name.l, \
351 			      &mfifo_fifo_##name.f)
352