From 32e838ddb658bf8e06ae431596bf6707e4769a34 Mon Sep 17 00:00:00 2001 From: "Ing. Jaroslav Safka" Date: Fri, 2 Mar 2018 23:17:32 +0100 Subject: [PATCH 1/2] Add xRingbufferCanRead, xRingbufferCanWrite Add function xRingbufferCanRead & xRingbufferCanWrite to be able use queue sets. Without it is not possible to check to which ringbuffer returned semaphore belongs. --- .../freertos/include/freertos/ringbuf.h | 27 +++++++++ components/freertos/ringbuf.c | 60 ++++++++++++------- 2 files changed, 66 insertions(+), 21 deletions(-) diff --git a/components/freertos/include/freertos/ringbuf.h b/components/freertos/include/freertos/ringbuf.h index 0f23a44e6..8eae4061c 100644 --- a/components/freertos/include/freertos/ringbuf.h +++ b/components/freertos/include/freertos/ringbuf.h @@ -297,6 +297,33 @@ BaseType_t xRingbufferAddToQueueSetRead(RingbufHandle_t ringbuf, QueueSetHandle_ */ BaseType_t xRingbufferAddToQueueSetWrite(RingbufHandle_t ringbuf, QueueSetHandle_t xQueueSet); +/** + * @brief Check if the selected queue set member is the ringbuffer's read semaphore + * + * This API checks if queue set member returned from xQueueSelectFromSet + * is the read semaphore of this ring buffer. If so, this indicates the ring buffer + * has items waiting to be read. + * + * @param ringbuf Ring buffer which should be checked + * @param member Member returned from xQueueSelectFromSet + * + * @return pdTRUE when semaphore belongs to ringbuffer, pdFALSE otherwise. + */ +BaseType_t xRingbufferCanRead(RingbufHandle_t ringbuf, QueueSetMemberHandle_t member); + +/** + * @brief Check if the selected queue set member is the ringbuffer's write semaphore + * + * This API checks if queue set member returned from xQueueSelectFromSet + * is the write semaphore of this ring buffer. If so, this indicates the ring buffer + * has items waiting for write. + * + * @param ringbuf Ring buffer which should be checked + * @param member Member returned from xQueueSelectFromSet + * + * @return pdTRUE when semaphore belongs to ringbuffer, pdFALSE otherwise. + */ +BaseType_t xRingbufferCanWrite(RingbufHandle_t ringbuf, QueueSetMemberHandle_t member); /** * @brief Remove the ringbuffer from a queue set. diff --git a/components/freertos/ringbuf.c b/components/freertos/ringbuf.c index 7cb24a5ca..2f1998bfe 100644 --- a/components/freertos/ringbuf.c +++ b/components/freertos/ringbuf.c @@ -75,11 +75,11 @@ typedef struct { //Calculate space free in the buffer -static int ringbufferFreeMem(ringbuf_t *rb) +static int ringbufferFreeMem(ringbuf_t *rb) { int free_size = rb->free_ptr-rb->write_ptr; if (free_size <= 0) free_size += rb->size; - //Reserve one byte. If we do not do this and the entire buffer is filled, we get a situation + //Reserve one byte. If we do not do this and the entire buffer is filled, we get a situation //where read_ptr == free_ptr, messing up the next calculation. return free_size-1; } @@ -89,19 +89,19 @@ static int ringbufferFreeMem(ringbuf_t *rb) //success, pdFALSE if it can't make the item fit and the calling routine needs to retry //later or fail. //This function by itself is not threadsafe, always call from within a muxed section. -static BaseType_t copyItemToRingbufNoSplit(ringbuf_t *rb, uint8_t *buffer, size_t buffer_size) +static BaseType_t copyItemToRingbufNoSplit(ringbuf_t *rb, uint8_t *buffer, size_t buffer_size) { size_t rbuffer_size; rbuffer_size=(buffer_size+3)&~3; //Payload length, rounded to next 32-bit value configASSERT(((int)rb->write_ptr&3)==0); //write_ptr needs to be 32-bit aligned - configASSERT(rb->write_ptr-(rb->data+rb->size) >= sizeof(buf_entry_hdr_t)); //need to have at least the size + configASSERT(rb->write_ptr-(rb->data+rb->size) >= sizeof(buf_entry_hdr_t)); //need to have at least the size //of a header to the end of the ringbuff size_t rem_len=(rb->data + rb->size) - rb->write_ptr; //length remaining until end of ringbuffer - + //See if we have enough contiguous space to write the buffer. if (rem_len < rbuffer_size + sizeof(buf_entry_hdr_t)) { - //Buffer plus header is not going to fit in the room from wr_pos to the end of the - //ringbuffer... but we're not allowed to split the buffer. We need to fill the + //Buffer plus header is not going to fit in the room from wr_pos to the end of the + //ringbuffer... but we're not allowed to split the buffer. We need to fill the //rest of the ringbuffer with a dummy item so we can place the data at the _start_ of //the ringbuffer.. //First, find out if we actually have enough space at the start of the ringbuffer to @@ -141,7 +141,7 @@ static BaseType_t copyItemToRingbufNoSplit(ringbuf_t *rb, uint8_t *buffer, size_ //The buffer will wrap around if we don't have room for a header anymore. if ((rb->data+rb->size)-rb->write_ptr < sizeof(buf_entry_hdr_t)) { //'Forward' the write buffer until we are at the start of the ringbuffer. - //The read pointer will always be at the start of a full header, which cannot + //The read pointer will always be at the start of a full header, which cannot //exist at the point of the current write pointer, so there's no chance of overtaking //that. rb->write_ptr=rb->data; @@ -154,29 +154,29 @@ static BaseType_t copyItemToRingbufNoSplit(ringbuf_t *rb, uint8_t *buffer, size_ //success, pdFALSE if it can't make the item fit and the calling routine needs to retry //later or fail. //This function by itself is not threadsafe, always call from within a muxed section. -static BaseType_t copyItemToRingbufAllowSplit(ringbuf_t *rb, uint8_t *buffer, size_t buffer_size) +static BaseType_t copyItemToRingbufAllowSplit(ringbuf_t *rb, uint8_t *buffer, size_t buffer_size) { size_t rbuffer_size; rbuffer_size=(buffer_size+3)&~3; //Payload length, rounded to next 32-bit value configASSERT(((int)rb->write_ptr&3)==0); //write_ptr needs to be 32-bit aligned - configASSERT(rb->write_ptr-(rb->data+rb->size) >= sizeof(buf_entry_hdr_t)); //need to have at least the size + configASSERT(rb->write_ptr-(rb->data+rb->size) >= sizeof(buf_entry_hdr_t)); //need to have at least the size //of a header to the end of the ringbuff size_t rem_len=(rb->data + rb->size) - rb->write_ptr; //length remaining until end of ringbuffer - + //See if we have enough contiguous space to write the buffer. if (rem_len < rbuffer_size + sizeof(buf_entry_hdr_t)) { //The buffer can't be contiguously written to the ringbuffer, but needs special handling. Do //that depending on how the ringbuffer is configured. //The code here is also expected to check if the buffer, mangled in whatever way is implemented, //will still fit, and return pdFALSE if that is not the case. - //Buffer plus header is not going to fit in the room from wr_pos to the end of the + //Buffer plus header is not going to fit in the room from wr_pos to the end of the //ringbuffer... we need to split the write in two. //First, see if this will fit at all. if (ringbufferFreeMem(rb) < (sizeof(buf_entry_hdr_t)*2)+rbuffer_size) { //Will not fit. return pdFALSE; } - //Because the code at the end of the function makes sure we always have + //Because the code at the end of the function makes sure we always have //room for a header, this should never assert. configASSERT(rem_len>=sizeof(buf_entry_hdr_t)); //Okay, it should fit. Write everything. @@ -233,7 +233,7 @@ static BaseType_t copyItemToRingbufAllowSplit(ringbuf_t *rb, uint8_t *buffer, si //The buffer will wrap around if we don't have room for a header anymore. if ((rb->data+rb->size)-rb->write_ptr < sizeof(buf_entry_hdr_t)) { //'Forward' the write buffer until we are at the start of the ringbuffer. - //The read pointer will always be at the start of a full header, which cannot + //The read pointer will always be at the start of a full header, which cannot //exist at the point of the current write pointer, so there's no chance of overtaking //that. rb->write_ptr=rb->data; @@ -247,10 +247,10 @@ static BaseType_t copyItemToRingbufAllowSplit(ringbuf_t *rb, uint8_t *buffer, si //success, pdFALSE if it can't make the item fit and the calling routine needs to retry //later or fail. //This function by itself is not threadsafe, always call from within a muxed section. -static BaseType_t copyItemToRingbufByteBuf(ringbuf_t *rb, uint8_t *buffer, size_t buffer_size) +static BaseType_t copyItemToRingbufByteBuf(ringbuf_t *rb, uint8_t *buffer, size_t buffer_size) { size_t rem_len=(rb->data + rb->size) - rb->write_ptr; //length remaining until end of ringbuffer - + //See if we have enough contiguous space to write the buffer. if (rem_len < buffer_size) { //...Nope. Write the data bit that fits. @@ -409,6 +409,24 @@ static void returnItemToRingbufBytebuf(ringbuf_t *rb, void *item) { //Free the read memory. rb->free_ptr=rb->read_ptr; } +/* + Check if the selected queue set member is the ringbuffer's read semaphore +*/ +BaseType_t xRingbufferCanRead(RingbufHandle_t ringbuf, QueueSetMemberHandle_t member) +{ + ringbuf_t *rb=(ringbuf_t *)ringbuf; + configASSERT(rb); + return (rb->items_buffered_sem == member)? pdTRUE : pdFALSE; +} +/* + Check if the selected queue set member is the ringbuffer's write semaphore +*/ +BaseType_t xRingbufferCanWrite(RingbufHandle_t ringbuf, QueueSetMemberHandle_t member) +{ + ringbuf_t *rb=(ringbuf_t *)ringbuf; + configASSERT(rb); + return (rb->free_space_sem == member)? pdTRUE : pdFALSE; +} void xRingbufferPrintInfo(RingbufHandle_t ringbuf) { @@ -631,7 +649,7 @@ BaseType_t xRingbufferSend(RingbufHandle_t ringbuf, void *data, size_t dataSize, } -BaseType_t xRingbufferSendFromISR(RingbufHandle_t ringbuf, void *data, size_t dataSize, BaseType_t *higher_prio_task_awoken) +BaseType_t xRingbufferSendFromISR(RingbufHandle_t ringbuf, void *data, size_t dataSize, BaseType_t *higher_prio_task_awoken) { ringbuf_t *rb=(ringbuf_t *)ringbuf; BaseType_t write_succeeded; @@ -652,7 +670,7 @@ BaseType_t xRingbufferSendFromISR(RingbufHandle_t ringbuf, void *data, size_t da } -static void *xRingbufferReceiveGeneric(RingbufHandle_t ringbuf, size_t *item_size, TickType_t ticks_to_wait, size_t wanted_size) +static void *xRingbufferReceiveGeneric(RingbufHandle_t ringbuf, size_t *item_size, TickType_t ticks_to_wait, size_t wanted_size) { ringbuf_t *rb=(ringbuf_t *)ringbuf; uint8_t *itemData; @@ -685,7 +703,7 @@ void *xRingbufferReceive(RingbufHandle_t ringbuf, size_t *item_size, TickType_t } -void *xRingbufferReceiveFromISR(RingbufHandle_t ringbuf, size_t *item_size) +void *xRingbufferReceiveFromISR(RingbufHandle_t ringbuf, size_t *item_size) { ringbuf_t *rb=(ringbuf_t *)ringbuf; uint8_t *itemData; @@ -717,7 +735,7 @@ void *xRingbufferReceiveUpToFromISR(RingbufHandle_t ringbuf, size_t *item_size, } -void vRingbufferReturnItem(RingbufHandle_t ringbuf, void *item) +void vRingbufferReturnItem(RingbufHandle_t ringbuf, void *item) { ringbuf_t *rb=(ringbuf_t *)ringbuf; portENTER_CRITICAL(&rb->mux); @@ -727,7 +745,7 @@ void vRingbufferReturnItem(RingbufHandle_t ringbuf, void *item) } -void vRingbufferReturnItemFromISR(RingbufHandle_t ringbuf, void *item, BaseType_t *higher_prio_task_awoken) +void vRingbufferReturnItemFromISR(RingbufHandle_t ringbuf, void *item, BaseType_t *higher_prio_task_awoken) { ringbuf_t *rb=(ringbuf_t *)ringbuf; portENTER_CRITICAL_ISR(&rb->mux); From 4bfa30967ff4ef7fff20180436f761e448c119b8 Mon Sep 17 00:00:00 2001 From: Darian Leung Date: Thu, 19 Apr 2018 01:20:34 +0800 Subject: [PATCH 2/2] freeRTOS/Re-factor ring buffers This fixes multiple bugs with ring buffers and re-factors the code. The public API has not changed, however the underlying implementation have various private functions have been changed. The following behavioral changes have been made - Size of ring buffers for No-Split/Allow-Split buffers will not be rounded up to the nearest 32-bit aligned size. This was done to simplify the implementation - Item size for No-Split/Allow-Split buffers will also be rounded up to the nearest 32-bit aligned size. The following bugs have been fixed - In copyItemToRingbufAllowSplit(), when copying an item where the aligned size is smaller than the remaining length, the function does not consider the case where the true size of the item is less than 4 bytes. - The copy functions will automatically wrap around the write pointers when the remaining length of the buffer is not large enough to fit a header, but does not consider if wrapping around will cause an overlap with the read pointer. This will make a full buffer be mistaken for an empty buffer closes #1711 - xRingbufferSend() can get stuck in a infinite loop when the size of the free memory is larger than the needed_size, but too small to fit in the ring buffer due to alignment and extra overhead of wrapping around. closes #1846 - Fixed documentation with ring buffer queue set API - Adding and removing from queue set does not consider the case where the read/write semaphores actually hold a value. The following functions have been deprecated - xRingbufferIsNextItemWrapped() due to lack of thread safety - xRingbufferAddToQueueSetWrite() and xRingbufferRemoveFromQueueSetWrite() as adding the queue sets only work under receive operations. The following functions have been added - xRingbufferReceiveSplit() and xRingbufferReceiveSplitFromISR() as a thread safe way to receive from allow-split buffers - vRingbufferGetInfo() Documentation for ring buffers has also been added. --- .../freertos/include/freertos/ringbuf.h | 533 ++--- components/freertos/ringbuf.c | 1747 ++++++++++------- components/freertos/test/test_ringbuf.c | 791 ++++++-- .../ring_buffer_read_ret_byte_buf.diag | 30 + .../ring_buffer_read_ret_non_byte_buf.diag | 86 + .../ring_buffer_send_byte_buf.diag | 21 + .../ring_buffer_send_non_byte_buf.diag | 30 + .../ring_buffer_wrap_allow_split.diag | 37 + .../ring_buffer_wrap_byte_buf.diag | 23 + .../ring_buffer_wrap_no_split.diag | 35 + docs/en/api-guides/freertos-smp.rst | 7 +- docs/en/api-reference/system/freertos.rst | 7 +- .../system/freertos_additions.rst | 403 ++++ docs/en/api-reference/system/hooks.rst | 51 - docs/en/api-reference/system/index.rst | 2 +- .../system/freertos_additions.rst | 1 + docs/zh_CN/api-reference/system/hooks.rst | 1 - 17 files changed, 2619 insertions(+), 1186 deletions(-) create mode 100644 docs/_static/diagrams/ring-buffer/ring_buffer_read_ret_byte_buf.diag create mode 100644 docs/_static/diagrams/ring-buffer/ring_buffer_read_ret_non_byte_buf.diag create mode 100644 docs/_static/diagrams/ring-buffer/ring_buffer_send_byte_buf.diag create mode 100644 docs/_static/diagrams/ring-buffer/ring_buffer_send_non_byte_buf.diag create mode 100644 docs/_static/diagrams/ring-buffer/ring_buffer_wrap_allow_split.diag create mode 100644 docs/_static/diagrams/ring-buffer/ring_buffer_wrap_byte_buf.diag create mode 100644 docs/_static/diagrams/ring-buffer/ring_buffer_wrap_no_split.diag create mode 100644 docs/en/api-reference/system/freertos_additions.rst delete mode 100644 docs/en/api-reference/system/hooks.rst create mode 100644 docs/zh_CN/api-reference/system/freertos_additions.rst delete mode 100644 docs/zh_CN/api-reference/system/hooks.rst diff --git a/components/freertos/include/freertos/ringbuf.h b/components/freertos/include/freertos/ringbuf.h index 8eae4061c..de8a36909 100644 --- a/components/freertos/include/freertos/ringbuf.h +++ b/components/freertos/include/freertos/ringbuf.h @@ -1,3 +1,17 @@ +// Copyright 2015-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef FREERTOS_RINGBUF_H #define FREERTOS_RINGBUF_H @@ -11,71 +25,48 @@ extern "C" { #include -//An opaque handle for a ringbuff object. +/** + * Type by which ring buffers are referenced. For example, a call to xRingbufferCreate() + * returns a RingbufHandle_t variable that can then be used as a parameter to + * xRingbufferSend(), xRingbufferReceive(), etc. + */ typedef void * RingbufHandle_t; -/** - * @brief The various types of buffer - * - * A ringbuffer instantiated by these functions essentially acts like a - * FreeRTOS queue, with the difference that it's strictly FIFO and with - * the main advantage that you can put in randomly-sized items. The capacity, - * accordingly, isn't measured in the amount of items, but the amount of - * memory that is used for storing the items. Dependent on the size of - * the items, more or less of them will fit in the ring buffer. - * - * This ringbuffer tries to be efficient with memory: when inserting an item, - * the item data will be copied to the ringbuffer memory. When retrieving - * an item, however, a reference to ringbuffer memory will be returned. - * The returned memory is guaranteed to be 32-bit aligned and contiguous. - * The application can use this memory, but as long as it does, ringbuffer - * writes that would write to this bit of memory will block. - * - * The requirement for items to be contiguous is slightly problematic when - * the only way to place the next item would involve a wraparound from the end - * to the beginning of the ringbuffer. This can be solved (or not) in a few ways, - * see descriptions of possible ringbuf_type_t types below. - * - * The maximum size of an item will be affected by ringbuffer type. - * When split items are allowed, it is acceptable to push items of - * (buffer_size)-16 bytes into the buffer. - * When it's not allowed, the maximum size is (buffer_size/2)-8 bytes. - * The bytebuf can fill the entire buffer with data, it has no overhead. - */ typedef enum { - /** The insertion code will leave the room at the end of the ringbuffer - * unused and instead will put the entire item at the start of the ringbuffer, - * as soon as there is enough free space. + /** + * No-split buffers will only store an item in contiguous memory and will + * never split an item. Each item requires an 8 byte overhead for a header + * and will always internally occupy a 32-bit aligned size of space. */ RINGBUF_TYPE_NOSPLIT = 0, - /** The insertion code will split the item in two items; one which fits - * in the space left at the end of the ringbuffer, one that contains - * the remaining data which is placed in the beginning. - * Two xRingbufferReceive calls will be needed to retrieve the data. + /** + * Allow-split buffers will split an item into two parts if necessary in + * order to store it. Each item requires an 8 byte overhead for a header, + * splitting incurs an extra header. Each item will always internally occupy + * a 32-bit aligned size of space. */ RINGBUF_TYPE_ALLOWSPLIT, - /** This is your conventional byte-based ringbuffer. It does have no - * overhead, but it has no item contiguousness either: a read will just - * give you the entire written buffer space, or the space up to the end - * of the buffer, and writes can be broken up in any way possible. - * Note that this type cannot do a 2nd read before returning the memory - * of the 1st. + /** + * Byte buffers store data as a sequence of bytes and do not maintain separate + * items, therefore byte buffers have no overhead. All data is stored as a + * sequence of byte and any number of bytes can be sent or retrieved each + * time. */ RINGBUF_TYPE_BYTEBUF } ringbuf_type_t; - /** - * @brief Create a ring buffer + * @brief Create a ring buffer * - * @param buf_length Length of circular buffer, in bytes. Each entry will - * take up its own length, plus a header that at the moment - * is equal to sizeof(size_t). - * @param type Type of ring buffer, see ringbuf_type_t. + * @param[in] xBufferSize Size of the buffer in bytes. Note that items require + * space for overhead in no-split/allow-split buffers + * @param[in] xBufferType Type of ring buffer, see documentation. * - * @return A RingbufHandle_t handle to the created ringbuffer, or NULL in case of error. + * @note xBufferSize of no-split/allow-split buffers will be rounded up to the nearest 32-bit aligned size. + * + * @return A handle to the created ring buffer, or NULL in case of error. */ -RingbufHandle_t xRingbufferCreate(size_t buf_length, ringbuf_type_t type); +RingbufHandle_t xRingbufferCreate(size_t xBufferSize, ringbuf_type_t xBufferType); /** * @brief Create a ring buffer of type RINGBUF_TYPE_NOSPLIT for a fixed item_size @@ -83,282 +74,338 @@ RingbufHandle_t xRingbufferCreate(size_t buf_length, ringbuf_type_t type); * This API is similar to xRingbufferCreate(), but it will internally allocate * additional space for the headers. * - * @param item_size Size of each item to be put into the ring buffer - * @param num_item Maximum number of items the buffer needs to hold simultaneously + * @param[in] xItemSize Size of each item to be put into the ring buffer + * @param[in] xItemNum Maximum number of items the buffer needs to hold simultaneously * - * @return A RingbufHandle_t handle to the created ringbuffer, or NULL in case of error. + * @return A RingbufHandle_t handle to the created ring buffer, or NULL in case of error. */ -RingbufHandle_t xRingbufferCreateNoSplit(size_t item_size, size_t num_item); +RingbufHandle_t xRingbufferCreateNoSplit(size_t xItemSize, size_t xItemNum); /** - * @brief Delete a ring buffer + * @brief Insert an item into the ring buffer * - * @param ringbuf Ring buffer to delete - */ -void vRingbufferDelete(RingbufHandle_t ringbuf); - - -/** - * @brief Get maximum size of an item that can be placed in the ring buffer + * Attempt to insert an item into the ring buffer. This function will block until + * enough free space is available or until it timesout. * - * @param ringbuf Ring buffer to query + * @param[in] xRingbuffer Ring buffer to insert the item into + * @param[in] pvItem Pointer to data to insert. NULL is allowed if xItemSize is 0. + * @param[in] xItemSize Size of data to insert. + * @param[in] xTicksToWait Ticks to wait for room in the ring buffer. * - * @return Maximum size, in bytes, of an item that can be placed in a ring buffer. - */ -size_t xRingbufferGetMaxItemSize(RingbufHandle_t ringbuf); - -/** - * @brief Get current free size available in the buffer - * - * This gives the real time free space available in the ring buffer. So basically, - * this will be the maximum size of the entry that can be sent into the buffer. - * - * @note This API is not thread safe. So, if multiple threads are accessing the same - * ring buffer, it is the application's responsibility to ensure atomic access to this - * API and the subsequent Send - * - * @param ringbuf - Ring buffer to query - * - * @return Current free size, in bytes, available for an entry - */ -size_t xRingbufferGetCurFreeSize(RingbufHandle_t ringbuf); - -/** - * @brief Check if the next item is wrapped - * - * This API tells if the next item that is available for a Receive is wrapped - * or not. This is valid only if the ring buffer type is RINGBUF_TYPE_ALLOWSPLIT - * - * @note This API is not thread safe. So, if multiple threads are accessing the same - * ring buffer, it is the application's responsibility to ensure atomic access to this - * API and the subsequent Receive - * - * @param ringbuf - Ring buffer to query - * - * @return true if the next item is wrapped around - * @return false if the next item is not wrapped - */ -bool xRingbufferIsNextItemWrapped(RingbufHandle_t ringbuf); - -/** - * @brief Insert an item into the ring buffer - * - * @param ringbuf Ring buffer to insert the item into - * @param data Pointer to data to insert. NULL is allowed if data_size is 0. - * @param data_size Size of data to insert. A value of 0 is allowed. - * @param ticks_to_wait Ticks to wait for room in the ringbuffer. + * @note For no-split/allow-split ring buffers, the actual size of memory that + * the item will occupy will be rounded up to the nearest 32-bit aligned + * size. This is done to ensure all items are always stored in 32-bit + * aligned fashion. * * @return * - pdTRUE if succeeded - * - pdFALSE on time-out or when the buffer is larger than indicated - * by xRingbufferGetMaxItemSize(ringbuf). + * - pdFALSE on time-out or when the data is larger than the maximum permissible size of the buffer */ -BaseType_t xRingbufferSend(RingbufHandle_t ringbuf, void *data, size_t data_size, TickType_t ticks_to_wait); - +BaseType_t xRingbufferSend(RingbufHandle_t xRingbuffer, const void *pvItem, size_t xItemSize, TickType_t xTicksToWait); /** - * @brief Insert an item into the ring buffer from an ISR + * @brief Insert an item into the ring buffer in an ISR * - * @param ringbuf Ring buffer to insert the item into - * @param data Pointer to data to insert. NULL is allowed if data_size is 0. - * @param data_size Size of data to insert. A value of 0 is allowed. - * @param[out] higher_prio_task_awoken Value pointed to will be set to pdTRUE - * if the push woke up a higher priority task. + * Attempt to insert an item into the ring buffer from an ISR. This function + * will return immediately if there is insufficient free space in the buffer. * - * @return pdTRUE if succeeded, pdFALSE when the ring buffer does not have space. - */ -BaseType_t xRingbufferSendFromISR(RingbufHandle_t ringbuf, void *data, size_t data_size, BaseType_t *higher_prio_task_awoken); - -/** - * @brief Retrieve an item from the ring buffer + * @param[in] xRingbuffer Ring buffer to insert the item into + * @param[in] pvItem Pointer to data to insert. NULL is allowed if xItemSize is 0. + * @param[in] xItemSize Size of data to insert. + * @param[out] pxHigherPriorityTaskWoken Value pointed to will be set to pdTRUE if the function woke up a higher priority task. * - * @note A call to vRingbufferReturnItem() is required after this to free up - * the data received. - * - * @param ringbuf Ring buffer to retrieve the item from - * @param[out] item_size Pointer to a variable to which the size of the - * retrieved item will be written. - * @param ticks_to_wait Ticks to wait for items in the ringbuffer. + * @note For no-split/allow-split ring buffers, the actual size of memory that + * the item will occupy will be rounded up to the nearest 32-bit aligned + * size. This is done to ensure all items are always stored in 32-bit + * aligned fashion. * * @return - * - pointer to the retrieved item on success; *item_size filled with - * the length of the item. - * - NULL on timeout, *item_size is untouched in that case. + * - pdTRUE if succeeded + * - pdFALSE when the ring buffer does not have space. */ -void *xRingbufferReceive(RingbufHandle_t ringbuf, size_t *item_size, TickType_t ticks_to_wait); - +BaseType_t xRingbufferSendFromISR(RingbufHandle_t xRingbuffer, const void *pvItem, size_t xItemSize, BaseType_t *pxHigherPriorityTaskWoken); /** - * @brief Retrieve an item from the ring buffer from an ISR + * @brief Retrieve an item from the ring buffer * - * @note A call to vRingbufferReturnItemFromISR() is required after this to - * free up the data received + * Attempt to retrieve an item from the ring buffer. This function will block + * until an item is available or until it timesout. * - * @param ringbuf Ring buffer to retrieve the item from - * @param[out] item_size Pointer to a variable to which the size of the - * retrieved item will be written. + * @param[in] xRingbuffer Ring buffer to retrieve the item from + * @param[out] pxItemSize Pointer to a variable to which the size of the retrieved item will be written. + * @param[in] xTicksToWait Ticks to wait for items in the ring buffer. + * + * @note A call to vRingbufferReturnItem() is required after this to free the item retrieved. * * @return - * - Pointer to the retrieved item on success; *item_size filled with - * the length of the item. - * - NULL when the ringbuffer is empty, *item_size is untouched in that case. + * - Pointer to the retrieved item on success; *pxItemSize filled with the length of the item. + * - NULL on timeout, *pxItemSize is untouched in that case. */ -void *xRingbufferReceiveFromISR(RingbufHandle_t ringbuf, size_t *item_size); - +void *xRingbufferReceive(RingbufHandle_t xRingbuffer, size_t *pxItemSize, TickType_t xTicksToWait); /** - * @brief Retrieve bytes from a ByteBuf type of ring buffer, - * specifying the maximum amount of bytes to return + * @brief Retrieve an item from the ring buffer in an ISR * - * @note A call to vRingbufferReturnItem() is required after this to free up - * the data received. + * Attempt to retrieve an item from the ring buffer. This function returns immediately + * if there are no items available for retrieval * - * @param ringbuf Ring buffer to retrieve the item from - * @param[out] item_size Pointer to a variable to which the size - * of the retrieved item will be written. - * @param ticks_to_wait Ticks to wait for items in the ringbuffer. - * @param wanted_size Maximum number of bytes to return. + * @param[in] xRingbuffer Ring buffer to retrieve the item from + * @param[out] pxItemSize Pointer to a variable to which the size of the + * retrieved item will be written. + * + * @note A call to vRingbufferReturnItemFromISR() is required after this to free the item retrieved. + * @note Byte buffers do not allow multiple retrievals before returning an item * * @return - * - Pointer to the retrieved item on success; *item_size filled with - * the length of the item. - * - NULL on timeout, *item_size is untouched in that case. + * - Pointer to the retrieved item on success; *pxItemSize filled with the length of the item. + * - NULL when the ring buffer is empty, *pxItemSize is untouched in that case. */ -void *xRingbufferReceiveUpTo(RingbufHandle_t ringbuf, size_t *item_size, TickType_t ticks_to_wait, size_t wanted_size); - +void *xRingbufferReceiveFromISR(RingbufHandle_t xRingbuffer, size_t *pxItemSize); /** - * @brief Retrieve bytes from a ByteBuf type of ring buffer, - * specifying the maximum amount of bytes to return. Call this from an ISR. + * @brief Retrieve a split item from an allow-split ring buffer * - * @note A call to vRingbufferReturnItemFromISR() is required after this - * to free up the data received. + * Attempt to retrieve a split item from an allow-split ring buffer. If the item + * is not split, only a single item is retried. If the item is split, both parts + * will be retrieved. This function will block until an item is available or + * until it timesout. * - * @param ringbuf Ring buffer to retrieve the item from - * @param[out] item_size Pointer to a variable to which the size of the - * retrieved item will be written. - * @param wanted_size Maximum number of bytes to return. + * @param[in] xRingbuffer Ring buffer to retrieve the item from + * @param[out] ppvHeadItem Double pointer to first part (set to NULL if no items were retrieved) + * @param[out] ppvTailItem Double pointer to second part (set to NULL if item is not split) + * @param[out] pxHeadItemSize Pointer to size of first part (unmodified if no items were retrieved) + * @param[out] pxTailItemSize Pointer to size of second part (unmodified if item is not split) + * @param[in] xTicksToWait Ticks to wait for items in the ring buffer. + * + * @note Call(s) to vRingbufferReturnItem() is required after this to free up the item(s) retrieved. + * @note This function should only be called on allow-split buffers * * @return - * - Pointer to the retrieved item on success; *item_size filled with + * - pdTRUE if an item (split or unsplit) was retrieved + * - pdFALSE when no item was retrieved + */ +BaseType_t xRingbufferReceiveSplit(RingbufHandle_t xRingbuffer, void **ppvHeadItem, void **ppvTailItem, size_t *pxHeadItemSize, size_t *pxTailItemSize, TickType_t xTicksToWait); + +/** + * @brief Retrieve a split item from an allow-split ring buffer in an ISR + * + * Attempt to retrieve a split item from an allow-split ring buffer. If the item + * is not split, only a single item is retried. If the item is split, both parts + * will be retrieved. This function returns immediately if there are no items + * available for retrieval + * + * @param[in] xRingbuffer Ring buffer to retrieve the item from + * @param[out] ppvHeadItem Double pointer to first part (set to NULL if no items were retrieved) + * @param[out] ppvTailItem Double pointer to second part (set to NULL if item is not split) + * @param[out] pxHeadItemSize Pointer to size of first part (unmodified if no items were retrieved) + * @param[out] pxTailItemSize Pointer to size of second part (unmodified if item is not split) + * + * @note Calls to vRingbufferReturnItemFromISR() is required after this to free up the item(s) retrieved. + * @note This function should only be called on allow-split buffers + * + * @return + * - pdTRUE if an item (split or unsplit) was retrieved + * - pdFALSE when no item was retrieved + */ +BaseType_t xRingbufferReceiveSplitFromISR(RingbufHandle_t xRingbuffer, void **ppvHeadItem, void **ppvTailItem, size_t *pxHeadItemSize, size_t *pxTailItemSize); + +/** + * @brief Retrieve bytes from a byte buffer, specifying the maximum amount of bytes to retrieve + * + * Attempt to retrieve data from a byte buffer whilst specifying a maximum number + * of bytes to retrieve. This function will block until there is data available + * for retrieval or until it timesout. + * + * @param[in] xRingbuffer Ring buffer to retrieve the item from + * @param[out] pxItemSize Pointer to a variable to which the size of the retrieved item will be written. + * @param[in] xTicksToWait Ticks to wait for items in the ring buffer. + * @param[in] xMaxSize Maximum number of bytes to return. + * + * @note A call to vRingbufferReturnItem() is required after this to free up the data retrieved. + * @note This function should only be called on byte buffers + * @note Byte buffers do not allow multiple retrievals before returning an item + * + * @return + * - Pointer to the retrieved item on success; *pxItemSize filled with * the length of the item. - * - NULL when the ringbuffer is empty, *item_size is untouched in that case. + * - NULL on timeout, *pxItemSize is untouched in that case. */ -void *xRingbufferReceiveUpToFromISR(RingbufHandle_t ringbuf, size_t *item_size, size_t wanted_size); - - +void *xRingbufferReceiveUpTo(RingbufHandle_t xRingbuffer, size_t *pxItemSize, TickType_t xTicksToWait, size_t xMaxSize); /** - * @brief Return a previously-retrieved item to the ringbuffer + * @brief Retrieve bytes from a byte buffer, specifying the maximum amount of + * bytes to retrieve. Call this from an ISR. * - * @param ringbuf Ring buffer the item was retrieved from - * @param item Item that was received earlier + * Attempt to retrieve bytes from a byte buffer whilst specifying a maximum number + * of bytes to retrieve. This function will return immediately if there is no data + * available for retrieval. + * + * @param[in] xRingbuffer Ring buffer to retrieve the item from + * @param[out] pxItemSize Pointer to a variable to which the size of the retrieved item will be written. + * @param[in] xMaxSize Maximum number of bytes to return. + * + * @note A call to vRingbufferReturnItemFromISR() is required after this to free up the data received. + * @note This function should only be called on byte buffers + * @note Byte buffers do not allow multiple retrievals before returning an item + * + * @return + * - Pointer to the retrieved item on success; *pxItemSize filled with + * the length of the item. + * - NULL when the ring buffer is empty, *pxItemSize is untouched in that case. */ -void vRingbufferReturnItem(RingbufHandle_t ringbuf, void *item); - - +void *xRingbufferReceiveUpToFromISR(RingbufHandle_t xRingbuffer, size_t *pxItemSize, size_t xMaxSize); /** - * @brief Return a previously-retrieved item to the ringbuffer from an ISR + * @brief Return a previously-retrieved item to the ring buffer * - * @param ringbuf Ring buffer the item was retrieved from - * @param item Item that was received earlier - * @param[out] higher_prio_task_awoken Value pointed to will be set to pdTRUE - * if the push woke up a higher priority task. + * @param[in] xRingbuffer Ring buffer the item was retrieved from + * @param[in] pvItem Item that was received earlier + * + * @note If a split item is retrieved, both parts should be returned by calling this function twice */ -void vRingbufferReturnItemFromISR(RingbufHandle_t ringbuf, void *item, BaseType_t *higher_prio_task_awoken); - +void vRingbufferReturnItem(RingbufHandle_t xRingbuffer, void *pvItem); /** - * @brief Add the ringbuffer to a queue set. + * @brief Return a previously-retrieved item to the ring buffer from an ISR * - * This specifically adds the semaphore that indicates more space - * has become available in the ringbuffer. + * @param[in] xRingbuffer Ring buffer the item was retrieved from + * @param[in] pvItem Item that was received earlier + * @param[out] pxHigherPriorityTaskWoken Value pointed to will be set to pdTRUE + * if the function woke up a higher priority task. * - * @param ringbuf Ring buffer to add to the queue set - * @param xQueueSet Queue set to add the ringbuffer to + * @note If a split item is retrieved, both parts should be returned by calling this function twice + */ +void vRingbufferReturnItemFromISR(RingbufHandle_t xRingbuffer, void *pvItem, BaseType_t *pxHigherPriorityTaskWoken); + +/** + * @brief Delete a ring buffer + * + * @param[in] xRingbuffer Ring buffer to delete + */ +void vRingbufferDelete(RingbufHandle_t xRingbuffer); + +/** + * @brief Get maximum size of an item that can be placed in the ring buffer + * + * This function returns the maximum size an item can have if it was placed in + * an empty ring buffer. + * + * @param[in] xRingbuffer Ring buffer to query + * + * @return Maximum size, in bytes, of an item that can be placed in a ring buffer. + */ +size_t xRingbufferGetMaxItemSize(RingbufHandle_t xRingbuffer); + +/** + * @brief Get current free size available for an item/data in the buffer + * + * This gives the real time free space available for an item/data in the ring + * buffer. This represents the maximum size an item/data can have if it was + * currently sent to the ring buffer. + * + * @warning This API is not thread safe. So, if multiple threads are accessing + * the same ring buffer, it is the application's responsibility to + * ensure atomic access to this API and the subsequent Send + * + * @param[in] xRingbuffer Ring buffer to query + * + * @return Current free size, in bytes, available for an entry + */ +size_t xRingbufferGetCurFreeSize(RingbufHandle_t xRingbuffer); + +/** + * @brief Add the ring buffer's read semaphore to a queue set. + * + * The ring buffer's read semaphore indicates that data has been written + * to the ring buffer. This function adds the ring buffer's read semaphore to + * a queue set. + * + * @param[in] xRingbuffer Ring buffer to add to the queue set + * @param[in] xQueueSet Queue set to add the ring buffer's read semaphore to * * @return * - pdTRUE on success, pdFALSE otherwise */ -BaseType_t xRingbufferAddToQueueSetRead(RingbufHandle_t ringbuf, QueueSetHandle_t xQueueSet); +BaseType_t xRingbufferAddToQueueSetRead(RingbufHandle_t xRingbuffer, QueueSetHandle_t xQueueSet); /** - * @brief Add the ringbuffer to a queue set. + * @brief Check if the selected queue set member is the ring buffer's read semaphore * - * This specifically adds the semaphore that indicates something has been - * written into the ringbuffer. - * - * @param ringbuf Ring buffer to add to the queue set - * @param xQueueSet Queue set to add the ringbuffer to - * - * @return pdTRUE on success, pdFALSE otherwise - */ -BaseType_t xRingbufferAddToQueueSetWrite(RingbufHandle_t ringbuf, QueueSetHandle_t xQueueSet); - -/** - * @brief Check if the selected queue set member is the ringbuffer's read semaphore - * - * This API checks if queue set member returned from xQueueSelectFromSet + * This API checks if queue set member returned from xQueueSelectFromSet() * is the read semaphore of this ring buffer. If so, this indicates the ring buffer - * has items waiting to be read. + * has items waiting to be retrieved. * - * @param ringbuf Ring buffer which should be checked - * @param member Member returned from xQueueSelectFromSet + * @param[in] xRingbuffer Ring buffer which should be checked + * @param[in] xMember Member returned from xQueueSelectFromSet * - * @return pdTRUE when semaphore belongs to ringbuffer, pdFALSE otherwise. + * @return + * - pdTRUE when semaphore belongs to ring buffer + * - pdFALSE otherwise. */ -BaseType_t xRingbufferCanRead(RingbufHandle_t ringbuf, QueueSetMemberHandle_t member); +BaseType_t xRingbufferCanRead(RingbufHandle_t xRingbuffer, QueueSetMemberHandle_t xMember); /** - * @brief Check if the selected queue set member is the ringbuffer's write semaphore + * @brief Remove the ring buffer's read semaphore from a queue set. * - * This API checks if queue set member returned from xQueueSelectFromSet - * is the write semaphore of this ring buffer. If so, this indicates the ring buffer - * has items waiting for write. + * This specifically removes a ring buffer's read semaphore from a queue set. The + * read semaphore is used to indicate when data has been written to the ring buffer * - * @param ringbuf Ring buffer which should be checked - * @param member Member returned from xQueueSelectFromSet + * @param[in] xRingbuffer Ring buffer to remove from the queue set + * @param[in] xQueueSet Queue set to remove the ring buffer's read semaphore from * - * @return pdTRUE when semaphore belongs to ringbuffer, pdFALSE otherwise. + * @return + * - pdTRUE on success + * - pdFALSE otherwise */ -BaseType_t xRingbufferCanWrite(RingbufHandle_t ringbuf, QueueSetMemberHandle_t member); +BaseType_t xRingbufferRemoveFromQueueSetRead(RingbufHandle_t xRingbuffer, QueueSetHandle_t xQueueSet); /** - * @brief Remove the ringbuffer from a queue set. + * @brief Get information about ring buffer status * - * This specifically removes the semaphore that indicates more space - * has become available in the ringbuffer. + * Get information of the a ring buffer's current status such as + * free/read/write pointer positions, and number of items waiting to be retrieved. + * Arguments can be set to NULL if they are not required. * - * @param ringbuf Ring buffer to remove from the queue set - * @param xQueueSet Queue set to remove the ringbuffer from - * - * @return pdTRUE on success, pdFALSE otherwise + * @param[in] xRingbuffer Ring buffer to remove from the queue set + * @param[out] uxFree Pointer use to store free pointer position + * @param[out] uxRead Pointer use to store read pointer position + * @param[out] uxWrite Pointer use to store write pointer position + * @param[out] uxItemsWaiting Pointer use to store number of items (bytes for byte buffer) waiting to be retrieved */ -BaseType_t xRingbufferRemoveFromQueueSetRead(RingbufHandle_t ringbuf, QueueSetHandle_t xQueueSet); - +void vRingbufferGetInfo(RingbufHandle_t xRingbuffer, UBaseType_t *uxFree, UBaseType_t *uxRead, UBaseType_t *uxWrite, UBaseType_t *uxItemsWaiting); /** - * @brief Remove the ringbuffer from a queue set. + * @brief Debugging function to print the internal pointers in the ring buffer * - * This specifically removes the semaphore that indicates something - * has been written to the ringbuffer. - * - * @param ringbuf Ring buffer to remove from the queue set - * @param xQueueSet Queue set to remove the ringbuffer from - * - * @return pdTRUE on success, pdFALSE otherwise + * @param xRingbuffer Ring buffer to show */ -BaseType_t xRingbufferRemoveFromQueueSetWrite(RingbufHandle_t ringbuf, QueueSetHandle_t xQueueSet); +void xRingbufferPrintInfo(RingbufHandle_t xRingbuffer); +/* -------------------------------- Deprecated Functions --------------------------- */ -/** - * @brief Debugging function to print the internal pointers in the ring buffer - * - * @param ringbuf Ring buffer to show +/** @cond */ //Doxygen command to hide deprecated function from API Reference +/* + * Deprecated as function is not thread safe and does not check if an item is + * actually available for retrieval. Use xRingbufferReceiveSplit() instead for + * thread safe method of retrieve a split item. */ -void xRingbufferPrintInfo(RingbufHandle_t ringbuf); +bool xRingbufferIsNextItemWrapped(RingbufHandle_t xRingbuffer) __attribute__((deprecated)); + +/* + * Deprecated as queue sets are not meant to be used for writing to buffers. Adding + * the ring buffer write semaphore to a queue set will break queue set usage rules, + * as every read of a semaphore must be preceded by a call to xQueueSelectFromSet(). + * QueueSetWrite no longer supported. + */ +BaseType_t xRingbufferAddToQueueSetWrite(RingbufHandle_t xRingbuffer, QueueSetHandle_t xQueueSet) __attribute__((deprecated)); + +/* + * Deprecated as queue sets are not meant to be used for writing to buffers. + * QueueSetWrite no longer supported. + */ +BaseType_t xRingbufferRemoveFromQueueSetWrite(RingbufHandle_t xRingbuffer, QueueSetHandle_t xQueueSet) __attribute__((deprecated)); +/** @endcond */ #ifdef __cplusplus } diff --git a/components/freertos/ringbuf.c b/components/freertos/ringbuf.c index 2f1998bfe..606cea148 100644 --- a/components/freertos/ringbuf.c +++ b/components/freertos/ringbuf.c @@ -1,4 +1,4 @@ -// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// Copyright 2015-2018 Espressif Systems (Shanghai) PTE LTD // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,776 +12,1173 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "freertos/semphr.h" -#include "freertos/queue.h" -#include "freertos/xtensa_api.h" -#include "freertos/ringbuf.h" -#include "esp_attr.h" -#include -#include #include -#include +#include +#include "FreeRTOS.h" +#include "task.h" +#include "semphr.h" +#include "ringbuf.h" -typedef enum { - flag_allowsplit = 1, - flag_bytebuf = 2, -} rbflag_t; +//32-bit alignment macros +#define rbALIGN_SIZE( xSize ) ( ( xSize + portBYTE_ALIGNMENT_MASK ) & ~portBYTE_ALIGNMENT_MASK ) +#define rbCHECK_ALIGNED( pvPtr ) ( ( ( UBaseType_t ) pvPtr & portBYTE_ALIGNMENT_MASK ) == 0 ) -typedef enum { - iflag_free = 1, //Buffer is not read and given back by application, free to overwrite - iflag_dummydata = 2, //Data from here to end of ringbuffer is dummy. Restart reading at start of ringbuffer. - iflag_wrap = 4, //Valid for RINGBUF_TYPE_ALLOWSPLIT, indicating that rest of the data is wrapped around -} itemflag_t; +//Ring buffer flags +#define rbALLOW_SPLIT_FLAG ( ( UBaseType_t ) 1 ) //The ring buffer allows items to be split +#define rbBYTE_BUFFER_FLAG ( ( UBaseType_t ) 2 ) //The ring buffer is a byte buffer +#define rbBUFFER_FULL_FLAG ( ( UBaseType_t ) 4 ) //The ring buffer is currently full (write pointer == free pointer) +//Item flags +#define rbITEM_FREE_FLAG ( ( UBaseType_t ) 1 ) //Item has been retrieved and returned by application, free to overwrite +#define rbITEM_DUMMY_DATA_FLAG ( ( UBaseType_t ) 2 ) //Data from here to end of the ring buffer is dummy data. Restart reading at start of head of the buffer +#define rbITEM_SPLIT_FLAG ( ( UBaseType_t ) 4 ) //Valid for RINGBUF_TYPE_ALLOWSPLIT, indicating that rest of the data is wrapped around -typedef struct ringbuf_t ringbuf_t; +typedef struct { + //This size of this structure must be 32-bit aligned + size_t xItemLen; + UBaseType_t uxItemFlags; +} ItemHeader_t; -//The ringbuffer structure -struct ringbuf_t { - SemaphoreHandle_t free_space_sem; //Binary semaphore, wakes up writing threads when there's more free space - SemaphoreHandle_t items_buffered_sem; //Binary semaphore, indicates there are new packets in the circular buffer. See remark. - size_t size; //Size of the data storage - uint8_t *write_ptr; //Pointer where the next item is written - uint8_t *read_ptr; //Pointer from where the next item is read - uint8_t *free_ptr; //Pointer to the last block that hasn't been given back to the ringbuffer yet - uint8_t *data; //Data storage - portMUX_TYPE mux; //Spinlock for actual data/ptr/struct modification - rbflag_t flags; - size_t maxItemSize; - //The following keep function pointers to hold different implementations for ringbuffer management. - BaseType_t (*copyItemToRingbufImpl)(ringbuf_t *rb, uint8_t *buffer, size_t buffer_size); - uint8_t *(*getItemFromRingbufImpl)(ringbuf_t *rb, size_t *length, int wanted_length); - void (*returnItemToRingbufImpl)(ringbuf_t *rb, void *item); - size_t (*getFreeSizeImpl)(ringbuf_t *rb); +#define rbHEADER_SIZE sizeof(ItemHeader_t) +typedef struct Ringbuffer_t Ringbuffer_t; +typedef BaseType_t (*CheckItemFitsFunction_t)(Ringbuffer_t *pxRingbuffer, size_t xItemSize); +typedef void (*CopyItemFunction_t)(Ringbuffer_t *pxRingbuffer, const uint8_t *pcItem, size_t xItemSize); +typedef BaseType_t (*CheckItemAvailFunction_t) (Ringbuffer_t *pxRingbuffer); +typedef void *(*GetItemFunction_t)(Ringbuffer_t *pxRingbuffer, BaseType_t *pxIsSplit, size_t xMaxSize, size_t *pxItemSize); +typedef void (*ReturnItemFunction_t)(Ringbuffer_t *pxRingbuffer, uint8_t *pvItem); +typedef size_t (*GetCurMaxSizeFunction_t)(Ringbuffer_t *pxRingbuffer); + +struct Ringbuffer_t { + size_t xSize; //Size of the data storage + UBaseType_t uxRingbufferFlags; //Flags to indicate the type and status of ring buffer + size_t xMaxItemSize; //Maximum item size + + CheckItemFitsFunction_t xCheckItemFits; //Function to check if item can currently fit in ring buffer + CopyItemFunction_t vCopyItem; //Function to copy item to ring buffer + GetItemFunction_t pvGetItem; //Function to get item from ring buffer + ReturnItemFunction_t vReturnItem; //Function to return item to ring buffer + GetCurMaxSizeFunction_t xGetCurMaxSize; //Function to get current free size + + uint8_t *pucWrite; //Write Pointer. Points to where the next item should be written + uint8_t *pucRead; //Read Pointer. Points to where the next item should be read from + uint8_t *pucFree; //Free Pointer. Points to the last item that has yet to be returned to the ring buffer + uint8_t *pucHead; //Pointer to the start of the ring buffer storage area + uint8_t *pucTail; //Pointer to the end of the ring buffer storage area + + BaseType_t xItemsWaiting; //Number of items/bytes(for byte buffers) currently in ring buffer that have not yet been read + SemaphoreHandle_t xFreeSpaceSemaphore; //Binary semaphore, wakes up writing threads when more free space becomes available or when another thread times out attempting to write + SemaphoreHandle_t xItemsBufferedSemaphore; //Binary semaphore, indicates there are new packets in the circular buffer. See remark. + portMUX_TYPE mux; //Spinlock required for SMP }; - - /* Remark: A counting semaphore for items_buffered_sem would be more logical, but counting semaphores in FreeRTOS need a maximum count, and allocate more memory the larger the maximum count is. Here, we -would need to set the maximum to the maximum amount of times a null-byte unit firs in the buffer, +would need to set the maximum to the maximum amount of times a null-byte unit first in the buffer, which is quite high and so would waste a fair amount of memory. */ +/* ------------------------------------------------ Static Declarations ------------------------------------------ */ +/* + * WARNING: All of the following static functions (except generic functions) + * ARE NOT THREAD SAFE. Therefore they should only be called within a critical + * section (using spin locks) + */ -//The header prepended to each ringbuffer entry. Size is assumed to be a multiple of 32bits. -typedef struct { - size_t len; - itemflag_t flags; -} buf_entry_hdr_t; +//Calculate current amount of free space (in bytes) in the ring buffer +static size_t prvGetFreeSize(Ringbuffer_t *pxRingbuffer); +//Checks if an item/data is currently available for retrieval +static BaseType_t prvCheckItemAvail(Ringbuffer_t *pxRingbuffer); -//Calculate space free in the buffer -static int ringbufferFreeMem(ringbuf_t *rb) +//Checks if an item will currently fit in a no-split/allow-split ring buffer +static BaseType_t prvCheckItemFitsDefault( Ringbuffer_t *pxRingbuffer, size_t xItemSize); + +//Checks if an item will currently fit in a byte buffer +static BaseType_t prvCheckItemFitsByteBuffer( Ringbuffer_t *pxRingbuffer, size_t xItemSize); + +//Copies an item to a no-split ring buffer. Only call this function after calling prvCheckItemFitsDefault() +static void prvCopyItemNoSplit(Ringbuffer_t *pxRingbuffer, const uint8_t *pucItem, size_t xItemSize); + +//Copies an item to a allow-split ring buffer. Only call this function after calling prvCheckItemFitsDefault() +static void prvCopyItemAllowSplit(Ringbuffer_t *pxRingbuffer, const uint8_t *pucItem, size_t xItemSize); + +//Copies an item to a byte buffer. Only call this function after calling prvCheckItemFitsByteBuffer() +static void prvCopyItemByteBuf(Ringbuffer_t *pxRingbuffer, const uint8_t *pucItem, size_t xItemSize); + +//Retrieve item from no-split/allow-split ring buffer. *pxIsSplit is set to pdTRUE if the retrieved item is split +static void *prvGetItemDefault(Ringbuffer_t *pxRingbuffer, BaseType_t *pxIsSplit, size_t xUnusedParam, size_t *pxItemSize); + +//Retrieve data from byte buffer. If xMaxSize is 0, all continuous data is retrieved +static void *prvGetItemByteBuf(Ringbuffer_t *pxRingbuffer, BaseType_t *pxUnusedParam ,size_t xMaxSize, size_t *pxItemSize); + +//Return an item to a split/no-split ring buffer +static void prvReturnItemDefault(Ringbuffer_t *pxRingbuffer, uint8_t *pucItem); + +//Return data to a byte buffer +static void prvReturnItemByteBuf(Ringbuffer_t *pxRingbuffer, uint8_t *pucItem); + +//Get the maximum size an item that can currently have if sent to a no-split ring buffer +static size_t prvGetCurMaxSizeNoSplit(Ringbuffer_t *pxRingbuffer); + +//Get the maximum size an item that can currently have if sent to a allow-split ring buffer +static size_t prvGetCurMaxSizeAllowSplit(Ringbuffer_t *pxRingbuffer); + +//Get the maximum size an item that can currently have if sent to a byte buffer +static size_t prvGetCurMaxSizeByteBuf(Ringbuffer_t *pxRingbuffer); + +/** + * Generic function used to retrieve an item/data from ring buffers. If called on + * an allow-split buffer, and pvItem2 and xItemSize2 are not NULL, both parts of + * a split item will be retrieved. xMaxSize will only take effect if called on + * byte buffers. + */ +static BaseType_t prvReceiveGeneric(Ringbuffer_t *pxRingbuffer, void **pvItem1, void **pvItem2, size_t *xItemSize1, size_t *xItemSize2, size_t xMaxSize, TickType_t xTicksToWait); + +//Generic function used to retrieve an item/data from ring buffers in an ISR +static BaseType_t prvReceiveGenericFromISR(Ringbuffer_t *pxRingbuffer, void **pvItem1, void **pvItem2, size_t *xItemSize1, size_t *xItemSize2, size_t xMaxSize); + +/* ------------------------------------------------ Static Definitions ------------------------------------------- */ + +static size_t prvGetFreeSize(Ringbuffer_t *pxRingbuffer) { - int free_size = rb->free_ptr-rb->write_ptr; - if (free_size <= 0) free_size += rb->size; - //Reserve one byte. If we do not do this and the entire buffer is filled, we get a situation - //where read_ptr == free_ptr, messing up the next calculation. - return free_size-1; -} - -//Copies a single item to the ring buffer; refuses to split items. Assumes there is space in the ringbuffer and -//the ringbuffer is locked. Increases write_ptr to the next item. Returns pdTRUE on -//success, pdFALSE if it can't make the item fit and the calling routine needs to retry -//later or fail. -//This function by itself is not threadsafe, always call from within a muxed section. -static BaseType_t copyItemToRingbufNoSplit(ringbuf_t *rb, uint8_t *buffer, size_t buffer_size) -{ - size_t rbuffer_size; - rbuffer_size=(buffer_size+3)&~3; //Payload length, rounded to next 32-bit value - configASSERT(((int)rb->write_ptr&3)==0); //write_ptr needs to be 32-bit aligned - configASSERT(rb->write_ptr-(rb->data+rb->size) >= sizeof(buf_entry_hdr_t)); //need to have at least the size - //of a header to the end of the ringbuff - size_t rem_len=(rb->data + rb->size) - rb->write_ptr; //length remaining until end of ringbuffer - - //See if we have enough contiguous space to write the buffer. - if (rem_len < rbuffer_size + sizeof(buf_entry_hdr_t)) { - //Buffer plus header is not going to fit in the room from wr_pos to the end of the - //ringbuffer... but we're not allowed to split the buffer. We need to fill the - //rest of the ringbuffer with a dummy item so we can place the data at the _start_ of - //the ringbuffer.. - //First, find out if we actually have enough space at the start of the ringbuffer to - //make this work (Again, we need 4 bytes extra because otherwise read_ptr==free_ptr) - if (rb->free_ptr-rb->data < rbuffer_size+sizeof(buf_entry_hdr_t)+4) { - //Will not fit. - return pdFALSE; - } - //If the read buffer hasn't wrapped around yet, there's no way this will work either. - if (rb->free_ptr > rb->write_ptr) { - //No luck. - return pdFALSE; - } - - //Okay, it will fit. Mark the rest of the ringbuffer space with a dummy packet. - buf_entry_hdr_t *hdr=(buf_entry_hdr_t *)rb->write_ptr; - hdr->flags=iflag_dummydata; - //Reset the write pointer to the start of the ringbuffer so the code later on can - //happily write the data. - rb->write_ptr=rb->data; + size_t xReturn; + if (pxRingbuffer->uxRingbufferFlags & rbBUFFER_FULL_FLAG) { + xReturn = 0; } else { - //No special handling needed. Checking if it's gonna fit probably still is a good idea. - if (ringbufferFreeMem(rb) < sizeof(buf_entry_hdr_t)+rbuffer_size) { - //Buffer is not going to fit, period. - return pdFALSE; + BaseType_t xFreeSize = pxRingbuffer->pucFree - pxRingbuffer->pucWrite; + //Check if xFreeSize has underflowed + if (xFreeSize <= 0) { + xFreeSize += pxRingbuffer->xSize; } + xReturn = xFreeSize; } - - //If we are here, the buffer is guaranteed to fit in the space starting at the write pointer. - buf_entry_hdr_t *hdr=(buf_entry_hdr_t *)rb->write_ptr; - hdr->len=buffer_size; - hdr->flags=0; - rb->write_ptr+=sizeof(buf_entry_hdr_t); - memcpy(rb->write_ptr, buffer, buffer_size); - rb->write_ptr+=rbuffer_size; - - //The buffer will wrap around if we don't have room for a header anymore. - if ((rb->data+rb->size)-rb->write_ptr < sizeof(buf_entry_hdr_t)) { - //'Forward' the write buffer until we are at the start of the ringbuffer. - //The read pointer will always be at the start of a full header, which cannot - //exist at the point of the current write pointer, so there's no chance of overtaking - //that. - rb->write_ptr=rb->data; - } - return pdTRUE; + configASSERT(xReturn <= pxRingbuffer->xSize); + return xReturn; } -//Copies a single item to the ring buffer; allows split items. Assumes there is space in the ringbuffer and -//the ringbuffer is locked. Increases write_ptr to the next item. Returns pdTRUE on -//success, pdFALSE if it can't make the item fit and the calling routine needs to retry -//later or fail. -//This function by itself is not threadsafe, always call from within a muxed section. -static BaseType_t copyItemToRingbufAllowSplit(ringbuf_t *rb, uint8_t *buffer, size_t buffer_size) +static BaseType_t prvCheckItemFitsDefault( Ringbuffer_t *pxRingbuffer, size_t xItemSize) { - size_t rbuffer_size; - rbuffer_size=(buffer_size+3)&~3; //Payload length, rounded to next 32-bit value - configASSERT(((int)rb->write_ptr&3)==0); //write_ptr needs to be 32-bit aligned - configASSERT(rb->write_ptr-(rb->data+rb->size) >= sizeof(buf_entry_hdr_t)); //need to have at least the size - //of a header to the end of the ringbuff - size_t rem_len=(rb->data + rb->size) - rb->write_ptr; //length remaining until end of ringbuffer + //Check arguments and buffer state + configASSERT(rbCHECK_ALIGNED(pxRingbuffer->pucWrite)); //pucWrite is always aligned in no-split ring buffers + configASSERT(pxRingbuffer->pucWrite >= pxRingbuffer->pucHead && pxRingbuffer->pucWrite < pxRingbuffer->pucTail); //Check write pointer is within bounds - //See if we have enough contiguous space to write the buffer. - if (rem_len < rbuffer_size + sizeof(buf_entry_hdr_t)) { - //The buffer can't be contiguously written to the ringbuffer, but needs special handling. Do - //that depending on how the ringbuffer is configured. - //The code here is also expected to check if the buffer, mangled in whatever way is implemented, - //will still fit, and return pdFALSE if that is not the case. - //Buffer plus header is not going to fit in the room from wr_pos to the end of the - //ringbuffer... we need to split the write in two. - //First, see if this will fit at all. - if (ringbufferFreeMem(rb) < (sizeof(buf_entry_hdr_t)*2)+rbuffer_size) { - //Will not fit. - return pdFALSE; + size_t xTotalItemSize = rbALIGN_SIZE(xItemSize) + rbHEADER_SIZE; //Rounded up aligned item size with header + if (pxRingbuffer->pucWrite == pxRingbuffer->pucFree) { + //Buffer is either complete empty or completely full + return (pxRingbuffer->uxRingbufferFlags & rbBUFFER_FULL_FLAG) ? pdFALSE : pdTRUE; + } + if (pxRingbuffer->pucFree > pxRingbuffer->pucWrite) { + //Free space does not wrap around + return (xTotalItemSize <= pxRingbuffer->pucFree - pxRingbuffer->pucWrite) ? pdTRUE : pdFALSE; + } + //Free space wraps around + if (xTotalItemSize <= pxRingbuffer->pucTail - pxRingbuffer->pucWrite) { + return pdTRUE; //Item fits without wrapping around + } + //Check if item fits by wrapping + if (pxRingbuffer->uxRingbufferFlags & rbALLOW_SPLIT_FLAG) { + //Allow split wrapping incurs an extra header + return (xTotalItemSize + rbHEADER_SIZE <= pxRingbuffer->xSize - (pxRingbuffer->pucWrite - pxRingbuffer->pucFree)) ? pdTRUE : pdFALSE; + } else { + return (xTotalItemSize <= pxRingbuffer->pucFree - pxRingbuffer->pucHead) ? pdTRUE : pdFALSE; + } +} + +static BaseType_t prvCheckItemFitsByteBuffer( Ringbuffer_t *pxRingbuffer, size_t xItemSize) +{ + //Check arguments and buffer state + configASSERT(pxRingbuffer->pucWrite >= pxRingbuffer->pucHead && pxRingbuffer->pucWrite < pxRingbuffer->pucTail); //Check write pointer is within bounds + + if (pxRingbuffer->pucWrite == pxRingbuffer->pucFree) { + //Buffer is either complete empty or completely full + return (pxRingbuffer->uxRingbufferFlags & rbBUFFER_FULL_FLAG) ? pdFALSE : pdTRUE; + } + if (pxRingbuffer->pucFree > pxRingbuffer->pucWrite) { + //Free space does not wrap around + return (xItemSize <= pxRingbuffer->pucFree - pxRingbuffer->pucWrite) ? pdTRUE : pdFALSE; + } + //Free space wraps around + return (xItemSize <= pxRingbuffer->xSize - (pxRingbuffer->pucWrite - pxRingbuffer->pucFree)) ? pdTRUE : pdFALSE; +} + +static void prvCopyItemNoSplit(Ringbuffer_t *pxRingbuffer, const uint8_t *pucItem, size_t xItemSize) +{ + //Check arguments and buffer state + size_t xAlignedItemSize = rbALIGN_SIZE(xItemSize); //Rounded up aligned item size + size_t xRemLen = pxRingbuffer->pucTail - pxRingbuffer->pucWrite; //Length from pucWrite until end of buffer + configASSERT(rbCHECK_ALIGNED(pxRingbuffer->pucWrite)); //pucWrite is always aligned in no-split ring buffers + configASSERT(pxRingbuffer->pucWrite >= pxRingbuffer->pucHead && pxRingbuffer->pucWrite < pxRingbuffer->pucTail); //Check write pointer is within bounds + configASSERT(xRemLen >= rbHEADER_SIZE); //Remaining length must be able to at least fit an item header + + //If remaining length can't fit item, set as dummy data and wrap around + if (xRemLen < xAlignedItemSize + rbHEADER_SIZE) { + ItemHeader_t *pxDummy = (ItemHeader_t *)pxRingbuffer->pucWrite; + pxDummy->uxItemFlags = rbITEM_DUMMY_DATA_FLAG; //Set remaining length as dummy data + pxDummy->xItemLen = 0; //Dummy data should have no length + pxRingbuffer->pucWrite = pxRingbuffer->pucHead; //Reset write pointer to wrap around + } + + //Item should be guaranteed to fit at this point. Set item header and copy data + ItemHeader_t *pxHeader = (ItemHeader_t *)pxRingbuffer->pucWrite; + pxHeader->xItemLen = xItemSize; + pxHeader->uxItemFlags = 0; + pxRingbuffer->pucWrite += rbHEADER_SIZE; //Advance pucWrite past header + memcpy(pxRingbuffer->pucWrite, pucItem, xItemSize); + pxRingbuffer->xItemsWaiting++; + pxRingbuffer->pucWrite += xAlignedItemSize; //Advance pucWrite past item to next aligned address + + //If current remaining length can't fit a header, wrap around write pointer + if (pxRingbuffer->pucTail - pxRingbuffer->pucWrite < rbHEADER_SIZE) { + pxRingbuffer->pucWrite = pxRingbuffer->pucHead; //Wrap around pucWrite + } + //Check if buffer is full + if (pxRingbuffer->pucWrite == pxRingbuffer->pucFree) { + //Mark the buffer as full to distinguish with an empty buffer + pxRingbuffer->uxRingbufferFlags |= rbBUFFER_FULL_FLAG; + } +} + +static void prvCopyItemAllowSplit(Ringbuffer_t *pxRingbuffer, const uint8_t *pucItem, size_t xItemSize) +{ + //Check arguments and buffer state + size_t xAlignedItemSize = rbALIGN_SIZE(xItemSize); //Rounded up aligned item size + size_t xRemLen = pxRingbuffer->pucTail - pxRingbuffer->pucWrite; //Length from pucWrite until end of buffer + configASSERT(rbCHECK_ALIGNED(pxRingbuffer->pucWrite)); //pucWrite is always aligned in split ring buffers + configASSERT(pxRingbuffer->pucWrite >= pxRingbuffer->pucHead && pxRingbuffer->pucWrite < pxRingbuffer->pucTail); //Check write pointer is within bounds + configASSERT(xRemLen >= rbHEADER_SIZE); //Remaining length must be able to at least fit an item header + + //Split item if necessary + if (xRemLen < xAlignedItemSize + rbHEADER_SIZE) { + //Write first part of the item + ItemHeader_t *pxFirstHeader = (ItemHeader_t *)pxRingbuffer->pucWrite; + pxFirstHeader->uxItemFlags = 0; + pxFirstHeader->xItemLen = xRemLen - rbHEADER_SIZE; //Fill remaining length with first part + pxRingbuffer->pucWrite += rbHEADER_SIZE; //Advance pucWrite past header + xRemLen -= rbHEADER_SIZE; + if (xRemLen > 0) { + memcpy(pxRingbuffer->pucWrite, pucItem, xRemLen); + pxRingbuffer->xItemsWaiting++; + //Update item arguments to account for data already copied + pucItem += xRemLen; + xItemSize -= xRemLen; + xAlignedItemSize -= xRemLen; + pxFirstHeader->uxItemFlags |= rbITEM_SPLIT_FLAG; //There must be more data + } else { + //Remaining length was only large enough to fit header + pxFirstHeader->uxItemFlags |= rbITEM_DUMMY_DATA_FLAG; //Item will completely be stored in 2nd part } - //Because the code at the end of the function makes sure we always have - //room for a header, this should never assert. - configASSERT(rem_len>=sizeof(buf_entry_hdr_t)); - //Okay, it should fit. Write everything. - //First, place bit of buffer that does fit. Write header first... - buf_entry_hdr_t *hdr=(buf_entry_hdr_t *)rb->write_ptr; - hdr->flags=0; - hdr->len=rem_len-sizeof(buf_entry_hdr_t); - rb->write_ptr+=sizeof(buf_entry_hdr_t); - rem_len-=sizeof(buf_entry_hdr_t); - if (rem_len!=0) { - //..then write the data bit that fits. - memcpy(rb->write_ptr, buffer, rem_len); - //Update vars so the code later on will write the rest of the data. - buffer+=rem_len; - buffer_size-=rem_len; - //Re-adjust the rbuffer value to be 4 byte aligned - rbuffer_size=(buffer_size+3)&~3; - //It is possible that we are here because we checked for 4byte aligned - //size, but actual data was smaller. - //Eg. For buffer_size = 34, rbuffer_size will be 36. Suppose we had only - //42 bytes of memory available, the top level check will fail, as it will - //check for availability of 36 + 8 = 44 bytes. - //However, the 42 bytes available memory is sufficient for 34 + 8 bytes data - //and so, we can return after writing the data. Hence, this check - if (buffer_size == 0) { - rb->write_ptr=rb->data; - return pdTRUE; + pxRingbuffer->pucWrite = pxRingbuffer->pucHead; //Reset write pointer to start of buffer + } + + //Item (whole or second part) should be guaranteed to fit at this point + ItemHeader_t *pxSecondHeader = (ItemHeader_t *)pxRingbuffer->pucWrite; + pxSecondHeader->xItemLen = xItemSize; + pxSecondHeader->uxItemFlags = 0; + pxRingbuffer->pucWrite += rbHEADER_SIZE; //Advance write pointer past header + memcpy(pxRingbuffer->pucWrite, pucItem, xItemSize); + pxRingbuffer->xItemsWaiting++; + pxRingbuffer->pucWrite += xAlignedItemSize; //Advance pucWrite past item to next aligned address + + //If current remaining length can't fit a header, wrap around write pointer + if (pxRingbuffer->pucTail - pxRingbuffer->pucWrite < rbHEADER_SIZE) { + pxRingbuffer->pucWrite = pxRingbuffer->pucHead; //Wrap around pucWrite + } + //Check if buffer is full + if (pxRingbuffer->pucWrite == pxRingbuffer->pucFree) { + //Mark the buffer as full to distinguish with an empty buffer + pxRingbuffer->uxRingbufferFlags |= rbBUFFER_FULL_FLAG; + } +} + +static void prvCopyItemByteBuf(Ringbuffer_t *pxRingbuffer, const uint8_t *pucItem, size_t xItemSize) +{ + //Check arguments and buffer state + configASSERT(pxRingbuffer->pucWrite >= pxRingbuffer->pucHead && pxRingbuffer->pucWrite < pxRingbuffer->pucTail); //Check write pointer is within bounds + + size_t xRemLen = pxRingbuffer->pucTail - pxRingbuffer->pucWrite; //Length from pucWrite until end of buffer + if (xRemLen < xItemSize) { + //Copy as much as possible into remaining length + memcpy(pxRingbuffer->pucWrite, pucItem, xRemLen); + pxRingbuffer->xItemsWaiting += xRemLen; + //Update item arguments to account for data already written + pucItem += xRemLen; + xItemSize -= xRemLen; + pxRingbuffer->pucWrite = pxRingbuffer->pucHead; //Reset write pointer to start of buffer + } + //Copy all or remaining portion of the item + memcpy(pxRingbuffer->pucWrite, pucItem, xItemSize); + pxRingbuffer->xItemsWaiting += xItemSize; + pxRingbuffer->pucWrite += xItemSize; + + //Wrap around pucWrite if it reaches the end + if (pxRingbuffer->pucWrite == pxRingbuffer->pucTail) { + pxRingbuffer->pucWrite = pxRingbuffer->pucHead; + } + //Check if buffer is full + if (pxRingbuffer->pucWrite == pxRingbuffer->pucFree) { + pxRingbuffer->uxRingbufferFlags |= rbBUFFER_FULL_FLAG; //Mark the buffer as full to avoid confusion with an empty buffer + } +} + +static BaseType_t prvCheckItemAvail(Ringbuffer_t *pxRingbuffer) +{ + if ((pxRingbuffer->uxRingbufferFlags & rbBYTE_BUFFER_FLAG) && pxRingbuffer->pucRead != pxRingbuffer->pucFree) { + return pdFALSE; //Byte buffers do not allow multiple retrievals before return + } + if ((pxRingbuffer->xItemsWaiting > 0) && ((pxRingbuffer->pucRead != pxRingbuffer->pucWrite) || (pxRingbuffer->uxRingbufferFlags & rbBUFFER_FULL_FLAG))) { + return pdTRUE; //Items/data available for retrieval + } else { + return pdFALSE; //No items/data available for retrieval + } +} + +static void *prvGetItemDefault(Ringbuffer_t *pxRingbuffer, BaseType_t *pxIsSplit, size_t xUnusedParam, size_t *pxItemSize) +{ + //Check arguments and buffer state + ItemHeader_t *pxHeader = (ItemHeader_t *)pxRingbuffer->pucRead; + configASSERT(pxIsSplit != NULL); + configASSERT((pxRingbuffer->xItemsWaiting > 0) && ((pxRingbuffer->pucRead != pxRingbuffer->pucWrite) || (pxRingbuffer->uxRingbufferFlags & rbBUFFER_FULL_FLAG))); //Check there are items to be read + configASSERT(rbCHECK_ALIGNED(pxRingbuffer->pucRead)); //pucRead is always aligned in split ring buffers + configASSERT(pxRingbuffer->pucRead >= pxRingbuffer->pucHead && pxRingbuffer->pucRead < pxRingbuffer->pucTail); //Check read pointer is within bounds + configASSERT((pxHeader->xItemLen <= pxRingbuffer->xMaxItemSize) || (pxHeader->uxItemFlags & rbITEM_DUMMY_DATA_FLAG)); + + uint8_t *pcReturn; + //Wrap around if dummy data (dummy data indicates wrap around in no-split buffers) + if (pxHeader->uxItemFlags & rbITEM_DUMMY_DATA_FLAG) { + pxRingbuffer->pucRead = pxRingbuffer->pucHead; + //Check for errors with the next item + pxHeader = (ItemHeader_t *)pxRingbuffer->pucRead; + configASSERT(pxHeader->xItemLen <= pxRingbuffer->xMaxItemSize); + } + pcReturn = pxRingbuffer->pucRead + rbHEADER_SIZE; //Get pointer to part of item containing data (point past the header) + if (pxHeader->xItemLen == 0) { + //Inclusive of pucTail for special case where item of zero length just fits at the end of the buffer + configASSERT(pcReturn >= pxRingbuffer->pucHead && pcReturn <= pxRingbuffer->pucTail); + } else { + //Exclusive of pucTali if length is larger than zero, pcReturn should never point to pucTail + configASSERT(pcReturn >= pxRingbuffer->pucHead && pcReturn < pxRingbuffer->pucTail); + } + *pxItemSize = pxHeader->xItemLen; //Get length of item + pxRingbuffer->xItemsWaiting --; //Update item count + *pxIsSplit = (pxHeader->uxItemFlags & rbITEM_SPLIT_FLAG) ? pdTRUE : pdFALSE; + + pxRingbuffer->pucRead += rbHEADER_SIZE + rbALIGN_SIZE(pxHeader->xItemLen); //Update pucRead + //Check if pucRead requires wrap around + if ((pxRingbuffer->pucTail - pxRingbuffer->pucRead) < rbHEADER_SIZE) { + pxRingbuffer->pucRead = pxRingbuffer->pucHead; + } + return (void *)pcReturn; +} + +static void *prvGetItemByteBuf(Ringbuffer_t *pxRingbuffer, BaseType_t *pxUnusedParam ,size_t xMaxSize, size_t *pxItemSize) +{ + //Check arguments and buffer state + configASSERT((pxRingbuffer->xItemsWaiting > 0) && ((pxRingbuffer->pucRead != pxRingbuffer->pucWrite) || (pxRingbuffer->uxRingbufferFlags & rbBUFFER_FULL_FLAG))); //Check there are items to be read + configASSERT(pxRingbuffer->pucRead >= pxRingbuffer->pucHead && pxRingbuffer->pucRead < pxRingbuffer->pucTail); //Check read pointer is within bounds + configASSERT(pxRingbuffer->pucRead == pxRingbuffer->pucFree); + + uint8_t *ret = pxRingbuffer->pucRead; + if ((pxRingbuffer->pucRead > pxRingbuffer->pucWrite) || (pxRingbuffer->uxRingbufferFlags & rbBUFFER_FULL_FLAG)) { //Available data wraps around + //Return contiguous piece from read pointer until buffer tail, or xMaxSize + if (xMaxSize == 0 || pxRingbuffer->pucTail - pxRingbuffer->pucRead <= xMaxSize) { + //All contiguous data from read pointer to tail + *pxItemSize = pxRingbuffer->pucTail - pxRingbuffer->pucRead; + pxRingbuffer->xItemsWaiting -= pxRingbuffer->pucTail - pxRingbuffer->pucRead; + pxRingbuffer->pucRead = pxRingbuffer->pucHead; //Wrap around read pointer + } else { + //Return xMaxSize amount of data + *pxItemSize = xMaxSize; + pxRingbuffer->xItemsWaiting -= xMaxSize; + pxRingbuffer->pucRead += xMaxSize; //Advance read pointer past retrieved data + } + } else { //Available data is contiguous between read and write pointer + if (xMaxSize == 0 || pxRingbuffer->pucWrite - pxRingbuffer->pucRead <= xMaxSize) { + //Return all contiguous data from read to write pointer + *pxItemSize = pxRingbuffer->pucWrite - pxRingbuffer->pucRead; + pxRingbuffer->xItemsWaiting -= pxRingbuffer->pucWrite - pxRingbuffer->pucRead; + pxRingbuffer->pucRead = pxRingbuffer->pucWrite; + } else { + //Return xMaxSize data from read pointer + *pxItemSize = xMaxSize; + pxRingbuffer->xItemsWaiting -= xMaxSize; + pxRingbuffer->pucRead += xMaxSize; //Advance read pointer past retrieved data + + } + } + return (void *)ret; +} + +static void prvReturnItemDefault(Ringbuffer_t *pxRingbuffer, uint8_t *pucItem) +{ + //Check arguments and buffer state + configASSERT(rbCHECK_ALIGNED(pucItem)); + configASSERT(pucItem >= pxRingbuffer->pucHead); + configASSERT(pucItem <= pxRingbuffer->pucTail); //Inclusive of pucTail in the case of zero length item at the very end + + //Get and check header of the item + ItemHeader_t *pxCurHeader = (ItemHeader_t *)(pucItem - rbHEADER_SIZE); + configASSERT(pxCurHeader->xItemLen <= pxRingbuffer->xMaxItemSize); + configASSERT((pxCurHeader->uxItemFlags & rbITEM_DUMMY_DATA_FLAG) == 0); //Dummy items should never have been read + configASSERT((pxCurHeader->uxItemFlags & rbITEM_FREE_FLAG) == 0); //Indicates item has already been returned before + pxCurHeader->uxItemFlags &= ~rbITEM_SPLIT_FLAG; //Clear wrap flag if set (not strictly necessary) + pxCurHeader->uxItemFlags |= rbITEM_FREE_FLAG; //Mark as free + + /* + * Items might not be returned in the order they were retrieved. Move the free pointer + * up to the next item that has not been marked as free (by free flag) or up + * till the read pointer. When advancing the free pointer, items that have already been + * freed or items with dummy data should be skipped over + */ + pxCurHeader = (ItemHeader_t *)pxRingbuffer->pucFree; + //Skip over Items that have already been freed or are dummy items + while (((pxCurHeader->uxItemFlags & rbITEM_FREE_FLAG) || (pxCurHeader->uxItemFlags & rbITEM_DUMMY_DATA_FLAG)) && pxRingbuffer->pucFree != pxRingbuffer->pucRead) { + if (pxCurHeader->uxItemFlags & rbITEM_DUMMY_DATA_FLAG) { + pxCurHeader->uxItemFlags |= rbITEM_FREE_FLAG; //Mark as freed (not strictly necessary but adds redundancy) + pxRingbuffer->pucFree = pxRingbuffer->pucHead; //Wrap around due to dummy data + } else { + //Item with data that has already been freed, advance free pointer past this item + size_t xAlignedItemSize = rbALIGN_SIZE(pxCurHeader->xItemLen); + pxRingbuffer->pucFree += xAlignedItemSize + rbHEADER_SIZE; + //Redundancy check to ensure free pointer has not overshot buffer bounds + configASSERT(pxRingbuffer->pucFree <= pxRingbuffer->pucHead + pxRingbuffer->xSize); + } + //Check if pucRead requires wrap around + if ((pxRingbuffer->pucTail - pxRingbuffer->pucFree) < rbHEADER_SIZE) { + pxRingbuffer->pucFree = pxRingbuffer->pucHead; + } + pxCurHeader = (ItemHeader_t *)pxRingbuffer->pucFree; //Update header to point to item + } + + //Check if the buffer full flag should be reset + if (pxRingbuffer->uxRingbufferFlags & rbBUFFER_FULL_FLAG) { + if (pxRingbuffer->pucFree != pxRingbuffer->pucWrite) { + pxRingbuffer->uxRingbufferFlags &= ~rbBUFFER_FULL_FLAG; + } else if (pxRingbuffer->pucFree == pxRingbuffer->pucWrite && pxRingbuffer->pucFree == pxRingbuffer->pucRead) { + //Special case where a full buffer is completely freed in one go + pxRingbuffer->uxRingbufferFlags &= ~rbBUFFER_FULL_FLAG; + } + } +} + +static void prvReturnItemByteBuf(Ringbuffer_t *pxRingbuffer, uint8_t *pucItem) +{ + //Check pointer points to address inside buffer + configASSERT((uint8_t *)pucItem >= pxRingbuffer->pucHead); + configASSERT((uint8_t *)pucItem < pxRingbuffer->pucTail); + //Free the read memory. Simply moves free pointer to read pointer as byte buffers do not allow multiple outstanding reads + pxRingbuffer->pucFree = pxRingbuffer->pucRead; + //If buffer was full before, reset full flag as free pointer has moved + if (pxRingbuffer->uxRingbufferFlags & rbBUFFER_FULL_FLAG) { + pxRingbuffer->uxRingbufferFlags &= ~rbBUFFER_FULL_FLAG; + } +} + +static size_t prvGetCurMaxSizeNoSplit(Ringbuffer_t *pxRingbuffer) +{ + BaseType_t xFreeSize; + //Check if buffer is full + if (pxRingbuffer->uxRingbufferFlags & rbBUFFER_FULL_FLAG) { + return 0; + } + if (pxRingbuffer->pucWrite < pxRingbuffer->pucFree) { + //Free space is contiguous between pucWrite and pucFree + xFreeSize = pxRingbuffer->pucFree - pxRingbuffer->pucWrite; + } else { + //Free space wraps around (or overlapped at pucHead), select largest + //contiguous free space as no-split items require contiguous space + size_t xSize1 = pxRingbuffer->pucTail - pxRingbuffer->pucWrite; + size_t xSize2 = pxRingbuffer->pucFree - pxRingbuffer->pucHead; + xFreeSize = (xSize1 > xSize2) ? xSize1 : xSize2; + } + + //No-split ring buffer items need space for a header + xFreeSize -= rbHEADER_SIZE; + //Limit free size to be within bounds + if (xFreeSize > pxRingbuffer->xMaxItemSize) { + xFreeSize = pxRingbuffer->xMaxItemSize; + } else if (xFreeSize < 0) { + //Occurs when free space is less than header size + xFreeSize = 0; + } + return xFreeSize; +} + +static size_t prvGetCurMaxSizeAllowSplit(Ringbuffer_t *pxRingbuffer) +{ + BaseType_t xFreeSize; + //Check if buffer is full + if (pxRingbuffer->uxRingbufferFlags & rbBUFFER_FULL_FLAG) { + return 0; + } + if (pxRingbuffer->pucWrite == pxRingbuffer->pucHead && pxRingbuffer->pucFree == pxRingbuffer->pucHead) { + //Check for special case where pucWrite and pucFree are both at pucHead + xFreeSize = pxRingbuffer->xSize - rbHEADER_SIZE; + } else if (pxRingbuffer->pucWrite < pxRingbuffer->pucFree) { + //Free space is contiguous between pucWrite and pucFree, requires single header + xFreeSize = (pxRingbuffer->pucFree - pxRingbuffer->pucWrite) - rbHEADER_SIZE; + } else { + //Free space wraps around, requires two headers + xFreeSize = (pxRingbuffer->pucFree - pxRingbuffer->pucHead) + + (pxRingbuffer->pucTail - pxRingbuffer->pucWrite) - + (rbHEADER_SIZE * 2); + } + + //Limit free size to be within bounds + if (xFreeSize > pxRingbuffer->xMaxItemSize) { + xFreeSize = pxRingbuffer->xMaxItemSize; + } else if (xFreeSize < 0) { + xFreeSize = 0; + } + return xFreeSize; +} + +static size_t prvGetCurMaxSizeByteBuf(Ringbuffer_t *pxRingbuffer) +{ + BaseType_t xFreeSize; + //Check if buffer is full + if (pxRingbuffer->uxRingbufferFlags & rbBUFFER_FULL_FLAG) { + return 0; + } + + /* + * Return whatever space is available depending on relative positions of the free + * pointer and write pointer. There is no overhead of headers in this mode + */ + xFreeSize = pxRingbuffer->pucFree - pxRingbuffer->pucWrite; + if (xFreeSize <= 0) { + xFreeSize += pxRingbuffer->xSize; + } + return xFreeSize; +} + +static BaseType_t prvReceiveGeneric(Ringbuffer_t *pxRingbuffer, void **pvItem1, void **pvItem2, size_t *xItemSize1, size_t *xItemSize2, size_t xMaxSize, TickType_t xTicksToWait) +{ + BaseType_t xReturn = pdFALSE; + BaseType_t xReturnSemaphore = pdFALSE; + TickType_t xTicksEnd = xTaskGetTickCount() + xTicksToWait; + TickType_t xTicksRemaining = xTicksToWait; + while (xTicksRemaining <= xTicksToWait) { //xTicksToWait will underflow once xTaskGetTickCount() > ticks_end + //Block until more free space becomes available or timeout + if (xSemaphoreTake(pxRingbuffer->xItemsBufferedSemaphore, xTicksRemaining) != pdTRUE) { + xReturn = pdFALSE; //Timed out attempting to get semaphore + break; + } + + //Semaphore obtained, check if item can be retrieved + taskENTER_CRITICAL(&pxRingbuffer->mux); + if (prvCheckItemAvail(pxRingbuffer) == pdTRUE) { + //Item is available for retrieval + BaseType_t xIsSplit; + if (pxRingbuffer->uxRingbufferFlags & rbBYTE_BUFFER_FLAG) { + //Second argument (pxIsSplit) is unused for byte buffers + *pvItem1 = pxRingbuffer->pvGetItem(pxRingbuffer, NULL, xMaxSize, xItemSize1); } else { - /* Indicate the wrapping */ - hdr->flags|=iflag_wrap; + //Third argument (xMaxSize) is unused for no-split/allow-split buffers + *pvItem1 = pxRingbuffer->pvGetItem(pxRingbuffer, &xIsSplit, 0, xItemSize1); } + //Check for item split if configured to do so + if ((pxRingbuffer->uxRingbufferFlags & rbALLOW_SPLIT_FLAG) && (pvItem2 != NULL) && (xItemSize2 != NULL)) { + if (xIsSplit == pdTRUE) { + *pvItem2 = pxRingbuffer->pvGetItem(pxRingbuffer, &xIsSplit, 0, xItemSize2); + configASSERT(*pvItem2 < *pvItem1); //Check wrap around has occurred + configASSERT(xIsSplit == pdFALSE); //Second part should not have wrapped flag + } else { + *pvItem2 = NULL; + } + } + xReturn = pdTRUE; + if (pxRingbuffer->xItemsWaiting > 0) { + xReturnSemaphore = pdTRUE; + } + taskEXIT_CRITICAL(&pxRingbuffer->mux); + break; + } + //No item available for retrieval, adjust ticks and take the semaphore again + if (xTicksToWait != portMAX_DELAY) { + xTicksRemaining = xTicksEnd - xTaskGetTickCount(); + } + taskEXIT_CRITICAL(&pxRingbuffer->mux); + /* + * Gap between critical section and re-acquiring of the semaphore. If + * semaphore is given now, priority inversion might occur (see docs) + */ + } + + if (xReturnSemaphore == pdTRUE) { + xSemaphoreGive(pxRingbuffer->xItemsBufferedSemaphore); //Give semaphore back so other tasks can retrieve + } + return xReturn; +} + +static BaseType_t prvReceiveGenericFromISR(Ringbuffer_t *pxRingbuffer, void **pvItem1, void **pvItem2, size_t *xItemSize1, size_t *xItemSize2, size_t xMaxSize) +{ + BaseType_t xReturn = pdFALSE; + BaseType_t xReturnSemaphore = pdFALSE; + + taskENTER_CRITICAL_ISR(&pxRingbuffer->mux); + if(prvCheckItemAvail(pxRingbuffer) == pdTRUE) { + BaseType_t xIsSplit; + if (pxRingbuffer->uxRingbufferFlags & rbBYTE_BUFFER_FLAG) { + //Second argument (pxIsSplit) is unused for byte buffers + *pvItem1 = pxRingbuffer->pvGetItem(pxRingbuffer, NULL, xMaxSize, xItemSize1); } else { - //Huh, only the header fit. Mark as dummy so the receive function doesn't receive - //an useless zero-byte packet. - hdr->flags|=iflag_dummydata; + //Third argument (xMaxSize) is unused for no-split/allow-split buffers + *pvItem1 = pxRingbuffer->pvGetItem(pxRingbuffer, &xIsSplit, 0, xItemSize1); } - rb->write_ptr=rb->data; - } else { - //No special handling needed. Checking if it's gonna fit probably still is a good idea. - if (ringbufferFreeMem(rb) < sizeof(buf_entry_hdr_t)+rbuffer_size) { - //Buffer is not going to fit, period. - return pdFALSE; - } - } - - //If we are here, the buffer is guaranteed to fit in the space starting at the write pointer. - buf_entry_hdr_t *hdr=(buf_entry_hdr_t *)rb->write_ptr; - hdr->len=buffer_size; - hdr->flags=0; - rb->write_ptr+=sizeof(buf_entry_hdr_t); - memcpy(rb->write_ptr, buffer, buffer_size); - rb->write_ptr+=rbuffer_size; - - //The buffer will wrap around if we don't have room for a header anymore. - if ((rb->data+rb->size)-rb->write_ptr < sizeof(buf_entry_hdr_t)) { - //'Forward' the write buffer until we are at the start of the ringbuffer. - //The read pointer will always be at the start of a full header, which cannot - //exist at the point of the current write pointer, so there's no chance of overtaking - //that. - rb->write_ptr=rb->data; - } - return pdTRUE; -} - - -//Copies a bunch of daya to the ring bytebuffer. Assumes there is space in the ringbuffer and -//the ringbuffer is locked. Increases write_ptr to the next item. Returns pdTRUE on -//success, pdFALSE if it can't make the item fit and the calling routine needs to retry -//later or fail. -//This function by itself is not threadsafe, always call from within a muxed section. -static BaseType_t copyItemToRingbufByteBuf(ringbuf_t *rb, uint8_t *buffer, size_t buffer_size) -{ - size_t rem_len=(rb->data + rb->size) - rb->write_ptr; //length remaining until end of ringbuffer - - //See if we have enough contiguous space to write the buffer. - if (rem_len < buffer_size) { - //...Nope. Write the data bit that fits. - memcpy(rb->write_ptr, buffer, rem_len); - //Update vars so the code later on will write the rest of the data. - buffer+=rem_len; - buffer_size-=rem_len; - rb->write_ptr=rb->data; - } - - //If we are here, the buffer is guaranteed to fit in the space starting at the write pointer. - memcpy(rb->write_ptr, buffer, buffer_size); - rb->write_ptr+=buffer_size; - //The buffer will wrap around if we're at the end. - if ((rb->data+rb->size)==rb->write_ptr) { - rb->write_ptr=rb->data; - } - return pdTRUE; -} - -//Retrieves a pointer to the data of the next item, or NULL if this is not possible. -//This function by itself is not threadsafe, always call from within a muxed section. -//Because we always return one item, this function ignores the wanted_length variable. -static uint8_t *getItemFromRingbufDefault(ringbuf_t *rb, size_t *length, int wanted_length) -{ - uint8_t *ret; - configASSERT(((int)rb->read_ptr&3)==0); - if (rb->read_ptr == rb->write_ptr) { - //No data available. - return NULL; - } - //The item written at the point of the read pointer may be a dummy item. - //We need to skip past it first, if that's the case. - buf_entry_hdr_t *hdr=(buf_entry_hdr_t *)rb->read_ptr; - configASSERT((hdr->len < rb->size) || (hdr->flags & iflag_dummydata)); - if (hdr->flags & iflag_dummydata) { - //Hdr is dummy data. Reset to start of ringbuffer. - rb->read_ptr=rb->data; - //Get real header - hdr=(buf_entry_hdr_t *)rb->read_ptr; - configASSERT(hdr->len < rb->size); - //No need to re-check if the ringbuffer is empty: the write routine will - //always write a dummy item plus the real data item in one go, so now we must - //be at the real data item by definition. - } - //Okay, pass the data back. - ret=rb->read_ptr+sizeof(buf_entry_hdr_t); - *length=hdr->len; - //...and move the read pointer past the data. - rb->read_ptr+=sizeof(buf_entry_hdr_t)+((hdr->len+3)&~3); - //The buffer will wrap around if we don't have room for a header anymore. - //Integer typecasting is used because the first operand can result into a -ve - //value for cases wherein the ringbuffer size is not a multiple of 4, but the - //implementation logic aligns read_ptr to 4-byte boundary - if ((int)((rb->data + rb->size) - rb->read_ptr) < (int)sizeof(buf_entry_hdr_t)) { - rb->read_ptr=rb->data; - } - return ret; -} - -//Retrieves a pointer to the data in the buffer, or NULL if this is not possible. -//This function by itself is not threadsafe, always call from within a muxed section. -//This function honours the wanted_length and will never return more data than this. -static uint8_t *getItemFromRingbufByteBuf(ringbuf_t *rb, size_t *length, int wanted_length) -{ - uint8_t *ret; - if (rb->read_ptr != rb->free_ptr) { - //This type of ringbuff does not support multiple outstanding buffers. - return NULL; - } - if (rb->read_ptr == rb->write_ptr) { - //No data available. - return NULL; - } - ret=rb->read_ptr; - if (rb->read_ptr > rb->write_ptr) { - //Available data wraps around. Give data until the end of the buffer. - *length=rb->size-(rb->read_ptr - rb->data); - if (wanted_length != 0 && *length > wanted_length) { - *length=wanted_length; - rb->read_ptr+=wanted_length; - } else { - rb->read_ptr=rb->data; - } - } else { - //Return data up to write pointer. - *length=rb->write_ptr -rb->read_ptr; - if (wanted_length != 0 && *length > wanted_length) { - *length=wanted_length; - rb->read_ptr+=wanted_length; - } else { - rb->read_ptr=rb->write_ptr; - } - } - return ret; -} - - -//Returns an item to the ringbuffer. Will mark the item as free, and will see if the free pointer -//can be increase. -//This function by itself is not threadsafe, always call from within a muxed section. -static void returnItemToRingbufDefault(ringbuf_t *rb, void *item) { - uint8_t *data=(uint8_t*)item; - configASSERT(((int)rb->free_ptr&3)==0); - configASSERT(data >= rb->data); - configASSERT(data <= rb->data+rb->size); - //Grab the buffer entry that preceeds the buffer - buf_entry_hdr_t *hdr=(buf_entry_hdr_t*)(data-sizeof(buf_entry_hdr_t)); - configASSERT(hdr->len < rb->size); - configASSERT((hdr->flags & iflag_dummydata)==0); - configASSERT((hdr->flags & iflag_free)==0); - //Mark the buffer as free. - hdr->flags&=~iflag_wrap; - hdr->flags|=iflag_free; - - //Do a cleanup pass. - hdr=(buf_entry_hdr_t *)rb->free_ptr; - //basically forward free_ptr until we run into either a block that is still in use or the write pointer. - while (((hdr->flags & iflag_free) || (hdr->flags & iflag_dummydata)) && rb->free_ptr != rb->write_ptr) { - if (hdr->flags & iflag_dummydata) { - //Rest is dummy data. Reset to start of ringbuffer. - rb->free_ptr=rb->data; - } else { - //Skip past item - rb->free_ptr+=sizeof(buf_entry_hdr_t); - //Check if the free_ptr overshoots the buffer. - //Checking this before aligning free_ptr since it is possible that alignment - //will cause pointer to overshoot, if the ringbuf size is not a multiple of 4 - configASSERT(rb->free_ptr+hdr->len<=rb->data+rb->size); - //Align free_ptr to 4 byte boundary. Overshoot condition will result in wrap around below - size_t len=(hdr->len+3)&~3; - rb->free_ptr+=len; - } - //The buffer will wrap around if we don't have room for a header anymore. - //Integer typecasting is used because the first operand can result into a -ve - //value for cases wherein the ringbuffer size is not a multiple of 4, but the - //implementation logic aligns free_ptr to 4-byte boundary - if ((int)((rb->data+rb->size)-rb->free_ptr) < (int)sizeof(buf_entry_hdr_t)) { - rb->free_ptr=rb->data; - } - //The free_ptr can not exceed read_ptr, otherwise write_ptr might overwrite read_ptr. - //Read_ptr can not set to rb->data with free_ptr, otherwise write_ptr might wrap around to rb->data. - if(rb->free_ptr == rb->read_ptr) break; - //Next header - hdr=(buf_entry_hdr_t *)rb->free_ptr; - } -} - - -//Returns an item to the ringbuffer. Will mark the item as free, and will see if the free pointer -//can be increase. -//This function by itself is not threadsafe, always call from within a muxed section. -static void returnItemToRingbufBytebuf(ringbuf_t *rb, void *item) { - configASSERT((uint8_t *)item >= rb->data); - configASSERT((uint8_t *)item < rb->data+rb->size); - //Free the read memory. - rb->free_ptr=rb->read_ptr; -} -/* - Check if the selected queue set member is the ringbuffer's read semaphore -*/ -BaseType_t xRingbufferCanRead(RingbufHandle_t ringbuf, QueueSetMemberHandle_t member) -{ - ringbuf_t *rb=(ringbuf_t *)ringbuf; - configASSERT(rb); - return (rb->items_buffered_sem == member)? pdTRUE : pdFALSE; -} -/* - Check if the selected queue set member is the ringbuffer's write semaphore -*/ -BaseType_t xRingbufferCanWrite(RingbufHandle_t ringbuf, QueueSetMemberHandle_t member) -{ - ringbuf_t *rb=(ringbuf_t *)ringbuf; - configASSERT(rb); - return (rb->free_space_sem == member)? pdTRUE : pdFALSE; -} - -void xRingbufferPrintInfo(RingbufHandle_t ringbuf) -{ - ringbuf_t *rb=(ringbuf_t *)ringbuf; - configASSERT(rb); - ets_printf("Rb size %d free %d rptr %d freeptr %d wptr %d\n", - rb->size, ringbufferFreeMem(rb), rb->read_ptr-rb->data, rb->free_ptr-rb->data, rb->write_ptr-rb->data); -} - - -size_t xRingbufferGetCurFreeSize(RingbufHandle_t ringbuf) -{ - ringbuf_t *rb=(ringbuf_t *)ringbuf; - configASSERT(rb); - configASSERT(rb->getFreeSizeImpl); - int free_size = rb->getFreeSizeImpl(rb); - //Reserve one byte. If we do not do this and the entire buffer is filled, we get a situation - //where read_ptr == free_ptr, messing up the next calculation. - return free_size - 1; -} - -static size_t getCurFreeSizeByteBuf(ringbuf_t *rb) -{ - //Return whatever space is available depending on relative positions of - //the free pointer and write pointer. There is no overhead of headers in - //this mode - int free_size = rb->free_ptr-rb->write_ptr; - if (free_size <= 0) - free_size += rb->size; - return free_size; -} - -static size_t getCurFreeSizeAllowSplit(ringbuf_t *rb) -{ - int free_size; - //If Both, the write and free pointer are at the start. Hence, the entire buffer - //is available (minus the space for the header) - if (rb->write_ptr == rb->free_ptr && rb->write_ptr == rb->data) { - free_size = rb->size - sizeof(buf_entry_hdr_t); - } else if (rb->write_ptr < rb->free_ptr) { - //Else if the free pointer is beyond the write pointer, only the space between - //them would be available (minus the space for the header) - free_size = rb->free_ptr - rb->write_ptr - sizeof(buf_entry_hdr_t); - } else { - //Else the data can wrap around and 2 headers will be required - free_size = rb->free_ptr - rb->write_ptr + rb->size - (2 * sizeof(buf_entry_hdr_t)); - } - return free_size; -} - -static size_t getCurFreeSizeNoSplit(ringbuf_t *rb) -{ - int free_size; - //If the free pointer is beyond the write pointer, only the space between - //them would be available - if (rb->write_ptr < rb->free_ptr) { - free_size = rb->free_ptr - rb->write_ptr; - } else { - //Else check which one is bigger amongst the below 2 - //1) Space from the write pointer to the end of buffer - int size1 = rb->data + rb->size - rb->write_ptr; - //2) Space from the start of buffer to the free pointer - int size2 = rb->free_ptr - rb->data; - //And then select the larger of the two - free_size = size1 > size2 ? size1 : size2; - } - //In any case, a single header will be used, so subtracting the space that - //would be required for it - return free_size - sizeof(buf_entry_hdr_t); -} - - -RingbufHandle_t xRingbufferCreate(size_t buf_length, ringbuf_type_t type) -{ - ringbuf_t *rb = malloc(sizeof(ringbuf_t)); - if (rb==NULL) goto err; - memset(rb, 0, sizeof(ringbuf_t)); - rb->data = malloc(buf_length); - if (rb->data == NULL) goto err; - rb->size = buf_length; - rb->free_ptr = rb->data; - rb->read_ptr = rb->data; - rb->write_ptr = rb->data; - rb->free_space_sem = xSemaphoreCreateBinary(); - rb->items_buffered_sem = xSemaphoreCreateBinary(); - rb->flags=0; - if (type==RINGBUF_TYPE_ALLOWSPLIT) { - rb->flags|=flag_allowsplit; - rb->copyItemToRingbufImpl=copyItemToRingbufAllowSplit; - rb->getItemFromRingbufImpl=getItemFromRingbufDefault; - rb->returnItemToRingbufImpl=returnItemToRingbufDefault; - //Calculate max item size. Worst case, we need to split an item into two, which means two headers of overhead. - rb->maxItemSize=rb->size-(sizeof(buf_entry_hdr_t)*2)-4; - rb->getFreeSizeImpl=getCurFreeSizeAllowSplit; - } else if (type==RINGBUF_TYPE_BYTEBUF) { - rb->flags|=flag_bytebuf; - rb->copyItemToRingbufImpl=copyItemToRingbufByteBuf; - rb->getItemFromRingbufImpl=getItemFromRingbufByteBuf; - rb->returnItemToRingbufImpl=returnItemToRingbufBytebuf; - //Calculate max item size. We have no headers and can split anywhere -> size is total size minus one. - rb->maxItemSize=rb->size-1; - rb->getFreeSizeImpl=getCurFreeSizeByteBuf; - } else if (type==RINGBUF_TYPE_NOSPLIT) { - rb->copyItemToRingbufImpl=copyItemToRingbufNoSplit; - rb->getItemFromRingbufImpl=getItemFromRingbufDefault; - rb->returnItemToRingbufImpl=returnItemToRingbufDefault; - //Calculate max item size. Worst case, we have the write ptr in such a position that we are lacking four bytes of free - //memory to put an item into the rest of the memory. If this happens, we have to dummy-fill - //(item_data-4) bytes of buffer, then we only have (size-(item_data-4) bytes left to fill - //with the real item. (item size being header+data) - rb->maxItemSize=(rb->size/2)-sizeof(buf_entry_hdr_t)-4; - rb->getFreeSizeImpl=getCurFreeSizeNoSplit; + //Check for item split if configured to do so + if ((pxRingbuffer->uxRingbufferFlags & rbALLOW_SPLIT_FLAG) && pvItem2 != NULL && xItemSize2 != NULL) { + if (xIsSplit == pdTRUE) { + *pvItem2 = pxRingbuffer->pvGetItem(pxRingbuffer, &xIsSplit, 0, xItemSize2); + configASSERT(*pvItem2 < *pvItem1); //Check wrap around has occurred + configASSERT(xIsSplit == pdFALSE); //Second part should not have wrapped flag + } else { + *pvItem2 = NULL; + } + } + xReturn = pdTRUE; + if (pxRingbuffer->xItemsWaiting > 0) { + xReturnSemaphore = pdTRUE; + } + } + taskEXIT_CRITICAL_ISR(&pxRingbuffer->mux); + + if (xReturnSemaphore == pdTRUE) { + xSemaphoreGiveFromISR(pxRingbuffer->xItemsBufferedSemaphore, NULL); //Give semaphore back so other tasks can retrieve + } + return xReturn; +} + +/* ------------------------------------------------- Public Definitions -------------------------------------------- */ + +RingbufHandle_t xRingbufferCreate(size_t xBufferSize, ringbuf_type_t xBufferType) +{ + //Allocate memory + Ringbuffer_t *pxRingbuffer = calloc(1, sizeof(Ringbuffer_t)); + if (pxRingbuffer == NULL) { + goto err; + } + if (xBufferType != RINGBUF_TYPE_BYTEBUF) { + xBufferSize = rbALIGN_SIZE(xBufferSize); //xBufferSize is rounded up for no-split/allow-split buffers + } + pxRingbuffer->pucHead = malloc(xBufferSize); + if (pxRingbuffer->pucHead == NULL) { + goto err; + } + + //Initialize values + pxRingbuffer->xSize = xBufferSize; + pxRingbuffer->pucTail = pxRingbuffer->pucHead + xBufferSize; + pxRingbuffer->pucFree = pxRingbuffer->pucHead; + pxRingbuffer->pucRead = pxRingbuffer->pucHead; + pxRingbuffer->pucWrite = pxRingbuffer->pucHead; + pxRingbuffer->xItemsWaiting = 0; + pxRingbuffer->xFreeSpaceSemaphore = xSemaphoreCreateBinary(); + pxRingbuffer->xItemsBufferedSemaphore = xSemaphoreCreateBinary(); + pxRingbuffer->uxRingbufferFlags = 0; + + //Initialize type dependent values and function pointers + if (xBufferType == RINGBUF_TYPE_NOSPLIT) { + pxRingbuffer->xCheckItemFits = prvCheckItemFitsDefault; + pxRingbuffer->vCopyItem = prvCopyItemNoSplit; + pxRingbuffer->pvGetItem = prvGetItemDefault; + pxRingbuffer->vReturnItem = prvReturnItemDefault; + /* + * Buffer lengths are always aligned. No-split buffer (read/write/free) + * pointers are also always aligned. Therefore worse case scenario is + * the write pointer is at the most aligned halfway point. + */ + pxRingbuffer->xMaxItemSize = rbALIGN_SIZE(pxRingbuffer->xSize / 2) - rbHEADER_SIZE; + pxRingbuffer->xGetCurMaxSize = prvGetCurMaxSizeNoSplit; + } else if (xBufferType == RINGBUF_TYPE_ALLOWSPLIT) { + pxRingbuffer->uxRingbufferFlags |= rbALLOW_SPLIT_FLAG; + pxRingbuffer->xCheckItemFits = prvCheckItemFitsDefault; + pxRingbuffer->vCopyItem = prvCopyItemAllowSplit; + pxRingbuffer->pvGetItem = prvGetItemDefault; + pxRingbuffer->vReturnItem = prvReturnItemDefault; + //Worst case an item is split into two, incurring two headers of overhead + pxRingbuffer->xMaxItemSize = pxRingbuffer->xSize - (sizeof(ItemHeader_t) * 2); + pxRingbuffer->xGetCurMaxSize = prvGetCurMaxSizeAllowSplit; + } else if (xBufferType == RINGBUF_TYPE_BYTEBUF) { + pxRingbuffer->uxRingbufferFlags |= rbBYTE_BUFFER_FLAG; + pxRingbuffer->xCheckItemFits = prvCheckItemFitsByteBuffer; + pxRingbuffer->vCopyItem = prvCopyItemByteBuf; + pxRingbuffer->pvGetItem = prvGetItemByteBuf; + pxRingbuffer->vReturnItem = prvReturnItemByteBuf; + //Byte buffers do not incur any overhead + pxRingbuffer->xMaxItemSize = pxRingbuffer->xSize; + pxRingbuffer->xGetCurMaxSize = prvGetCurMaxSizeByteBuf; } else { + //Unsupported type configASSERT(0); } - if (rb->free_space_sem == NULL || rb->items_buffered_sem == NULL) goto err; - vPortCPUInitializeMutex(&rb->mux); - return (RingbufHandle_t)rb; + if (pxRingbuffer->xFreeSpaceSemaphore == NULL || pxRingbuffer->xItemsBufferedSemaphore == NULL) { + goto err; + } + xSemaphoreGive(pxRingbuffer->xFreeSpaceSemaphore); + vPortCPUInitializeMutex(&pxRingbuffer->mux); + + return (RingbufHandle_t)pxRingbuffer; err: //Some error has happened. Free/destroy all allocated things and return NULL. - if (rb) { - free(rb->data); - if (rb->free_space_sem) vSemaphoreDelete(rb->free_space_sem); - if (rb->items_buffered_sem) vSemaphoreDelete(rb->items_buffered_sem); + if (pxRingbuffer) { + free(pxRingbuffer->pucHead); + if (pxRingbuffer->xFreeSpaceSemaphore) { + vSemaphoreDelete(pxRingbuffer->xFreeSpaceSemaphore); + } + if (pxRingbuffer->xItemsBufferedSemaphore) { + vSemaphoreDelete(pxRingbuffer->xItemsBufferedSemaphore); + } } - free(rb); + free(pxRingbuffer); return NULL; } -RingbufHandle_t xRingbufferCreateNoSplit(size_t item_size, size_t num_item) +RingbufHandle_t xRingbufferCreateNoSplit(size_t xItemSize, size_t xItemNum) { - size_t aligned_size = (item_size+3)&~3; - return xRingbufferCreate((aligned_size + sizeof(buf_entry_hdr_t)) * num_item, RINGBUF_TYPE_NOSPLIT); + return xRingbufferCreate((rbALIGN_SIZE(xItemSize) + rbHEADER_SIZE) * xItemNum, RINGBUF_TYPE_NOSPLIT); } -void vRingbufferDelete(RingbufHandle_t ringbuf) { - ringbuf_t *rb=(ringbuf_t *)ringbuf; - if (rb) { - free(rb->data); - if (rb->free_space_sem) vSemaphoreDelete(rb->free_space_sem); - if (rb->items_buffered_sem) vSemaphoreDelete(rb->items_buffered_sem); +BaseType_t xRingbufferSend(RingbufHandle_t xRingbuffer, const void *pvItem, size_t xItemSize, TickType_t xTicksToWait) +{ + //Check arguments + Ringbuffer_t *pxRingbuffer = (Ringbuffer_t *)xRingbuffer; + configASSERT(pxRingbuffer); + configASSERT(pvItem != NULL || xItemSize == 0); + if (xItemSize > pxRingbuffer->xMaxItemSize) { + return pdFALSE; //Data will never ever fit in the queue. } - free(rb); + if ((pxRingbuffer->uxRingbufferFlags & rbBYTE_BUFFER_FLAG) && xItemSize == 0) { + return pdTRUE; //Sending 0 bytes to byte buffer has no effect + } + + //Attempt to send an item + BaseType_t xReturn = pdFALSE; + BaseType_t xReturnSemaphore = pdFALSE; + TickType_t xTicksEnd = xTaskGetTickCount() + xTicksToWait; + TickType_t xTicksRemaining = xTicksToWait; + while (xTicksRemaining <= xTicksToWait) { //xTicksToWait will underflow once xTaskGetTickCount() > ticks_end + //Block until more free space becomes available or timeout + if (xSemaphoreTake(pxRingbuffer->xFreeSpaceSemaphore, xTicksRemaining) != pdTRUE) { + xReturn = pdFALSE; + break; + } + //Semaphore obtained, check if item can fit + taskENTER_CRITICAL(&pxRingbuffer->mux); + if(pxRingbuffer->xCheckItemFits(pxRingbuffer, xItemSize) == pdTRUE) { + //Item will fit, copy item + pxRingbuffer->vCopyItem(pxRingbuffer, pvItem, xItemSize); + xReturn = pdTRUE; + //Check if the free semaphore should be returned to allow other tasks to send + if (prvGetFreeSize(pxRingbuffer) > 0) { + xReturnSemaphore = pdTRUE; + } + taskEXIT_CRITICAL(&pxRingbuffer->mux); + break; + } + //Item doesn't fit, adjust ticks and take the semaphore again + if (xTicksToWait != portMAX_DELAY) { + xTicksRemaining = xTicksEnd - xTaskGetTickCount(); + } + taskEXIT_CRITICAL(&pxRingbuffer->mux); + /* + * Gap between critical section and re-acquiring of the semaphore. If + * semaphore is given now, priority inversion might occur (see docs) + */ + } + + if (xReturn == pdTRUE) { + //Indicate item was successfully sent + xSemaphoreGive(pxRingbuffer->xItemsBufferedSemaphore); + } + if (xReturnSemaphore == pdTRUE) { + xSemaphoreGive(pxRingbuffer->xFreeSpaceSemaphore); //Give back semaphore so other tasks can send + } + return xReturn; } -size_t xRingbufferGetMaxItemSize(RingbufHandle_t ringbuf) +BaseType_t xRingbufferSendFromISR(RingbufHandle_t xRingbuffer, const void *pvItem, size_t xItemSize, BaseType_t *pxHigherPriorityTaskWoken) { - ringbuf_t *rb=(ringbuf_t *)ringbuf; - configASSERT(rb); - return rb->maxItemSize; + //Check arguments + Ringbuffer_t *pxRingbuffer = (Ringbuffer_t *)xRingbuffer; + configASSERT(pxRingbuffer); + configASSERT(pvItem != NULL || xItemSize == 0); + if (xItemSize > pxRingbuffer->xMaxItemSize) { + return pdFALSE; //Data will never ever fit in the queue. + } + if ((pxRingbuffer->uxRingbufferFlags & rbBYTE_BUFFER_FLAG) && xItemSize == 0) { + return pdTRUE; //Sending 0 bytes to byte buffer has no effect + } + + //Attempt to send an item + BaseType_t xReturn; + BaseType_t xReturnSemaphore = pdFALSE; + taskENTER_CRITICAL_ISR(&pxRingbuffer->mux); + if (pxRingbuffer->xCheckItemFits(xRingbuffer, xItemSize) == pdTRUE) { + pxRingbuffer->vCopyItem(xRingbuffer, pvItem, xItemSize); + xReturn = pdTRUE; + //Check if the free semaphore should be returned to allow other tasks to send + if (prvGetFreeSize(pxRingbuffer) > 0) { + xReturnSemaphore = pdTRUE; + } + } else { + xReturn = pdFALSE; + } + taskEXIT_CRITICAL_ISR(&pxRingbuffer->mux); + + if (xReturn == pdTRUE) { + //Indicate item was successfully sent + xSemaphoreGiveFromISR(pxRingbuffer->xItemsBufferedSemaphore, pxHigherPriorityTaskWoken); + } + if (xReturnSemaphore == pdTRUE) { + xSemaphoreGiveFromISR(pxRingbuffer->xFreeSpaceSemaphore, pxHigherPriorityTaskWoken); //Give back semaphore so other tasks can send + } + return xReturn; } -bool xRingbufferIsNextItemWrapped(RingbufHandle_t ringbuf) +void *xRingbufferReceive(RingbufHandle_t xRingbuffer, size_t *pxItemSize, TickType_t xTicksToWait) { - ringbuf_t *rb=(ringbuf_t *)ringbuf; - configASSERT(rb); - buf_entry_hdr_t *hdr=(buf_entry_hdr_t *)rb->read_ptr; - return hdr->flags & iflag_wrap; + //Check arguments + Ringbuffer_t *pxRingbuffer = (Ringbuffer_t *)xRingbuffer; + configASSERT(pxRingbuffer); + + //Attempt to retrieve an item + void *pvTempItem; + size_t xTempSize; + if (prvReceiveGeneric(pxRingbuffer, &pvTempItem, NULL, &xTempSize, NULL, 0, xTicksToWait) == pdTRUE) { + if (pxItemSize != NULL) { + *pxItemSize = xTempSize; + } + return pvTempItem; + } else { + return NULL; + } } - -BaseType_t xRingbufferSend(RingbufHandle_t ringbuf, void *data, size_t dataSize, TickType_t ticks_to_wait) +void *xRingbufferReceiveFromISR(RingbufHandle_t xRingbuffer, size_t *pxItemSize) { - ringbuf_t *rb=(ringbuf_t *)ringbuf; - size_t needed_size=dataSize+sizeof(buf_entry_hdr_t); - BaseType_t done=pdFALSE; - TickType_t ticks_end = xTaskGetTickCount() + ticks_to_wait; - TickType_t ticks_remaining = ticks_to_wait; + //Check arguments + Ringbuffer_t *pxRingbuffer = (Ringbuffer_t *)xRingbuffer; + configASSERT(pxRingbuffer); - configASSERT(rb); + //Attempt to retrieve an item + void *pvTempItem; + size_t xTempSize; + if (prvReceiveGenericFromISR(pxRingbuffer, &pvTempItem, NULL, &xTempSize, NULL, 0) == pdTRUE) { + if (pxItemSize != NULL) { + *pxItemSize = xTempSize; + } + return pvTempItem; + } else { + return NULL; + } +} - if (dataSize > xRingbufferGetMaxItemSize(ringbuf)) { - //Data will never ever fit in the queue. +BaseType_t xRingbufferReceiveSplit(RingbufHandle_t xRingbuffer, void **ppvHeadItem, void **ppvTailItem, size_t *pxHeadItemSize, size_t *pxTailItemSize, TickType_t xTicksToWait) +{ + //Check arguments + Ringbuffer_t *pxRingbuffer = (Ringbuffer_t *)xRingbuffer; + configASSERT(pxRingbuffer); + configASSERT(pxRingbuffer->uxRingbufferFlags & rbALLOW_SPLIT_FLAG); + configASSERT(ppvHeadItem != NULL && ppvTailItem != NULL); + + //Attempt to retrieve multiple items + void *pvTempHeadItem, *pvTempTailItem; + size_t xTempHeadSize, xTempTailSize; + if (prvReceiveGeneric(pxRingbuffer, &pvTempHeadItem, &pvTempTailItem, &xTempHeadSize, &xTempTailSize, 0, xTicksToWait) == pdTRUE) { + //At least one item was retrieved + *ppvHeadItem = pvTempHeadItem; + if(pxHeadItemSize != NULL){ + *pxHeadItemSize = xTempHeadSize; + } + //Check to see if a second item was also retrieved + if (pvTempTailItem != NULL) { + *ppvTailItem = pvTempTailItem; + if (pxTailItemSize != NULL) { + *pxTailItemSize = xTempTailSize; + } + } else { + *ppvTailItem = NULL; + } + return pdTRUE; + } else { + //No items retrieved + *ppvHeadItem = NULL; + *ppvTailItem = NULL; return pdFALSE; } - - while (!done) { - //Check if there is enough room in the buffer. If not, wait until there is. - do { - if (ringbufferFreeMem(rb) < needed_size) { - //Data does not fit yet. Wait until the free_space_sem is given, then re-evaluate. - - BaseType_t r = xSemaphoreTake(rb->free_space_sem, ticks_remaining); - if (r == pdFALSE) { - //Timeout. - return pdFALSE; - } - //Adjust ticks_remaining; we may have waited less than that and in the case the free memory still is not enough, - //we will need to wait some more. - if (ticks_to_wait != portMAX_DELAY) { - ticks_remaining = ticks_end - xTaskGetTickCount(); - - // ticks_remaining will always be less than or equal to the original ticks_to_wait, - // unless the timeout is reached - in which case it unsigned underflows to a much - // higher value. - // - // (Check is written this non-intuitive way to allow for the case where xTaskGetTickCount() - // has overflowed but the ticks_end value has not overflowed.) - if(ticks_remaining > ticks_to_wait) { - //Timeout, but there is not enough free space for the item that need to be sent. - xSemaphoreGive(rb->free_space_sem); - return pdFALSE; - } - } - - } - } while (ringbufferFreeMem(rb) < needed_size); - - //Lock the mux in order to make sure no one else is messing with the ringbuffer and do the copy. - portENTER_CRITICAL(&rb->mux); - //Another thread may have been able to sneak its write first. Check again now we locked the ringbuff, and retry - //everything if this is the case. Otherwise, we can write and are done. - done=rb->copyItemToRingbufImpl(rb, data, dataSize); - portEXIT_CRITICAL(&rb->mux); - } - xSemaphoreGive(rb->items_buffered_sem); - return pdTRUE; } - -BaseType_t xRingbufferSendFromISR(RingbufHandle_t ringbuf, void *data, size_t dataSize, BaseType_t *higher_prio_task_awoken) +BaseType_t xRingbufferReceiveSplitFromISR(RingbufHandle_t xRingbuffer, void **ppvHeadItem, void **ppvTailItem, size_t *pxHeadItemSize, size_t *pxTailItemSize) { - ringbuf_t *rb=(ringbuf_t *)ringbuf; - BaseType_t write_succeeded; - configASSERT(rb); - size_t needed_size=dataSize+sizeof(buf_entry_hdr_t); - portENTER_CRITICAL_ISR(&rb->mux); - if (needed_size>ringbufferFreeMem(rb)) { - //Does not fit in the remaining space in the ringbuffer. - write_succeeded=pdFALSE; + //Check arguments + Ringbuffer_t *pxRingbuffer = (Ringbuffer_t *)xRingbuffer; + configASSERT(pxRingbuffer); + configASSERT(pxRingbuffer->uxRingbufferFlags & rbALLOW_SPLIT_FLAG); + configASSERT(ppvHeadItem != NULL && ppvTailItem != NULL); + + //Attempt to retrieve multiple items + void *pvTempHeadItem, *pvTempTailItem; + size_t xTempHeadSize, xTempTailSize; + if (prvReceiveGenericFromISR(pxRingbuffer, &pvTempHeadItem, &pvTempTailItem, &xTempHeadSize, &xTempTailSize, 0) == pdTRUE) { + //At least one item was received + *ppvHeadItem = pvTempHeadItem; + if (pxHeadItemSize != NULL) { + *pxHeadItemSize = xTempHeadSize; + } + //Check to see if a second item was also retrieved + if (pvTempTailItem != NULL) { + *ppvTailItem = pvTempTailItem; + if (pxTailItemSize != NULL) { + *pxTailItemSize = xTempTailSize; + } + } else { + *ppvTailItem = NULL; + } + return pdTRUE; } else { - write_succeeded = rb->copyItemToRingbufImpl(rb, data, dataSize); + *ppvHeadItem = NULL; + *ppvTailItem = NULL; + return pdFALSE; } - portEXIT_CRITICAL_ISR(&rb->mux); - if (write_succeeded) { - xSemaphoreGiveFromISR(rb->items_buffered_sem, higher_prio_task_awoken); - } - return write_succeeded; } - -static void *xRingbufferReceiveGeneric(RingbufHandle_t ringbuf, size_t *item_size, TickType_t ticks_to_wait, size_t wanted_size) +void *xRingbufferReceiveUpTo(RingbufHandle_t xRingbuffer, size_t *pxItemSize, TickType_t xTicksToWait, size_t xMaxSize) { - ringbuf_t *rb=(ringbuf_t *)ringbuf; - uint8_t *itemData; - BaseType_t done=pdFALSE; - configASSERT(rb); - while(!done) { - //See if there's any data available. If not, wait until there is. - while (rb->read_ptr == rb->write_ptr) { - BaseType_t r=xSemaphoreTake(rb->items_buffered_sem, ticks_to_wait); - if (r == pdFALSE) { - //Timeout. - return NULL; - } + //Check arguments + Ringbuffer_t *pxRingbuffer = (Ringbuffer_t *)xRingbuffer; + configASSERT(pxRingbuffer); + configASSERT(pxRingbuffer->uxRingbufferFlags & rbBYTE_BUFFER_FLAG); //This function should only be called for byte buffers + if (xMaxSize == 0) { + return NULL; + } + + //Attempt to retrieve up to xMaxSize bytes + void *pvTempItem; + size_t xTempSize; + if (prvReceiveGeneric(pxRingbuffer, &pvTempItem, NULL, &xTempSize, NULL, xMaxSize, xTicksToWait) == pdTRUE) { + if (pxItemSize != NULL) { + *pxItemSize = xTempSize; } - //Okay, we seem to have data in the buffer. Grab the mux and copy it out if it's still there. - portENTER_CRITICAL(&rb->mux); - itemData=rb->getItemFromRingbufImpl(rb, item_size, wanted_size); - portEXIT_CRITICAL(&rb->mux); - if (itemData) { - //We managed to get an item. - done=pdTRUE; + return pvTempItem; + } else { + return NULL; + } +} + +void *xRingbufferReceiveUpToFromISR(RingbufHandle_t xRingbuffer, size_t *pxItemSize, size_t xMaxSize) +{ + //Check arguments + Ringbuffer_t *pxRingbuffer = (Ringbuffer_t *)xRingbuffer; + configASSERT(pxRingbuffer); + configASSERT(pxRingbuffer->uxRingbufferFlags & rbBYTE_BUFFER_FLAG); //This function should only be called for byte buffers + if (xMaxSize == 0) { + return NULL; + } + + //Attempt to retrieve up to xMaxSize bytes + void *pvTempItem; + size_t xTempSize; + if (prvReceiveGenericFromISR(pxRingbuffer, &pvTempItem, NULL, &xTempSize, NULL, xMaxSize) == pdTRUE) { + if (pxItemSize != NULL) { + *pxItemSize = xTempSize; + } + return pvTempItem; + } else { + return NULL; + } +} + +void vRingbufferReturnItem(RingbufHandle_t xRingbuffer, void *pvItem) +{ + Ringbuffer_t *pxRingbuffer = (Ringbuffer_t *)xRingbuffer; + configASSERT(pxRingbuffer); + configASSERT(pvItem != NULL); + + taskENTER_CRITICAL(&pxRingbuffer->mux); + pxRingbuffer->vReturnItem(pxRingbuffer, (uint8_t *)pvItem); + taskEXIT_CRITICAL(&pxRingbuffer->mux); + xSemaphoreGive(pxRingbuffer->xFreeSpaceSemaphore); +} + +void vRingbufferReturnItemFromISR(RingbufHandle_t xRingbuffer, void *pvItem, BaseType_t *pxHigherPriorityTaskWoken) +{ + Ringbuffer_t *pxRingbuffer = (Ringbuffer_t *)xRingbuffer; + configASSERT(pxRingbuffer); + configASSERT(pvItem != NULL); + + taskENTER_CRITICAL_ISR(&pxRingbuffer->mux); + pxRingbuffer->vReturnItem(pxRingbuffer, (uint8_t *)pvItem); + taskEXIT_CRITICAL_ISR(&pxRingbuffer->mux); + xSemaphoreGiveFromISR(pxRingbuffer->xFreeSpaceSemaphore, pxHigherPriorityTaskWoken); +} + +void vRingbufferDelete(RingbufHandle_t xRingbuffer) +{ + Ringbuffer_t *pxRingbuffer = (Ringbuffer_t *)xRingbuffer; + configASSERT(pxRingbuffer); + + if (pxRingbuffer) { + free(pxRingbuffer->pucHead); + if (pxRingbuffer->xFreeSpaceSemaphore) { + vSemaphoreDelete(pxRingbuffer->xFreeSpaceSemaphore); + } + if (pxRingbuffer->xItemsBufferedSemaphore) { + vSemaphoreDelete(pxRingbuffer->xItemsBufferedSemaphore); } } - return (void*)itemData; + free(pxRingbuffer); } -void *xRingbufferReceive(RingbufHandle_t ringbuf, size_t *item_size, TickType_t ticks_to_wait) +size_t xRingbufferGetMaxItemSize(RingbufHandle_t xRingbuffer) { - return xRingbufferReceiveGeneric(ringbuf, item_size, ticks_to_wait, 0); + Ringbuffer_t *pxRingbuffer = (Ringbuffer_t *)xRingbuffer; + configASSERT(pxRingbuffer); + return pxRingbuffer->xMaxItemSize; } - -void *xRingbufferReceiveFromISR(RingbufHandle_t ringbuf, size_t *item_size) +size_t xRingbufferGetCurFreeSize(RingbufHandle_t xRingbuffer) { - ringbuf_t *rb=(ringbuf_t *)ringbuf; - uint8_t *itemData; - configASSERT(rb); - portENTER_CRITICAL_ISR(&rb->mux); - itemData=rb->getItemFromRingbufImpl(rb, item_size, 0); - portEXIT_CRITICAL_ISR(&rb->mux); - return (void*)itemData; + Ringbuffer_t *pxRingbuffer = (Ringbuffer_t *)xRingbuffer; + configASSERT(pxRingbuffer); + + size_t xFreeSize; + taskENTER_CRITICAL(&pxRingbuffer->mux); + xFreeSize = pxRingbuffer->xGetCurMaxSize(pxRingbuffer); + taskEXIT_CRITICAL(&pxRingbuffer->mux); + return xFreeSize; } -void *xRingbufferReceiveUpTo(RingbufHandle_t ringbuf, size_t *item_size, TickType_t ticks_to_wait, size_t wanted_size) { - if (wanted_size == 0) return NULL; - configASSERT(ringbuf); - configASSERT(((ringbuf_t *)ringbuf)->flags & flag_bytebuf); - return xRingbufferReceiveGeneric(ringbuf, item_size, ticks_to_wait, wanted_size); -} - -void *xRingbufferReceiveUpToFromISR(RingbufHandle_t ringbuf, size_t *item_size, size_t wanted_size) +BaseType_t xRingbufferAddToQueueSetRead(RingbufHandle_t xRingbuffer, QueueSetHandle_t xQueueSet) { - ringbuf_t *rb=(ringbuf_t *)ringbuf; - uint8_t *itemData; - if (wanted_size == 0) return NULL; - configASSERT(rb); - configASSERT(rb->flags & flag_bytebuf); - portENTER_CRITICAL_ISR(&rb->mux); - itemData=rb->getItemFromRingbufImpl(rb, item_size, wanted_size); - portEXIT_CRITICAL_ISR(&rb->mux); - return (void*)itemData; + Ringbuffer_t *pxRingbuffer = (Ringbuffer_t *)xRingbuffer; + configASSERT(pxRingbuffer); + + BaseType_t xReturn; + taskENTER_CRITICAL(&pxRingbuffer->mux); + //Cannot add semaphore to queue set if semaphore is not empty. Temporarily hold semaphore + BaseType_t xHoldSemaphore = xSemaphoreTake(pxRingbuffer->xItemsBufferedSemaphore, 0); + xReturn = xQueueAddToSet(pxRingbuffer->xItemsBufferedSemaphore, xQueueSet); + if (xHoldSemaphore == pdTRUE) { + //Return semaphore if temporarily held + configASSERT(xSemaphoreGive(pxRingbuffer->xItemsBufferedSemaphore) == pdTRUE); + } + taskEXIT_CRITICAL(&pxRingbuffer->mux); + return xReturn; } - -void vRingbufferReturnItem(RingbufHandle_t ringbuf, void *item) +BaseType_t xRingbufferCanRead(RingbufHandle_t xRingbuffer, QueueSetMemberHandle_t xMember) { - ringbuf_t *rb=(ringbuf_t *)ringbuf; - portENTER_CRITICAL(&rb->mux); - rb->returnItemToRingbufImpl(rb, item); - portEXIT_CRITICAL(&rb->mux); - xSemaphoreGive(rb->free_space_sem); + //Check if the selected queue set member is the ring buffer's read semaphore + Ringbuffer_t *pxRingbuffer = (Ringbuffer_t *)xRingbuffer; + configASSERT(pxRingbuffer); + return (pxRingbuffer->xItemsBufferedSemaphore == xMember) ? pdTRUE : pdFALSE; } - -void vRingbufferReturnItemFromISR(RingbufHandle_t ringbuf, void *item, BaseType_t *higher_prio_task_awoken) +BaseType_t xRingbufferRemoveFromQueueSetRead(RingbufHandle_t xRingbuffer, QueueSetHandle_t xQueueSet) { - ringbuf_t *rb=(ringbuf_t *)ringbuf; - portENTER_CRITICAL_ISR(&rb->mux); - rb->returnItemToRingbufImpl(rb, item); - portEXIT_CRITICAL_ISR(&rb->mux); - xSemaphoreGiveFromISR(rb->free_space_sem, higher_prio_task_awoken); + Ringbuffer_t *pxRingbuffer = (Ringbuffer_t *)xRingbuffer; + configASSERT(pxRingbuffer); + + BaseType_t xReturn; + taskENTER_CRITICAL(&pxRingbuffer->mux); + //Cannot remove semaphore from queue set if semaphore is not empty. Temporarily hold semaphore + BaseType_t xHoldSemaphore = xSemaphoreTake(pxRingbuffer->xItemsBufferedSemaphore, 0); + xReturn = xQueueRemoveFromSet(pxRingbuffer->xItemsBufferedSemaphore, xQueueSet); + if (xHoldSemaphore == pdTRUE) { + //Return semaphore if temporarily held + configASSERT(xSemaphoreGive(pxRingbuffer->xItemsBufferedSemaphore) == pdTRUE); + } + taskEXIT_CRITICAL(&pxRingbuffer->mux); + return xReturn; } - -BaseType_t xRingbufferAddToQueueSetRead(RingbufHandle_t ringbuf, QueueSetHandle_t xQueueSet) +void vRingbufferGetInfo(RingbufHandle_t xRingbuffer, UBaseType_t *uxFree, UBaseType_t *uxRead, UBaseType_t *uxWrite, UBaseType_t *uxItemsWaiting) { - ringbuf_t *rb=(ringbuf_t *)ringbuf; - configASSERT(rb); - return xQueueAddToSet(rb->items_buffered_sem, xQueueSet); + Ringbuffer_t *pxRingbuffer = (Ringbuffer_t *)xRingbuffer; + configASSERT(pxRingbuffer); + + taskENTER_CRITICAL(&pxRingbuffer->mux); + if (uxFree != NULL) { + *uxFree = (UBaseType_t)(pxRingbuffer->pucFree - pxRingbuffer->pucHead); + } + if (uxRead != NULL) { + *uxRead = (UBaseType_t)(pxRingbuffer->pucRead - pxRingbuffer->pucHead); + } + if (uxWrite != NULL) { + *uxWrite = (UBaseType_t)(pxRingbuffer->pucWrite - pxRingbuffer->pucHead); + } + if (uxItemsWaiting != NULL) { + *uxItemsWaiting = (UBaseType_t)(pxRingbuffer->xItemsWaiting); + } + taskEXIT_CRITICAL(&pxRingbuffer->mux); } - -BaseType_t xRingbufferAddToQueueSetWrite(RingbufHandle_t ringbuf, QueueSetHandle_t xQueueSet) +void xRingbufferPrintInfo(RingbufHandle_t xRingbuffer) { - ringbuf_t *rb=(ringbuf_t *)ringbuf; - configASSERT(rb); - return xQueueAddToSet(rb->free_space_sem, xQueueSet); + Ringbuffer_t *pxRingbuffer = (Ringbuffer_t *)xRingbuffer; + configASSERT(pxRingbuffer); + printf("Rb size:%d\tfree: %d\trptr: %d\tfreeptr: %d\twptr: %d\n", + pxRingbuffer->xSize, prvGetFreeSize(pxRingbuffer), + pxRingbuffer->pucRead - pxRingbuffer->pucHead, + pxRingbuffer->pucFree - pxRingbuffer->pucHead, + pxRingbuffer->pucWrite - pxRingbuffer->pucHead); } +/* --------------------------------- Deprecated Functions ------------------------------ */ +//Todo: Remove the following deprecated functions in next release -BaseType_t xRingbufferRemoveFromQueueSetRead(RingbufHandle_t ringbuf, QueueSetHandle_t xQueueSet) +bool xRingbufferIsNextItemWrapped(RingbufHandle_t xRingbuffer) { - ringbuf_t *rb=(ringbuf_t *)ringbuf; - configASSERT(rb); - return xQueueRemoveFromSet(rb->items_buffered_sem, xQueueSet); + //This function is deprecated, use xRingbufferReceiveSplit() instead + Ringbuffer_t *pxRingbuffer = (Ringbuffer_t *)xRingbuffer; + configASSERT(pxRingbuffer); + bool is_wrapped; + + portENTER_CRITICAL(&pxRingbuffer->mux); + ItemHeader_t *xHeader = (ItemHeader_t *)pxRingbuffer->pucRead; + is_wrapped = xHeader->uxItemFlags & rbITEM_SPLIT_FLAG; + portEXIT_CRITICAL(&pxRingbuffer->mux); + return is_wrapped; } -BaseType_t xRingbufferRemoveFromQueueSetWrite(RingbufHandle_t ringbuf, QueueSetHandle_t xQueueSet) +BaseType_t xRingbufferAddToQueueSetWrite(RingbufHandle_t xRingbuffer, QueueSetHandle_t xQueueSet) { - ringbuf_t *rb=(ringbuf_t *)ringbuf; - configASSERT(rb); - return xQueueRemoveFromSet(rb->free_space_sem, xQueueSet); + //This function is deprecated. QueueSetWrite no longer supported + Ringbuffer_t *pxRingbuffer = (Ringbuffer_t *)xRingbuffer; + configASSERT(pxRingbuffer); + + BaseType_t xReturn; + portENTER_CRITICAL(&pxRingbuffer->mux); + //Cannot add semaphore to queue set if semaphore is not empty. Temporary hold semaphore + BaseType_t xHoldSemaphore = xSemaphoreTake(pxRingbuffer->xFreeSpaceSemaphore, 0); + xReturn = xQueueAddToSet(pxRingbuffer->xFreeSpaceSemaphore, xQueueSet); + if (xHoldSemaphore == pdTRUE) { + //Return semaphore is temporarily held + configASSERT(xSemaphoreGive(pxRingbuffer->xFreeSpaceSemaphore) == pdTRUE); + } + portEXIT_CRITICAL(&pxRingbuffer->mux); + return xReturn; +} + +BaseType_t xRingbufferRemoveFromQueueSetWrite(RingbufHandle_t xRingbuffer, QueueSetHandle_t xQueueSet) +{ + //This function is deprecated. QueueSetWrite no longer supported + Ringbuffer_t *pxRingbuffer = (Ringbuffer_t *)xRingbuffer; + configASSERT(pxRingbuffer); + + BaseType_t xReturn; + portENTER_CRITICAL(&pxRingbuffer->mux); + //Cannot remove semaphore from queue set if semaphore is not empty. Temporary hold semaphore + BaseType_t xHoldSemaphore = xSemaphoreTake(pxRingbuffer->xFreeSpaceSemaphore, 0); + xReturn = xQueueRemoveFromSet(pxRingbuffer->xFreeSpaceSemaphore, xQueueSet); + if (xHoldSemaphore == pdTRUE) { + //Return semaphore is temporarily held + configASSERT(xSemaphoreGive(pxRingbuffer->xFreeSpaceSemaphore) == pdTRUE); + } + portEXIT_CRITICAL(&pxRingbuffer->mux); + return xReturn; } diff --git a/components/freertos/test/test_ringbuf.c b/components/freertos/test/test_ringbuf.c index 6a721aae3..e47512d8e 100644 --- a/components/freertos/test/test_ringbuf.c +++ b/components/freertos/test/test_ringbuf.c @@ -1,227 +1,606 @@ -/* - Test for multicore FreeRTOS ringbuffer. -*/ - -#include #include -#include "rom/ets_sys.h" - +#include #include "freertos/FreeRTOS.h" #include "freertos/task.h" -#include "freertos/semphr.h" #include "freertos/queue.h" +#include "freertos/semphr.h" #include "freertos/ringbuf.h" -#include "freertos/xtensa_api.h" +#include "driver/timer.h" #include "unity.h" -#include "soc/uart_reg.h" -#include "soc/dport_reg.h" -#include "soc/io_mux_reg.h" -#include "esp_intr_alloc.h" +//Definitions used in multiple test cases +#define TIMEOUT_TICKS 10 +#define NO_OF_RB_TYPES 3 +#define ITEM_HDR_SIZE 8 +#define SMALL_ITEM_SIZE 8 +#define LARGE_ITEM_SIZE (2 * SMALL_ITEM_SIZE) +#define BUFFER_SIZE 160 //4Byte aligned size -static RingbufHandle_t rb; -typedef enum { - TST_MOSTLYFILLED, - TST_MOSTLYEMPTY, - TST_INTTOTASK, - TST_TASKTOINT, -} testtype_t; +static const uint8_t small_item[SMALL_ITEM_SIZE] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}; +static const uint8_t large_item[LARGE_ITEM_SIZE] = { 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17}; +static RingbufHandle_t buffer_handles[NO_OF_RB_TYPES]; +static SemaphoreHandle_t done_sem; -static volatile testtype_t testtype; - -intr_handle_t s_intr_handle; - -static void task1(void *arg) +static void send_item_and_check(RingbufHandle_t handle, const uint8_t *item, size_t item_size, TickType_t ticks_to_wait, bool in_isr) { - testtype_t oldtest; - char buf[100]; - int i = 0; - int x, r; - while (1) { - oldtest = testtype; - if (testtype == TST_MOSTLYFILLED || testtype == TST_MOSTLYEMPTY) { - for (x = 0; x < 10; x++) { - sprintf(buf, "This is test %d item %d.", (int)testtype, i++); - ets_printf("TSK w"); - xRingbufferPrintInfo(rb); - r = xRingbufferSend(rb, buf, strlen(buf) + 1, 2000 / portTICK_PERIOD_MS); - if (!r) { - printf("Test %d: Timeout on send!\n", (int)testtype); - } - if (testtype == TST_MOSTLYEMPTY) { - vTaskDelay(300 / portTICK_PERIOD_MS); - } - } - //Send NULL event to stop other side. - r = xRingbufferSend(rb, NULL, 0, 10000 / portTICK_PERIOD_MS); - } - while (oldtest == testtype) { - vTaskDelay(300 / portTICK_PERIOD_MS); - } - } -} - -static void task2(void *arg) -{ - testtype_t oldtest; - char *buf; - size_t len; - while (1) { - oldtest = testtype; - if (testtype == TST_MOSTLYFILLED || testtype == TST_MOSTLYEMPTY) { - while (1) { - ets_printf("TSK r"); - xRingbufferPrintInfo(rb); - buf = xRingbufferReceive(rb, &len, 2000 / portTICK_PERIOD_MS); - if (buf == NULL) { - printf("Test %d: Timeout on recv!\n", (int)testtype); - } else if (len == 0) { - printf("End packet received.\n"); - vRingbufferReturnItem(rb, buf); - break; - } else { - printf("Received: %s (%d bytes, %p)\n", buf, len, buf); - vRingbufferReturnItem(rb, buf); - } - if (testtype == TST_MOSTLYFILLED) { - vTaskDelay(300 / portTICK_PERIOD_MS); - } - } - } - while (oldtest == testtype) { - vTaskDelay(300 / portTICK_PERIOD_MS); - } - } -} - - - -static void uartIsrHdl(void *arg) -{ - char c; - char buf[50]; - char *item; - int r; - size_t len; - BaseType_t xHigherPriorityTaskWoken; - SET_PERI_REG_MASK(UART_INT_CLR_REG(0), UART_RXFIFO_FULL_INT_CLR); - while (READ_PERI_REG(UART_STATUS_REG(0)) & (UART_RXFIFO_CNT << UART_RXFIFO_CNT_S)) { - c = READ_PERI_REG(UART_FIFO_REG(0)); - if (c == 'r') { - ets_printf("ISR r"); - xRingbufferPrintInfo(rb); - item = xRingbufferReceiveFromISR(rb, &len); - if (item == NULL) { - ets_printf("ISR recv fail!\n"); - } else if (len == 0) { - ets_printf("ISR recv NULL!\n"); - vRingbufferReturnItemFromISR(rb, item, &xHigherPriorityTaskWoken); - } else { - ets_printf("ISR recv '%s' (%d bytes, %p)\n", buf, len, buf); - vRingbufferReturnItemFromISR(rb, item, &xHigherPriorityTaskWoken); - } - } else { - sprintf(buf, "UART: %c", c); - ets_printf("ISR w"); - xRingbufferPrintInfo(rb); - r = xRingbufferSendFromISR(rb, buf, strlen(buf) + 1, &xHigherPriorityTaskWoken); - if (!r) { - ets_printf("ISR send fail\n"); - } - } - } - if (xHigherPriorityTaskWoken) { - portYIELD_FROM_ISR(); - } -} - -static void uartRxInit() -{ - WRITE_PERI_REG(UART_CONF1_REG(0), 1 << UART_RXFIFO_FULL_THRHD_S); - CLEAR_PERI_REG_MASK(UART_INT_ENA_REG(0), UART_TXFIFO_EMPTY_INT_ENA | UART_RXFIFO_TOUT_INT_ENA); - SET_PERI_REG_MASK(UART_INT_ENA_REG(0), UART_RXFIFO_FULL_INT_ENA); - - ESP_ERROR_CHECK(esp_intr_alloc(ETS_UART0_INTR_SOURCE, 0, &uartIsrHdl, NULL, &s_intr_handle)); -} - -static void uartRxDeinit() -{ - esp_intr_free(s_intr_handle); -} - -static void testRingbuffer(int type, bool arbitrary) -{ - TaskHandle_t th[2]; - int i; - /* Arbitrary Length means buffer length which is not a multiple of 4 */ - if (arbitrary) { - rb = xRingbufferCreate(31 * 3, type); + BaseType_t ret; + if (in_isr) { + ret = xRingbufferSendFromISR(handle, (void *)item, item_size, NULL); } else { - rb = xRingbufferCreate(32 * 3, type); + ret = xRingbufferSend(handle, (void *)item, item_size, ticks_to_wait); + } + TEST_ASSERT_MESSAGE(ret == pdTRUE, "Failed to send item"); +} + +static void receive_check_and_return_item_no_split(RingbufHandle_t handle, const uint8_t *expected_data, size_t expected_size, TickType_t ticks_to_wait, bool in_isr) +{ + //Receive item from no-split buffer + size_t item_size; + uint8_t *item; + if (in_isr) { + item = (uint8_t *)xRingbufferReceiveFromISR(handle, &item_size); + } else { + item = (uint8_t *)xRingbufferReceive(handle, &item_size, ticks_to_wait); + } + TEST_ASSERT_MESSAGE(item != NULL, "Failed to receive item"); + TEST_ASSERT_MESSAGE(item_size == expected_size, "Item size is incorrect"); + //Check data of received item + for (int i = 0; i < item_size; i++) { + TEST_ASSERT_MESSAGE(item[i] == expected_data[i], "Item data is invalid"); + } + //Return item + if (in_isr) { + vRingbufferReturnItemFromISR(handle, (void *)item, NULL); + } else { + vRingbufferReturnItem(handle, (void *)item); } - testtype = TST_MOSTLYFILLED; +} - xTaskCreatePinnedToCore(task1, "tskone", 2048, NULL, 3, &th[0], 0); - xTaskCreatePinnedToCore(task2, "tsktwo", 2048, NULL, 3, &th[1], 0); - uartRxInit(); - - printf("Press 'r' to read an event in isr, any other key to write one.\n"); - printf("Test: mostlyfilled; putting 10 items in ringbuff ASAP, reading 1 a second\n"); - vTaskDelay(5000 / portTICK_PERIOD_MS); - printf("Test: mostlyempty; putting 10 items in ringbuff @ 1/sec, reading as fast as possible\n"); - testtype = TST_MOSTLYEMPTY; - vTaskDelay(5000 / portTICK_PERIOD_MS); - - //Shut down all the tasks - for (i = 0; i < 2; i++) { - vTaskDelete(th[i]); +static void receive_check_and_return_item_allow_split(RingbufHandle_t handle, const uint8_t *expected_data, size_t expected_size, TickType_t ticks_to_wait, bool in_isr) +{ + //Receive item + size_t item_size1, item_size2; + uint8_t *item1, *item2; + BaseType_t ret; + if (in_isr) { + ret = xRingbufferReceiveSplitFromISR(handle, (void**)&item1, (void **)&item2, &item_size1, &item_size2); + } else { + ret = xRingbufferReceiveSplit(handle, (void**)&item1, (void **)&item2, &item_size1, &item_size2, ticks_to_wait); } - vRingbufferDelete(rb); - uartRxDeinit(); -} + //= xRingbufferReceiveSplit(handle, (void**)&item1, (void **)&item2, &item_size1, &item_size2, ticks_to_wait); + TEST_ASSERT_MESSAGE(ret == pdTRUE, "Failed to receive item"); + TEST_ASSERT_MESSAGE(item1 != NULL, "Failed to receive item"); -// TODO: split this thing into separate orthogonal tests -TEST_CASE("FreeRTOS ringbuffer test, no splitting items", "[freertos]") -{ - testRingbuffer(0, false); -} - -TEST_CASE("FreeRTOS ringbuffer test, w/ splitting items", "[freertos]") -{ - testRingbuffer(1, false); -} - -TEST_CASE("FreeRTOS ringbuffer test, no splitting items, arbitrary length buffer", "[freertos]") -{ - testRingbuffer(0, true); -} - -TEST_CASE("FreeRTOS ringbuffer test, w/ splitting items, arbitrary length buffer", "[freertos]") -{ - testRingbuffer(1, true); -} - - -TEST_CASE("FreeRTOS ringbuffer test, check if zero-length items are handled correctly", "[freertos]") -{ - rb = xRingbufferCreate(32, 0); - int r; - void *v; - size_t sz; - for (int x=0; x<128; x++) { - if (x!=127) { - //Send an item - r = xRingbufferSend(rb, NULL, 0, 10000 / portTICK_PERIOD_MS); - assert(r==pdTRUE); + //Check data of received item(s) and return them + if (item2 == NULL) { + TEST_ASSERT_MESSAGE(item_size1 == expected_size, "Item size is incorrect"); + for (int i = 0; i < item_size1; i++) { + TEST_ASSERT_MESSAGE(item1[i] == expected_data[i], "Item data is invalid"); } - if (x!=0) { - //Receive an item - v=xRingbufferReceive(rb, &sz, 10000 / portTICK_PERIOD_MS); - assert(sz==0); - vRingbufferReturnItem(rb, v); //actually not needed for NULL data... + //Return item + if (in_isr) { + vRingbufferReturnItemFromISR(handle, (void *)item1, NULL); + } else { + vRingbufferReturnItem(handle, (void *)item1); + } + } else { + //Item was split + TEST_ASSERT_MESSAGE(item_size1 + item_size2 == expected_size, "Total item size is incorrect"); + for (int i = 0; i < item_size1; i++) { + TEST_ASSERT_MESSAGE(item1[i] == expected_data[i], "Head item data is invalid"); + } + for (int i = 0; i < item_size2; i++) { + TEST_ASSERT_MESSAGE(item2[i] == expected_data[item_size1 + i], "Head item data is invalid"); + } + //Return Items + if (in_isr) { + vRingbufferReturnItemFromISR(handle, (void *)item1, NULL); + vRingbufferReturnItemFromISR(handle, (void *)item2, NULL); + } else { + vRingbufferReturnItem(handle, (void *)item1); + vRingbufferReturnItem(handle, (void *)item2); } } - vRingbufferDelete(rb); } +static void receive_check_and_return_item_byte_buffer(RingbufHandle_t handle, const uint8_t *expected_data, size_t expected_size, TickType_t ticks_to_wait, bool in_isr) +{ + //Receive item + size_t item_size; + uint8_t *item; + if (in_isr) { + item = (uint8_t *)xRingbufferReceiveUpToFromISR(handle, &item_size, expected_size); + } else { + item = (uint8_t *)xRingbufferReceiveUpTo(handle, &item_size, ticks_to_wait, expected_size); //Limit amount of bytes returned to the size of one item + } + TEST_ASSERT_MESSAGE(item != NULL, "Failed to receive item"); + + //Check data of received item + for (int i = 0; i < item_size; i++) { + TEST_ASSERT_MESSAGE(item[i] == expected_data[i], "Item data is invalid"); + } + //Return item + if (in_isr) { + vRingbufferReturnItemFromISR(handle, (void *)item, NULL); + } else { + vRingbufferReturnItem(handle, (void *)item); + } + + //Check if item wrapped around + if (item_size < expected_size) { + //Item is wrapped, receive second portion + size_t item_size2; + uint8_t *item2; + if (in_isr) { + item2 = (uint8_t *)xRingbufferReceiveUpToFromISR(handle, &item_size2, expected_size - item_size); + } else { + item2 = (uint8_t *)xRingbufferReceiveUpTo(handle, &item_size2, ticks_to_wait, expected_size - item_size); + } + //= (uint8_t *)xRingbufferReceiveUpTo(handle, &item_size2, ticks_to_wait, expected_size - item_size); + TEST_ASSERT_MESSAGE(item2 != NULL, "Failed to receive item"); + TEST_ASSERT_MESSAGE(item_size + item_size2 == expected_size, "Total item size is incorrect"); + for (int i = 0; i < item_size2; i++) { + TEST_ASSERT_MESSAGE(item2[i] == expected_data[item_size + i], "Item data is invalid"); + } + if (in_isr) { + vRingbufferReturnItemFromISR(handle, (void *)item2, NULL); + } else { + vRingbufferReturnItem(handle, (void *)item2); + } + } else { + TEST_ASSERT_MESSAGE(item_size == expected_size, "Item size is incorrect"); + } +} + +/* ----------------- Basic ring buffer behavior tests cases -------------------- + * The following test cases will test basic send, receive, and wrap around + * behavior of each type of ring buffer. Each test case will do the following + * 1) Send multiple items (nearly fill the buffer) + * 2) Receive and check the sent items (also prepares the buffer for a wrap around + * 3) Send a final item that causes a wrap around + * 4) Receive and check the wrapped item + */ + +TEST_CASE("Test ring buffer No-Split", "[freertos]") +{ + //Create buffer + RingbufHandle_t buffer_handle = xRingbufferCreate(BUFFER_SIZE, RINGBUF_TYPE_NOSPLIT); + TEST_ASSERT_MESSAGE(buffer_handle != NULL, "Failed to create ring buffer"); + //Calculate number of items to send. Aim to almost fill buffer to setup for wrap around + int no_of_items = (BUFFER_SIZE - (ITEM_HDR_SIZE + SMALL_ITEM_SIZE)) / (ITEM_HDR_SIZE + SMALL_ITEM_SIZE); + + //Test sending items + for (int i = 0; i < no_of_items; i++) { + send_item_and_check(buffer_handle, small_item, SMALL_ITEM_SIZE, TIMEOUT_TICKS, false); + } + //Test receiving items + for (int i = 0; i < no_of_items; i++) { + receive_check_and_return_item_no_split(buffer_handle, small_item, SMALL_ITEM_SIZE, TIMEOUT_TICKS, false); + } + + //Write pointer should be near the end, test wrap around + uint32_t write_pos_before, write_pos_after; + vRingbufferGetInfo(buffer_handle, NULL, NULL, &write_pos_before, NULL); + //Send large item that causes wrap around + send_item_and_check(buffer_handle, large_item, LARGE_ITEM_SIZE, TIMEOUT_TICKS, false); + //Receive wrapped item + receive_check_and_return_item_no_split(buffer_handle, large_item, LARGE_ITEM_SIZE, TIMEOUT_TICKS, false); + vRingbufferGetInfo(buffer_handle, NULL, NULL, &write_pos_after, NULL); + TEST_ASSERT_MESSAGE(write_pos_after < write_pos_before, "Failed to wrap around"); + + //Cleanup + vRingbufferDelete(buffer_handle); +} + +TEST_CASE("Test ring buffer Allow-Split", "[freertos]") +{ + //Create buffer + RingbufHandle_t buffer_handle = xRingbufferCreate(BUFFER_SIZE, RINGBUF_TYPE_ALLOWSPLIT); + TEST_ASSERT_MESSAGE(buffer_handle != NULL, "Failed to create ring buffer"); + //Calculate number of items to send. Aim to almost fill buffer to setup for wrap around + int no_of_items = (BUFFER_SIZE - (ITEM_HDR_SIZE + SMALL_ITEM_SIZE)) / (ITEM_HDR_SIZE + SMALL_ITEM_SIZE); + + //Test sending items + for (int i = 0; i < no_of_items; i++) { + send_item_and_check(buffer_handle, small_item, SMALL_ITEM_SIZE, TIMEOUT_TICKS, false); + } + //Test receiving items + for (int i = 0; i < no_of_items; i++) { + receive_check_and_return_item_allow_split(buffer_handle, small_item, SMALL_ITEM_SIZE, TIMEOUT_TICKS, false); + } + + //Write pointer should be near the end, test wrap around + uint32_t write_pos_before, write_pos_after; + vRingbufferGetInfo(buffer_handle, NULL, NULL, &write_pos_before, NULL); + //Send large item that causes wrap around + send_item_and_check(buffer_handle, large_item, LARGE_ITEM_SIZE, TIMEOUT_TICKS, false); + //Receive wrapped item + receive_check_and_return_item_allow_split(buffer_handle, large_item, LARGE_ITEM_SIZE, TIMEOUT_TICKS, false); + vRingbufferGetInfo(buffer_handle, NULL, NULL, &write_pos_after, NULL); + TEST_ASSERT_MESSAGE(write_pos_after < write_pos_before, "Failed to wrap around"); + + //Cleanup + vRingbufferDelete(buffer_handle); +} + +TEST_CASE("Test ring buffer Byte Buffer", "[freertos]") +{ + //Create buffer + RingbufHandle_t buffer_handle = xRingbufferCreate(BUFFER_SIZE, RINGBUF_TYPE_BYTEBUF); + TEST_ASSERT_MESSAGE(buffer_handle != NULL, "Failed to create ring buffer"); + //Calculate number of items to send. Aim to almost fill buffer to setup for wrap around + int no_of_items = (BUFFER_SIZE - SMALL_ITEM_SIZE) / SMALL_ITEM_SIZE; + + //Test sending items + for (int i = 0; i < no_of_items; i++) { + send_item_and_check(buffer_handle, small_item, SMALL_ITEM_SIZE, TIMEOUT_TICKS, false); + } + //Test receiving items + for (int i = 0; i < no_of_items; i++) { + receive_check_and_return_item_byte_buffer(buffer_handle, small_item, SMALL_ITEM_SIZE, TIMEOUT_TICKS, false); + } + + //Write pointer should be near the end, test wrap around + uint32_t write_pos_before, write_pos_after; + vRingbufferGetInfo(buffer_handle, NULL, NULL, &write_pos_before, NULL); + //Send large item that causes wrap around + send_item_and_check(buffer_handle, large_item, LARGE_ITEM_SIZE, TIMEOUT_TICKS, false); + //Receive wrapped item + receive_check_and_return_item_byte_buffer(buffer_handle, large_item, LARGE_ITEM_SIZE, TIMEOUT_TICKS, false); + vRingbufferGetInfo(buffer_handle, NULL, NULL, &write_pos_after, NULL); + TEST_ASSERT_MESSAGE(write_pos_after < write_pos_before, "Failed to wrap around"); + + //Cleanup + vRingbufferDelete(buffer_handle); +} + +/* ----------------------- Ring buffer queue sets test ------------------------ + * The following test case will test receiving from ring buffers that have been + * added to a queue set. The test case will do the following... + * 1) Ring buffer of each type is created and added to the queue set + * 2) A receiving task is created to select from the queue set and read from the appropriate ring buffer + */ + +static void queue_set_receiving_task(void *queue_set_handle) +{ + QueueSetHandle_t queue_set = (QueueSetHandle_t)queue_set_handle; + + //Receive multiple items via queue set + BaseType_t done = pdFALSE; + int no_of_items = BUFFER_SIZE / SMALL_ITEM_SIZE; + int items_rec_count[NO_OF_RB_TYPES] = {0}; + while (done != pdTRUE) { + xQueueSetMemberHandle member = xQueueSelectFromSet(queue_set, TIMEOUT_TICKS); + //Read from selected ring buffer + if (xRingbufferCanRead(buffer_handles[0], member) == pdTRUE) { + //No-split buffer + receive_check_and_return_item_no_split(buffer_handles[0], small_item, SMALL_ITEM_SIZE, 0, false); + items_rec_count[0] ++; + } else if (xRingbufferCanRead(buffer_handles[1], member) == pdTRUE) { + //Allow-split buffer + receive_check_and_return_item_allow_split(buffer_handles[1], small_item, SMALL_ITEM_SIZE, 0, false); + items_rec_count[1] ++; + } else if (xRingbufferCanRead(buffer_handles[2], member) == pdTRUE){ + //Byte buffer + receive_check_and_return_item_byte_buffer(buffer_handles[2], small_item, SMALL_ITEM_SIZE, 0, false); + items_rec_count[2] ++; + } else { + TEST_ASSERT_MESSAGE( false, "Error with queue set member"); + } + + //Check for completion + if (items_rec_count[0] == no_of_items && + items_rec_count[1] == no_of_items && + items_rec_count[2] == no_of_items) { + done = pdTRUE; + } + } + + xSemaphoreGive(done_sem); + vTaskDelete(NULL); +} + +TEST_CASE("Test ring buffer with queue sets", "[freertos]") +{ + QueueSetHandle_t queue_set = xQueueCreateSet(NO_OF_RB_TYPES); + done_sem = xSemaphoreCreateBinary(); + + //Create ring buffer of each type, then add them to a queue set + for (int i = 0; i < NO_OF_RB_TYPES; i++) { + buffer_handles[i] = xRingbufferCreate(BUFFER_SIZE, i); + TEST_ASSERT_MESSAGE(buffer_handles[i] != NULL, "Failed to create ring buffer"); + TEST_ASSERT_MESSAGE(xRingbufferAddToQueueSetRead(buffer_handles[i], queue_set) == pdPASS, "Failed to add to read queue set"); + } + //Create a task to send items to each ring buffer + int no_of_items = BUFFER_SIZE / SMALL_ITEM_SIZE; + xTaskCreatePinnedToCore(queue_set_receiving_task, "rec tsk", 2048, (void *)queue_set, UNITY_FREERTOS_PRIORITY + 1 , NULL, 0); + + //Send multiple items to each type of ring buffer + for (int i = 0; i < no_of_items; i++) { + for (int j = 0; j < NO_OF_RB_TYPES; j++) { + send_item_and_check(buffer_handles[j], small_item, SMALL_ITEM_SIZE, TIMEOUT_TICKS, false); + } + } + + xSemaphoreTake(done_sem, portMAX_DELAY); + vSemaphoreDelete(done_sem); + //Remove and delete ring buffers from queue sets + for (int i = 0; i < NO_OF_RB_TYPES; i++) { + TEST_ASSERT_MESSAGE(xRingbufferRemoveFromQueueSetRead(buffer_handles[i], queue_set) == pdTRUE, "Failed to remove from read queue set"); + vRingbufferDelete(buffer_handles[i]); + } + vQueueDelete(queue_set); +} + +/* -------------------------- Test ring buffer ISR ----------------------------- + * The following test case tests ring buffer ISR API. A timer is used to trigger + * the ISR. The test case will do the following + * 1) ISR will be triggered periodically by timer + * 2) The ISR will iterate through all ring buffer types where each iteration + * will send then receive an item to a ring buffer. + */ + +#define TIMER_GROUP 0 +#define TIMER_NUMBER 0 +#define ISR_ITERATIONS ((BUFFER_SIZE / SMALL_ITEM_SIZE) * 2) + +intr_handle_t ringbuffer_isr_handle; +static int buf_type; +static int iterations; + +static void ringbuffer_isr(void *arg) +{ + //Clear timer interrupt + TIMERG0.int_clr_timers.t0 = 1; + TIMERG0.hw_timer[xPortGetCoreID()].config.alarm_en = 1; + + //Test sending to buffer from ISR from ISR + if (buf_type < NO_OF_RB_TYPES) { + send_item_and_check(buffer_handles[buf_type], (void *)small_item, SMALL_ITEM_SIZE, 0, true); + } + + //Receive item from ISR + if (buf_type == RINGBUF_TYPE_NOSPLIT) { + //Test receive from ISR for no-split buffer + receive_check_and_return_item_no_split(buffer_handles[buf_type], (void *)small_item, SMALL_ITEM_SIZE, 0, true); + buf_type++; + } else if (buf_type == RINGBUF_TYPE_ALLOWSPLIT) { + //Test send from ISR to allow-split buffer + receive_check_and_return_item_allow_split(buffer_handles[buf_type], (void *)small_item, SMALL_ITEM_SIZE, 0, true); + buf_type++; + } else if (buf_type == RINGBUF_TYPE_BYTEBUF) { + //Test receive from ISR for byte buffer + receive_check_and_return_item_byte_buffer(buffer_handles[buf_type], (void *)small_item, SMALL_ITEM_SIZE, 0, true); + buf_type++; + } else if (buf_type == NO_OF_RB_TYPES) { + //Check if all iterations complete + if (iterations < ISR_ITERATIONS) { + iterations++; + buf_type = 0; //Reset and iterate through each buffer type again + return; + } else { + //Signal complete + BaseType_t task_woken = pdFALSE; + xSemaphoreGiveFromISR(done_sem, &task_woken); + if (task_woken == pdTRUE) { + buf_type++; + portYIELD_FROM_ISR(); + } + } + } +} + +static void setup_timer() +{ + //Setup timer for ISR + int timer_group = TIMER_GROUP; + int timer_idx = TIMER_NUMBER; + timer_config_t config; + config.alarm_en = 1; + config.auto_reload = 1; + config.counter_dir = TIMER_COUNT_UP; + config.divider = 10000; + config.intr_type = TIMER_INTR_LEVEL; + config.counter_en = TIMER_PAUSE; + timer_init(timer_group, timer_idx, &config); //Configure timer + timer_pause(timer_group, timer_idx); //Stop timer counter + timer_set_counter_value(timer_group, timer_idx, 0x00000000ULL); //Load counter value + timer_set_alarm_value(timer_group, timer_idx, 20); //Set alarm value + timer_enable_intr(timer_group, timer_idx); //Enable timer interrupt + timer_set_auto_reload(timer_group, timer_idx, 1); //Auto Reload + timer_isr_register(timer_group, timer_idx, ringbuffer_isr, NULL, 0, &ringbuffer_isr_handle); //Set ISR handler +} + +static void cleanup_timer() +{ + timer_disable_intr(TIMER_GROUP, TIMER_NUMBER); + esp_intr_free(ringbuffer_isr_handle); +} + +TEST_CASE("Test ring buffer ISR", "[freertos]") +{ + for (int i = 0; i < NO_OF_RB_TYPES; i++) { + buffer_handles[i] = xRingbufferCreate(BUFFER_SIZE, i); + } + done_sem = xSemaphoreCreateBinary(); + buf_type = 0; + iterations = 0; + setup_timer(); + //Start timer to trigger ISR + timer_start(TIMER_GROUP, TIMER_NUMBER); + //Wait for ISR to complete multiple iterations + xSemaphoreTake(done_sem, portMAX_DELAY); + + //Cleanup + cleanup_timer(); + vSemaphoreDelete(done_sem); + for (int i = 0; i < NO_OF_RB_TYPES; i++) { + vRingbufferDelete(buffer_handles[i]); + } +} + +/* ---------------------------- Test ring buffer SMP --------------------------- + * The following test case tests each type of ring buffer in an SMP fashion. A + * sending task and a receiving task is created. The sending task will split + * a continuous piece of data into items of random length and send it to a ring + * buffer. The receiving task will receive and check those items. + * Every permutation of core pinning of the sending and receiving task will be + * tested. + */ + +#define SRAND_SEED 3 //Arbitrarily chosen srand() seed +#define SMP_TEST_ITERATIONS 4 + +static const char continuous_data[] = {"A_very_long_string_that_will_be_split_into_" + "items_of_random_lengths_and_sent_to_the_ring_" + "buffer._The_maximum_random_length_will_also_" + "be_increased_over_multiple_iterations_in_this" + "_test"}; +#define CONT_DATA_LEN sizeof(continuous_data) +#define CONT_DATA_TEST_BUFF_LEN (CONT_DATA_LEN/2) //This will guarantee that the buffer must do a wrap around at some point + +typedef struct { + RingbufHandle_t buffer; + ringbuf_type_t type; +} task_args_t; + +static SemaphoreHandle_t tasks_done; +static SemaphoreHandle_t tx_done; +static SemaphoreHandle_t rx_done; + +static void send_to_buffer(RingbufHandle_t buffer, size_t max_item_size) +{ + for (int iter = 0; iter < SMP_TEST_ITERATIONS; iter++) { + size_t bytes_sent = 0; //Number of data bytes sent in this iteration + size_t next_item_size; //Size of next item to send + + while (bytes_sent < CONT_DATA_LEN) { + //Get size of next item + next_item_size = rand() % (max_item_size + 1); + if (next_item_size + bytes_sent > CONT_DATA_LEN) { + next_item_size = CONT_DATA_LEN - bytes_sent; + } + + //Send item + TEST_ASSERT_MESSAGE(xRingbufferSend(buffer, (void *)&(continuous_data[bytes_sent]), next_item_size, TIMEOUT_TICKS) == pdTRUE, "Failed to send an item"); + bytes_sent += next_item_size; + } + xSemaphoreGive(tx_done); + xSemaphoreTake(rx_done, portMAX_DELAY); + } +} + +static void read_from_buffer(RingbufHandle_t buffer, ringbuf_type_t buf_type, size_t max_rec_size) +{ + for (int iter = 0; iter < SMP_TEST_ITERATIONS; iter++) { + size_t bytes_rec = 0; //Number of data bytes received in this iteration + while (bytes_rec < CONT_DATA_LEN) { + size_t item_size, item_size2; //Possible for allow split buffers to receive two items + char *item_data, *item_data2; + + //Select appropriate receive function for type of ring buffer + if (buf_type == RINGBUF_TYPE_NOSPLIT) { + item_data = (char *)xRingbufferReceive(buffer, &item_size, TIMEOUT_TICKS); + } else if (buf_type == RINGBUF_TYPE_ALLOWSPLIT) { + BaseType_t ret = xRingbufferReceiveSplit(buffer, (void **)&item_data, (void **)&item_data2, &item_size, &item_size2, TIMEOUT_TICKS); + TEST_ASSERT_MESSAGE(ret == pdTRUE, "Failed to receive any item"); + } else { + item_data = (char *)xRingbufferReceiveUpTo(buffer, &item_size, TIMEOUT_TICKS, max_rec_size); + } + + //Check received item and return it + TEST_ASSERT_MESSAGE(item_data != NULL, "Failed to receive an item"); + if (buf_type == RINGBUF_TYPE_BYTEBUF) { + TEST_ASSERT_MESSAGE(item_size <= max_rec_size, "Received data exceeds max size"); + } + for (int i = 0; i < item_size; i++) { + //Check item_data is valid + TEST_ASSERT_MESSAGE(item_data[i] == continuous_data[bytes_rec + i], "Received data is corrupted"); + } + bytes_rec += item_size; + vRingbufferReturnItem(buffer, item_data); + if (buf_type == RINGBUF_TYPE_ALLOWSPLIT && item_data2 != NULL) { + //Check item_data2 is valid + for (int i = 0; i < item_size2; i++) { + TEST_ASSERT_MESSAGE(item_data2[i] == continuous_data[bytes_rec + i], "Received split data is corrupted"); + } + bytes_rec += item_size2; + vRingbufferReturnItem(buffer, item_data2); + } + } + TEST_ASSERT_MESSAGE(bytes_rec == CONT_DATA_LEN, "Total length of received data is incorrect"); + xSemaphoreGive(rx_done); + xSemaphoreTake(tx_done, portMAX_DELAY); + } +} + +static void send_task(void *args) +{ + RingbufHandle_t buffer = ((task_args_t *)args)->buffer; + size_t max_item_len = xRingbufferGetMaxItemSize(buffer); + + //Test sending short length items + send_to_buffer(buffer, 1); + //Test sending mid length items + send_to_buffer(buffer, max_item_len/2); + //Test sending long length items + send_to_buffer(buffer, max_item_len); + vTaskDelete(NULL); +} + +static void rec_task(void *args) +{ + RingbufHandle_t buffer = ((task_args_t *)args)->buffer; + size_t max_rec_len = xRingbufferGetMaxItemSize(buffer); + + //Test receiving short length items + read_from_buffer(buffer, ((task_args_t *)args)->type, 1); + //Test receiving mid length items + read_from_buffer(buffer, ((task_args_t *)args)->type, max_rec_len/2); + //Test receiving long length items + read_from_buffer(buffer, ((task_args_t *)args)->type, max_rec_len); + + xSemaphoreGive(tasks_done); + vTaskDelete(NULL); +} + +TEST_CASE("Test ring buffer SMP", "[freertos]") +{ + ets_printf("size of buf %d\n", CONT_DATA_LEN); + tx_done = xSemaphoreCreateBinary(); //Semaphore to indicate send is done for a particular iteration + rx_done = xSemaphoreCreateBinary(); //Semaphore to indicate receive is done for a particular iteration + tasks_done = xSemaphoreCreateBinary(); //Semaphore used to to indicate send and receive tasks completed running + srand(SRAND_SEED); //Seed RNG + + //Iterate through buffer types (No split, split, then byte buff) + for (ringbuf_type_t buf_type = 0; buf_type <= RINGBUF_TYPE_BYTEBUF; buf_type++) { + //Create buffer + task_args_t task_args; + task_args.buffer = xRingbufferCreate(CONT_DATA_TEST_BUFF_LEN, buf_type); //Create buffer of selected type + task_args.type = buf_type; + + for (int prior_mod = -1; prior_mod < 2; prior_mod++) { //Test different relative priorities + //Test every permutation of core affinity + for (int send_core = 0; send_core < portNUM_PROCESSORS; send_core++) { + for (int rec_core = 0; rec_core < portNUM_PROCESSORS; rec_core ++) { + ets_printf("Type: %d, PM: %d, SC: %d, RC: %d\n", buf_type, prior_mod, send_core, rec_core); + xTaskCreatePinnedToCore(send_task, "send tsk", 2048, (void *)&task_args, 10 + prior_mod, NULL, send_core); + xTaskCreatePinnedToCore(rec_task, "rec tsk", 2048, (void *)&task_args, 10, NULL, rec_core); + xSemaphoreTake(tasks_done, portMAX_DELAY); + vTaskDelay(5); //Allow idle to clean up + } + } + } + + //Delete ring buffer + vRingbufferDelete(task_args.buffer); + vTaskDelay(10); + } + + //Cleanup + vSemaphoreDelete(tx_done); + vSemaphoreDelete(rx_done); + vSemaphoreDelete(tasks_done); +} diff --git a/docs/_static/diagrams/ring-buffer/ring_buffer_read_ret_byte_buf.diag b/docs/_static/diagrams/ring-buffer/ring_buffer_read_ret_byte_buf.diag new file mode 100644 index 000000000..9ad33b05f --- /dev/null +++ b/docs/_static/diagrams/ring-buffer/ring_buffer_read_ret_byte_buf.diag @@ -0,0 +1,30 @@ +#Diagram demonstrating reading and returning an item in a byte buffer +#Buffer of 128 bytes, with 68 bytes occupied but wrapped. All data is read + +packetdiag ring_buffer_read_ret_byte_buf { + node_width = 6 + node_height = 24 + default_fontsize = 12 + colwidth = 128 + + #Initial + 0-29: 30 [color = lightyellow]; + 30-89: 60 Free + 90-127: 38 [color = lightyellow]; + + #Read all continuous data + 128-157: 30 [color = lightyellow]; + 158-217: 60 Free + 218-255: 38 [color = pink]; + + #Return data + 256-285: 30 [color = lightyellow]; + 286-383: 98 Free + + #Read remaining data + 384-413: 30 [color = pink]; + 414-511: 98 Free + + #Return data + 512-639: 128 Free +} \ No newline at end of file diff --git a/docs/_static/diagrams/ring-buffer/ring_buffer_read_ret_non_byte_buf.diag b/docs/_static/diagrams/ring-buffer/ring_buffer_read_ret_non_byte_buf.diag new file mode 100644 index 000000000..a7773b991 --- /dev/null +++ b/docs/_static/diagrams/ring-buffer/ring_buffer_read_ret_non_byte_buf.diag @@ -0,0 +1,86 @@ +#Diagram demonstrating reading and returning an item in a No-Split/Allow-Split ring buffer +#Buffer of 128 bytes, with 4 items of 16, 20, 8 and 24 bytes. First 3 items are read and returned + +packetdiag ring_buffer_read_ret_non_byte_buf { + node_width = 6 + node_height = 24 + default_fontsize = 12 + colwidth = 128 + + #Initial + 0-7: 8 [color = lightblue]; + 8-23: 16 [color = lightyellow]; + 24-31: 8 [color = lightblue]; + 32-51: 20 [color = lightyellow]; + 52-59: 8 [color = lightblue]; + 60-67: 8 [color = lightyellow]; + 68-75: 8 [color = lightblue]; + 76-99: 24 [color = lightyellow]; + 100-127: 28 Free + + #Read item 1 + 128-135: 8 [color = pink]; + 136-151: 16 [color = pink]; + 152-159: 8 [color = lightblue]; + 160-179: 20 [color = lightyellow]; + 180-187: 8 [color = lightblue]; + 188-195: 8 [color = lightyellow]; + 196-203: 8 [color = lightblue]; + 204-227: 24 [color = lightyellow]; + 228-255: 28 Free + + #Read item 2 + 256-263: 8 [color = pink]; + 264-279: 16 [color = pink]; + 280-287: 8 [color = pink]; + 288-307: 20 [color = pink]; + 308-315: 8 [color = lightblue]; + 316-323: 8 [color = lightyellow]; + 324-331: 8 [color = lightblue]; + 332-355: 24 [color = lightyellow]; + 356-383: 28 Free + + #Read item 3 + 384-391: 8 [color = pink]; + 392-407: 16 [color = pink]; + 408-415: 8 [color = pink]; + 416-435: 20 [color = pink]; + 436-443: 8 [color = pink]; + 444-451: 8 [color = pink]; + 452-459: 8 [color = lightblue]; + 460-483: 24 [color = lightyellow]; + 484-511: 28 Free + + #Return item 2 + 512-519: 8 [color = pink]; + 520-535: 16 [color = pink]; + 536-563: Ret [color = lightgrey]; + 564-571: 8 [color = pink]; + 572-579: 8 [color = pink]; + 580-587: 8 [color = lightblue]; + 588-611: 24 [color = lightyellow]; + 612-639: 28 Free + + #Return item 3 + 640-647: 8 [color = pink]; + 648-663: 16 [color = pink]; + 664-691: Ret [color = lightgrey]; + 692-707: Ret [color = lightgrey]; + 708-715: 8 [color = lightblue]; + 716-739: 24 [color = lightyellow]; + 740-767: 28 Free + + #Return item 1 + 768-791: Ret [color = lightgrey]; + 792-819: Ret [color = lightgrey]; + 820-835: Ret [color = lightgrey]; + 836-843: 8 [color = lightblue]; + 844-867: 24 [color = lightyellow]; + 868-895: 28 Free + + #End state + 896-963: 68 Free + 964-971: 8 [color = lightblue]; + 972-995: 24 [color = lightyellow]; + 996-1023: 28 Free +} \ No newline at end of file diff --git a/docs/_static/diagrams/ring-buffer/ring_buffer_send_byte_buf.diag b/docs/_static/diagrams/ring-buffer/ring_buffer_send_byte_buf.diag new file mode 100644 index 000000000..9bac57042 --- /dev/null +++ b/docs/_static/diagrams/ring-buffer/ring_buffer_send_byte_buf.diag @@ -0,0 +1,21 @@ +#Diagram demonstrating sending in byte buffer +#Buffer of 128 bytes, and 3 items of size 18, 3, and 27 bytes sent + +packetdiag ring_buffer_send_byte_buf { + node_width = 6 + node_height = 24 + default_fontsize = 12 + colwidth = 128 + + #Add 18 byte item + 0-17: 18 [color = lightyellow]; + 18-127: 110 Free + + #Add 3 byte item + 128-148: 21 [color = lightyellow]; + 149-255: 107 Free + + #Add 27 byte item + 256-303: 48 [color = lightyellow]; + 304-383: 80 Free +} \ No newline at end of file diff --git a/docs/_static/diagrams/ring-buffer/ring_buffer_send_non_byte_buf.diag b/docs/_static/diagrams/ring-buffer/ring_buffer_send_non_byte_buf.diag new file mode 100644 index 000000000..25cb71e79 --- /dev/null +++ b/docs/_static/diagrams/ring-buffer/ring_buffer_send_non_byte_buf.diag @@ -0,0 +1,30 @@ +#Diagram demonstrating sending in a No-Split/Allow-Split ring buffer +#Buffer of 128 bytes, and 3 items of size 18, 3, and 27 bytes sent + +packetdiag ring_buffer_send_non_byte_buf { + node_width = 6 + node_height = 24 + default_fontsize = 12 + colwidth = 128 + + #Add 18 byte item + 0-7: 8 [color = lightblue]; + 8-27: 20 [color = lightyellow]; + 28-127: 100 Free + + #Add 3 byte item + 128-135: 8 [color = lightblue]; + 136-155: 20 [color = lightyellow]; + 156-163: 8 [color = lightblue]; + 164-167: 4 [color = lightyellow]; + 168-255: 88 Free + + #Add 27 byte item + 256-263: 8 [color = lightblue]; + 264-283: 20 [color = lightyellow]; + 284-291: 8 [color = lightblue]; + 292-295: 4 [color = lightyellow]; + 296-303: 8 [color = lightblue]; + 304-331: 28 [color = lightyellow]; + 332-383: 52 Free +} \ No newline at end of file diff --git a/docs/_static/diagrams/ring-buffer/ring_buffer_wrap_allow_split.diag b/docs/_static/diagrams/ring-buffer/ring_buffer_wrap_allow_split.diag new file mode 100644 index 000000000..94e26e87a --- /dev/null +++ b/docs/_static/diagrams/ring-buffer/ring_buffer_wrap_allow_split.diag @@ -0,0 +1,37 @@ +#Diagram demonstrating wrap around in a Allow-Split ring buffer +#Buffer of 128 bytes, with 56 bytes free, and 28 bytes sent + +packetdiag ring_buffer_wrap_allow_split { + node_width = 6 + node_height = 24 + default_fontsize = 12 + colwidth = 128 + + #Initial state + 0-39: 40 Free + 40-47: 8 [color = lightblue]; + 48-63: 16 [color = lightyellow]; + 64-71: 8 [color = lightblue]; + 72-111: 40 [color = lightyellow]; + 112-127: 16 Free + + #Send first part + 128-167: 40 Free + 168-175: 8 [color = lightblue]; + 176-191: 16 [color = lightyellow]; + 192-199: 8 [color = lightblue]; + 200-239: 40 [color = lightyellow]; + 240-247: 8 [color = lightblue]; + 248-255: 8 [color = lightyellow]; + + #Send second part + 256-263: 8 [color = lightblue]; + 264-283: 20 [color = lightyellow]; + 284-295: 12 Free + 296-303: 8 [color = lightblue]; + 304-319: 16 [color = lightyellow]; + 320-327: 8 [color = lightblue]; + 328-367: 40 [color = lightyellow]; + 368-375: 8 [color = lightblue]; + 376-383: 8 [color = lightyellow]; +} \ No newline at end of file diff --git a/docs/_static/diagrams/ring-buffer/ring_buffer_wrap_byte_buf.diag b/docs/_static/diagrams/ring-buffer/ring_buffer_wrap_byte_buf.diag new file mode 100644 index 000000000..bf693779d --- /dev/null +++ b/docs/_static/diagrams/ring-buffer/ring_buffer_wrap_byte_buf.diag @@ -0,0 +1,23 @@ +#Diagram demonstrating wrap around in byte buffer +#Buffer of 128 bytes, with 56 bytes free, and 28 bytes sent + +packetdiag ring_buffer_wrap_byte_buf { + node_width = 6 + node_height = 24 + default_fontsize = 12 + colwidth = 128 + + #Initial state + 0-39: 40 Free + 40-111: 72 [color = lightyellow]; + 112-127: 16 Free + + #Fill up free space at the end of the buffer + 128-167: 40 Free + 168-255: 88 [color = lightyellow]; + + #Wrap around remaining data + 256-267: 12 [color = lightyellow]; + 268-295: 28 Free + 296-383: 88 [color = lightyellow]; +} \ No newline at end of file diff --git a/docs/_static/diagrams/ring-buffer/ring_buffer_wrap_no_split.diag b/docs/_static/diagrams/ring-buffer/ring_buffer_wrap_no_split.diag new file mode 100644 index 000000000..ff4a11a8f --- /dev/null +++ b/docs/_static/diagrams/ring-buffer/ring_buffer_wrap_no_split.diag @@ -0,0 +1,35 @@ +#Diagram demonstrating wrap around in a No-Split ring buffer +#Buffer of 128 bytes, with 56 bytes free, and 28 bytes sent + +packetdiag ring_buffer_wrap_no_split { + node_width = 6 + node_height = 24 + default_fontsize = 12 + colwidth = 128 + + #Initial state + 0-39: 40 Free + 40-47: 8 [color = lightblue]; + 48-63: 16 [color = lightyellow]; + 64-71: 8 [color = lightblue]; + 72-111: 40 [color = lightyellow]; + 112-127: 16 Free + + #Set dummy data + 128-167: 40 Free + 168-175: 8 [color = lightblue]; + 176-191: 16 [color = lightyellow]; + 192-199: 8 [color = lightblue]; + 200-239: 40 [color = lightyellow]; + 240-255: Dummy [color = lightgrey]; + + #Send wrap around item + 256-263: 8 [color = lightblue]; + 264-291: 28 [color = lightyellow]; + 292-295: 4 Free + 296-303: 8 [color = lightblue]; + 304-319: 16 [color = lightyellow]; + 320-327: 8 [color = lightblue]; + 328-367: 40 [color = lightyellow]; + 368-383: Dummy [color = lightgrey]; +} \ No newline at end of file diff --git a/docs/en/api-guides/freertos-smp.rst b/docs/en/api-guides/freertos-smp.rst index 9851929af..40665d9c5 100644 --- a/docs/en/api-guides/freertos-smp.rst +++ b/docs/en/api-guides/freertos-smp.rst @@ -16,6 +16,9 @@ of FreeRTOS v8.2.0. This guide outlines the major differences between vanilla FreeRTOS and ESP-IDF FreeRTOS. The API reference for vanilla FreeRTOS can be found via http://www.freertos.org/a00106.html +For information regarding features that are exclusive to ESP-IDF FreeRTOS, +see :doc:`ESP-IDF FreeRTOS Additions<../api-reference/system/freertos_additions>`. + :ref:`backported-features`: Although ESP-IDF FreeRTOS is based on the Xtensa port of FreeRTOS v8.2.0, a number of FreeRTOS v9.0.0 features have been backported to ESP-IDF. @@ -70,10 +73,6 @@ used to free memory pointed to by TLSP. Call :cpp:func:`vTaskSetThreadLocalStoragePointerAndDelCallback()` to set TLSP and Deletion Callbacks. -:ref:`FreeRTOS Hooks`: Vanilla FreeRTOS Hooks were not designed for SMP. -ESP-IDF provides its own Idle and Tick Hooks in addition to the Vanilla FreeRTOS -hooks. For full details, see the ESP-IDF Hooks API Reference. - :ref:`esp-idf-freertos-configuration`: Several aspects of ESP-IDF FreeRTOS can be configured using ``make meunconfig`` such as running ESP-IDF in Unicore Mode, or configuring the number of Thread Local Storage Pointers each task will have. diff --git a/docs/en/api-reference/system/freertos.rst b/docs/en/api-reference/system/freertos.rst index 2b0b0929f..f5b2b8dc0 100644 --- a/docs/en/api-reference/system/freertos.rst +++ b/docs/en/api-reference/system/freertos.rst @@ -6,7 +6,8 @@ Overview This section contains documentation of FreeRTOS types, functions, and macros. It is automatically generated from FreeRTOS header files. -For more information about FreeRTOS features specific to ESP-IDF, see :doc:`ESP-IDF FreeRTOS SMP Changes<../../api-guides/freertos-smp>`. +For more information about FreeRTOS features specific to ESP-IDF, see :doc:`ESP-IDF FreeRTOS SMP Changes<../../api-guides/freertos-smp>` +and :doc:`ESP-IDF FreeRTOS Additions`. Task API @@ -35,8 +36,4 @@ Event Group API .. include:: /_build/inc/event_groups.inc -Ringbuffer API --------------- - -.. include:: /_build/inc/ringbuf.inc diff --git a/docs/en/api-reference/system/freertos_additions.rst b/docs/en/api-reference/system/freertos_additions.rst new file mode 100644 index 000000000..57d25e9a7 --- /dev/null +++ b/docs/en/api-reference/system/freertos_additions.rst @@ -0,0 +1,403 @@ +FreeRTOS Additions +================== + +Overview +-------- + +ESP-IDF FreeRTOS is based on the Xtensa port of FreeRTOS v8.2.0 with significant modifications +for SMP compatibility (see :doc:`ESP-IDF FreeRTOS SMP Changes<../../api-guides/freertos-smp>`). +However various features specific to ESP-IDF FreeRTOS have been added. The features are as follows: + +:ref:`ring-buffers`: Ring buffers were added to provide a form of buffer that could accept +entries of arbitrary lengths. + +:ref:`hooks`: ESP-IDF FreeRTOS hooks provides support for registering extra Idle and +Tick hooks at run time. Moreover, the hooks can be asymmetric amongst both CPUs. + + +.. _ring-buffers: + +Ring Buffers +------------ + +The ESP-IDF FreeRTOS ring buffer is a strictly FIFO buffer that supports arbitrarily sized items. +Ring buffers are a more memory efficient alternative to FreeRTOS queues in situations where the +size of items is variable. The capacity of a ring buffer is not measured by the number of items +it can store, but rather by the amount of memory used for storing items. Items are sent to +ring buffers by copy, however for efficiency reasons **items are retrieved by reference**. As a +result, all retrieved items **must also be returned** in order for them to be removed from +the ring buffer completely. The ring buffers are split into the three following types: + +**No-Split** buffers will guarantee that an item is stored in contiguous memory and will not +attempt to split an item under any circumstances. Use no-split buffers when items must occupy +contiguous memory. + +**Allow-Split** buffers will allow an item to be split when wrapping around if doing so will allow +the item to be stored. Allow-split buffers are more memory efficient than no-split buffers but +can return an item in two parts when retrieving. + +**Byte buffers** do not store data as separate items. All data is stored as a sequence of bytes, +and any number of bytes and be sent or retrieved each time. Use byte buffers when separate items +do not need to be maintained (e.g. a byte stream). + +.. note:: + No-split/allow-split buffers will always store items at 32-bit aligned addresses. Therefore when + retrieving an item, the item pointer is guaranteed to be 32-bit aligned. + +.. note:: + Each item stored in no-split/allow-split buffers will **require an additional 8 bytes for a header**. + Item sizes will also be rounded up to a 32-bit aligned size (multiple of 4 bytes), however the true + item size is recorded within the header. The sizes of no-split/allow-split buffers will also + be rounded up when created. + +Usage +^^^^^ + +The following example demonstrates the usage of :cpp:func:`xRingbufferCreate` +and :cpp:func:`xRingbufferSend` to create a ring buffer then send an item to it. + +.. code-block:: c + + #include "freertos/ringbuf.h" + static char tx_item[] = "test_item"; + + ... + + //Create ring buffer + RingbufHandle_t buf_handle; + buf_handle = xRingbufferCreate(1028, RINGBUF_TYPE_NOSPLIT); + if (buf_handle == NULL) { + printf("Failed to create ring buffer\n"); + } + + //Send an item + UBaseType_t res = xRingbufferSend(buf_handle, tx_item, sizeof(tx_item), pdMS_TO_TICKS(1000)); + if (res != pdTRUE) { + printf("Failed to send item\n"); + } + + +The following example demonstrates retrieving and returning an item from a **no-split ring buffer** +using :cpp:func:`xRingbufferReceive` and :cpp:func:`vRingbufferReturnItem` + +.. code-block:: c + + ... + + //Receive an item from no-split ring buffer + size_t item_size; + char *item = (char *)xRingbufferReceive(buf_handle, &item_size, pdMS_TO_TICKS(1000)); + + //Check received item + if (item != NULL) { + //Print item + for (int i = 0; i < item_size; i++) { + printf("%c", item[i]); + } + printf("\n"); + //Return Item + vRingbufferReturnItem(buf_handle, (void *)item); + } else { + //Failed to receive item + printf("Failed to receive item\n"); + } + + +The following example demonstrates retrieving and returning an item from an **allow-split ring buffer** +using :cpp:func:`xRingbufferReceiveSplit` and :cpp:func:`vRingbufferReturnItem` + +.. code-block:: c + + ... + + //Receive an item from allow-split ring buffer + size_t item_size1, item_size2; + char *item1, *item2; + BaseType_t ret = xRingbufferReceiveSplit(buf_handle, (void **)&item1, (void **)&item2, &item_size1, &item_size2, pdMS_TO_TICKS(1000)); + + //Check received item + if (ret == pdTRUE && item1 != NULL) { + for (int i = 0; i < item_size1; i++) { + printf("%c", item1[i]); + } + vRingbufferReturnItem(buf_handle, (void *)item1); + //Check if item was split + if (item2 != NULL) { + for (int i = 0; i < item_size2; i++) { + printf("%c", item2[i]); + } + vRingbufferReturnItem(buf_handle, (void *)item2); + } + printf("\n"); + } else { + //Failed to receive item + printf("Failed to receive item\n"); + } + + +The following example demonstrates retrieving and returning an item from a **byte buffer** +using :cpp:func:`xRingbufferReceiveUpTo` and :cpp:func:`vRingbufferReturnItem` + +.. code-block:: c + + ... + + //Receive data from byte buffer + size_t item_size; + char *item = (char *)xRingbufferReceiveUpTo(buf_handle, &item_size, pdMS_TO_TICKS(1000), sizeof(tx_item)); + + //Check received data + if (item != NULL) { + //Print item + for (int i = 0; i < item_size; i++) { + printf("%c", item[i]); + } + printf("\n"); + //Return Item + vRingbufferReturnItem(buf_handle, (void *)item); + } else { + //Failed to receive item + printf("Failed to receive item\n"); + } + + +For ISR safe versions of the functions used above, call :cpp:func:`xRingbufferSendFromISR`, :cpp:func:`xRingbufferReceiveFromISR`, +:cpp:func:`xRingbufferReceiveSplitFromISR`, :cpp:func:`xRingbufferReceiveUpToFromISR`, and :cpp:func:`vRingbufferReturnItemFromISR` + + +Sending to Ring Buffer +^^^^^^^^^^^^^^^^^^^^^^ + +The following diagrams illustrate the differences between no-split/allow-split buffers +and byte buffers with regards to sending items/data. The diagrams assume that three +items of sizes **18, 3, and 27 bytes** are sent respectively to a **buffer of 128 bytes**. + +.. packetdiag:: ../../../_static/diagrams/ring-buffer/ring_buffer_send_non_byte_buf.diag + :caption: Sending items to no-split/allow-split ring buffers + :align: center + +For no-split/allow-split buffers, a header of 8 bytes precedes every data item. Furthermore, the space +occupied by each item is **rounded up to the nearest 32-bit aligned size** in order to maintain overall +32-bit alignment. However the true size of the item is recorded inside the header which will be +returned when the item is retrieved. + +Referring to the diagram above, the 18, 3, and 27 byte items are **rounded up to 20, 4, and 28 bytes** +respectively. An 8 byte header is then added in front of each item. + +.. packetdiag:: ../../../_static/diagrams/ring-buffer/ring_buffer_send_byte_buf.diag + :caption: Sending items to byte buffers + :align: center + +Byte buffers treat data as a sequence of bytes and does not incur any overhead +(no headers). As a result, all data sent to a byte buffer is merged into a single item. + +Referring to the diagram above, the 18, 3, and 27 byte items are sequentially written to the +byte buffer and **merged into a single item of 48 bytes**. + +Wrap around +^^^^^^^^^^^ + +The following diagrams illustrate the differences between no-split, allow-split, and byte +buffers when a sent item requires a wrap around. The diagrams assumes a buffer of **128 bytes** +with **56 bytes of free space that wraps around** and a sent item of **28 bytes**. + +.. packetdiag:: ../../../_static/diagrams/ring-buffer/ring_buffer_wrap_no_split.diag + :caption: Wrap around in no-split buffers + :align: center + +No-split buffers will **only store an item in continuous free space and will not split +an item under any circumstances**. When the free space at the tail of the buffer is insufficient +to completely store the item and its header, the free space at the tail will be **marked as dummy data**. +The buffer will then wrap around and store the item in the free space at the head of the buffer. + +Referring to the diagram above, the 16 bytes of free space at the tail of the buffer is +insufficient to store the 28 byte item. Therefore the 16 bytes is marked as dummy data and +the item is written to the free space at the head of the buffer instead. + +.. packetdiag:: ../../../_static/diagrams/ring-buffer/ring_buffer_wrap_allow_split.diag + :caption: Wrap around in allow-split buffers + :align: center + +Allow-split buffers will attempt to **split the item into two parts** when the free space at the tail +of the buffer is insufficient to store the item data and its header. Both parts of the +split item will have their own headers (therefore incurring an extra 8 bytes of overhead). + +Referring to the diagram above, the 16 bytes of free space at the tail of the buffer is insufficient +to store the 28 byte item. Therefore the item is split into two parts (8 and 20 bytes) and written +as two parts to the buffer. + +.. note:: + Allow-split buffers treats the both parts of the split item as two separate items, therefore call + :cpp:func:`xRingbufferReceiveSplit` instead of :cpp:func:`xRingbufferReceive` to receive both + parts of a split item in a thread safe manner. + +.. packetdiag:: ../../../_static/diagrams/ring-buffer/ring_buffer_wrap_byte_buf.diag + :caption: Wrap around in byte buffers + :align: center + +Byte buffers will **store as much data as possible into the free space at the tail of buffer**. The remaining +data will then be stored in the free space at the head of the buffer. No overhead is incurred when wrapping +around in byte buffers. + +Referring to the diagram above, the 16 bytes of free space at the tail of the buffer is insufficient to +completely store the 28 bytes of data. Therefore the 16 bytes of free space is filled with data, and the +remaining 12 bytes are written to the free space at the head of the buffer. The buffer now contains +data in two separate continuous parts, and each part continuous will be treated as a separate item by the +byte buffer. + +Retrieving/Returning +^^^^^^^^^^^^^^^^^^^^ + +The following diagrams illustrates the differences between no-split/allow-split and +byte buffers in retrieving and returning data. + +.. packetdiag:: ../../../_static/diagrams/ring-buffer/ring_buffer_read_ret_non_byte_buf.diag + :caption: Retrieving/Returning items in no-split/allow-split ring buffers + :align: center + +Items in no-split/allow-split buffers are **retrieved in strict FIFO order** and **must be returned** +for the occupied space to be freed. Multiple items can be retrieved before returning, and the items +do not necessarily need to be returned in the order they were retrieved. However the freeing of space +must occur in FIFO order, therefore not returning the earliest retrieved item will prevent the space +of subsequent items from being freed. + +Referring to the diagram above, the **16, 20, and 8 byte items are retrieved in FIFO order**. However the items +are not returned in they were retrieved (20, 8, 16). As such, the space is not freed until the first item +(16 byte) is returned. + +.. packetdiag:: ../../../_static/diagrams/ring-buffer/ring_buffer_read_ret_byte_buf.diag + :caption: Retrieving/Returning data in byte buffers + :align: center + +Byte buffers **do not allow multiple retrievals before returning** (every retrieval must be followed by a return +before another retrieval is permitted). When using :cpp:func:`xRingbufferReceive` or +:cpp:func:`xRingbufferReceiveFromISR`, all continuous stored data will be retrieved. :cpp:func:`xRingbufferReceiveUpTo` +or :cpp:func:`xRingbufferReceiveUpToFromISR` can be used to restrict the maximum number of bytes retrieved. Since +every retrieval must be followed by a return, the space will be freed as soon as the data is returned. + +Referring to the diagram above, the 38 bytes of continuous stored data at the tail of the buffer is retrieved, +returned, and freed. The next call to :cpp:func:`xRingbufferReceive` or :cpp:func:`xRingbufferReceiveFromISR` +then wraps around and does the same to the 30 bytes of continuous stored data at the head of the buffer. + +Ring Buffers with Queue Sets +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Ring buffers can be added to FreeRTOS queue sets using :cpp:func:`xRingbufferAddToQueueSetRead` such that every +time a ring buffer receives an item or data, the queue set is notified. Once added to a queue set, every +attempt to retrieve an item from a ring buffer should be preceded by a call to :cpp:func:`xQueueSelectFromSet`. +To check whether the selected queue set member is the ring buffer, call :cpp:func:`xRingbufferCanRead`. + +The following example demonstrates queue set usage with ring buffers. + +.. code-block:: c + + #include "freertos/queue.h" + #include "freertos/ringbuf.h" + + ... + + //Create ring buffer and queue set + RingbufHandle_t buf_handle = xRingbufferCreate(1028, RINGBUF_TYPE_NOSPLIT); + QueueSetHandle_t queue_set = xQueueCreateSet(3); + + //Add ring buffer to queue set + if (xRingbufferAddToQueueSetRead(buf_handle, queue_set) != pdTRUE) { + printf("Failed to add to queue set\n"); + } + + ... + + //Block on queue set + xQueueSetMemberHandle member = xQueueSelectFromSet(queue_set, pdMS_TO_TICKS(1000)); + + //Check if member is ring buffer + if (member != NULL && xRingbufferCanRead(buf_handle, member) == pdTRUE) { + //Member is ring buffer, receive item from ring buffer + size_t item_size; + char *item = (char *)xRingbufferReceive(buf_handle, &item_size, 0); + + //Handle item + ... + + } else { + ... + } + + +Ring Buffer API Reference +------------------------- + +.. note:: + Ideally, ring buffers can be used with multiple tasks in an SMP fashion where the **highest + priority task will always be serviced first.** However due to the usage of binary semaphores + in the ring buffer's underlying implementation, priority inversion may occur under very + specific circumstances. + + The ring buffer governs sending by a binary semaphore which is given whenever space is + freed on the ring buffer. The highest priority task waiting to send will repeatedly take + the semaphore until sufficient free space becomes available or until it times out. Ideally + this should prevent any lower priority tasks from being serviced as the semaphore should + always be given to the highest priority task. + + However in between iterations of acquiring the semaphore, there is a **gap in the critical + section** which may permit another task (on the other core or with an even higher priority) to + free some space on the ring buffer and as a result give the semaphore. Therefore the semaphore + will be given before the highest priority task can re-acquire the semaphore. This will result + in the **semaphore being acquired by the second highest priority task** waiting to send, hence + causing priority inversion. + + This side effect will not affect ring buffer performance drastically given if the number + of tasks using the ring buffer simultaneously is low, and the ring buffer is not operating + near maximum capacity. + +.. include:: /_build/inc/ringbuf.inc + + +.. _hooks: + +Hooks +----- + +FreeRTOS consists of Idle Hooks and Tick Hooks which allow for application +specific functionality to be added to the Idle Task and Tick Interrupt. +ESP-IDF provides its own Idle and Tick Hook API in addition to the hooks +provided by Vanilla FreeRTOS. ESP-IDF hooks have the added benefit of +being run time configurable and asymmetrical. + +Vanilla FreeRTOS Hooks +^^^^^^^^^^^^^^^^^^^^^^ + +Idle and Tick Hooks in vanilla FreeRTOS are implemented by the user +defining the functions ``vApplicationIdleHook()`` and ``vApplicationTickHook()`` +respectively somewhere in the application. Vanilla FreeRTOS will run the user +defined Idle Hook and Tick Hook on every iteration of the Idle Task and Tick +Interrupt respectively. + +Vanilla FreeRTOS hooks are referred to as **Legacy Hooks** in ESP-IDF FreeRTOS. +To enable legacy hooks, :ref:`CONFIG_FREERTOS_LEGACY_HOOKS`, +:ref:`CONFIG_FREERTOS_LEGACY_IDLE_HOOK`, and :ref:`CONFIG_FREERTOS_LEGACY_TICK_HOOK` +should all be enabled in ``make menuconfig``. + +Due to vanilla FreeRTOS being designed for single core, ``vApplicationIdleHook()`` +and ``vApplicationTickHook()`` can only be defined once. However, the ESP32 is dual core +in nature, therefore same Idle Hook and Tick Hook are used for both cores (in other words, +the hooks are symmetrical for both cores). + +ESP-IDF Idle and Tick Hooks +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Due to the the dual core nature of the ESP32, it may be necessary for some +applications to have separate hooks for each core. Furthermore, it may +be necessary for the Idle Tasks or Tick Interrupts to execute multiple hooks +that are configurable at run time. Therefore the ESP-IDF provides it's own hooks +API in addition to the legacy hooks provided by Vanilla FreeRTOS. + +The ESP-IDF tick/idle hooks are registered at run time, and each tick/idle hook +must be registered to a specific CPU. When the idle task runs/tick Interrupt +occurs on a particular CPU, the CPU will run each of its registered idle/tick hooks +in turn. + + +Hooks API Reference +------------------- + +.. include:: /_build/inc/esp_freertos_hooks.inc diff --git a/docs/en/api-reference/system/hooks.rst b/docs/en/api-reference/system/hooks.rst deleted file mode 100644 index 5a65b29a1..000000000 --- a/docs/en/api-reference/system/hooks.rst +++ /dev/null @@ -1,51 +0,0 @@ -.. _hooks_api_reference: - -FreeRTOS Hooks -============== - -Overview --------- - -FreeRTOS consists of Idle Hooks and Tick Hooks which allow for application -specific funtiionality to be added to the Idle Task and Tick Interrupt. The -ESP32 is dual core in nature, hence the ESP-IDF provides its own Idle and Tick -Hooks that are dual core compatible in addition to the hooks provided by Vanilla -FreeRTOS. - -Vanilla FreeRTOS Hooks ----------------------- - -Idle and Tick Hooks in vanilla FreeRTOS are implemented by defining -implementations for the functions ``vApplicationIdleHook`` and -``vApplicationTickHook`` respectively somewhere in the application. Vanilla -FreeRTOS will run the user defined Idle Hook every iteration of the Idle Task, -whereas the user defined Tick Hook will run once per tick interrupt (given that -there are no pended ticks). - -Due to vanilla FreeRTOS being designed for single core, ``vApplicationIdleHook`` -and ``vApplicationTickHook`` will be run in both cores on the ESP32. In -other words, the same Idle Hook and Tick Hook are used for both cores. - -To enable the vanilla FreeRTOS hooks in ESP-IDF, :ref:`CONFIG_FREERTOS_LEGACY_HOOKS` -must be enabled in ``make menuconfig``. :ref:`CONFIG_FREERTOS_LEGACY_IDLE_HOOK` -and :ref:`CONFIG_FREERTOS_LEGACY_TICK_HOOK` should also be enabled. - -ESP-IDF Idle and Tick Hooks ---------------------------- - -Due to the dual core nature of the ESP32, it may be necessary for some -applications to have seperate Idle Hooks for each core. Furthermore, it may -be necessary for Idle and Tick Hooks to have execute multiple functionalities -that are configurable at run time. Therefore the ESP-IDF provides it's own Idle -and Tick Hooks in addition to the hooks provided by Vanilla FreeRTOS. - -The ESP-IDF Hooks do not operate in the same way as Vanilla FreeRTOS Hooks -where users provide a definition for each of the hooks. Instead, the ESP-IDF -Hooks are predefined to call a list of user registered callbacks specific to -each core. Users can register and deregister callbacks which are run on the -Idle or Tick Hook of a specific core. - -API Reference -------------- - -.. include:: /_build/inc/esp_freertos_hooks.inc \ No newline at end of file diff --git a/docs/en/api-reference/system/index.rst b/docs/en/api-reference/system/index.rst index 7dd9991c4..bdc5d68e2 100644 --- a/docs/en/api-reference/system/index.rst +++ b/docs/en/api-reference/system/index.rst @@ -5,7 +5,7 @@ System API :maxdepth: 1 FreeRTOS - FreeRTOS Hooks + FreeRTOS Additions Heap Memory Allocation Heap Memory Debugging Interrupt Allocation diff --git a/docs/zh_CN/api-reference/system/freertos_additions.rst b/docs/zh_CN/api-reference/system/freertos_additions.rst new file mode 100644 index 000000000..36e4800fb --- /dev/null +++ b/docs/zh_CN/api-reference/system/freertos_additions.rst @@ -0,0 +1 @@ +.. include:: ../../../en/api-reference/system/freertos_additions.rst \ No newline at end of file diff --git a/docs/zh_CN/api-reference/system/hooks.rst b/docs/zh_CN/api-reference/system/hooks.rst deleted file mode 100644 index 487fb9910..000000000 --- a/docs/zh_CN/api-reference/system/hooks.rst +++ /dev/null @@ -1 +0,0 @@ -.. include:: ../../../en/api-reference/system/hooks.rst \ No newline at end of file