Linux Audio

Check our new training course

Embedded Linux Audio

Check our new training course
with Creative Commons CC-BY-SA
lecture materials

Bootlin logo

Elixir Cross Referencer

Loading...
/*
 * Copyright (c) 2018-2019 Nordic Semiconductor ASA
 *
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * Memory FIFO permitting enqueue at tail (last) and dequeue from head (first).
 *
 * Implemented as a circular queue over buffers. Buffers lie contiguously in
 * the backing storage.
 *
 * Enqueuing is a 2 step procedure: Alloc and commit. We say an allocated
 * buffer yet to be committed, exists in a limbo state - until committed.
 * It is only safe to write to a buffer while it is in limbo.
 *
 * Invariant: last-index refers to the buffer that is safe to write while in
 * limbo-state. Outside limbo state, last-index refers one buffer ahead of what
 * has been enqueued.
 *
 * There are essentially two APIs, distinguished by the buffer-type:
 *  API 1 Value-type   : MFIFO_DEFINE(name1, sizeof(struct foo), cnt1);
 *  API 2 Pointer-type : MFIFO_DEFINE(name2, sizeof(void *),     cnt2);
 *
 * Enqueuing is performed differently between APIs:
 *         | Allocate               | Commit
 *   ------+------------------------+----------------------
 *   API 1 | MFIFO_ENQUEUE_GET      | MFIFO_ENQUEUE
 *   API 2 | MFIFO_ENQUEUE_IDX_GET  | MFIFO_BY_IDX_ENQUEUE
 *
 * TODO: Reduce size: All functions except mfifo_enqueue should not be inline
 */

/**
 * @brief   Define a Memory FIFO implemented as a circular queue.
 * @details API 1 and 2.
 *   Contains one-more buffer than needed.
 *
 * TODO: We can avoid string-concat macros below by setting type in
 *       MFIFO_DEFINE struct or use a typedef. Yes the size of field 'm' may be
 *       different, but it is trailing and sizeof is not applied here, so it can
 *       be a flexible array member.
 */
#define MFIFO_DEFINE(name, sz, cnt)                                         \
		struct {                                                    \
			/* TODO: const, optimise RAM use */                 \
			/* TODO: Separate s,n,f,l out into common struct */ \
			uint8_t const s;         /* Stride between elements */ \
			uint8_t const n;         /* Number of buffers */       \
			uint8_t f;               /* First. Read index */       \
			uint8_t l;               /* Last. Write index */       \
			uint8_t MALIGN(4) m[MROUND(sz) * ((cnt) + 1)];         \
		} mfifo_##name = {                                          \
			.n = ((cnt) + 1),                                   \
			.s = MROUND(sz),                                    \
			.f = 0,                                             \
			.l = 0,                                             \
		}

/**
 * @brief   Initialize an MFIFO to be empty
 * @details API 1 and 2. An MFIFO is empty if first == last
 */
#define MFIFO_INIT(name) \
	mfifo_##name.f = mfifo_##name.l = 0

/**
 * @brief   Non-destructive: Allocate buffer from the queue's tail, by index
 * @details API 2. Used internally by API 1.
 *   Note that enqueue is split in 2 parts, allocation and commit:
 *   1. Allocation: Buffer allocation from tail. May fail.
 *   2. Commit:     If allocation was successful, the enqueue can be committed.
 *   Committing can not fail, as only allocation can fail.
 *   Allocation is non-destructive as operations are performed on index copies,
 *   however the buffer remains in a state of limbo until committed.
 *   Note: The limbo state opens up a potential race where successive
 *   un-committed allocations returns the same buffer index.
 *
 * @param idx[out]  Index of newly allocated buffer
 * @return  True if buffer could be allocated; otherwise false
 */
static inline bool mfifo_enqueue_idx_get(uint8_t count, uint8_t first, uint8_t last,
					 uint8_t *idx)
{
	/* Non-destructive: Advance write-index modulo 'count' */
	last = last + 1;
	if (last == count) {
		last = 0U;
	}

	/* Is queue full?
	 * We want to maintain the invariant of emptiness defined by
	 * first == last, but we just advanced a copy of the write-index before
	 * and may have wrapped. So if first == last the queue is full and we
	 * can not continue
	 */
	if (last == first) {
		return false; /* Queue is full */
	}

	*idx = last; /* Emit the allocated buffer's index */
	return true; /* Successfully allocated new buffer */
}

