diff --git a/components/freertos/include/freertos/ringbuf.h b/components/freertos/include/freertos/ringbuf.h index 0f23a44e6..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,255 +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. + * 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 retrieved. * - * @param ringbuf Ring buffer to add to the queue set - * @param xQueueSet Queue set to add the ringbuffer to + * @param[in] xRingbuffer Ring buffer which should be checked + * @param[in] xMember Member returned from xQueueSelectFromSet * - * @return pdTRUE on success, pdFALSE otherwise + * @return + * - pdTRUE when semaphore belongs to ring buffer + * - pdFALSE otherwise. */ -BaseType_t xRingbufferAddToQueueSetWrite(RingbufHandle_t ringbuf, QueueSetHandle_t xQueueSet); - +BaseType_t xRingbufferCanRead(RingbufHandle_t xRingbuffer, QueueSetMemberHandle_t xMember); /** - * @brief Remove the ringbuffer from a queue set. + * @brief Remove the ring buffer's read semaphore from a queue set. * - * This specifically removes the semaphore that indicates more space - * has become available in the ringbuffer. + * 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 to remove from the queue set - * @param xQueueSet Queue set to remove the ringbuffer from + * @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 on success, pdFALSE otherwise + * @return + * - pdTRUE on success + * - pdFALSE otherwise */ -BaseType_t xRingbufferRemoveFromQueueSetRead(RingbufHandle_t ringbuf, QueueSetHandle_t xQueueSet); - +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 something - * has been written to 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 xRingbufferRemoveFromQueueSetWrite(RingbufHandle_t ringbuf, QueueSetHandle_t xQueueSet); - +void vRingbufferGetInfo(RingbufHandle_t xRingbuffer, UBaseType_t *uxFree, UBaseType_t *uxRead, UBaseType_t *uxWrite, UBaseType_t *uxItemsWaiting); /** - * @brief Debugging function to print the internal pointers in the ring buffer + * @brief Debugging function to print the internal pointers in the ring buffer * - * @param ringbuf Ring buffer to show + * @param xRingbuffer Ring buffer to show */ -void xRingbufferPrintInfo(RingbufHandle_t ringbuf); +void xRingbufferPrintInfo(RingbufHandle_t xRingbuffer); + +/* -------------------------------- Deprecated Functions --------------------------- */ + +/** @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. + */ +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 7cb24a5ca..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,758 +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 - - //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; + //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 + + 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; -} - -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