This implementation of the FIFO buffer allows accessing the data in the buffer atomically, from the main loop and interrupts, without the need of interrupt locking. The mechanism implemented here can be applied in an application in which access can be distributed between the main loop and any interrupts (with many levels). However, it will not work in an application where the FIFO access is distributed between different tasks in a multithreaded preemptive operating system.
Before you can use an instance of the FIFO, you must first create and initialize it.
The macro NRF_ATFIFO_DEF creates the FIFO buffer using the target data type. It means that the data in the buffer will be properly aligned in memory.
Writing to the FIFO is based on the following scheme:
Other simple instructions are also available, like nrf_atfifo_alloc_put and nrf_atfifo_get_free. They are using memcpy internally and take care of buffer allocation or freeing. In most cases, memcpy is less efficient than direct buffer access, but in simple usage, using them greatly limits code typing overhead.
This FIFO implementation requires the presence of LDREX and STREX instructions. The exclusive access instructions are not available in Cortex M0. It limits the usage of this atomic FIFO implementation to nRF52 only.
The implementation assumes that the possible interruption of FIFO access operation works like a stack. A task has to finish the whole access before exiting. It might be interrupted by another task, but this interrupting task must also finish its access. The following is the underlying mechanism of handling interruptions:
This kind of atomic FIFO access works well if access can be split between the main loop and any interrupts. It will not work however, if the FIFO access is distributed between different tasks in a multithreaded preemptive operating system. The problem with a multithreaded preemptive operating system is that the operation might be interrupted by another operation, which would not be finished before going back to the previous operation. For example:
For a simple FIFO, two indexes are remembered: tail and head. Tail is a pointer that keeps track of the historically last element in a list. Head is a pointer that keeps track of the first free space in a list. Thus, it points to an element just after the last stored value.
In the atomic FIFO implementation, these indices are not simple values. They are nrf_atfifo_postag_t structures. They holds read index and write index, 16 bits each. Together, they create a 32-bit value, which makes them available for single LDREX and STREX instructions. Each operation on this pair of read/write indices is run atomically. Therefore, even if the operation is interrupted while new indices are processed, they are just processed once again.
Every allocation moves only the write pointer, leaving the read pointer intact. During a push operation, if it is detected that the old read pointer equals the old write pointer, the read pointer is moved to the current write pointer. Because the implementation uses LDREX and STREX, it is possible to keep track of the fact that the processing during the copying was interrupted, so no data loss is possible at this point.
The old index is stored in operation context - compare it with nrf_atfifo_item_alloc and nrf_atfifo_item_put.
When data is read from the FIFO, the read pointer is moved in the read/write tag in a tail and the pointer to the data is returned. If some interrupt decides to read another data, then the next piece of data is accessed, but during the free operation, nothing is released because it is detected that the old read position does not equal the write position. When the free operation from the first task is executed, all the currently read data is marked as free. The write part of the tail tag is then changed to be equal to the read part.