/**
 * @brief   Non-destructive: Allocate buffer from named queue
 * @details API 2.
 * @param   i[out]  Index of newly allocated buffer
 * @return  True if buffer could be allocated; otherwise false
 */
#define MFIFO_ENQUEUE_IDX_GET(name, i) \
		mfifo_enqueue_idx_get(mfifo_##name.n, mfifo_##name.f, \
				      mfifo_##name.l, (i))

/**
 * @brief   Commit a previously allocated buffer (=void-ptr)
 * @details API 2
 */
static inline void mfifo_by_idx_enqueue(uint8_t *fifo, uint8_t size, uint8_t idx,
					void *mem, uint8_t *last)
{
	/* API 2: fifo is array of void-ptrs */
	void **p = (void **)(fifo + (*last) * size); /* buffer preceding idx */
	*p = mem; /* store the payload which for API 2 is only a void-ptr */

	*last = idx; /* Commit: Update write index */
}

/**
 * @brief   Commit a previously allocated buffer (=void-ptr)
 * @details API 2
 */
#define MFIFO_BY_IDX_ENQUEUE(name, i, mem) \
		mfifo_by_idx_enqueue(mfifo_##name.m, mfifo_##name.s, (i), \
				     (mem), &mfifo_##name.l)

/**
 * @brief   Non-destructive: Allocate buffer from named queue
 * @details API 1.
 *   The allocated buffer exists in limbo until committed.
 *   To commit the enqueue process, mfifo_enqueue() must be called afterwards
 * @return  Index of newly allocated buffer; only valid if mem != NULL
 */
static inline uint8_t mfifo_enqueue_get(uint8_t *fifo, uint8_t size, uint8_t count,
				     uint8_t first, uint8_t last, void **mem)
{
	uint8_t idx;

	/* Attempt to allocate new buffer (idx) */
	if (!mfifo_enqueue_idx_get(count, first, last, &idx)) {
		/* Buffer could not be allocated */
		*mem = NULL; /* Signal the failure */
		return 0;    /* DontCare */
	}

	/* We keep idx as the always-one-free, so we return preceding
	 * buffer (last). Recall that last has not been updated,
	 * so idx != last
	 */
	*mem = (void *)(fifo + last * size); /* preceding buffer */

	return idx;
}

/**
 * @brief   Non-destructive: Allocate buffer from named queue
 * @details API 1.
 *   The allocated buffer exists in limbo until committed.
 *   To commit the enqueue process, MFIFO_ENQUEUE() must be called afterwards
 * @param mem[out] Pointer to newly allocated buffer; NULL if allocation failed
 * @return Index to the buffer one-ahead of allocated buffer
 */
#define MFIFO_ENQUEUE_GET(name, mem) \
		mfifo_enqueue_get(mfifo_##name.m, mfifo_##name.s, \
				  mfifo_##name.n, mfifo_##name.f, \
				  mfifo_##name.l, (mem))

/**
 * @brief   Atomically commit a previously allocated buffer
 * @details API 1.
 *   Destructive update: Update the queue, bringing the allocated buffer out of
 *   limbo state -- thus completing its enqueue.
 *   Can not fail.
 *   The buffer must have been allocated using mfifo_enqueue_idx_get() or
 *   mfifo_enqueue_get().
 *
 * @param idx[in]   Index one-ahead of previously allocated buffer
 * @param last[out] Write-index
 */
static inline void mfifo_enqueue(uint8_t idx, uint8_t *last)
{
	*last = idx; /* Commit: Update write index */
}

/**
 * @brief   Atomically commit a previously allocated buffer
 * @details API 1
 *   The buffer should have been allocated using MFIFO_ENQUEUE_GET
 * @param idx[in]  Index one-ahead of previously allocated buffer
 */
#define MFIFO_ENQUEUE(name, idx) \
		mfifo_enqueue((idx), &mfifo_##name.l)

/**
 * @brief Number of available buffers
 * @details API 1 and 2
 *   Empty if first == last
 */
static inline uint8_t mfifo_avail_count_get(uint8_t count, uint8_t first, uint8_t last)
{
	if (last >= first) {
		return last - first;
	} else {
		return count - first + last;
	}
}

/**
 * @brief Number of available buffers
 * @details API 1 and 2
 */
#define MFIFO_AVAIL_COUNT_GET(name) \
		mfifo_avail_count_get(mfifo_##name.n, mfifo_##name.f, \
				      mfifo_##name.l)

/**
 * @brief Non-destructive peek
 * @details API 1
 */
static inline void *mfifo_dequeue_get(uint8_t *fifo, uint8_t size, uint8_t first,
				      uint8_t last)
{
	if (first == last) {
		return NULL;
	}

	/* API 1: fifo is array of some value type */
	return (void *)(fifo + first * size);
}

/**
 * @details API 1
 */
#define MFIFO_DEQUEUE_GET(name) \
		mfifo_dequeue_get(mfifo_##name.m, mfifo_##name.s, \
				   mfifo_##name.f, mfifo_##name.l)

/**
 * @brief Non-destructive: Peek at head (oldest) buffer
 * @details API 2
 */
static inline void *mfifo_dequeue_peek(uint8_t *fifo, uint8_t size, uint8_t first,
				       uint8_t last)
{
	if (first == last) {
		return NULL; /* Queue is empty */
	}

	/* API 2: fifo is array of void-ptrs */
	return *((void **)(fifo + first * size));
}

/**
 * @brief Non-destructive: Peek at head (oldest) buffer
 * @details API 2
 */
#define MFIFO_DEQUEUE_PEEK(name) \
		mfifo_dequeue_peek(mfifo_##name.m, mfifo_##name.s, \
				   mfifo_##name.f, mfifo_##name.l)

static inline void *mfifo_dequeue_iter_get(uint8_t *fifo, uint8_t size, uint8_t count,
					   uint8_t first, uint8_t last, uint8_t *idx)
{
	void *p;
	uint8_t i;

	if (*idx >= count) {
		*idx = first;
	}

	if (*idx == last) {
		return NULL;
	}

	i = *idx + 1;
	if (i == count) {
		i = 0U;
	}

	p = (void *)(fifo + (*idx) * size);

	*idx = i;

	return p;
}

#define MFIFO_DEQUEUE_ITER_GET(name, idx) \
		mfifo_dequeue_iter_get(mfifo_##name.m, mfifo_##name.s, \
				       mfifo_##name.n, mfifo_##name.f, \
				       mfifo_##name.l, (idx))

/**
 * @brief Dequeue head-buffer from queue of buffers
 *
 * @param fifo[in]      Contigous memory holding the circular queue
 * @param size[in]      Size of each buffer in circular queue
 * @param count[in]     Number of buffers in circular queue
 * @param last[in]      Tail index, Span: [0 .. count-1]
 * @param first[in,out] Head index, Span: [0 .. count-1]. Will be updated
 * @return              Head buffer; or NULL if queue was empty
 */
static inline void *mfifo_dequeue(uint8_t *fifo, uint8_t size, uint8_t count,
				  uint8_t last, uint8_t *first)
{
	uint8_t _first = *first; /* Copy read-index */
	void *mem;

	/* Queue is empty if first == last */
	if (_first == last) {
		return NULL;
	}

	/* Obtain address of head buffer.
	 * API 2: fifo is array of void-ptrs
	 */
	mem = *((void **)(fifo + _first * size));

	/* Circular buffer increment read-index modulo 'count' */
	_first += 1U;
	if (_first == count) {
		_first = 0U;
	}

	*first = _first; /* Write back read-index */

	return mem;
}

/**
 * @brief   Dequeue head-buffer from named queue of buffers
 *
 * @param name[in]  Name-fragment of circular queue
 * @return          Head buffer; or NULL if queue was empty
 */
#define MFIFO_DEQUEUE(name) \
		mfifo_dequeue(mfifo_##name.m, mfifo_##name.s, \
			      mfifo_##name.n, mfifo_##name.l, \
			      &mfifo_##name.f)