| /** |
| * Copyright (c) 2011 - 2019, Nordic Semiconductor ASA |
| * |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without modification, |
| * are permitted provided that the following conditions are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright notice, this |
| * list of conditions and the following disclaimer. |
| * |
| * 2. Redistributions in binary form, except as embedded into a Nordic |
| * Semiconductor ASA integrated circuit in a product or a software update for |
| * such product, must reproduce the above copyright notice, this list of |
| * conditions and the following disclaimer in the documentation and/or other |
| * materials provided with the distribution. |
| * |
| * 3. Neither the name of Nordic Semiconductor ASA nor the names of its |
| * contributors may be used to endorse or promote products derived from this |
| * software without specific prior written permission. |
| * |
| * 4. This software, with or without modification, must only be used with a |
| * Nordic Semiconductor ASA integrated circuit. |
| * |
| * 5. Any software provided in binary form under this license must not be reverse |
| * engineered, decompiled, modified and/or disassembled. |
| * |
| * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS |
| * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
| * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE |
| * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE |
| * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
| * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT |
| * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| * |
| */ |
| |
| /** |
| * @file |
| * @brief Atomic FIFO internal file |
| * |
| * This file should be included only by nrf_atfifo internally. |
| * Needs nrf_atfifo.h included first. |
| */ |
| #ifndef NRF_ATFIFO_H__ |
| #error This is internal file. Do not include this file in your program. |
| #endif |
| |
| #ifndef NRF_ATFIFO_INTERNAL_H__ |
| #define NRF_ATFIFO_INTERNAL_H__ |
| #include <stddef.h> |
| #include "nrf.h" |
| #include "app_util.h" |
| #include "nordic_common.h" |
| |
| #if ((__CORTEX_M >= 0x03U) || (__CORTEX_SC >= 300U)) == 0 |
| #error Unsupported core version |
| #endif |
| |
| /* |
| * Make sure that rd and wr pos in a tag are aligned like expected |
| * Changing this would require changes inside assembly code! |
| */ |
| STATIC_ASSERT(offsetof(nrf_atfifo_postag_pos_t, wr) == 0); |
| STATIC_ASSERT(offsetof(nrf_atfifo_postag_pos_t, rd) == 2); |
| |
| /** |
| * @brief Atomically reserve space for a new write. |
| * |
| * @param[in,out] p_fifo FIFO object. |
| * @param[out] old_tail Tail position tag before new space is reserved. |
| * |
| * @retval true Space available. |
| * @retval false Memory full. |
| * |
| * @sa nrf_atfifo_wspace_close |
| */ |
| static bool nrf_atfifo_wspace_req(nrf_atfifo_t * const p_fifo, nrf_atfifo_postag_t * const p_old_tail); |
| |
| /** |
| * @brief Atomically mark all written data available. |
| * |
| * This function marks all data available for reading. |
| * This marking is done by copying tail.pos.wr into tail.pos.rd. |
| * |
| * It must be called only when closing the first write. |
| * It cannot be called if any write access was interrupted. |
| * See the code below: |
| * @code |
| * if (old_tail.pos.wr == old_tail.pos.rd) |
| * { |
| * nrf_atfifo_wspace_close(my_fifo); |
| * return true; |
| * } |
| * return false; |
| * @endcode |
| * |
| * @param[in,out] p_fifo FIFO object. |
| * |
| * @sa nrf_atfifo_wspace_req |
| */ |
| static void nrf_atfifo_wspace_close(nrf_atfifo_t * const p_fifo); |
| |
| /** |
| * @brief Atomically get a part of a buffer to read data. |
| * |
| * @param[in,out] p_fifo FIFO object. |
| * @param[out] old_head Head position tag before the data buffer is read. |
| * |
| * @retval true Data available for reading. |
| * @retval false No data in the buffer. |
| * |
| * @sa nrf_atfifo_rspace_close |
| */ |
| static bool nrf_atfifo_rspace_req(nrf_atfifo_t * const p_fifo, nrf_atfifo_postag_t * const p_old_head); |
| |
| /** |
| * @brief Atomically release all read data. |
| * |
| * This function marks all data that was read as free space, |
| * which is available for writing. |
| * This marking is done by copying head.pos.rd into head.pos.wr. |
| * |
| * It must be called only when closing the first read. |
| * It cannot be called when the current read access interrupted any other read access. |
| * See code below: |
| * @code |
| * if (old_head.pos.wr == old_head.pos.rd) |
| * { |
| * nrf_atfifo_rspace_close(my_fifo); |
| * return true; |
| * } |
| * return false; |
| * @endcode |
| * |
| * @param[in,out] p_fifo FIFO object. |
| * |
| * @sa nrf_atfifo_rspace_req |
| */ |
| static void nrf_atfifo_rspace_close(nrf_atfifo_t * const p_fifo); |
| |
| /** |
| * @brief Safely clear the FIFO, internal function. |
| * |
| * This function realizes the functionality required by @ref nrf_atfifo_clear. |
| * |
| * @param[in,out] p_fifo FIFO object. |
| * |
| * @retval true All the data was released. |
| * @retval false All the data available for releasing was released, but there is some pending transfer. |
| */ |
| static bool nrf_atfifo_space_clear(nrf_atfifo_t * const p_fifo); |
| |
| |
| /* --------------------------------------------------------------------------- |
| * Implementation starts here |
| */ |
| |
| #if defined ( __CC_ARM ) |
| |
| |
| __ASM bool nrf_atfifo_wspace_req(nrf_atfifo_t * const p_fifo, nrf_atfifo_postag_t * const p_old_tail) |
| { |
| /* Registry usage: |
| * R0 - p_fifo |
| * R1 - p_old_tail |
| * R2 - internal variable old_tail (saved by caller) |
| * R3 - internal variable new_tail (saved by caller) |
| * R4 - internal temporary register (saved by this function) |
| * R5 - not used stored to keep the stack aligned to 8 bytes |
| * Returned value: |
| * R0 (bool - 32 bits) |
| */ |
| push {r4, r5} |
| nrf_atfifo_wspace_req_repeat |
| /* Load tail tag and set memory monitor !!! R2 - old tail !!! */ |
| ldrex r2, [r0, #__cpp(offsetof(nrf_atfifo_t, tail))] |
| /* Extract write position !!! R3 !!! */ |
| uxth r3, r2 |
| /* Increment address with overload support !!! R4 used temporary !!! */ |
| ldrh r4, [r0, #__cpp(offsetof(nrf_atfifo_t, item_size))] |
| add r3, r4 |
| ldrh r4, [r0, #__cpp(offsetof(nrf_atfifo_t, buf_size))] |
| cmp r3, r4 |
| it hs |
| subhs r3, r3, r4 |
| |
| /* Check if FIFO would overload after making this increment !!! R4 used temporary !!! */ |
| ldrh r4, [r0, #__cpp(offsetof(nrf_atfifo_t, head) + offsetof(nrf_atfifo_postag_pos_t, wr))] |
| cmp r3, r4 |
| ittt eq |
| clrexeq |
| moveq r0, #__cpp(false) |
| beq nrf_atfifo_wspace_req_exit |
| |
| /* Pack everything back !!! R3 - new tail !!! */ |
| /* Copy lower byte from new_tail, and higher byte is a value from the top of old_tail */ |
| pkhbt r3, r3, r2 |
| |
| /* Store new value clearing memory monitor !!! R4 used temporary !!! */ |
| strex r4, r3, [r0, #__cpp(offsetof(nrf_atfifo_t, tail))] |
| cmp r4, #0 |
| bne nrf_atfifo_wspace_req_repeat |
| |
| /* Return true */ |
| mov r0, #__cpp(true) |
| nrf_atfifo_wspace_req_exit |
| /* Save old tail */ |
| str r2, [r1] |
| pop {r4, r5} |
| bx lr |
| } |
| |
| |
| __ASM void nrf_atfifo_wspace_close(nrf_atfifo_t * const p_fifo) |
| { |
| /* Registry usage: |
| * R0 - p_fifo |
| * R1 - internal temporary register |
| * R2 - new_tail |
| */ |
| nrf_atfifo_wspace_close_repeat |
| ldrex r2, [r0, #__cpp(offsetof(nrf_atfifo_t, tail))] |
| /* Copy from lower byte to higher */ |
| pkhbt r2, r2, r2, lsl #16 |
| |
| strex r1, r2, [r0, #__cpp(offsetof(nrf_atfifo_t, tail))] |
| cmp r1, #0 |
| bne nrf_atfifo_wspace_close_repeat |
| bx lr |
| } |
| |
| |
| __ASM bool nrf_atfifo_rspace_req(nrf_atfifo_t * const p_fifo, nrf_atfifo_postag_t * const p_old_head) |
| { |
| /* Registry usage: |
| * R0 - p_fifo |
| * R1 - p_old_head |
| * R2 - internal variable old_head (saved by caller) |
| * R3 - internal variable new_head (saved by caller) |
| * R4 - internal temporary register (saved by this function) |
| * R5 - not used stored to keep the stack aligned to 8 bytes |
| * Returned value: |
| * R0 (bool - 32 bits) |
| */ |
| push {r4, r5} |
| nrf_atfifo_rspace_req_repeat |
| /* Load tail tag and set memory monitor !!! R2 - old tail !!! */ |
| ldrex r2, [r0, #__cpp(offsetof(nrf_atfifo_t, head))] |
| /* Extract read position !!! R3 !!! */ |
| uxth r3, r2, ror #16 |
| |
| /* Check if we have any data !!! R4 used temporary !!! */ |
| ldrh r4, [r0, #__cpp(offsetof(nrf_atfifo_t, tail) + offsetof(nrf_atfifo_postag_pos_t, rd))] |
| cmp r3, r4 |
| ittt eq |
| clrexeq |
| moveq r0, #__cpp(false) |
| beq nrf_atfifo_rspace_req_exit |
| |
| /* Increment address with overload support !!! R4 used temporary !!! */ |
| ldrh r4, [r0, #__cpp(offsetof(nrf_atfifo_t, item_size))] |
| add r3, r4 |
| ldrh r4, [r0, #__cpp(offsetof(nrf_atfifo_t, buf_size))] |
| cmp r3, r4 |
| it hs |
| subhs r3, r3, r4 |
| |
| /* Pack everything back !!! R3 - new tail !!! */ |
| /* Copy lower byte from old_head, and higher byte is a value from write_pos */ |
| pkhbt r3, r2, r3, lsl #16 |
| |
| /* Store new value clearing memory monitor !!! R4 used temporary !!! */ |
| strex r4, r3, [r0, #__cpp(offsetof(nrf_atfifo_t, head))] |
| cmp r4, #0 |
| bne nrf_atfifo_rspace_req_repeat |
| |
| /* Return true */ |
| mov r0, #__cpp(true) |
| nrf_atfifo_rspace_req_exit |
| /* Save old head */ |
| str r2, [r1] |
| pop {r4, r5} |
| bx lr |
| } |
| |
| |
| __ASM void nrf_atfifo_rspace_close(nrf_atfifo_t * const p_fifo) |
| { |
| /* Registry usage: |
| * R0 - p_fifo |
| * R1 - internal temporary register |
| * R2 - new_tail |
| */ |
| nrf_atfifo_rspace_close_repeat |
| ldrex r2, [r0, #__cpp(offsetof(nrf_atfifo_t, head))] |
| /* Copy from higher byte to lower */ |
| pkhtb r2, r2, r2, asr #16 |
| |
| strex r1, r2, [r0, #__cpp(offsetof(nrf_atfifo_t, head))] |
| cmp r1, #0 |
| bne nrf_atfifo_rspace_close_repeat |
| bx lr |
| } |
| |
| |
| __ASM bool nrf_atfifo_space_clear(nrf_atfifo_t * const p_fifo) |
| { |
| /* Registry usage: |
| * R0 - p_fifo as input, bool output after |
| * R1 - tail, rd pointer, new_head |
| * R2 - head_old, destroyed when creating new_head |
| * R3 - p_fifo - copy |
| */ |
| mov r3, r0 |
| nrf_atfifo_space_clear_repeat |
| /* Load old head in !!! R2 register !!! and read pointer of tail in !!! R1 register !!! */ |
| ldrex r2, [r3, #__cpp(offsetof(nrf_atfifo_t, head))] |
| ldrh r1, [r3, #__cpp(offsetof(nrf_atfifo_t, tail) + offsetof(nrf_atfifo_postag_pos_t, rd))] |
| cmp r2, r2, ror #16 |
| /* Return false as default */ |
| mov r0, #__cpp(false) |
| /* Create new head in !!! R1 register !!! Data in !!! R2 register broken !!! */ |
| itett ne |
| uxthne r2, r2 |
| orreq r1, r1, r1, lsl #16 |
| orrne r1, r2, r1, lsl #16 |
| |
| /* Skip header test */ |
| bne nrf_atfifo_space_clear_head_test_skip |
| |
| /* Load whole tail and test it !!! R2 used !!! */ |
| ldr r2, [r3, #__cpp(offsetof(nrf_atfifo_t, tail))] |
| cmp r2, r2, ror #16 |
| /* Return true if equal */ |
| it eq |
| moveq r0, #__cpp(true) |
| |
| nrf_atfifo_space_clear_head_test_skip |
| /* Store and test if success !!! R2 used temporary !!! */ |
| strex r2, r1, [r3, #__cpp(offsetof(nrf_atfifo_t, head))] |
| cmp r2, #0 |
| bne nrf_atfifo_space_clear_repeat |
| bx lr |
| } |
| |
| #elif defined ( __ICCARM__ ) || defined ( __GNUC__ ) |
| |
| bool nrf_atfifo_wspace_req(nrf_atfifo_t * const p_fifo, nrf_atfifo_postag_t * const p_old_tail) |
| { |
| volatile bool ret; |
| volatile uint32_t old_tail; |
| uint32_t new_tail; |
| uint32_t temp; |
| |
| __ASM volatile( |
| /* For more comments see Keil version above */ |
| "1: \n" |
| " ldrex %[old_tail], [%[p_fifo], %[offset_tail]] \n" |
| " uxth %[new_tail], %[old_tail] \n" |
| " \n" |
| " ldrh %[temp], [%[p_fifo], %[offset_item_size]] \n" |
| " add %[new_tail], %[temp] \n" |
| " ldrh %[temp], [%[p_fifo], %[offset_buf_size]] \n" |
| " cmp %[new_tail], %[temp] \n" |
| " it hs \n" |
| " subhs %[new_tail], %[new_tail], %[temp] \n" |
| " \n" |
| " ldrh %[temp], [%[p_fifo], %[offset_head_wr]] \n" |
| " cmp %[new_tail], %[temp] \n" |
| " ittt eq \n" |
| " clrexeq \n" |
| " moveq %[ret], %[false_val] \n" |
| " beq.n 2f \n" |
| " \n" |
| " pkhbt %[new_tail], %[new_tail], %[old_tail] \n" |
| " \n" |
| " strex %[temp], %[new_tail], [%[p_fifo], %[offset_tail]] \n" |
| " cmp %[temp], #0 \n" |
| " bne.n 1b \n" |
| " \n" |
| " mov %[ret], %[true_val] \n" |
| "2: \n" |
| : /* Output operands */ |
| [ret] "=r"(ret), |
| [temp] "=&r"(temp), |
| [old_tail]"=&r"(old_tail), |
| [new_tail]"=&r"(new_tail) |
| : /* Input operands */ |
| [p_fifo] "r"(p_fifo), |
| [offset_tail] "J"(offsetof(nrf_atfifo_t, tail)), |
| [offset_head_wr] "J"(offsetof(nrf_atfifo_t, head) + offsetof(nrf_atfifo_postag_pos_t, wr)), |
| [offset_item_size]"J"(offsetof(nrf_atfifo_t, item_size)), |
| [offset_buf_size] "J"(offsetof(nrf_atfifo_t, buf_size)), |
| [true_val] "I"(true), |
| [false_val] "I"(false) |
| : /* Clobbers */ |
| "cc"); |
| |
| p_old_tail->tag = old_tail; |
| UNUSED_VARIABLE(new_tail); |
| UNUSED_VARIABLE(temp); |
| return ret; |
| } |
| |
| |
| void nrf_atfifo_wspace_close(nrf_atfifo_t * const p_fifo) |
| { |
| uint32_t temp; |
| uint32_t new_tail; |
| |
| __ASM volatile( |
| /* For more comments see Keil version above */ |
| "1: \n" |
| " ldrex %[new_tail], [%[p_fifo], %[offset_tail]] \n" |
| " pkhbt %[new_tail],%[new_tail], %[new_tail], lsl #16 \n" |
| " \n" |
| " strex %[temp], %[new_tail], [%[p_fifo], %[offset_tail]] \n" |
| " cmp %[temp], #0 \n" |
| " bne.n 1b \n" |
| : /* Output operands */ |
| [temp] "=&r"(temp), |
| [new_tail] "=&r"(new_tail) |
| : /* Input operands */ |
| [p_fifo] "r"(p_fifo), |
| [offset_tail] "J"(offsetof(nrf_atfifo_t, tail)) |
| : /* Clobbers */ |
| "cc"); |
| |
| UNUSED_VARIABLE(temp); |
| UNUSED_VARIABLE(new_tail); |
| } |
| |
| |
| bool nrf_atfifo_rspace_req(nrf_atfifo_t * const p_fifo, nrf_atfifo_postag_t * const p_old_head) |
| { |
| volatile bool ret; |
| volatile uint32_t old_head; |
| uint32_t new_head; |
| uint32_t temp; |
| |
| __ASM volatile( |
| /* For more comments see Keil version above */ |
| "1: \n" |
| " ldrex %[old_head], [%[p_fifo], %[offset_head]] \n" |
| " uxth %[new_head], %[old_head], ror #16 \n" |
| " \n" |
| " ldrh %[temp], [%[p_fifo], %[offset_tail_rd]] \n" |
| " cmp %[new_head], %[temp] \n" |
| " ittt eq \n" |
| " clrexeq \n" |
| " moveq %[ret], %[false_val] \n" |
| " beq.n 2f \n" |
| " \n" |
| " ldrh %[temp], [%[p_fifo], %[offset_item_size]] \n" |
| " add %[new_head], %[temp] \n" |
| " ldrh %[temp], [%[p_fifo], %[offset_buf_size]] \n" |
| " cmp %[new_head], %[temp] \n" |
| " it hs \n" |
| " subhs %[new_head], %[new_head], %[temp] \n" |
| " \n" |
| " pkhbt %[new_head], %[old_head], %[new_head], lsl #16 \n" |
| " \n" |
| " strex %[temp], %[new_head], [%[p_fifo], %[offset_head]] \n" |
| " cmp %[temp], #0 \n" |
| " bne.n 1b \n" |
| " \n" |
| " mov %[ret], %[true_val] \n" |
| "2: \n" |
| : /* Output operands */ |
| [ret] "=r"(ret), |
| [temp] "=&r"(temp), |
| [old_head]"=&r"(old_head), |
| [new_head]"=&r"(new_head) |
| : /* Input operands */ |
| [p_fifo] "r"(p_fifo), |
| [offset_head] "J"(offsetof(nrf_atfifo_t, head)), |
| [offset_tail_rd] "J"(offsetof(nrf_atfifo_t, tail) + offsetof(nrf_atfifo_postag_pos_t, rd)), |
| [offset_item_size]"J"(offsetof(nrf_atfifo_t, item_size)), |
| [offset_buf_size] "J"(offsetof(nrf_atfifo_t, buf_size)), |
| [true_val] "I"(true), |
| [false_val] "I"(false) |
| : /* Clobbers */ |
| "cc"); |
| |
| p_old_head->tag = old_head; |
| UNUSED_VARIABLE(new_head); |
| UNUSED_VARIABLE(temp); |
| return ret; |
| } |
| |
| |
| void nrf_atfifo_rspace_close(nrf_atfifo_t * const p_fifo) |
| { |
| uint32_t temp; |
| uint32_t new_head; |
| |
| __ASM volatile( |
| /* For more comments see Keil version above */ |
| "1: \n" |
| " ldrex %[new_head], [%[p_fifo], %[offset_head]] \n" |
| " pkhtb %[new_head],%[new_head], %[new_head], asr #16 \n" |
| " \n" |
| " strex %[temp], %[new_head], [%[p_fifo], %[offset_head]] \n" |
| " cmp %[temp], #0 \n" |
| " bne.n 1b \n" |
| : /* Output operands */ |
| [temp] "=&r"(temp), |
| [new_head] "=&r"(new_head) |
| : /* Input operands */ |
| [p_fifo] "r"(p_fifo), |
| [offset_head] "J"(offsetof(nrf_atfifo_t, head)) |
| : /* Clobbers */ |
| "cc"); |
| |
| UNUSED_VARIABLE(temp); |
| UNUSED_VARIABLE(new_head); |
| } |
| |
| |
| bool nrf_atfifo_space_clear(nrf_atfifo_t * const p_fifo) |
| { |
| volatile bool ret; |
| uint32_t old_head; /* This variable is left broken after assembly code finishes */ |
| uint32_t new_head; |
| |
| __ASM volatile( |
| "1: \n" |
| " ldrex %[old_head], [%[p_fifo], %[offset_head]] \n" |
| " ldrh %[new_head], [%[p_fifo], %[offset_tail_rd]] \n" |
| " cmp %[old_head], %[old_head], ror #16 \n" |
| " \n" |
| " mov %[ret], %[false_val] \n" |
| " \n" |
| " itett ne \n" |
| " uxthne %[old_head], %[old_head] \n" |
| " orreq %[new_head], %[new_head], %[new_head], lsl #16 \n" |
| " orrne %[new_head], %[old_head], %[new_head], lsl #16 \n" |
| " \n" |
| " bne.n 2f \n" |
| " \n" |
| " ldr %[old_head], [%[p_fifo], %[offset_tail]] \n" |
| " cmp %[old_head], %[old_head], ror #16 \n" |
| " it eq \n" |
| " moveq %[ret], %[true_val] \n" |
| " \n" |
| "2: \n" |
| " strex %[old_head], %[new_head], [%[p_fifo], %[offset_head]] \n" |
| " cmp %[old_head], #0 \n" |
| " bne.n 1b \n" |
| : /* Output operands */ |
| [ret] "=&r"(ret), |
| [old_head] "=&r"(old_head), |
| [new_head] "=&r"(new_head) |
| : /* Input operands */ |
| [p_fifo] "r"(p_fifo), |
| [offset_head] "J"(offsetof(nrf_atfifo_t, head)), |
| [offset_tail] "J"(offsetof(nrf_atfifo_t, tail)), |
| [offset_tail_rd] "J"(offsetof(nrf_atfifo_t, tail) + offsetof(nrf_atfifo_postag_pos_t, rd)), |
| [true_val] "I"(true), |
| [false_val] "I"(false) |
| : /* Clobbers */ |
| "cc"); |
| |
| UNUSED_VARIABLE(old_head); |
| UNUSED_VARIABLE(new_head); |
| return ret; |
| } |
| |
| #else |
| #error Unsupported compiler |
| #endif |
| |
| #endif /* NRF_ATFIFO_INTERNAL_H__ */ |